diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8cf5efbf0..ffe7c22fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/.gitmodules b/.gitmodules index c0ba5e79d..e54658932 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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,3 +119,10 @@ [submodule "externals/aacdec/fdk-aac"] path = externals/aacdec/fdk-aac url = https://android.googlesource.com/platform/external/aac +[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 + diff --git a/CMakeLists.txt b/CMakeLists.txt index ab144aa37..484a1d4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later # Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE @@ -202,7 +202,7 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "13") +set(EMULATOR_VERSION_MINOR "14") set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") @@ -221,6 +221,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(Boost 1.84.0 CONFIG) +find_package(CLI11 2.6.1 CONFIG) find_package(FFmpeg 5.1.2 MODULE) find_package(fmt 10.2.0 CONFIG) find_package(glslang 15 CONFIG) @@ -281,15 +282,16 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp set(AUDIO_LIB src/core/libraries/audio/audioin.cpp src/core/libraries/audio/audioin.h - src/core/libraries/audio/sdl_in.h - src/core/libraries/audio/sdl_in.cpp + src/core/libraries/audio/audioin_backend.h + src/core/libraries/audio/audioin_error.h + src/core/libraries/audio/sdl_audio_in.cpp src/core/libraries/voice/voice.cpp src/core/libraries/voice/voice.h src/core/libraries/audio/audioout.cpp src/core/libraries/audio/audioout.h src/core/libraries/audio/audioout_backend.h src/core/libraries/audio/audioout_error.h - src/core/libraries/audio/sdl_audio.cpp + src/core/libraries/audio/sdl_audio_out.cpp src/core/libraries/ngs2/ngs2.cpp src/core/libraries/ngs2/ngs2.h ) @@ -494,6 +496,8 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp src/core/libraries/libc_internal/libc_internal_str.h src/core/libraries/libc_internal/libc_internal_math.cpp src/core/libraries/libc_internal/libc_internal_math.h + src/core/libraries/libc_internal/libc_internal_threads.cpp + src/core/libraries/libc_internal/libc_internal_threads.h src/core/libraries/libc_internal/printf.h ) @@ -524,6 +528,9 @@ set(SYSTEM_GESTURE_LIB set(PNG_LIB src/core/libraries/libpng/pngdec.cpp src/core/libraries/libpng/pngdec.h src/core/libraries/libpng/pngdec_error.h + src/core/libraries/libpng/pngenc.cpp + src/core/libraries/libpng/pngenc.h + src/core/libraries/libpng/pngenc_error.h ) set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h @@ -581,14 +588,21 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_commerce.h src/core/libraries/np/np_manager.cpp src/core/libraries/np/np_manager.h + src/core/libraries/np/np_matching2.cpp + src/core/libraries/np/np_matching2.h src/core/libraries/np/np_score.cpp src/core/libraries/np/np_score.h src/core/libraries/np/np_trophy.cpp src/core/libraries/np/np_trophy.h + src/core/libraries/np/np_tus.cpp + src/core/libraries/np/np_tus.h src/core/libraries/np/trophy_ui.cpp src/core/libraries/np/trophy_ui.h src/core/libraries/np/np_web_api.cpp src/core/libraries/np/np_web_api.h + src/core/libraries/np/np_web_api_error.h + src/core/libraries/np/np_web_api_internal.cpp + src/core/libraries/np/np_web_api_internal.h src/core/libraries/np/np_web_api2.cpp src/core/libraries/np/np_web_api2.h src/core/libraries/np/np_party.cpp @@ -599,6 +613,9 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_profile_dialog.h src/core/libraries/np/np_sns_facebook_dialog.cpp src/core/libraries/np/np_sns_facebook_dialog.h + src/core/libraries/np/np_partner.cpp + src/core/libraries/np/np_partner.h + src/core/libraries/np/object_manager.h ) set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp @@ -744,6 +761,8 @@ set(COMMON src/common/logging/backend.cpp src/common/memory_patcher.cpp ${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp src/common/scm_rev.h + src/common/key_manager.cpp + src/common/key_manager.h ) if (ENABLE_DISCORD_RPC) @@ -787,6 +806,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/playgo_chunk.h src/core/file_format/trp.cpp src/core/file_format/trp.h + src/core/file_format/npbind.cpp + src/core/file_format/npbind.h src/core/file_sys/fs.cpp src/core/file_sys/fs.h src/core/ipc/ipc.cpp @@ -912,6 +933,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 @@ -1093,7 +1115,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/README.md b/README.md index 69ee64b13..0fb5c26ed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ @@ -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). @@ -150,7 +155,7 @@ The following firmware modules are supported and must be placed in shadPS4's `sy | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | | libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx | | libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx | -| libSceUlt.sprx | | | | +| libSceUlt.sprx | libSceAudiodec.sprx | | | > [!Caution] diff --git a/REUSE.toml b/REUSE.toml index 18200ab28..22bed2a50 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -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", diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index d2a6747d9..210ca1c5e 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -38,6 +38,9 @@ Game + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0 + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0 diff --git a/documents/Docker Builder/.devcontainer/devcontainer.json b/documents/Docker Builder/.devcontainer/devcontainer.json new file mode 100644 index 000000000..1139ffa33 --- /dev/null +++ b/documents/Docker Builder/.devcontainer/devcontainer.json @@ -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" + } +} \ No newline at end of file diff --git a/documents/Docker Builder/.docker/Dockerfile b/documents/Docker Builder/.docker/Dockerfile new file mode 100644 index 000000000..6ca9b2da5 --- /dev/null +++ b/documents/Docker Builder/.docker/Dockerfile @@ -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 \ No newline at end of file diff --git a/documents/Docker Builder/docker-compose.yml b/documents/Docker Builder/docker-compose.yml new file mode 100644 index 000000000..39efefa72 --- /dev/null +++ b/documents/Docker Builder/docker-compose.yml @@ -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 diff --git a/documents/Screenshots/Windows/vscode-ext-1.png b/documents/Screenshots/Windows/vscode-ext-1.png new file mode 100644 index 000000000..b8427b80b Binary files /dev/null and b/documents/Screenshots/Windows/vscode-ext-1.png differ diff --git a/documents/Screenshots/Windows/vscode-ext-2.png b/documents/Screenshots/Windows/vscode-ext-2.png new file mode 100644 index 000000000..082e478a4 Binary files /dev/null and b/documents/Screenshots/Windows/vscode-ext-2.png differ diff --git a/documents/Screenshots/Windows/vscode-ext-3.png b/documents/Screenshots/Windows/vscode-ext-3.png new file mode 100644 index 000000000..c362d6490 Binary files /dev/null and b/documents/Screenshots/Windows/vscode-ext-3.png differ diff --git a/documents/building-docker.md b/documents/building-docker.md new file mode 100644 index 000000000..84d238751 --- /dev/null +++ b/documents/building-docker.md @@ -0,0 +1,100 @@ + + +# 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 +``` + +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. \ No newline at end of file diff --git a/documents/building-windows.md b/documents/building-windows.md index 88c5b6830..8251189ff 100644 --- a/documents/building-windows.md +++ b/documents/building-windows.md @@ -41,10 +41,171 @@ Go through the Git for Windows installation as normal Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\` -## Option 2: MSYS2/MinGW +## Option 2: VSCode with Visual Studio Build Tools + +If your default IDE is VSCode, we have a fully functional example for that as well. + +### Requirements + +* [**Git for Windows**](https://git-scm.com/download/win) +* [**LLVM 19.1.1**](https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.1/LLVM-19.1.1-win64.exe) +* [**CMake 4.2.3 or newer**](https://github.com/Kitware/CMake/releases/download/v4.2.3/cmake-4.2.3-windows-x86_64.msi) +* [**Ninja 1.13.2 or newer**](https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-win.zip) + +**The main reason we use clang19 is because that version is used in CI for formatting.** + +### Installs + +1. Go through the Git for Windows installation as normal +2. Download and Run LLVM Installer and `Add LLVM to the system PATH for all users` +3. Download and Run CMake Installer and `Add CMake to the system PATH for all users` +4. Download Ninja and extract it to `C:\ninja` and add it to the system PATH for all users + * You can do this by going to `Search with Start Menu -> Environment Variables -> System Variables -> Path -> Edit -> New -> C:\ninja` + +### Validate the installs + +```bash +git --version +# git version 2.49.0.windows.1 + +cmake --version +# cmake version 4.2.3 + +ninja --version +# 1.13.2 + +clang --version +# clang version 19.1.1 +``` + +### Install Visual Studio Build Tools + +1. Download [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe) +2. Select `MSVC - Windows SDK` and install (you don't need to install an IDE) + +* Or you can install via `.vsconfig` file: + +``` +{ + "version": "1.0", + "components": [ + "Microsoft.VisualStudio.Component.Roslyn.Compiler", + "Microsoft.Component.MSBuild", + "Microsoft.VisualStudio.Component.CoreBuildTools", + "Microsoft.VisualStudio.Workload.MSBuildTools", + "Microsoft.VisualStudio.Component.Windows10SDK", + "Microsoft.VisualStudio.Component.VC.CoreBuildTools", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "Microsoft.VisualStudio.Component.VC.Redist.14.Latest", + "Microsoft.VisualStudio.Component.Windows11SDK.26100", + "Microsoft.VisualStudio.Component.TestTools.BuildTools", + "Microsoft.VisualStudio.Component.VC.ASAN", + "Microsoft.VisualStudio.Component.TextTemplating", + "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core", + "Microsoft.VisualStudio.Workload.VCTools" + ], + "extensions": [] +} + +Save the file as `.vsconfig` and run the following command: + +%userprofile%\Downloads\vs_BuildTools.exe --passive --config ".vsconfig" + +Be carefull path to vs_BuildTools.exe and .vsconfig file. +``` + +__This will install the necessary components to build shadPS4.__ + +### Project structure + +``` +shadps4/ + ├── shared (shadps4 main files) + └── shadps4.code-workspace +``` + +### Content of `shadps4.code-workspace` + +```json +{ + "folders": [ + { + "path": "shared" + } + ], + "settings": { + "cmake.generator": "Ninja", + + "cmake.configureEnvironment": { + "CMAKE_CXX_STANDARD": "23", + "CMAKE_CXX_STANDARD_REQUIRED": "ON", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + }, + + "cmake.configureOnOpen": false, + + "C_Cpp.intelliSenseEngine": "Disabled", + + "clangd.arguments": [ + "--background-index", + "--clang-tidy", + "--completion-style=detailed", + "--header-insertion=never", + "--compile-commands-dir=Build/x64-Clang-Release" + ], + + "editor.formatOnSave": true, + "clang-format.executable": "clang-format" + }, + + "extensions": { + "recommendations": [ + "llvm-vs-code-extensions.vscode-clangd", + "ms-vscode.cmake-tools", + "xaver.clang-format" + ] + } +} +``` + +### Cloning the source code + +1. Open your terminal and where to shadPS4 folder: `cd shadps4\shared` +3. Clone the repository by running + `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4 .` + +_or fork link_ + +* If you have already cloned repo: +```bash +git submodule update --init --recursive +``` + +### Requirements VSCode extensions +1. CMake Tools +2. Clangd +3. Clang-Format + +_These plugins are suggested in the workspace file above and are already configured._ + +![CMake Tools](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-1.png) + +![Clangd](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-2.png) + +![Clang Format](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-3.png) + +### Building +1. Open VS Code, `File > Open workspace from file > shadps4.code-workspace` +2. Go to the CMake Tools extension on left side bar +3. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build. +4. Click build. + +Your shadps4.exe will be in `shadps4\shared\Build\x64-Clang-Release\` + +## Option 3: MSYS2/MinGW > [!IMPORTANT] -> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022). +> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022) or [Option 2: VSCode with Visual Studio Build Tools](#option-2-vscode-with-visual-studio-build-tools). ### (Prerequisite) Download [**MSYS2**](https://www.msys2.org/) diff --git a/externals/CLI11 b/externals/CLI11 new file mode 160000 index 000000000..bf5a16a26 --- /dev/null +++ b/externals/CLI11 @@ -0,0 +1 @@ +Subproject commit bf5a16a26a34a9a7ad75f4a7705585e44675fef0 diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 8e96f9bec..80a6ff7e2 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later set(BUILD_SHARED_LIBS OFF) @@ -204,6 +204,7 @@ add_subdirectory(tracy) # pugixml if (NOT TARGET pugixml::pugixml) + option(PUGIXML_NO_EXCEPTIONS "" ON) add_subdirectory(pugixml) endif() @@ -268,3 +269,10 @@ add_subdirectory(json) # miniz add_subdirectory(miniz) + +# cli11 +if (NOT TARGET CLI11::CLI11) + set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + add_subdirectory(CLI11) +endif() diff --git a/externals/MoltenVK b/externals/MoltenVK index f168dec05..f79c6c569 160000 --- a/externals/MoltenVK +++ b/externals/MoltenVK @@ -1 +1 @@ -Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e +Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030 diff --git a/src/common/config.cpp b/src/common/config.cpp index eac463d0a..657943c95 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -14,6 +15,8 @@ #include "common/path_util.h" #include "common/scm_rev.h" +#include "input/input_handler.h" + using std::nullopt; using std::optional; using std::string; @@ -361,7 +364,7 @@ u32 getWindowHeight() { } u32 getInternalScreenWidth() { - return internalScreenHeight.get(); + return internalScreenWidth.get(); } u32 getInternalScreenHeight() { @@ -1289,16 +1292,6 @@ void setDefaultValues(bool is_game_specific) { constexpr std::string_view GetDefaultGlobalConfig() { return R"(# Anything put here will be loaded for all games, # alongside the game's config or default.ini depending on your preference. - -hotkey_renderdoc_capture = f12 -hotkey_fullscreen = f11 -hotkey_show_fps = f10 -hotkey_pause = f9 -hotkey_reload_inputs = f8 -hotkey_toggle_mouse_to_joystick = f7 -hotkey_toggle_mouse_to_gyro = f6 -hotkey_toggle_mouse_to_touchpad = delete -hotkey_quit = lctrl, lshift, end )"; } @@ -1376,7 +1369,7 @@ analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } -std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { +std::filesystem::path GetInputConfigFile(const string& game_id) { // Read configuration file of the game, and if it doesn't exist, generate it from default // If that doesn't exist either, generate that from getDefaultConfig() and try again // If even the folder is missing, we start with that. @@ -1415,6 +1408,39 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { } } } + if (game_id == "global") { + std::map default_bindings_to_add = { + {"hotkey_renderdoc_capture", "f12"}, + {"hotkey_fullscreen", "f11"}, + {"hotkey_show_fps", "f10"}, + {"hotkey_pause", "f9"}, + {"hotkey_reload_inputs", "f8"}, + {"hotkey_toggle_mouse_to_joystick", "f7"}, + {"hotkey_toggle_mouse_to_gyro", "f6"}, + {"hotkey_toggle_mouse_to_touchpad", "delete"}, + {"hotkey_quit", "lctrl, lshift, end"}, + {"hotkey_volume_up", "kpplus"}, + {"hotkey_volume_down", "kpminus"}, + }; + std::ifstream global_in(config_file); + string line; + while (std::getline(global_in, line)) { + line.erase(std::remove_if(line.begin(), line.end(), + [](unsigned char c) { return std::isspace(c); }), + line.end()); + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + continue; + } + std::string output_string = line.substr(0, equal_pos); + default_bindings_to_add.erase(output_string); + } + global_in.close(); + std::ofstream global_out(config_file, std::ios::app); + for (auto const& b : default_bindings_to_add) { + global_out << b.first << " = " << b.second << "\n"; + } + } // If game-specific config doesn't exist, create it from the default config if (!std::filesystem::exists(config_file)) { diff --git a/src/common/config.h b/src/common/config.h index 2a95e6cf0..036d04c99 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -183,6 +183,6 @@ std::filesystem::path getAddonInstallDir(); void setDefaultValues(bool is_game_specific = false); constexpr std::string_view GetDefaultGlobalConfig(); -std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); +std::filesystem::path GetInputConfigFile(const std::string& game_id = ""); }; // namespace Config diff --git a/src/common/elf_info.h b/src/common/elf_info.h index 0b2589e95..0f2311cb0 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -75,6 +75,7 @@ class ElfInfo { std::filesystem::path game_folder{}; public: + static constexpr u32 FW_10 = 0x1000000; static constexpr u32 FW_15 = 0x1500000; static constexpr u32 FW_16 = 0x1600000; static constexpr u32 FW_17 = 0x1700000; diff --git a/src/common/key_manager.cpp b/src/common/key_manager.cpp new file mode 100644 index 000000000..cd0f668bf --- /dev/null +++ b/src/common/key_manager.cpp @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "common/logging/log.h" +#include "key_manager.h" +#include "path_util.h" + +std::shared_ptr KeyManager::s_instance = nullptr; +std::mutex KeyManager::s_mutex; + +// ------------------- Constructor & Singleton ------------------- +KeyManager::KeyManager() { + SetDefaultKeys(); +} +KeyManager::~KeyManager() { + SaveToFile(); +} + +std::shared_ptr KeyManager::GetInstance() { + std::lock_guard lock(s_mutex); + if (!s_instance) + s_instance = std::make_shared(); + return s_instance; +} + +void KeyManager::SetInstance(std::shared_ptr instance) { + std::lock_guard lock(s_mutex); + s_instance = instance; +} + +// ------------------- Load / Save ------------------- +bool KeyManager::LoadFromFile() { + try { + const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + const auto keysPath = userDir / "keys.json"; + + if (!std::filesystem::exists(keysPath)) { + SetDefaultKeys(); + SaveToFile(); + LOG_DEBUG(KeyManager, "Created default key file: {}", keysPath.string()); + return true; + } + + std::ifstream file(keysPath); + if (!file.is_open()) { + LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string()); + return false; + } + + json j; + file >> j; + + SetDefaultKeys(); // start from defaults + + if (j.contains("TrophyKeySet")) + j.at("TrophyKeySet").get_to(m_keys.TrophyKeySet); + + LOG_DEBUG(KeyManager, "Successfully loaded keys from: {}", keysPath.string()); + return true; + + } catch (const std::exception& e) { + LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what()); + SetDefaultKeys(); + return false; + } +} + +bool KeyManager::SaveToFile() { + try { + const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + const auto keysPath = userDir / "keys.json"; + + json j; + KeysToJson(j); + + std::ofstream file(keysPath); + if (!file.is_open()) { + LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string()); + return false; + } + + file << std::setw(4) << j; + file.flush(); + + if (file.fail()) { + LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string()); + return false; + } + + LOG_DEBUG(KeyManager, "Successfully saved keys to: {}", keysPath.string()); + return true; + + } catch (const std::exception& e) { + LOG_ERROR(KeyManager, "Error saving keys: {}", e.what()); + return false; + } +} + +// ------------------- JSON conversion ------------------- +void KeyManager::KeysToJson(json& j) const { + j = m_keys; +} +void KeyManager::JsonToKeys(const json& j) { + json current = m_keys; // serialize current defaults + current.update(j); // merge only fields present in file + m_keys = current.get(); // deserialize back +} + +// ------------------- Defaults / Checks ------------------- +void KeyManager::SetDefaultKeys() { + m_keys = AllKeys{}; +} + +bool KeyManager::HasKeys() const { + return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty(); +} + +// ------------------- Hex conversion ------------------- +std::vector KeyManager::HexStringToBytes(const std::string& hexStr) { + std::vector bytes; + if (hexStr.empty()) + return bytes; + + if (hexStr.size() % 2 != 0) + throw std::runtime_error("Invalid hex string length"); + + bytes.reserve(hexStr.size() / 2); + + auto hexCharToInt = [](char c) -> u8 { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + throw std::runtime_error("Invalid hex character"); + }; + + for (size_t i = 0; i < hexStr.size(); i += 2) { + u8 high = hexCharToInt(hexStr[i]); + u8 low = hexCharToInt(hexStr[i + 1]); + bytes.push_back((high << 4) | low); + } + + return bytes; +} + +std::string KeyManager::BytesToHexString(const std::vector& bytes) { + static const char hexDigits[] = "0123456789ABCDEF"; + std::string hexStr; + hexStr.reserve(bytes.size() * 2); + for (u8 b : bytes) { + hexStr.push_back(hexDigits[(b >> 4) & 0xF]); + hexStr.push_back(hexDigits[b & 0xF]); + } + return hexStr; +} \ No newline at end of file diff --git a/src/common/key_manager.h b/src/common/key_manager.h new file mode 100644 index 000000000..8925ccbd0 --- /dev/null +++ b/src/common/key_manager.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "common/types.h" +#include "nlohmann/json.hpp" + +using json = nlohmann::json; + +class KeyManager { +public: + // ------------------- Nested keysets ------------------- + struct TrophyKeySet { + std::vector ReleaseTrophyKey; + }; + + struct AllKeys { + KeyManager::TrophyKeySet TrophyKeySet; + }; + + // ------------------- Construction ------------------- + KeyManager(); + ~KeyManager(); + + // ------------------- Singleton ------------------- + static std::shared_ptr GetInstance(); + static void SetInstance(std::shared_ptr instance); + + // ------------------- File operations ------------------- + bool LoadFromFile(); + bool SaveToFile(); + + // ------------------- Key operations ------------------- + void SetDefaultKeys(); + bool HasKeys() const; + + // ------------------- Getters / Setters ------------------- + const AllKeys& GetAllKeys() const { + return m_keys; + } + void SetAllKeys(const AllKeys& keys) { + m_keys = keys; + } + + static std::vector HexStringToBytes(const std::string& hexStr); + static std::string BytesToHexString(const std::vector& bytes); + +private: + void KeysToJson(json& j) const; + void JsonToKeys(const json& j); + + AllKeys m_keys{}; + + static std::shared_ptr s_instance; + static std::mutex s_mutex; +}; + +// ------------------- NLOHMANN macros ------------------- +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::TrophyKeySet, ReleaseTrophyKey) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeyManager::AllKeys, TrophyKeySet) + +namespace nlohmann { +template <> +struct adl_serializer> { + static void to_json(json& j, const std::vector& vec) { + j = KeyManager::BytesToHexString(vec); + } + static void from_json(const json& j, std::vector& vec) { + vec = KeyManager::HexStringToBytes(j.get()); + } +}; +} // namespace nlohmann diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d7c816da3..168350b96 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -208,26 +209,41 @@ public: } } + std::unique_lock entry_loc(_mutex); + + if (_last_entry.message == message) { + ++_last_entry.counter; + return; + } + + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; - const Entry entry = { + this->_last_entry = { .timestamp = duration_cast(steady_clock::now() - time_origin), .log_class = log_class, .log_level = log_level, .filename = filename, .line_num = line_num, .function = function, - .message = std::move(message), + .message = message, .thread = Common::GetCurrentThreadName(), + .counter = 1, }; - if (Config::getLogType() == "async") { - message_queue.EmplaceWait(entry); - } else { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - std::fflush(stdout); - } } private: @@ -259,6 +275,22 @@ private: } void StopBackendThread() { + // log last message + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + + this->_last_entry = {}; + backend_thread.request_stop(); if (backend_thread.joinable()) { backend_thread.join(); @@ -292,6 +324,8 @@ private: MPSCQueue message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; std::jthread backend_thread; + Entry _last_entry; + std::mutex _mutex; }; } // namespace diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index d954f8601..9a3fe0aa1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -106,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) \ @@ -159,6 +163,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { CLS(ImGui) \ CLS(Input) \ CLS(Tty) \ + CLS(KeyManager) \ CLS(Loader) // GetClassName is a macro defined by Windows.h, grrr... diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h index 6c529f878..7b52ad7e1 100644 --- a/src/common/logging/log_entry.h +++ b/src/common/logging/log_entry.h @@ -22,6 +22,7 @@ struct Entry { std::string function; std::string message; std::string thread; + u32 counter = 0; }; } // namespace Common::Log diff --git a/src/common/logging/types.h b/src/common/logging/types.h index ee18cd161..9e176c698 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -73,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 @@ -109,6 +112,7 @@ enum class Class : u8 { Lib_Mouse, ///< The LibSceMouse implementation Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation Lib_NpParty, ///< The LibSceNpParty implementation + Lib_NpPartner, ///< The LibSceNpPartner implementation Lib_Zlib, ///< The LibSceZlib implementation. Lib_Hmd, ///< The LibSceHmd implementation. Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation. @@ -127,6 +131,7 @@ enum class Class : u8 { Loader, ///< ROM loader Input, ///< Input emulation Tty, ///< Debug output from emu + KeyManager, ///< Key management system Count ///< Total number of logging classes }; diff --git a/src/common/shared_first_mutex.h b/src/common/shared_first_mutex.h index b150c956b..fcf9d0c4f 100644 --- a/src/common/shared_first_mutex.h +++ b/src/common/shared_first_mutex.h @@ -17,6 +17,15 @@ public: writer_active = true; } + bool try_lock() { + std::lock_guard lock(mtx); + if (writer_active || readers > 0) { + return false; + } + writer_active = true; + return true; + } + void unlock() { std::lock_guard lock(mtx); writer_active = false; diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 54194186c..e56953fb6 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -174,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec // Sets the debugger-visible name of the current thread. void SetCurrentThreadName(const char* name) { + if (Libraries::Kernel::g_curthread) { + Libraries::Kernel::g_curthread->name = name; + } SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data()); } @@ -186,6 +189,9 @@ void SetThreadName(void* thread, const char* name) { // MinGW with the POSIX threading model does not support pthread_setname_np #if !defined(_WIN32) || defined(_MSC_VER) void SetCurrentThreadName(const char* name) { + if (Libraries::Kernel::g_curthread) { + Libraries::Kernel::g_curthread->name = name; + } #ifdef __APPLE__ pthread_setname_np(name); #elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) @@ -212,6 +218,9 @@ void SetThreadName(void* thread, const char* name) { #if defined(_WIN32) void SetCurrentThreadName(const char*) { + if (Libraries::Kernel::g_curthread) { + Libraries::Kernel::g_curthread->name = name; + } // Do Nothing on MinGW } diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 3f063ea76..194f676f9 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -93,7 +93,10 @@ static u64 BackingSize = ORBIS_KERNEL_TOTAL_MEM_DEV_PRO; struct MemoryRegion { VAddr base; - size_t size; + PAddr phys_base; + u64 size; + u32 prot; + s32 fd; bool is_mapped; }; @@ -159,7 +162,8 @@ struct AddressSpace::Impl { // Restrict region size to avoid overly fragmenting the virtual memory space. if (info.State == MEM_FREE && info.RegionSize > 0x1000000) { VAddr addr = Common::AlignUp(reinterpret_cast(info.BaseAddress), alignment); - regions.emplace(addr, MemoryRegion{addr, size, false}); + regions.emplace(addr, + MemoryRegion{addr, PAddr(-1), size, PAGE_NOACCESS, -1, false}); } } @@ -207,46 +211,52 @@ struct AddressSpace::Impl { ~Impl() { if (virtual_base) { if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) { - LOG_CRITICAL(Render, "Failed to free virtual memory"); + LOG_CRITICAL(Core, "Failed to free virtual memory"); } } if (backing_base) { if (!UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) { - LOG_CRITICAL(Render, "Failed to unmap backing memory placeholder"); + LOG_CRITICAL(Core, "Failed to unmap backing memory placeholder"); } if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) { - LOG_CRITICAL(Render, "Failed to free backing memory"); + LOG_CRITICAL(Core, "Failed to free backing memory"); } } if (!CloseHandle(backing_handle)) { - LOG_CRITICAL(Render, "Failed to free backing memory file handle"); + LOG_CRITICAL(Core, "Failed to free backing memory file handle"); } } - void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot, uintptr_t fd = 0) { - // Before mapping we must carve a placeholder with the exact properties of our mapping. - auto* region = EnsureSplitRegionForMapping(virtual_addr, size); - region->is_mapped = true; + void* MapRegion(MemoryRegion* region) { + VAddr virtual_addr = region->base; + PAddr phys_addr = region->phys_base; + u64 size = region->size; + ULONG prot = region->prot; + s32 fd = region->fd; + void* ptr = nullptr; if (phys_addr != -1) { - HANDLE backing = fd ? reinterpret_cast(fd) : backing_handle; - if (fd && prot == PAGE_READONLY) { + HANDLE backing = fd != -1 ? reinterpret_cast(fd) : backing_handle; + if (fd != -1 && prot == PAGE_READONLY) { DWORD resultvar; ptr = VirtualAlloc2(process, reinterpret_cast(virtual_addr), size, MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); - bool ret = ReadFile(backing, ptr, size, &resultvar, NULL); + + // phys_addr serves as an offset for file mmaps. + // Create an OVERLAPPED with the offset, then supply that to ReadFile + OVERLAPPED param{}; + // Offset is the least-significant 32 bits, OffsetHigh is the most-significant. + param.Offset = phys_addr & 0xffffffffull; + param.OffsetHigh = (phys_addr & 0xffffffff00000000ull) >> 32; + bool ret = ReadFile(backing, ptr, size, &resultvar, ¶m); ASSERT_MSG(ret, "ReadFile failed. {}", Common::GetLastErrorMsg()); ret = VirtualProtect(ptr, size, prot, &resultvar); ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg()); } else { ptr = MapViewOfFile3(backing, process, reinterpret_cast(virtual_addr), - phys_addr, size, MEM_REPLACE_PLACEHOLDER, - PAGE_EXECUTE_READWRITE, nullptr, 0); + phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); ASSERT_MSG(ptr, "MapViewOfFile3 failed. {}", Common::GetLastErrorMsg()); - DWORD resultvar; - bool ret = VirtualProtect(ptr, size, prot, &resultvar); - ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg()); } } else { ptr = @@ -257,135 +267,220 @@ struct AddressSpace::Impl { return ptr; } - void Unmap(VAddr virtual_addr, size_t size, bool has_backing) { - bool ret; - if (has_backing) { + void UnmapRegion(MemoryRegion* region) { + VAddr virtual_addr = region->base; + PAddr phys_base = region->phys_base; + u64 size = region->size; + ULONG prot = region->prot; + s32 fd = region->fd; + + bool ret = false; + if ((fd != -1 && prot != PAGE_READONLY) || (fd == -1 && phys_base != -1)) { ret = UnmapViewOfFile2(process, reinterpret_cast(virtual_addr), MEM_PRESERVE_PLACEHOLDER); } else { ret = VirtualFreeEx(process, reinterpret_cast(virtual_addr), size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); } - ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed: {}", virtual_addr, + ASSERT_MSG(ret, "Unmap on virtual_addr {:#x}, size {:#x} failed: {}", virtual_addr, size, Common::GetLastErrorMsg()); - - // The unmap call will create a new placeholder region. We need to see if we can coalesce it - // with neighbors. - JoinRegionsAfterUnmap(virtual_addr, size); } - // The following code is inspired from Dolphin's MemArena - // https://github.com/dolphin-emu/dolphin/blob/deee3ee4/Source/Core/Common/MemArenaWin.cpp#L212 - MemoryRegion* EnsureSplitRegionForMapping(VAddr address, size_t size) { - // Find closest region that is <= the given address by using upper bound and decrementing - auto it = regions.upper_bound(address); - ASSERT_MSG(it != regions.begin(), "Invalid address {:#x}", address); - --it; - ASSERT_MSG(!it->second.is_mapped, - "Attempt to map {:#x} with size {:#x} which overlaps with {:#x} mapping", - address, size, it->second.base); - auto& [base, region] = *it; + void SplitRegion(VAddr virtual_addr, u64 size) { + // First, get the region this range covers + auto it = std::prev(regions.upper_bound(virtual_addr)); - const VAddr mapping_address = region.base; - const size_t region_size = region.size; - if (mapping_address == address) { - // If this region is already split up correctly we don't have to do anything - if (region_size == size) { - return ®ion; + // All unmapped areas will coalesce, so there should be a region + // containing the full requested range. If not, then something is mapped here. + ASSERT_MSG(it->second.base + it->second.size >= virtual_addr + size, + "Cannot fit region into one placeholder"); + + // If the region is mapped, we need to unmap first before we can modify the placeholders. + if (it->second.is_mapped) { + ASSERT_MSG(it->second.phys_base != -1 || !it->second.is_mapped, + "Cannot split unbacked mapping"); + UnmapRegion(&it->second); + } + + // We need to split this region to create a matching placeholder. + if (it->second.base != virtual_addr) { + // Requested address is not the start of the containing region, + // create a new region to represent the memory before the requested range. + auto& region = it->second; + u64 base_offset = virtual_addr - region.base; + u64 next_region_size = region.size - base_offset; + PAddr next_region_phys_base = -1; + if (region.is_mapped) { + next_region_phys_base = region.phys_base + base_offset; } + region.size = base_offset; - ASSERT_MSG(region_size >= size, - "Region with address {:#x} and size {:#x} can't fit {:#x}", mapping_address, - region_size, size); - - // Split the placeholder. - if (!VirtualFreeEx(process, LPVOID(address), size, + // Use VirtualFreeEx to create the split. + if (!VirtualFreeEx(process, LPVOID(region.base), region.size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) { UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg()); - return nullptr; } - // Update tracked mappings and return the first of the two + // If the mapping was mapped, remap the region. + if (region.is_mapped) { + MapRegion(®ion); + } + + // Store a new region matching the removed area. + it = regions.emplace_hint(std::next(it), virtual_addr, + MemoryRegion(virtual_addr, next_region_phys_base, + next_region_size, region.prot, region.fd, + region.is_mapped)); + } + + // At this point, the region's base will match virtual_addr. + // Now check for a size difference. + if (it->second.size != size) { + // The requested size is smaller than the current region placeholder. + // Update region to match the requested region, + // then make a new region to represent the remaining space. + auto& region = it->second; + VAddr next_region_addr = region.base + size; + u64 next_region_size = region.size - size; + PAddr next_region_phys_base = -1; + if (region.is_mapped) { + next_region_phys_base = region.phys_base + size; + } region.size = size; - const VAddr new_mapping_start = address + size; - regions.emplace_hint(std::next(it), new_mapping_start, - MemoryRegion(new_mapping_start, region_size - size, false)); - return ®ion; + + // Store the new region matching the remaining space + regions.emplace_hint(std::next(it), next_region_addr, + MemoryRegion(next_region_addr, next_region_phys_base, + next_region_size, region.prot, region.fd, + region.is_mapped)); + + // Use VirtualFreeEx to create the split. + if (!VirtualFreeEx(process, LPVOID(region.base), region.size, + MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) { + UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg()); + } + + // If these regions were mapped, then map the unmapped area beyond the requested range. + if (region.is_mapped) { + MapRegion(&std::next(it)->second); + } } - ASSERT(mapping_address < address); - - // Is there enough space to map this? - const size_t offset_in_region = address - mapping_address; - const size_t minimum_size = size + offset_in_region; - ASSERT(region_size >= minimum_size); - - // Split the placeholder. - if (!VirtualFreeEx(process, LPVOID(address), size, - MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) { - UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg()); - return nullptr; - } - - // Do we now have two regions or three regions? - if (region_size == minimum_size) { - // Split into two; update tracked mappings and return the second one - region.size = offset_in_region; - it = regions.emplace_hint(std::next(it), address, MemoryRegion(address, size, false)); - return &it->second; - } else { - // Split into three; update tracked mappings and return the middle one - region.size = offset_in_region; - const VAddr middle_mapping_start = address; - const size_t middle_mapping_size = size; - const VAddr after_mapping_start = address + size; - const size_t after_mapping_size = region_size - minimum_size; - it = regions.emplace_hint(std::next(it), after_mapping_start, - MemoryRegion(after_mapping_start, after_mapping_size, false)); - it = regions.emplace_hint( - it, middle_mapping_start, - MemoryRegion(middle_mapping_start, middle_mapping_size, false)); - return &it->second; + // If the requested region was mapped, remap it. + if (it->second.is_mapped) { + MapRegion(&it->second); } } - void JoinRegionsAfterUnmap(VAddr address, size_t size) { - // There should be a mapping that matches the request exactly, find it - auto it = regions.find(address); - ASSERT_MSG(it != regions.end() && it->second.size == size, - "Invalid address/size given to unmap."); + void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, ULONG prot, s32 fd = -1) { + // Get a pointer to the region containing virtual_addr + auto it = std::prev(regions.upper_bound(virtual_addr)); + + // If needed, split surrounding regions to create a placeholder + if (it->first != virtual_addr || it->second.size != size) { + SplitRegion(virtual_addr, size); + it = std::prev(regions.upper_bound(virtual_addr)); + } + + // Get the address and region for this range. auto& [base, region] = *it; - region.is_mapped = false; + ASSERT_MSG(!region.is_mapped, "Cannot overwrite mapped region"); - // Check if a placeholder exists right before us. + // Now we have a region matching the requested region, perform the actual mapping. + region.is_mapped = true; + region.phys_base = phys_addr; + region.prot = prot; + region.fd = fd; + return MapRegion(®ion); + } + + void CoalesceFreeRegions(VAddr virtual_addr) { + // First, get the region to update + auto it = std::prev(regions.upper_bound(virtual_addr)); + ASSERT_MSG(!it->second.is_mapped, "Cannot coalesce mapped regions"); + + // Check if there are adjacent free placeholders before this area. + bool can_coalesce = false; auto it_prev = it != regions.begin() ? std::prev(it) : regions.end(); - if (it_prev != regions.end() && !it_prev->second.is_mapped) { - const size_t total_size = it_prev->second.size + size; - if (!VirtualFreeEx(process, LPVOID(it_prev->first), total_size, - MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) { - UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg()); - } - - it_prev->second.size = total_size; + while (it_prev != regions.end() && !it_prev->second.is_mapped && + it_prev->first + it_prev->second.size == it->first) { + // If there is an earlier region, move our iterator to that and increase size. + it_prev->second.size = it_prev->second.size + it->second.size; regions.erase(it); it = it_prev; + + // Mark this region as coalesce-able. + can_coalesce = true; + + // Get the next previous region. + it_prev = it != regions.begin() ? std::prev(it) : regions.end(); } - // Check if a placeholder exists right after us. + // Check if there are adjacent free placeholders after this area. auto it_next = std::next(it); - if (it_next != regions.end() && !it_next->second.is_mapped) { - const size_t total_size = it->second.size + it_next->second.size; - if (!VirtualFreeEx(process, LPVOID(it->first), total_size, + while (it_next != regions.end() && !it_next->second.is_mapped && + it->first + it->second.size == it_next->first) { + // If there is a later region, increase our current region's size + it->second.size = it->second.size + it_next->second.size; + regions.erase(it_next); + + // Mark this region as coalesce-able. + can_coalesce = true; + + // Get the next region + it_next = std::next(it); + } + + // If there are placeholders to coalesce, then coalesce them. + if (can_coalesce) { + if (!VirtualFreeEx(process, LPVOID(it->first), it->second.size, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) { UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg()); } - - it->second.size = total_size; - regions.erase(it_next); } } - void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) { + void Unmap(VAddr virtual_addr, u64 size) { + // Loop through all regions in the requested range + u64 remaining_size = size; + VAddr current_addr = virtual_addr; + while (remaining_size > 0) { + // Get a pointer to the region containing virtual_addr + auto it = std::prev(regions.upper_bound(current_addr)); + + // If necessary, split regions to ensure a valid unmap. + // To prevent complication, ensure size is within the bounds of the current region. + u64 base_offset = current_addr - it->second.base; + u64 size_to_unmap = std::min(it->second.size - base_offset, remaining_size); + if (current_addr != it->second.base || size_to_unmap != it->second.size) { + SplitRegion(current_addr, size_to_unmap); + it = std::prev(regions.upper_bound(current_addr)); + } + + // Get the address and region corresponding to this range. + auto& [base, region] = *it; + + // Unmap the region if it was previously mapped + if (region.is_mapped) { + UnmapRegion(®ion); + } + + // Update region data + region.is_mapped = false; + region.fd = -1; + region.phys_base = -1; + region.prot = PAGE_NOACCESS; + + // Update loop variables + remaining_size -= size_to_unmap; + current_addr += size_to_unmap; + } + + // Coalesce any free space produced from these unmaps. + CoalesceFreeRegions(virtual_addr); + } + + void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) { DWORD new_flags{}; if (write && !read) { @@ -415,7 +510,7 @@ struct AddressSpace::Impl { // If no flags are assigned, then something's gone wrong. if (new_flags == 0) { - LOG_CRITICAL(Common_Memory, + LOG_CRITICAL(Core, "Unsupported protection flag combination for address {:#x}, size {}, " "read={}, write={}, execute={}", virtual_addr, size, read, write, execute); @@ -424,13 +519,14 @@ struct AddressSpace::Impl { const VAddr virtual_end = virtual_addr + size; auto it = --regions.upper_bound(virtual_addr); + ASSERT_MSG(it != regions.end(), "addr {:#x} out of bounds", virtual_addr); for (; it->first < virtual_end; it++) { if (!it->second.is_mapped) { continue; } const auto& region = it->second; - const size_t range_addr = std::max(region.base, virtual_addr); - const size_t range_size = std::min(region.base + region.size, virtual_end) - range_addr; + const u64 range_addr = std::max(region.base, virtual_addr); + const u64 range_size = std::min(region.base + region.size, virtual_end) - range_addr; DWORD old_flags{}; if (!VirtualProtectEx(process, LPVOID(range_addr), range_size, new_flags, &old_flags)) { UNREACHABLE_MSG( @@ -453,11 +549,11 @@ struct AddressSpace::Impl { u8* backing_base{}; u8* virtual_base{}; u8* system_managed_base{}; - size_t system_managed_size{}; + u64 system_managed_size{}; u8* system_reserved_base{}; - size_t system_reserved_size{}; + u64 system_reserved_size{}; u8* user_base{}; - size_t user_size{}; + u64 user_size{}; std::map regions; }; #else @@ -601,7 +697,7 @@ struct AddressSpace::Impl { } } - void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot, + void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, PosixPageProtection prot, int fd = -1) { m_free_regions.subtract({virtual_addr, virtual_addr + size}); const int handle = phys_addr != -1 ? (fd == -1 ? backing_fd : fd) : -1; @@ -613,10 +709,10 @@ struct AddressSpace::Impl { return ret; } - void Unmap(VAddr virtual_addr, size_t size, bool) { + void Unmap(VAddr virtual_addr, u64 size) { // Check to see if we are adjacent to any regions. - auto start_address = virtual_addr; - auto end_address = start_address + size; + VAddr start_address = virtual_addr; + VAddr end_address = start_address + size; auto it = m_free_regions.find({start_address - 1, end_address + 1}); // If we are, join with them, ensuring we stay in bounds. @@ -634,7 +730,7 @@ struct AddressSpace::Impl { ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); } - void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) { + void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) { int flags = PROT_NONE; if (read) { flags |= PROT_READ; @@ -654,11 +750,11 @@ struct AddressSpace::Impl { int backing_fd; u8* backing_base{}; u8* system_managed_base{}; - size_t system_managed_size{}; + u64 system_managed_size{}; u8* system_reserved_base{}; - size_t system_reserved_size{}; + u64 system_reserved_size{}; u8* user_base{}; - size_t user_size{}; + u64 user_size{}; boost::icl::interval_set m_free_regions; }; #endif @@ -675,8 +771,7 @@ AddressSpace::AddressSpace() : impl{std::make_unique()} { AddressSpace::~AddressSpace() = default; -void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr, - bool is_exec) { +void* AddressSpace::Map(VAddr virtual_addr, u64 size, PAddr phys_addr, bool is_exec) { #if ARCH_X86_64 const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; #else @@ -687,8 +782,7 @@ void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr ph return impl->Map(virtual_addr, phys_addr, size, prot); } -void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, - uintptr_t fd) { +void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd) { #ifdef _WIN32 return impl->Map(virtual_addr, offset, size, ToWindowsProt(std::bit_cast(prot)), fd); @@ -698,31 +792,11 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 #endif } -void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma, - PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file) { -#ifdef _WIN32 - // There does not appear to be comparable support for partial unmapping on Windows. - // Unfortunately, a least one title was found to require this. The workaround is to unmap - // the entire allocation and remap the portions outside of the requested unmapping range. - impl->Unmap(virtual_addr, size, has_backing && !readonly_file); - - // TODO: Determine if any titles require partial unmapping support for un-backed allocations. - ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size), - "Partial unmapping of un-backed allocations is not supported"); - - if (start_in_vma != 0) { - Map(virtual_addr, start_in_vma, 0, phys_base, is_exec); - } - - if (end_in_vma != size) { - Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec); - } -#else - impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing); -#endif +void AddressSpace::Unmap(VAddr virtual_addr, u64 size) { + impl->Unmap(virtual_addr, size); } -void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) { +void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) { const bool read = True(perms & MemoryPermission::Read); const bool write = True(perms & MemoryPermission::Write); const bool execute = True(perms & MemoryPermission::Execute); diff --git a/src/core/address_space.h b/src/core/address_space.h index 5c50039bd..b71f66f28 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -39,7 +39,7 @@ public: [[nodiscard]] const u8* SystemManagedVirtualBase() const noexcept { return system_managed_base; } - [[nodiscard]] size_t SystemManagedVirtualSize() const noexcept { + [[nodiscard]] u64 SystemManagedVirtualSize() const noexcept { return system_managed_size; } @@ -49,7 +49,7 @@ public: [[nodiscard]] const u8* SystemReservedVirtualBase() const noexcept { return system_reserved_base; } - [[nodiscard]] size_t SystemReservedVirtualSize() const noexcept { + [[nodiscard]] u64 SystemReservedVirtualSize() const noexcept { return system_reserved_size; } @@ -59,7 +59,7 @@ public: [[nodiscard]] const u8* UserVirtualBase() const noexcept { return user_base; } - [[nodiscard]] size_t UserVirtualSize() const noexcept { + [[nodiscard]] u64 UserVirtualSize() const noexcept { return user_size; } @@ -73,17 +73,16 @@ public: * If zero is provided the mapping is considered as private. * @return A pointer to the mapped memory. */ - void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1, - bool exec = false); + void* Map(VAddr virtual_addr, u64 size, PAddr phys_addr = -1, bool exec = false); /// Memory maps a specified file descriptor. - void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd); + void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd); /// Unmaps specified virtual memory area. - void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma, - PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file); + void Unmap(VAddr virtual_addr, u64 size); - void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms); + /// Protects requested region. + void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms); // Returns an interval set containing all usable regions. boost::icl::interval_set GetUsableRegions(); @@ -93,11 +92,11 @@ private: std::unique_ptr impl; u8* backing_base{}; u8* system_managed_base{}; - size_t system_managed_size{}; + u64 system_managed_size{}; u8* system_reserved_base{}; - size_t system_reserved_size{}; + u64 system_reserved_size{}; u8* user_base{}; - size_t user_size{}; + u64 user_size{}; }; } // namespace Core diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 928040fec..4be107713 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -32,6 +32,9 @@ static bool show_simple_fps = false; static bool visibility_toggled = false; static bool show_quit_window = false; +static bool show_volume = false; +static float volume_start_time; + static float fps_scale = 1.0f; static int dump_frame_count = 1; @@ -454,6 +457,27 @@ void L::Draw() { End(); } + if (show_volume) { + float current_time = ImGui::GetTime(); + + // Show volume for 3 seconds + if (current_time - volume_start_time >= 3.0) { + show_volume = false; + } else { + SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->WorkPos.x + + ImGui::GetMainViewport()->WorkSize.x - 10, + ImGui::GetMainViewport()->WorkPos.y + 10), + ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + + if (ImGui::Begin("Volume Window", &show_volume, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) { + Text("Volume: %d", Config::getVolumeSlider()); + } + End(); + } + } + PopID(); } @@ -482,4 +506,9 @@ void ToggleQuitWindow() { show_quit_window = !show_quit_window; } +void ShowVolume() { + volume_start_time = ImGui::GetTime(); + show_volume = true; +} + } // namespace Overlay diff --git a/src/core/devtools/layer.h b/src/core/devtools/layer.h index 96b48a7f0..761135baf 100644 --- a/src/core/devtools/layer.h +++ b/src/core/devtools/layer.h @@ -32,5 +32,6 @@ namespace Overlay { void ToggleSimpleFps(); void SetSimpleFps(bool enabled); void ToggleQuitWindow(); +void ShowVolume(); } // namespace Overlay diff --git a/src/core/devtools/widget/memory_map.cpp b/src/core/devtools/widget/memory_map.cpp index 278c6595c..d1d1eb410 100644 --- a/src/core/devtools/widget/memory_map.cpp +++ b/src/core/devtools/widget/memory_map.cpp @@ -32,7 +32,7 @@ bool MemoryMapViewer::Iterator::DrawLine() { TableNextColumn(); Text("%s", magic_enum::enum_name(m.prot).data()); TableNextColumn(); - if (m.is_exec) { + if (True(m.prot & MemoryProt::CpuExec)) { Text("X"); } TableNextColumn(); @@ -44,7 +44,7 @@ bool MemoryMapViewer::Iterator::DrawLine() { return false; } auto m = dmem.it->second; - if (m.dma_type == DMAType::Free) { + if (m.dma_type == PhysicalMemoryType::Free) { ++dmem.it; return DrawLine(); } @@ -56,7 +56,8 @@ bool MemoryMapViewer::Iterator::DrawLine() { auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type); Text("%s", magic_enum::enum_name(type).data()); TableNextColumn(); - Text("%d", m.dma_type == DMAType::Pooled || m.dma_type == DMAType::Committed); + Text("%d", + m.dma_type == PhysicalMemoryType::Pooled || m.dma_type == PhysicalMemoryType::Committed); ++dmem.it; return true; } diff --git a/src/core/devtools/widget/memory_map.h b/src/core/devtools/widget/memory_map.h index cc7697c8c..3bbec4643 100644 --- a/src/core/devtools/widget/memory_map.h +++ b/src/core/devtools/widget/memory_map.h @@ -11,8 +11,8 @@ class MemoryMapViewer { struct Iterator { bool is_vma; struct { - MemoryManager::DMemMap::iterator it; - MemoryManager::DMemMap::iterator end; + MemoryManager::PhysMap::iterator it; + MemoryManager::PhysMap::iterator end; } dmem; struct { MemoryManager::VMAMap::iterator it; diff --git a/src/core/emulator_state.cpp b/src/core/emulator_state.cpp index 1f02043a3..20cffe53c 100644 --- a/src/core/emulator_state.cpp +++ b/src/core/emulator_state.cpp @@ -35,3 +35,11 @@ bool EmulatorState::IsAutoPatchesLoadEnabled() const { void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) { m_load_patches_auto = enable; } + +bool EmulatorState::IsGameSpecifigConfigUsed() const { + return m_game_specific_config_used; +} + +void EmulatorState::SetGameSpecifigConfigUsed(bool used) { + m_game_specific_config_used = used; +} diff --git a/src/core/emulator_state.h b/src/core/emulator_state.h index c12af5401..0764b8a81 100644 --- a/src/core/emulator_state.h +++ b/src/core/emulator_state.h @@ -18,6 +18,8 @@ public: void SetGameRunning(bool running); bool IsAutoPatchesLoadEnabled() const; void SetAutoPatchesLoadEnabled(bool enable); + bool IsGameSpecifigConfigUsed() const; + void SetGameSpecifigConfigUsed(bool used); private: static std::shared_ptr s_instance; @@ -26,4 +28,5 @@ private: // state variables bool m_running = false; bool m_load_patches_auto = true; -}; \ No newline at end of file + bool m_game_specific_config_used = false; +}; diff --git a/src/core/file_format/npbind.cpp b/src/core/file_format/npbind.cpp new file mode 100644 index 000000000..b2900efa0 --- /dev/null +++ b/src/core/file_format/npbind.cpp @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include "npbind.h" + +bool NPBindFile::Load(const std::string& path) { + Clear(); // Clear any existing data + + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) + return false; + + std::streamsize sz = f.tellg(); + if (sz <= 0) + return false; + + f.seekg(0, std::ios::beg); + std::vector buf(static_cast(sz)); + if (!f.read(reinterpret_cast(buf.data()), sz)) + return false; + + const u64 size = buf.size(); + if (size < sizeof(NpBindHeader)) + return false; + + // Read header + memcpy(&m_header, buf.data(), sizeof(NpBindHeader)); + if (m_header.magic != NPBIND_MAGIC) + return false; + + // offset start of bodies + size_t offset = sizeof(NpBindHeader); + + m_bodies.reserve(static_cast(m_header.num_entries)); + + // For each body: read 4 TLV entries then skip padding (0x98 = 152 bytes) + const u64 body_padding = 0x98; // 152 + + for (u64 bi = 0; bi < m_header.num_entries; ++bi) { + // Ensure we have room for 4 entries' headers at least + if (offset + 4 * 4 > size) + return false; // 4 entries x (type+size) + + NPBindBody body; + + // helper lambda to read one entry + auto read_entry = [&](NPBindEntryRaw& e) -> bool { + if (offset + 4 > size) + return false; + + memcpy(&e.type, &buf[offset], 2); + memcpy(&e.size, &buf[offset + 2], 2); + offset += 4; + + if (offset + e.size > size) + return false; + + e.data.assign(buf.begin() + offset, buf.begin() + offset + e.size); + offset += e.size; + return true; + }; + + // read 4 entries in order + if (!read_entry(body.npcommid)) + return false; + if (!read_entry(body.trophy)) + return false; + if (!read_entry(body.unk1)) + return false; + if (!read_entry(body.unk2)) + return false; + + // skip fixed padding after body if present (but don't overrun) + if (offset + body_padding <= size) { + offset += body_padding; + } else { + // If padding not fully present, allow file to end (some variants may omit) + offset = size; + } + + m_bodies.push_back(std::move(body)); + } + + // Read digest if available + if (size >= 20) { + // Digest is typically the last 20 bytes, independent of offset + memcpy(m_digest, &buf[size - 20], 20); + } else { + memset(m_digest, 0, 20); + } + + return true; +} + +std::vector NPBindFile::GetNpCommIds() const { + std::vector npcommids; + npcommids.reserve(m_bodies.size()); + + for (const auto& body : m_bodies) { + // Convert binary data to string directly + if (!body.npcommid.data.empty()) { + std::string raw_string(reinterpret_cast(body.npcommid.data.data()), + body.npcommid.data.size()); + npcommids.push_back(raw_string); + } + } + + return npcommids; +} \ No newline at end of file diff --git a/src/core/file_format/npbind.h b/src/core/file_format/npbind.h new file mode 100644 index 000000000..44d6528bd --- /dev/null +++ b/src/core/file_format/npbind.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include +#include +#include "common/endian.h" +#include "common/types.h" + +#define NPBIND_MAGIC 0xD294A018u + +#pragma pack(push, 1) +struct NpBindHeader { + u32_be magic; + u32_be version; + u64_be file_size; + u64_be entry_size; + u64_be num_entries; + char padding[0x60]; // 96 bytes +}; +#pragma pack(pop) + +struct NPBindEntryRaw { + u16_be type; + u16_be size; // includes internal padding + std::vector data; +}; + +struct NPBindBody { + NPBindEntryRaw npcommid; // expected type 0x0010, size 12 + NPBindEntryRaw trophy; // expected type 0x0011, size 12 + NPBindEntryRaw unk1; // expected type 0x0012, size 176 + NPBindEntryRaw unk2; // expected type 0x0013, size 16 + // The 0x98 padding after these entries is skipped while parsing +}; + +class NPBindFile { +private: + NpBindHeader m_header; + std::vector m_bodies; + u8 m_digest[20]; // zeroed if absent + +public: + NPBindFile() { + memset(m_digest, 0, sizeof(m_digest)); + } + + // Load from file + bool Load(const std::string& path); + + // Accessors + const NpBindHeader& Header() const { + return m_header; + } + const std::vector& Bodies() const { + return m_bodies; + } + const u8* Digest() const { + return m_digest; + } + + // Get npcommid data + std::vector GetNpCommIds() const; + + // Get specific body + const NPBindBody& GetBody(size_t index) const { + return m_bodies.at(index); + } + + // Get number of bodies + u64 BodyCount() const { + return m_bodies.size(); + } + + // Check if file was loaded successfully + bool IsValid() const { + return m_header.magic == NPBIND_MAGIC; + } + + // Clear all data + void Clear() { + m_header = NpBindHeader{}; + m_bodies.clear(); + memset(m_digest, 0, sizeof(m_digest)); + } +}; \ No newline at end of file diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index ab873d451..f0a258c12 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -1,44 +1,31 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/aes.h" -#include "common/config.h" +#include "common/key_manager.h" #include "common/logging/log.h" #include "common/path_util.h" +#include "core/file_format/npbind.h" #include "core/file_format/trp.h" -static void DecryptEFSM(std::span trophyKey, std::span NPcommID, - std::span efsmIv, std::span ciphertext, +static void DecryptEFSM(std::span trophyKey, std::span NPcommID, + std::span efsmIv, std::span ciphertext, std::span decrypted) { // Step 1: Encrypt NPcommID std::array trophyIv{}; std::array trpKey; + // Convert spans to pointers for the aes functions aes::encrypt_cbc(NPcommID.data(), NPcommID.size(), trophyKey.data(), trophyKey.size(), trophyIv.data(), trpKey.data(), trpKey.size(), false); // Step 2: Decrypt EFSM aes::decrypt_cbc(ciphertext.data(), ciphertext.size(), trpKey.data(), trpKey.size(), - efsmIv.data(), decrypted.data(), decrypted.size(), nullptr); + const_cast(efsmIv.data()), decrypted.data(), decrypted.size(), nullptr); } TRP::TRP() = default; TRP::~TRP() = default; -void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) { - std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat"; - Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read); - if (!npbindFile.IsOpen()) { - LOG_CRITICAL(Common_Filesystem, "Failed to open npbind.dat file"); - return; - } - if (!npbindFile.Seek(0x84 + (index * 0x180))) { - LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset"); - return; - } - npbindFile.ReadRaw(np_comm_id.data(), 12); - std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes. -} - static void removePadding(std::vector& vec) { for (auto it = vec.rbegin(); it != vec.rend(); ++it) { if (*it == '>') { @@ -63,91 +50,232 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit return false; } - const auto user_key_str = Config::getTrophyKey(); - if (user_key_str.size() != 32) { + const auto& user_key_vec = + KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey; + + if (user_key_vec.size() != 16) { LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified"); return false; } std::array user_key{}; - hexToBytes(user_key_str.c_str(), user_key.data()); + std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin()); - for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) { - if (it.is_regular_file()) { - GetNPcommID(trophyPath, index); + // Load npbind.dat using the new class + std::filesystem::path npbindPath = trophyPath / "sce_sys/npbind.dat"; + NPBindFile npbind; + if (!npbind.Load(npbindPath.string())) { + LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file"); + } + + auto npCommIds = npbind.GetNpCommIds(); + if (npCommIds.empty()) { + LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat"); + } + + bool success = true; + int trpFileIndex = 0; + + try { + // Process each TRP file in the trophy directory + for (const auto& it : std::filesystem::directory_iterator(gameSysDir)) { + if (!it.is_regular_file() || it.path().extension() != ".trp") { + continue; // Skip non-TRP files + } + + // Get NPCommID for this TRP file (if available) + std::string npCommId; + if (trpFileIndex < static_cast(npCommIds.size())) { + npCommId = npCommIds[trpFileIndex]; + LOG_DEBUG(Common_Filesystem, "Using NPCommID: {} for {}", npCommId, + it.path().filename().string()); + } else { + LOG_WARNING(Common_Filesystem, "No NPCommID found for TRP file index {}", + trpFileIndex); + } Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { - LOG_CRITICAL(Common_Filesystem, "Unable to open trophy file for read"); - return false; + LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string()); + success = false; + continue; } TrpHeader header; - file.Read(header); - if (header.magic != 0xDCA24D00) { - LOG_CRITICAL(Common_Filesystem, "Wrong trophy magic number"); - return false; + if (!file.Read(header)) { + LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}", + it.path().string()); + success = false; + continue; + } + + if (header.magic != TRP_MAGIC) { + LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string()); + success = false; + continue; } s64 seekPos = sizeof(TrpHeader); std::filesystem::path trpFilesPath( Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId / "TrophyFiles" / it.path().stem()); - std::filesystem::create_directories(trpFilesPath / "Icons"); - std::filesystem::create_directory(trpFilesPath / "Xml"); + // Create output directories + if (!std::filesystem::create_directories(trpFilesPath / "Icons") || + !std::filesystem::create_directories(trpFilesPath / "Xml")) { + LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", titleId); + success = false; + continue; + } + + // Process each entry in the TRP file for (int i = 0; i < header.entry_num; i++) { if (!file.Seek(seekPos)) { - LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); - return false; + LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset"); + success = false; + break; } - seekPos += (s64)header.entry_size; + seekPos += static_cast(header.entry_size); + TrpEntry entry; - file.Read(entry); - std::string_view name(entry.entry_name); - if (entry.flag == 0) { // PNG - if (!file.Seek(entry.entry_pos)) { - LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); - return false; - } - std::vector icon(entry.entry_len); - file.Read(icon); - Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon); + if (!file.Read(entry)) { + LOG_ERROR(Common_Filesystem, "Failed to read TRP entry"); + success = false; + break; } - if (entry.flag == 3 && np_comm_id[0] == 'N' && - np_comm_id[1] == 'P') { // ESFM, encrypted. - if (!file.Seek(entry.entry_pos)) { - LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset"); - return false; + + std::string_view name(entry.entry_name); + + if (entry.flag == ENTRY_FLAG_PNG) { + if (!ProcessPngEntry(file, entry, trpFilesPath, name)) { + success = false; + // Continue with next entry } - file.Read(esfmIv); // get iv key. - // Skip the first 16 bytes which are the iv key on every entry as we want a - // clean xml file. - std::vector ESFM(entry.entry_len - iv_len); - std::vector XML(entry.entry_len - iv_len); - if (!file.Seek(entry.entry_pos + iv_len)) { - LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset"); - return false; - } - file.Read(ESFM); - DecryptEFSM(user_key, np_comm_id, esfmIv, ESFM, XML); // decrypt - removePadding(XML); - std::string xml_name = entry.entry_name; - size_t pos = xml_name.find("ESFM"); - if (pos != std::string::npos) - xml_name.replace(pos, xml_name.length(), "XML"); - std::filesystem::path path = trpFilesPath / "Xml" / xml_name; - size_t written = Common::FS::IOFile::WriteBytes(path, XML); - if (written != XML.size()) { - LOG_CRITICAL( - Common_Filesystem, - "Trophy XML {} write failed, wanted to write {} bytes, wrote {}", - fmt::UTF(path.u8string()), XML.size(), written); + } else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) { + // Check if we have a valid NPCommID for decryption + if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') { + if (!ProcessEncryptedXmlEntry(file, entry, trpFilesPath, name, user_key, + npCommId)) { + success = false; + // Continue with next entry + } + } else { + LOG_WARNING(Common_Filesystem, + "Skipping encrypted XML entry - invalid NPCommID"); + // Skip this entry but continue } + } else { + LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}", + static_cast(entry.flag), name); } } + + trpFileIndex++; } - index++; + } catch (const std::filesystem::filesystem_error& e) { + LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what()); + return false; + } catch (const std::exception& e) { + LOG_CRITICAL(Common_Filesystem, "Error during trophy extraction: {}", e.what()); + return false; } + + if (success) { + LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex, + titleId); + } + + return success; +} + +bool TRP::ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry, + const std::filesystem::path& outputPath, std::string_view name) { + if (!file.Seek(entry.entry_pos)) { + LOG_ERROR(Common_Filesystem, "Failed to seek to PNG entry offset"); + return false; + } + + std::vector icon(entry.entry_len); + if (!file.Read(icon)) { + LOG_ERROR(Common_Filesystem, "Failed to read PNG data"); + return false; + } + + auto outputFile = outputPath / "Icons" / name; + size_t written = Common::FS::IOFile::WriteBytes(outputFile, icon); + if (written != icon.size()) { + LOG_ERROR(Common_Filesystem, "PNG write failed: wanted {} bytes, wrote {}", icon.size(), + written); + return false; + } + return true; } + +bool TRP::ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry, + const std::filesystem::path& outputPath, std::string_view name, + const std::array& user_key, + const std::string& npCommId) { + constexpr size_t IV_LEN = 16; + + if (!file.Seek(entry.entry_pos)) { + LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted XML entry offset"); + return false; + } + + std::array esfmIv; + if (!file.Read(esfmIv)) { + LOG_ERROR(Common_Filesystem, "Failed to read IV for encrypted XML"); + return false; + } + + if (entry.entry_len <= IV_LEN) { + LOG_ERROR(Common_Filesystem, "Encrypted XML entry too small"); + return false; + } + + // Skip to the encrypted data (after IV) + if (!file.Seek(entry.entry_pos + IV_LEN)) { + LOG_ERROR(Common_Filesystem, "Failed to seek to encrypted data"); + return false; + } + + std::vector ESFM(entry.entry_len - IV_LEN); + std::vector XML(entry.entry_len - IV_LEN); + + if (!file.Read(ESFM)) { + LOG_ERROR(Common_Filesystem, "Failed to read encrypted XML data"); + return false; + } + + // Decrypt the data - FIX: Don't check return value since DecryptEFSM returns void + std::span key_span(user_key); + + // Convert npCommId string to span (pad or truncate to 16 bytes) + std::array npcommid_array{}; + size_t copy_len = std::min(npCommId.size(), npcommid_array.size()); + std::memcpy(npcommid_array.data(), npCommId.data(), copy_len); + std::span npcommid_span(npcommid_array); + + DecryptEFSM(key_span, npcommid_span, esfmIv, ESFM, XML); + + // Remove padding + removePadding(XML); + + // Create output filename (replace ESFM with XML) + std::string xml_name(entry.entry_name); + size_t pos = xml_name.find("ESFM"); + if (pos != std::string::npos) { + xml_name.replace(pos, 4, "XML"); + } + + auto outputFile = outputPath / "Xml" / xml_name; + size_t written = Common::FS::IOFile::WriteBytes(outputFile, XML); + if (written != XML.size()) { + LOG_ERROR(Common_Filesystem, "XML write failed: wanted {} bytes, wrote {}", XML.size(), + written); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/core/file_format/trp.h b/src/core/file_format/trp.h index 01207475b..2b52a4d57 100644 --- a/src/core/file_format/trp.h +++ b/src/core/file_format/trp.h @@ -8,6 +8,10 @@ #include "common/io_file.h" #include "common/types.h" +static constexpr u32 TRP_MAGIC = 0xDCA24D00; +static constexpr u8 ENTRY_FLAG_PNG = 0; +static constexpr u8 ENTRY_FLAG_ENCRYPTED_XML = 3; + struct TrpHeader { u32_be magic; // (0xDCA24D00) u32_be version; @@ -33,9 +37,14 @@ public: TRP(); ~TRP(); bool Extract(const std::filesystem::path& trophyPath, const std::string titleId); - void GetNPcommID(const std::filesystem::path& trophyPath, int index); private: + bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry, + const std::filesystem::path& outputPath, std::string_view name); + bool ProcessEncryptedXmlEntry(Common::FS::IOFile& file, const TrpEntry& entry, + const std::filesystem::path& outputPath, std::string_view name, + const std::array& user_key, const std::string& npCommId); + std::vector NPcommID = std::vector(12); std::array np_comm_id{}; std::array esfmIv{}; diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index f6c34ae94..cba95fe37 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -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; } diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index b64bb47fd..2bec1bf0f 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -34,7 +34,7 @@ u32 GetChannelMask(u32 num_channels) { case 8: return ORBIS_AJM_CHANNELMASK_7POINT1; default: - UNREACHABLE(); + UNREACHABLE_MSG("Unexpected number of channels: {}", num_channels); } } diff --git a/src/core/libraries/ajm/ajm_aac.cpp b/src/core/libraries/ajm/ajm_aac.cpp index b96394b72..061b77890 100644 --- a/src/core/libraries/ajm/ajm_aac.cpp +++ b/src/core/libraries/ajm/ajm_aac.cpp @@ -5,20 +5,45 @@ #include "ajm_aac.h" #include "ajm_result.h" +#include // using this internal header to manually configure the decoder in RAW mode #include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h" -#include -#include +#include // std::transform +#include // std::back_inserter +#include namespace Libraries::Ajm { +std::span AjmAacDecoder::GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const { + const auto pcm_data = std::span(m_pcm_buffer).subspan(skipped_pcm); + return pcm_data.subspan(0, std::min(pcm_data.size(), max_pcm)); +} + +template <> +size_t AjmAacDecoder::WriteOutputSamples(SparseOutputBuffer& out, std::span pcm) { + if (pcm.empty()) { + return 0; + } + + m_resample_buffer.clear(); + constexpr float inv_scale = 1.0f / std::numeric_limits::max(); + std::transform(pcm.begin(), pcm.end(), std::back_inserter(m_resample_buffer), + [](auto sample) { return float(sample) * inv_scale; }); + + return out.Write(std::span(m_resample_buffer)); +} + AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels) - : m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(2048 * 8), - m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {} + : m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(1024 * 8), + m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) { + m_resample_buffer.reserve(m_pcm_buffer.size()); +} AjmAacDecoder::~AjmAacDecoder() { - aacDecoder_Close(m_decoder); + if (m_decoder) { + aacDecoder_Close(m_decoder); + } } TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) { @@ -98,7 +123,7 @@ AjmSidebandFormat AjmAacDecoder::GetFormat() const { .num_channels = static_cast(info->numChannels), .channel_mask = GetChannelMask(info->numChannels), .sampl_freq = static_cast(info->sampleRate), - .sample_encoding = m_format, // AjmFormatEncoding + .sample_encoding = m_format, .bitrate = static_cast(info->bitRate), }; } @@ -130,8 +155,7 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe const UINT sizes[] = {static_cast(input.size())}; UINT valid = sizes[0]; aacDecoder_Fill(m_decoder, buffers, sizes, &valid); - auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast(m_pcm_buffer.data()), - m_pcm_buffer.size() / 2, 0); + auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0); switch (ret) { case AAC_DEC_OK: @@ -167,16 +191,16 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame; size_t pcm_written = 0; + auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels); switch (m_format) { case AjmFormatEncoding::S16: - pcm_written = WriteOutputSamples(output, skip_samples * info->numChannels, - max_samples * info->numChannels); + pcm_written = output.Write(pcm); break; case AjmFormatEncoding::S32: UNREACHABLE_MSG("NOT IMPLEMENTED"); break; case AjmFormatEncoding::Float: - UNREACHABLE_MSG("NOT IMPLEMENTED"); + pcm_written = WriteOutputSamples(output, pcm); break; default: UNREACHABLE(); @@ -191,4 +215,4 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe return result; } -} // namespace Libraries::Ajm +} // namespace Libraries::Ajm \ No newline at end of file diff --git a/src/core/libraries/ajm/ajm_aac.h b/src/core/libraries/ajm/ajm_aac.h index 7ca8ecbf8..4ff55d843 100644 --- a/src/core/libraries/ajm/ajm_aac.h +++ b/src/core/libraries/ajm/ajm_aac.h @@ -52,22 +52,18 @@ private: }; template - size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) { - std::span pcm_data{reinterpret_cast(m_pcm_buffer.data()), - m_pcm_buffer.size() / sizeof(T)}; - pcm_data = pcm_data.subspan(skipped_pcm); - const auto pcm_size = std::min(u32(pcm_data.size()), max_pcm); - return output.Write(pcm_data.subspan(0, pcm_size)); - } + size_t WriteOutputSamples(SparseOutputBuffer& output, std::span pcm); + std::span GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const; const AjmFormatEncoding m_format; const AjmAacCodecFlags m_flags; const u32 m_channels; - std::vector m_pcm_buffer; + std::vector m_pcm_buffer; + std::vector m_resample_buffer; u32 m_skip_frames = 0; InitializeParameters m_init_params = {}; AAC_DECODER_INSTANCE* m_decoder = nullptr; }; -} // namespace Libraries::Ajm +} // namespace Libraries::Ajm \ No newline at end of file diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index f4ce22b8b..d1c9374cc 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "ajm_error.h" +#include "ajm_mp3.h" +#include "ajm_result.h" + #include "common/assert.h" -#include "core/libraries/ajm/ajm_error.h" -#include "core/libraries/ajm/ajm_mp3.h" #include "core/libraries/error_codes.h" extern "C" { @@ -122,7 +124,6 @@ void AjmMp3Decoder::Reset() { avcodec_flush_buffers(m_codec_context); m_header.reset(); m_frame_samples = 0; - m_frame_size = 0; } void AjmMp3Decoder::GetInfo(void* out_info) const { @@ -141,7 +142,7 @@ void AjmMp3Decoder::GetInfo(void* out_info) const { u32 AjmMp3Decoder::GetMinimumInputSize() const { // 4 bytes is for mp3 header that contains frame_size - return std::max(m_frame_size, 4); + return 4; } DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, @@ -149,12 +150,11 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuff DecoderResult result{}; AVPacket* pkt = av_packet_alloc(); - if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) { - m_header = std::byteswap(*reinterpret_cast(in_buf.data())); - AjmDecMp3ParseFrame info{}; - ParseMp3Header(in_buf.data(), in_buf.size(), true, &info); - m_frame_samples = info.samples_per_channel; - m_frame_size = info.frame_size; + m_header = std::byteswap(*reinterpret_cast(in_buf.data())); + AjmDecMp3ParseFrame info{}; + ParseMp3Header(in_buf.data(), in_buf.size(), true, &info); + m_frame_samples = info.samples_per_channel; + if (info.total_samples != 0 || info.encoder_delay != 0) { gapless.init = { .total_samples = info.total_samples, .skip_samples = static_cast(info.encoder_delay), @@ -163,6 +163,10 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuff gapless.current = gapless.init; } + if (in_buf.size() < info.frame_size) { + result.result |= ORBIS_AJM_RESULT_PARTIAL_INPUT; + } + int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(), in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); @@ -424,11 +428,7 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ frame->encoder_delay = std::byteswap(*reinterpret_cast(p_fgh + 1)); frame->total_samples = std::byteswap(*reinterpret_cast(p_fgh + 3)); frame->ofl_type = AjmDecMp3OflType::Fgh; - } else { - LOG_ERROR(Lib_Ajm, "FGH header CRC is incorrect."); } - } else { - LOG_ERROR(Lib_Ajm, "Could not find vendor header."); } } diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index ecbc77051..1113e222a 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -99,7 +99,6 @@ private: SwrContext* m_swr_context = nullptr; std::optional m_header; u32 m_frame_samples = 0; - u32 m_frame_size = 0; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 1523c2703..a5952c7ea 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -345,7 +345,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar if (addcont_count > 0) { SystemService::OrbisSystemServiceEvent event{}; event.event_type = SystemService::OrbisSystemServiceEventType::EntitlementUpdate; - event.service_entitlement_update.user_id = 0; + event.service_entitlement_update.userId = 0; event.service_entitlement_update.np_service_label = 0; SystemService::PushSystemServiceEvent(event); } diff --git a/src/core/libraries/audio/audioin.cpp b/src/core/libraries/audio/audioin.cpp index 563b5ae14..55c2891b2 100644 --- a/src/core/libraries/audio/audioin.cpp +++ b/src/core/libraries/audio/audioin.cpp @@ -1,23 +1,264 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "audioin_backend.h" +#include "audioin_error.h" #include "common/logging/log.h" #include "core/libraries/audio/audioin.h" -#include "core/libraries/audio/sdl_in.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" namespace Libraries::AudioIn { -static std::unique_ptr audio = std::make_unique(); +std::array, ORBIS_AUDIO_IN_NUM_PORTS> port_table{}; +std::shared_mutex port_table_mutex; +std::mutex port_allocation_mutex; -int PS4_SYSV_ABI sceAudioInChangeAppModuleState() { - LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); - return ORBIS_OK; +static std::unique_ptr audio; + +/* + * Helper functions + **/ +static int GetPortId(s32 handle) { + int port_id = handle & 0xFF; + + if (port_id >= ORBIS_AUDIO_IN_NUM_PORTS) { + LOG_ERROR(Lib_AudioIn, "Invalid port"); + return ORBIS_AUDIO_IN_ERROR_PORT_FULL; + } + + if ((handle & 0x7f000000) != 0x30000000) { + LOG_ERROR(Lib_AudioIn, "Invalid handle format"); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + + return port_id; +} + +static s32 GetPortType(s32 handle) { + return (handle >> 16) & 0xFF; +} + +static int AllocatePort(OrbisAudioInType type) { + // TODO implement port type ranges if needed + for (int i = 0; i <= ORBIS_AUDIO_IN_NUM_PORTS; i++) { + std::shared_lock read_lock{port_table_mutex}; + if (!port_table[i]) { + return i; + } + } + return -1; +} +/* + * sceAudioIn implementation + **/ +static bool initOnce = false; +int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type, + u32 index, u32 len, u32 freq, u32 param) { + LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId, + type, index, len, freq, param); + if (!initOnce) { + // sceAudioInInit doesn't seem to be called by most apps before sceAudioInOpen so we init + // here + audio = std::make_unique(); + initOnce = true; + } + + if (len == 0 || len > 2048) { + LOG_ERROR(Lib_AudioIn, "Invalid size"); + return ORBIS_AUDIO_IN_ERROR_INVALID_SIZE; + } + + // Validate parameters + OrbisAudioInType in_type = static_cast(type); + OrbisAudioInParamFormat format = static_cast(param); + + if (format != OrbisAudioInParamFormat::S16Mono && + format != OrbisAudioInParamFormat::S16Stereo) { + LOG_ERROR(Lib_AudioIn, "Invalid format"); + return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM; + } + + if (freq != 16000 && freq != 48000) { + LOG_ERROR(Lib_AudioIn, "Invalid sample rate"); + return ORBIS_AUDIO_IN_ERROR_INVALID_FREQ; + } + + std::unique_lock lock{port_allocation_mutex}; + + // Allocate port + int port_id = AllocatePort(in_type); + if (port_id < 0) { + LOG_ERROR(Lib_AudioIn, "No free audio input ports available"); + return ORBIS_AUDIO_IN_ERROR_PORT_FULL; + } + + // Create port object + std::shared_ptr port; + try { + port = std::make_shared(); + + port->type = in_type; + port->format = format; + port->samples_num = len; + port->freq = freq; + + // Determine channel count and sample size based on format + switch (format) { + case OrbisAudioInParamFormat::S16Mono: + port->channels_num = 1; + port->sample_size = 2; + break; + case OrbisAudioInParamFormat::S16Stereo: + port->channels_num = 2; + port->sample_size = 2; + break; + default: + LOG_ERROR(Lib_AudioIn, "Unsupported audio format: {}", static_cast(format)); + return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM; + } + + // Open backend + port->impl = audio->Open(*port); + if (!port->impl) { + throw std::runtime_error("Failed to create audio backend"); + } + + } catch (const std::bad_alloc&) { + LOG_ERROR(Lib_AudioIn, "Failed to allocate memory for audio port"); + return ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY; + } catch (const std::exception& e) { + LOG_ERROR(Lib_AudioIn, "Failed to open audio input port: {}", e.what()); + return ORBIS_AUDIO_IN_ERROR_NOT_OPENED; + } + + // Store the port pointer with write lock + { + std::unique_lock write_lock{port_table_mutex}; + port_table[port_id] = port; + } + + // Create handle + s32 handle = (type << 16) | port_id | 0x30000000; + + LOG_INFO(Lib_AudioIn, "Opened audio input port {}: type={}, samples={}, freq={}, format={}", + handle, static_cast(in_type), len, freq, static_cast(format)); + return handle; +} + +int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type, + u32 index, u32 len, u32 freq, u32 param) { + LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId, + type, index, len, freq, param); + int result = sceAudioInOpen(userId, type, index, len, freq, param); + if (result < 0) { + LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result); + } + return result; } int PS4_SYSV_ABI sceAudioInClose(s32 handle) { - audio->AudioInClose(handle); + LOG_INFO(Lib_AudioIn, "called, handle={:#x}", handle); + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioIn, "Invalid port id"); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + + std::unique_lock lock{port_allocation_mutex}; + std::shared_ptr port; + + // Get and clear the port pointer with write lock + { + std::unique_lock write_lock{port_table_mutex}; + port = std::move(port_table[port_id]); + if (!port) { + LOG_ERROR(Lib_AudioIn, "Port wasn't open {}", port_id); + return ORBIS_AUDIO_IN_ERROR_NOT_OPENED; + } + port_table[port_id].reset(); + } + + // Free resources + std::scoped_lock port_lock{port->mutex}; + port->impl.reset(); + + LOG_INFO(Lib_AudioIn, "Closed audio input port {}", handle); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) { + LOG_TRACE(Lib_AudioIn, "called, handle={:#x}, dest={}", handle, fmt::ptr(dest)); + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioIn, "Invalid port id"); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + + if (!dest) { + LOG_ERROR(Lib_AudioIn, "Invalid output buffer pointer"); + return ORBIS_AUDIO_IN_ERROR_INVALID_POINTER; + } + + // Get port with read lock + std::shared_ptr port; + { + std::shared_lock read_lock{port_table_mutex}; + if (port_id < 0 || port_id >= static_cast(port_table.size())) { + LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + port = port_table[port_id]; + } + + if (!port || !port->impl) { + LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle); + return ORBIS_AUDIO_IN_ERROR_NOT_OPENED; + } + + std::scoped_lock lock{port->mutex}; + return port->impl->Read(dest); +} + +int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle) { + LOG_TRACE(Lib_AudioIn, "called, handle={:#x}", handle); + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioIn, "Invalid port id"); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + // Get port with read lock + std::shared_ptr port; + { + std::shared_lock read_lock{port_table_mutex}; + if (port_id < 0 || port_id >= static_cast(port_table.size())) { + LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id); + return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE; + } + port = port_table[port_id]; + } + + if (!port || !port->impl) { + LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle); + return ORBIS_AUDIO_IN_ERROR_NOT_OPENED; + } + + u32 silent_state = 0; + std::scoped_lock lock{port->mutex}; + if (!port->impl->IsAvailable()) { // if no mic exist or is not available + silent_state |= ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE; + } + return silent_state; +} + +/* + * Stubbed functions + **/ +int PS4_SYSV_ABI sceAudioInChangeAppModuleState() { + LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); return ORBIS_OK; } @@ -91,20 +332,6 @@ int PS4_SYSV_ABI sceAudioInGetRerouteCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioInGetSilentState() { - LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type, - u32 index, u32 len, u32 freq, u32 param) { - int result = audio->AudioInOpen(type, len, freq, param); - if (result < 0) { - LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result); - } - return result; -} - int PS4_SYSV_ABI sceAudioInHqOpenEx() { LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); return ORBIS_OK; @@ -115,10 +342,6 @@ int PS4_SYSV_ABI sceAudioInInit() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) { - return audio->AudioInInput(handle, dest); -} - int PS4_SYSV_ABI sceAudioInInputs() { LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); return ORBIS_OK; @@ -129,15 +352,6 @@ int PS4_SYSV_ABI sceAudioInIsSharedDevice() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type, - u32 index, u32 len, u32 freq, u32 param) { - int result = audio->AudioInOpen(type, len, freq, param); - if (result < 0) { - LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result); - } - return result; -} - int PS4_SYSV_ABI sceAudioInOpenEx() { LOG_ERROR(Lib_AudioIn, "(STUBBED) called"); return ORBIS_OK; diff --git a/src/core/libraries/audio/audioin.h b/src/core/libraries/audio/audioin.h index f528c730e..0eda2013e 100644 --- a/src/core/libraries/audio/audioin.h +++ b/src/core/libraries/audio/audioin.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "common/types.h" @@ -12,10 +13,31 @@ class SymbolsResolver; namespace Libraries::AudioIn { +class PortInBackend; + +constexpr s32 ORBIS_AUDIO_IN_NUM_PORTS = 7; + enum class OrbisAudioInParamFormat : u32 { S16Mono = 0, S16Stereo = 2 }; enum class OrbisAudioInType : u32 { VoiceChat = 0, General = 1, VoiceRecognition = 5 }; +constexpr int ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x00000001; +constexpr int ORBIS_AUDIO_IN_SILENT_STATE_PRIORITY_LOW = 0x00000002; +constexpr int ORBIS_AUDIO_IN_SILENT_STATE_USER_SETTING = 0x0000000; +constexpr int ORBIS_AUDIO_IN_SILENT_STATE_UNABLE_FORMAT = 0x00000008; + +struct PortIn { + std::mutex mutex; + std::unique_ptr impl{}; + OrbisAudioInType type; + OrbisAudioInParamFormat format; + + u32 samples_num = 0; + u32 freq = 0; + u32 channels_num = 0; + u32 sample_size = 0; +}; + int PS4_SYSV_ABI sceAudioInChangeAppModuleState(); int PS4_SYSV_ABI sceAudioInClose(s32 handle); int PS4_SYSV_ABI sceAudioInCountPorts(); @@ -32,7 +54,7 @@ int PS4_SYSV_ABI sceAudioInExtSetAecMode(); int PS4_SYSV_ABI sceAudioInGetGain(); int PS4_SYSV_ABI sceAudioInGetHandleStatusInfo(); int PS4_SYSV_ABI sceAudioInGetRerouteCount(); -int PS4_SYSV_ABI sceAudioInGetSilentState(); +int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle); int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type, u32 index, u32 len, u32 freq, u32 param); int PS4_SYSV_ABI sceAudioInHqOpenEx(); diff --git a/src/core/libraries/audio/audioin_backend.h b/src/core/libraries/audio/audioin_backend.h new file mode 100644 index 000000000..dc06bb27c --- /dev/null +++ b/src/core/libraries/audio/audioin_backend.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +namespace Libraries::AudioIn { + +struct PortIn; + +class PortInBackend { +public: + virtual ~PortInBackend() = default; + virtual int Read(void* out_buffer) = 0; + virtual void Clear() = 0; + virtual bool IsAvailable() = 0; +}; + +class AudioInBackend { +public: + AudioInBackend() = default; + virtual ~AudioInBackend() = default; + virtual std::unique_ptr Open(PortIn& port) = 0; +}; + +class SDLAudioIn final : public AudioInBackend { +public: + std::unique_ptr Open(PortIn& port) override; +}; + +} // namespace Libraries::AudioIn \ No newline at end of file diff --git a/src/core/libraries/audio/audioin_error.h b/src/core/libraries/audio/audioin_error.h new file mode 100644 index 000000000..c392e8194 --- /dev/null +++ b/src/core/libraries/audio/audioin_error.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +// AudioIn library +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE = 0x80260101; +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_SIZE = 0x80260102; +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_FREQ = 0x80260103; +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_TYPE = 0x80260104; +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_POINTER = 0x80260105; +constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_PARAM = 0x80260106; +constexpr int ORBIS_AUDIO_IN_ERROR_PORT_FULL = 0x80260107; +constexpr int ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY = 0x80260108; +constexpr int ORBIS_AUDIO_IN_ERROR_NOT_OPENED = 0x80260109; +constexpr int ORBIS_AUDIO_IN_ERROR_BUSY = 0x8026010A; +constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_MEMORY = 0x8026010B; +constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_IPC = 0x8026010C; diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index bb1ca65fe..100ddd51c 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -19,315 +20,238 @@ namespace Libraries::AudioOut { -std::mutex port_open_mutex{}; -std::array ports_out{}; +// Port table with shared_ptr - use std::shared_mutex for RW locking +std::array, ORBIS_AUDIO_OUT_NUM_PORTS> port_table{}; +std::shared_mutex port_table_mutex; +std::mutex port_allocation_mutex; static std::unique_ptr audio; +static std::atomic lazy_init{0}; + +// Port allocation ranges +constexpr struct PortRange { + s32 start; + s32 end; + s32 count() const { + return end - start + 1; + } +} port_ranges[] = { + {0, 7}, // MAIN + {8, 8}, // BGM + {9, 12}, // VOICE + {13, 16}, // PERSONAL + {17, 20}, // PADSPK + {21, 21}, // Type 5-8 + {22, 22}, // Audio3d (126) + {23, 23}, // AUX (127) + {24, 24}, // Type 125 +}; static AudioFormatInfo GetFormatInfo(const OrbisAudioOutParamFormat format) { static constexpr std::array format_infos = {{ // S16Mono - {false, 2, 1, {0}}, + {false, 2, 1, {0}, false}, // S16Stereo - {false, 2, 2, {0, 1}}, + {false, 2, 2, {0, 1}, false}, // S16_8CH - {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}}, + {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}, false}, // FloatMono - {true, 4, 1, {0}}, + {true, 4, 1, {0}, false}, // FloatStereo - {true, 4, 2, {0, 1}}, + {true, 4, 2, {0, 1}, false}, // Float_8CH - {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}}, + {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}, false}, // S16_8CH_Std - {false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}}, + {false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}, true}, // Float_8CH_Std - {true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}}, + {true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}, true}, }}; const auto index = static_cast(format); ASSERT_MSG(index < format_infos.size(), "Unknown audio format {}", index); return format_infos[index]; } -int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} +/* + * Helper functions + **/ +static int GetPortRange(OrbisAudioOutPort type) { + s32 _type = static_cast(type); -int PS4_SYSV_ABI sceAudioDeviceControlGet() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioDeviceControlSet() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutA3dControl() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutA3dExit() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutA3dInit() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutClose(s32 handle) { - LOG_INFO(Lib_AudioOut, "handle = {}", handle); - if (audio == nullptr) { - return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + switch (_type) { + case 0: + return 0; // MAIN + case 1: + return 1; // BGM + case 2: + return 2; // VOICE + case 3: + return 3; // PERSONAL + case 4: + return 4; // PADSPK + case 5: + return 5; // Type 5 + case 6: + return 5; // Type 6 + case 7: + return 5; // Type 7 + case 8: + return 5; // Type 8 + case 126: + return 6; // Audio3d + case 125: + return 8; // Type 125 + case 127: + return 7; // AUX + default: + return -1; } - if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { +} + +static int GetPortId(s32 handle) { + int port_id = handle & 0xFF; + + if (port_id >= ORBIS_AUDIO_OUT_NUM_PORTS) { + LOG_ERROR(Lib_AudioOut, "Invalid port"); return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - std::unique_lock open_lock{port_open_mutex}; - auto& port = ports_out.at(handle - 1); - { - std::unique_lock lock{port.mutex}; - if (!port.IsOpen()) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - std::free(port.output_buffer); - port.output_buffer = nullptr; - port.output_ready = false; - port.impl = nullptr; - } - // Stop outside of port lock scope to prevent deadlocks. - port.output_thread.Stop(); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExGetSystemInfo() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExPtClose() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExPtOpen() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetInfo() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time) { - LOG_DEBUG(Lib_AudioOut, "called, handle: {}, output time: {}", handle, fmt::ptr(output_time)); - if (!output_time) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; - } - if (handle >= ports_out.size()) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - auto& port = ports_out.at(handle - 1); - if (!port.IsOpen()) { - return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; - } - *output_time = port.last_output_time; - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { - if (audio == nullptr) { - return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; - } - if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { + if ((handle & 0x3F000000) != 0x20000000) { + LOG_ERROR(Lib_AudioOut, "Invalid port"); return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - auto& port = ports_out.at(handle - 1); - { - std::unique_lock lock{port.mutex}; - if (!port.IsOpen()) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - switch (port.type) { - case OrbisAudioOutPort::Main: - case OrbisAudioOutPort::Bgm: - case OrbisAudioOutPort::Voice: - case OrbisAudioOutPort::Audio3d: - state->output = 1; - state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels; - break; - case OrbisAudioOutPort::Personal: - case OrbisAudioOutPort::PadSpk: - state->output = 4; - state->channel = 1; - break; - case OrbisAudioOutPort::Aux: - state->output = 0; - state->channel = 0; - break; - default: - UNREACHABLE(); - } - state->rerouteCounter = 0; - state->volume = 127; + return port_id; +} + +static s32 GetPortType(s32 handle) { + return (handle >> 16) & 0xFF; +} + +static int AllocatePort(OrbisAudioOutPort type) { + int range_idx = GetPortRange(type); + if (range_idx < 0) { + return -1; } - return ORBIS_OK; -} -int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetSparkVss() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutGetSystemState() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutInit() { - LOG_TRACE(Lib_AudioOut, "called"); - if (audio != nullptr) { - return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; + const auto& range = port_ranges[range_idx]; + for (int i = range.start; i <= range.end; i++) { + std::shared_lock read_lock{port_table_mutex}; + if (!port_table[i]) { + return i; + } } - audio = std::make_unique(); - return ORBIS_OK; + return -1; } -int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; +void AdjustVol() { + if (lazy_init.load(std::memory_order_relaxed) == 0 && audio == nullptr) { + return; + } + + std::shared_lock read_lock{port_table_mutex}; + for (int i = 0; i < ORBIS_AUDIO_OUT_NUM_PORTS; i++) { + if (auto port = port_table[i]) { + std::unique_lock lock{port->mutex, std::try_to_lock}; + if (lock.owns_lock()) { + port->impl->SetVolume(port->volume); + } + } + } } -int PS4_SYSV_ABI sceAudioOutMasteringGetState() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutMasteringInit() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutMasteringSetParam() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutMasteringTerm() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutMbusInit() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -static void AudioOutputThread(PortOut* port, const std::stop_token& stop) { +static void AudioOutputThread(std::shared_ptr port, const std::stop_token& stop) { { - const auto thread_name = fmt::format("shadPS4:AudioOutputThread:{}", fmt::ptr(port)); + const auto thread_name = fmt::format("shadPS4:AudioOutputThread:{}", fmt::ptr(port.get())); Common::SetCurrentThreadName(thread_name.c_str()); } Common::AccurateTimer timer( std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->sample_rate)); + while (true) { timer.Start(); + { std::unique_lock lock{port->mutex}; + if (!port->impl || stop.stop_requested()) { + break; + } + if (port->output_ready) { port->impl->Output(port->output_buffer); port->output_ready = false; + port->last_output_time = + Kernel::sceKernelGetProcessTime(); // moved from sceAudioOutOutput TOOD recheck } } + port->output_cv.notify_one(); + if (stop.stop_requested()) { break; } + timer.End(); } } +/* + * sceAudioOut implementation + **/ +s32 PS4_SYSV_ABI sceAudioOutInit() { + LOG_TRACE(Lib_AudioOut, "called"); + + int expected = 0; + if (!lazy_init.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) { + return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; + } + + audio = std::make_unique(); + + LOG_INFO(Lib_AudioOut, "Audio system initialized"); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, OrbisAudioOutPort port_type, s32 index, u32 length, u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type) { LOG_INFO(Lib_AudioOut, - "id = {} port_type = {} index = {} length = {} sample_rate = {} " - "param_type = {} attr = {}", - user_id, magic_enum::enum_name(port_type), index, length, sample_rate, - magic_enum::enum_name(param_type.data_format.Value()), - magic_enum::enum_name(param_type.attributes.Value())); - if (audio == nullptr) { + "called, user_id={}, port_type={}({}), index={}, length={}, " + "sample_rate={}, data_format={}({}), attributes={}({})", + user_id, magic_enum::enum_name(port_type), static_cast(port_type), index, length, + sample_rate, magic_enum::enum_name(param_type.data_format.Value()), + static_cast(param_type.data_format.Value()), + magic_enum::enum_name(param_type.attributes.Value()), + static_cast(param_type.attributes.Value())); + + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { LOG_ERROR(Lib_AudioOut, "Audio out not initialized"); return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } + + if (length == 0 || length > 2048 || (length & 0xFF) != 0) { + LOG_ERROR(Lib_AudioOut, "Invalid size"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE; + } + + s32 _type = static_cast(port_type); + u32 param_raw = param_type.Unpack(); + + // Extract attributes + bool is_restricted = (param_raw & ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED) != 0; + bool is_mix_to_main = (param_raw & ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN) != 0; + + if (_type != 3 && is_mix_to_main) { + LOG_ERROR(Lib_AudioOut, "Invalid format"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; + } + + if (_type != 0 && (param_raw & 0x70000000) != 0) { + LOG_ERROR(Lib_AudioOut, "Invalid format"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; + } + if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::PadSpk) && (port_type != OrbisAudioOutPort::Audio3d && port_type != OrbisAudioOutPort::Aux)) { LOG_ERROR(Lib_AudioOut, "Invalid port type"); @@ -337,14 +261,11 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, LOG_ERROR(Lib_AudioOut, "Invalid sample rate"); return ORBIS_AUDIO_OUT_ERROR_INVALID_SAMPLE_FREQ; } - if (length != 256 && length != 512 && length != 768 && length != 1024 && length != 1280 && - length != 1536 && length != 1792 && length != 2048) { - LOG_ERROR(Lib_AudioOut, "Invalid length"); - return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE; - } + if (index != 0) { LOG_ERROR(Lib_AudioOut, "index is not valid !=0 {}", index); } + const auto format = param_type.data_format.Value(); if (format < OrbisAudioOutParamFormat::S16Mono || format > OrbisAudioOutParamFormat::Float_8CH_Std) { @@ -358,270 +279,816 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; } - std::unique_lock open_lock{port_open_mutex}; - const auto port = - std::ranges::find_if(ports_out, [&](const PortOut& p) { return !p.IsOpen(); }); - if (port == ports_out.end()) { - LOG_ERROR(Lib_AudioOut, "Audio ports are full"); + std::unique_lock lock{port_allocation_mutex}; + + // Allocate port + int port_id = AllocatePort(port_type); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "Error allocated port"); return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; } - { - std::unique_lock port_lock(port->mutex); + // Create port object + std::shared_ptr port; + try { + port = std::make_shared(); - port->type = port_type; - port->format_info = GetFormatInfo(format); + port->userId = user_id; + port->type = static_cast(_type); + port->format_info = GetFormatInfo(param_type.data_format.Value()); port->sample_rate = sample_rate; port->buffer_frames = length; - port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); + port->volume.fill(ORBIS_AUDIO_OUT_VOLUME_0DB); + port->mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT; + // Set attributes + port->is_restricted = is_restricted; + port->is_mix_to_main = is_mix_to_main; + + // Log attributes if present + if (is_restricted) { + LOG_INFO(Lib_AudioOut, "Audio port opened with RESTRICTED attribute"); + } + if (is_mix_to_main) { + LOG_INFO(Lib_AudioOut, "Audio port opened with MIX_TO_MAIN attribute"); + } + + // Create backend port->impl = audio->Open(*port); + if (!port->impl) { + throw std::runtime_error("Failed to create audio backend"); + } + // Allocate buffer port->output_buffer = std::malloc(port->BufferSize()); - port->output_ready = false; + if (!port->output_buffer) { + throw std::bad_alloc(); + } + + // Start output thread - pass shared_ptr by value to keep port alive port->output_thread.Run( - [port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); }); + [port](const std::stop_token& stop) { AudioOutputThread(port, stop); }); + + // Set initial volume + port->impl->SetVolume(port->volume); + + } catch (const std::bad_alloc&) { + return ORBIS_AUDIO_OUT_ERROR_OUT_OF_MEMORY; + } catch (const std::exception& e) { + LOG_ERROR(Lib_AudioOut, "Failed to open audio port: {}", e.what()); + return ORBIS_AUDIO_OUT_ERROR_TRANS_EVENT; } - return std::distance(ports_out.begin(), port) + 1; + + { + std::unique_lock write_lock{port_table_mutex}; + port_table[port_id] = port; + } + + // Create handle + s32 handle = (_type << 16) | port_id | 0x20000000; + return handle; } -int PS4_SYSV_ABI sceAudioOutOpenEx() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle) { + LOG_INFO(Lib_AudioOut, "handle = {:#x}", handle); + + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "Audio out not initialized"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "Invalid port id"); + return port_id; + } + + s32 port_type = GetPortType(handle); + if (port_type >= 5 && port_type <= 13) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + // Check valid types + if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 || + port_type == 127)) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + std::unique_lock lock{port_allocation_mutex}; + + std::shared_ptr port; + { + std::unique_lock write_lock{port_table_mutex}; + port = std::move(port_table[port_id]); + port_table[port_id].reset(); + } + + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port wasn't open {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + // Stop the output thread + port->output_thread.Stop(); + + std::free(port->output_buffer); + + LOG_DEBUG(Lib_AudioOut, "Closed audio port {}", port_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time) { + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "Invalid port id"); + return port_id; + } + + if (!output_time) { + LOG_ERROR(Lib_AudioOut, "Invalid pointer"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + + std::shared_ptr port; + { + std::shared_lock read_lock{port_table_mutex}; + port = port_table[port_id]; + } + + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + std::unique_lock lock{port->mutex}; + *output_time = port->last_output_time; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) { + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "Invalid port id"); + return port_id; + } + + if (!state) { + LOG_ERROR(Lib_AudioOut, "Invalid pointer"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + + std::shared_ptr port; + { + std::shared_lock read_lock{port_table_mutex}; + port = port_table[port_id]; + } + + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port is not open {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + std::unique_lock lock{port->mutex}; + + switch (port->type) { + case OrbisAudioOutPort::Main: + case OrbisAudioOutPort::Bgm: + case OrbisAudioOutPort::Audio3d: + state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY; + state->channel = port->format_info.num_channels > 2 ? 2 : port->format_info.num_channels; + break; + case OrbisAudioOutPort::Voice: + case OrbisAudioOutPort::Personal: + state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE; + state->channel = 1; + break; + case OrbisAudioOutPort::PadSpk: + state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY; + state->channel = 1; + state->volume = 127; // max + break; + case OrbisAudioOutPort::Aux: + state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL; + state->channel = 0; + break; + default: + state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN; + state->channel = 0; + break; + } + + if (port->type != OrbisAudioOutPort::PadSpk) { + state->volume = -1; // invalid + } + + state->rerouteCounter = 0; + state->flag = 0; + LOG_DEBUG(Lib_AudioOut, + "called, handle={:#x}, state={}, output={}, channel={}, volume={}, " + "rerouteCounter={}, flag={}", + handle, fmt::ptr(state), state->output, state->channel, state->volume, + state->rerouteCounter, state->flag); return ORBIS_OK; } s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) { - if (audio == nullptr) { + LOG_TRACE(Lib_AudioOut, "(STUBBED) called, handle={:#x}, ptr={}", handle, fmt::ptr(ptr)); + + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } - if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "invalid port id"); + return port_id; } - auto samples_sent = 0; - auto& port = ports_out.at(handle - 1); + s32 port_type = GetPortType(handle); + if (port_type >= 5 && port_type <= 13) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + // Check valid types + if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 || + port_type == 127)) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + std::shared_ptr port; { - std::unique_lock lock{port.mutex}; - if (!port.IsOpen()) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - port.output_cv.wait(lock, [&] { return !port.output_ready; }); - if (ptr != nullptr && port.IsOpen()) { - std::memcpy(port.output_buffer, ptr, port.BufferSize()); - port.output_ready = true; - port.last_output_time = Kernel::sceKernelGetProcessTime(); - samples_sent = port.buffer_frames * port.format_info.num_channels; + std::shared_lock read_lock{port_table_mutex}; + port = port_table[port_id]; + } + + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port is not opened {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + s32 samples_sent = 0; + { + std::unique_lock lock{port->mutex}; + port->output_cv.wait(lock, [&] { return !port->output_ready; }); + + if (ptr != nullptr) { + std::memcpy(port->output_buffer, ptr, port->BufferSize()); + port->output_ready = true; + samples_sent = port->buffer_frames * port->format_info.num_channels; } } + return samples_sent; } -int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { - int ret = 0; - for (u32 i = 0; i < num; i++) { - const auto [handle, ptr] = param[i]; - if (ret = sceAudioOutOutput(handle, ptr); ret < 0) { - return ret; +s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { + if (param) { + LOG_TRACE(Lib_AudioOut, "(STUBBED) called, param={}, num={}", fmt::ptr(param), num); + for (u32 i = 0; i < num; i++) { + LOG_TRACE(Lib_AudioOut, " [{}] handle={:#x}, ptr={}", i, param[i].handle, + fmt::ptr(param[i].ptr)); + } + } else { + LOG_TRACE(Lib_AudioOut, "(STUBBED) called, param=nullptr, num={}", num); + } + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + + if (num == 0 || num > 25) { + LOG_ERROR(Lib_AudioOut, "ports is full"); + return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; + } + + if (!param) { + LOG_ERROR(Lib_AudioOut, "invalid pointer"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + + std::vector> ports; + std::vector> locks; + ports.reserve(num); + locks.reserve(num); + + u32 buffer_frames = 0; + + { + std::shared_lock read_lock{port_table_mutex}; + + for (u32 i = 0; i < num; i++) { + int port_id = GetPortId(param[i].handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "invalid port id"); + return port_id; + } + + s32 port_type = GetPortType(param[i].handle); + if (port_type >= 5 && port_type <= 13) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + // Check valid types + if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 || + port_type == 127)) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + // Check for duplicate handles + for (u32 j = 0; j < i; j++) { + if (param[i].handle == param[j].handle) { + LOG_ERROR(Lib_AudioOut, "Duplicate audio handles: {:#x}", param[i].handle); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + } + + // Get port + auto port = port_table[port_id]; + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + ports.push_back(port); + locks.emplace_back(port->mutex); + + // Check consistent buffer size + if (i == 0) { + buffer_frames = port->buffer_frames; + } else if (port->buffer_frames != buffer_frames) { + LOG_ERROR(Lib_AudioOut, "Invalid port size"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE; + } } } - return ret; -} -int PS4_SYSV_ABI sceAudioOutPtClose() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} + // Wait for all ports to be ready + for (u32 i = 0; i < num; i++) { + ports[i]->output_cv.wait(locks[i], [&] { return !ports[i]->output_ready; }); + } -int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} + // Copy data to all ports + for (u32 i = 0; i < num; i++) { + if (param[i].ptr != nullptr) { + std::memcpy(ports[i]->output_buffer, param[i].ptr, ports[i]->BufferSize()); + ports[i]->output_ready = true; + } + } -int PS4_SYSV_ABI sceAudioOutPtOpen() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetConnections() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetDevConnection() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetJediJackVolume() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetMainOutput() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetMorpheusParam() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetPortConnections() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetPortStatuses() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetRecMode() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetSparkParam() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceAudioOutSetUsbVolume() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); - return ORBIS_OK; + return buffer_frames; } s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { - if (audio == nullptr) { + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } - if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "invalid port_id"); + return port_id; } - auto& port = ports_out.at(handle - 1); + s32 port_type = GetPortType(handle); + if (port_type >= 5 && port_type <= 13) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + // Check valid types + if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 || + port_type == 127)) { + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + if (!vol) { + LOG_ERROR(Lib_AudioOut, "Invalid pointer"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + + if (*vol > ORBIS_AUDIO_OUT_VOLUME_0DB) { + LOG_ERROR(Lib_AudioOut, "Invalid volume"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_VOLUME; + } + + // Get port with shared lock (read-only access to table) + std::shared_ptr port; { - std::unique_lock lock{port.mutex}; - if (!port.IsOpen()) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) { - if (flag & 0x1u) { - port.volume[i] = vol[i]; - } - } - port.impl->SetVolume(port.volume); - } - AdjustVol(); - return ORBIS_OK; -} - -void AdjustVol() { - if (audio == nullptr) { - return; + std::shared_lock read_lock{port_table_mutex}; + port = port_table[port_id]; } - for (int i = 0; i < ports_out.size(); i++) { - std::unique_lock lock{ports_out[i].mutex}; - if (!ports_out[i].IsOpen()) { - continue; - } - ports_out[i].impl->SetVolume(ports_out[i].volume); + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; } + + std::unique_lock lock{port->mutex}; + + // Set volumes based on flags + if (flag & ORBIS_AUDIO_VOLUME_FLAG_L_CH) + port->volume[0] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_R_CH) + port->volume[1] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_C_CH) + port->volume[2] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_LFE_CH) + port->volume[3] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_LS_CH) + port->volume[4] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_RS_CH) + port->volume[5] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_LE_CH) + port->volume[6] = *vol; + if (flag & ORBIS_AUDIO_VOLUME_FLAG_RE_CH) + port->volume[7] = *vol; + + port->impl->SetVolume(port->volume); + + return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSetVolumeDown() { +s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel) { + LOG_INFO(Lib_AudioOut, "(STUBBED) called"); + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + + int port_id = GetPortId(handle); + if (port_id < 0) { + LOG_ERROR(Lib_AudioOut, "Invalid port_id"); + return port_id; + } + + if (GetPortType(handle) != 4) { // PadSpk + LOG_ERROR(Lib_AudioOut, "Invalid port type"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; + } + + if (mixLevel > ORBIS_AUDIO_OUT_VOLUME_0DB) { + LOG_ERROR(Lib_AudioOut, "Invalid mix level"); + return ORBIS_AUDIO_OUT_ERROR_INVALID_MIXLEVEL; + } + + std::shared_ptr port; + { + std::shared_lock read_lock{port_table_mutex}; + port = port_table[port_id]; + } + + if (!port) { + LOG_ERROR(Lib_AudioOut, "Port not opened"); + return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED; + } + + std::unique_lock lock{port->mutex}; + port->mixLevelPadSpk = mixLevel; + // TODO: Apply mix level to backend + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* state) { + if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) { + LOG_ERROR(Lib_AudioOut, "audio is not init"); + return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; + } + if (state == nullptr) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + memset(state, 0, sizeof(*state)); + LOG_DEBUG(Lib_AudioOut, "called"); + return ORBIS_OK; +} + +/* + * Stubbed functions + **/ +s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast() { +s32 PS4_SYSV_ABI sceAudioDeviceControlGet() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutStartSharePlay() { +s32 PS4_SYSV_ABI sceAudioDeviceControlSet() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast() { +s32 PS4_SYSV_ABI sceAudioOutA3dControl() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutStopSharePlay() { +s32 PS4_SYSV_ABI sceAudioOutA3dExit() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSuspendResume() { +s32 PS4_SYSV_ABI sceAudioOutA3dInit() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode() { +s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo() { +s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo() { +s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode() { +s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSystemControlGet() { +s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSystemControlSet() { +s32 PS4_SYSV_ABI sceAudioOutExPtClose() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef() { +s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutSetSystemDebugState() { +s32 PS4_SYSV_ABI sceAudioOutExPtOpen() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetInfo() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutOpenEx() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSparkVss() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutMasteringGetState() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags) { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + if (flags != 0) { + return ORBIS_AUDIO_OUT_ERROR_MASTERING_INVALID_API_PARAM; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutMasteringTerm() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutMbusInit() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutPtClose() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutPtOpen() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetConnections() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetDevConnection() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetMainOutput() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetPortConnections() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetRecMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetSparkParam() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutStartSharePlay() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutStopSharePlay() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSuspendResume() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSystemControlGet() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSystemControlSet() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef() { + LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index c993582d3..ffc1f9eb1 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -17,8 +17,32 @@ class PortBackend; // Main up to 8 ports, BGM 1 port, voice up to 4 ports, // personal up to 4 ports, padspk up to 5 ports, aux 1 port -constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22; -constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value +constexpr s32 ORBIS_AUDIO_OUT_NUM_PORTS = 25; +constexpr s32 ORBIS_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value + +constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT = 11626; // default -9db +constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_0DB = 32768; // max volume + +constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED = 0x00010000; +constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN = 0x00020000; + +// Volume flags +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_L_CH = (1u << 0); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_R_CH = (1u << 1); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_C_CH = (1u << 2); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LFE_CH = (1u << 3); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LS_CH = (1u << 4); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RS_CH = (1u << 5); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LE_CH = (1u << 6); +constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RE_CH = (1u << 7); + +// Port state constants +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN = 0x00; +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY = 0x01; +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_SECONDARY = 0x02; +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY = 0x04; +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE = 0x40; +constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL = 0x80; enum class OrbisAudioOutPort { Main = 0, @@ -53,6 +77,9 @@ union OrbisAudioOutParamExtendedInformation { BitField<16, 4, OrbisAudioOutParamAttr> attributes; BitField<20, 10, u32> reserve1; BitField<31, 1, u32> unused; + u32 Unpack() const { + return *reinterpret_cast(this); + } }; struct OrbisAudioOutOutputParam { @@ -70,6 +97,12 @@ struct OrbisAudioOutPortState { u64 reserved64[2]; }; +struct OrbisAudioOutSystemState { + float loudness; + u8 reserved8[4]; + u64 reserved64[3]; +}; + struct AudioFormatInfo { bool is_float; u8 sample_size; @@ -77,6 +110,7 @@ struct AudioFormatInfo { /// Layout array remapping channel indices, specified in this order: /// FL, FR, FC, LFE, BL, BR, SL, SR std::array channel_layout; + bool is_std; [[nodiscard]] u16 FrameSize() const { return sample_size * num_channels; @@ -87,100 +121,100 @@ struct PortOut { std::mutex mutex; std::unique_ptr impl{}; - void* output_buffer; + void* output_buffer = nullptr; std::condition_variable_any output_cv; - bool output_ready; + bool output_ready = false; Kernel::Thread output_thread{}; OrbisAudioOutPort type; AudioFormatInfo format_info; - u32 sample_rate; - u32 buffer_frames; - u64 last_output_time; + u32 sample_rate = 48000; + u32 buffer_frames = 1024; + u64 last_output_time = 0; std::array volume; - - [[nodiscard]] bool IsOpen() const { - return impl != nullptr; - } + s32 userId = 0; + s32 mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT; + bool is_restricted = false; + bool is_mix_to_main = false; [[nodiscard]] u32 BufferSize() const { return buffer_frames * format_info.FrameSize(); } }; -int PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); -int PS4_SYSV_ABI sceAudioDeviceControlGet(); -int PS4_SYSV_ABI sceAudioDeviceControlSet(); -int PS4_SYSV_ABI sceAudioOutA3dControl(); -int PS4_SYSV_ABI sceAudioOutA3dExit(); -int PS4_SYSV_ABI sceAudioOutA3dInit(); -int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid(); -int PS4_SYSV_ABI sceAudioOutChangeAppModuleState(); -int PS4_SYSV_ABI sceAudioOutClose(s32 handle); -int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid(); -int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode(); -int PS4_SYSV_ABI sceAudioOutExGetSystemInfo(); -int PS4_SYSV_ABI sceAudioOutExPtClose(); -int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime(); -int PS4_SYSV_ABI sceAudioOutExPtOpen(); -int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode(); -int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid(); -int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo(); -int PS4_SYSV_ABI sceAudioOutGetInfo(); -int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum(); -int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time); -int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state); -int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType(); -int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo(); -int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2(); -int PS4_SYSV_ABI sceAudioOutGetSparkVss(); -int PS4_SYSV_ABI sceAudioOutGetSystemState(); -int PS4_SYSV_ABI sceAudioOutInit(); -int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession(); -int PS4_SYSV_ABI sceAudioOutMasteringGetState(); -int PS4_SYSV_ABI sceAudioOutMasteringInit(); -int PS4_SYSV_ABI sceAudioOutMasteringSetParam(); -int PS4_SYSV_ABI sceAudioOutMasteringTerm(); -int PS4_SYSV_ABI sceAudioOutMbusInit(); +s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); +s32 PS4_SYSV_ABI sceAudioDeviceControlGet(); +s32 PS4_SYSV_ABI sceAudioDeviceControlSet(); +s32 PS4_SYSV_ABI sceAudioOutA3dControl(); +s32 PS4_SYSV_ABI sceAudioOutA3dExit(); +s32 PS4_SYSV_ABI sceAudioOutA3dInit(); +s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid(); +s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState(); +s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle); +s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid(); +s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode(); +s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo(); +s32 PS4_SYSV_ABI sceAudioOutExPtClose(); +s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime(); +s32 PS4_SYSV_ABI sceAudioOutExPtOpen(); +s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode(); +s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid(); +s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo(); +s32 PS4_SYSV_ABI sceAudioOutGetInfo(); +s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum(); +s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time); +s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state); +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType(); +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo(); +s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2(); +s32 PS4_SYSV_ABI sceAudioOutGetSparkVss(); +s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* state); +s32 PS4_SYSV_ABI sceAudioOutInit(); +s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession(); +s32 PS4_SYSV_ABI sceAudioOutMasteringGetState(); +s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags); +s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam(); +s32 PS4_SYSV_ABI sceAudioOutMasteringTerm(); +s32 PS4_SYSV_ABI sceAudioOutMbusInit(); s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, OrbisAudioOutPort port_type, s32 index, u32 length, u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type); -int PS4_SYSV_ABI sceAudioOutOpenEx(); +s32 PS4_SYSV_ABI sceAudioOutOpenEx(); s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr); s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num); -int PS4_SYSV_ABI sceAudioOutPtClose(); -int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime(); -int PS4_SYSV_ABI sceAudioOutPtOpen(); -int PS4_SYSV_ABI sceAudioOutSetConnections(); -int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser(); -int PS4_SYSV_ABI sceAudioOutSetDevConnection(); -int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode(); -int PS4_SYSV_ABI sceAudioOutSetJediJackVolume(); -int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume(); -int PS4_SYSV_ABI sceAudioOutSetMainOutput(); -int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(); -int PS4_SYSV_ABI sceAudioOutSetMorpheusParam(); -int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode(); -int PS4_SYSV_ABI sceAudioOutSetPortConnections(); -int PS4_SYSV_ABI sceAudioOutSetPortStatuses(); -int PS4_SYSV_ABI sceAudioOutSetRecMode(); -int PS4_SYSV_ABI sceAudioOutSetSparkParam(); -int PS4_SYSV_ABI sceAudioOutSetUsbVolume(); +s32 PS4_SYSV_ABI sceAudioOutPtClose(); +s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime(); +s32 PS4_SYSV_ABI sceAudioOutPtOpen(); +s32 PS4_SYSV_ABI sceAudioOutSetConnections(); +s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser(); +s32 PS4_SYSV_ABI sceAudioOutSetDevConnection(); +s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode(); +s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume(); +s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume(); +s32 PS4_SYSV_ABI sceAudioOutSetMainOutput(); +s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel); +s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam(); +s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode(); +s32 PS4_SYSV_ABI sceAudioOutSetPortConnections(); +s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses(); +s32 PS4_SYSV_ABI sceAudioOutSetRecMode(); +s32 PS4_SYSV_ABI sceAudioOutSetSparkParam(); +s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume(); s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol); -int PS4_SYSV_ABI sceAudioOutSetVolumeDown(); -int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast(); -int PS4_SYSV_ABI sceAudioOutStartSharePlay(); -int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast(); -int PS4_SYSV_ABI sceAudioOutStopSharePlay(); -int PS4_SYSV_ABI sceAudioOutSuspendResume(); -int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode(); -int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo(); -int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo(); -int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode(); -int PS4_SYSV_ABI sceAudioOutSystemControlGet(); -int PS4_SYSV_ABI sceAudioOutSystemControlSet(); -int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef(); -int PS4_SYSV_ABI sceAudioOutSetSystemDebugState(); +s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown(); +s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast(); +s32 PS4_SYSV_ABI sceAudioOutStartSharePlay(); +s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast(); +s32 PS4_SYSV_ABI sceAudioOutStopSharePlay(); +s32 PS4_SYSV_ABI sceAudioOutSuspendResume(); +s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode(); +s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo(); +s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo(); +s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode(); +s32 PS4_SYSV_ABI sceAudioOutSystemControlGet(); +s32 PS4_SYSV_ABI sceAudioOutSystemControlSet(); +s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef(); +s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState(); void AdjustVol(); void RegisterLib(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp deleted file mode 100644 index 46dd33d73..000000000 --- a/src/core/libraries/audio/sdl_audio.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include "common/config.h" -#include "common/logging/log.h" -#include "core/libraries/audio/audioout.h" -#include "core/libraries/audio/audioout_backend.h" - -#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro -namespace Libraries::AudioOut { - -class SDLPortBackend : public PortBackend { -public: - explicit SDLPortBackend(const PortOut& port) - : frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) { - const SDL_AudioSpec fmt = { - .format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE, - .channels = port.format_info.num_channels, - .freq = static_cast(port.sample_rate), - }; - - // Determine port type - std::string port_name = port.type == OrbisAudioOutPort::PadSpk - ? Config::getPadSpkOutputDevice() - : Config::getMainOutputDevice(); - SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID; - if (port_name == "None") { - stream = nullptr; - return; - } else if (port_name == "Default Device") { - dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - } else { - try { - SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr); - for (; dev_array != 0;) { - std::string dev_name(SDL_GetAudioDeviceName(*dev_array)); - if (dev_name == port_name) { - dev_id = *dev_array; - break; - } else { - dev_array++; - } - } - if (dev_id == SDL_INVALID_AUDIODEVICEID) { - LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name); - dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - } - } catch (const std::exception& e) { - LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name); - stream = nullptr; - return; - } - } - - // Open the audio stream - stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr); - if (stream == nullptr) { - LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); - return; - } - CalculateQueueThreshold(); - if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(), - port.format_info.num_channels)) { - LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}", - SDL_GetError()); - SDL_DestroyAudioStream(stream); - stream = nullptr; - return; - } - if (!SDL_ResumeAudioStreamDevice(stream)) { - LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError()); - SDL_DestroyAudioStream(stream); - stream = nullptr; - return; - } - SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f); - } - - ~SDLPortBackend() override { - if (!stream) { - return; - } - SDL_DestroyAudioStream(stream); - stream = nullptr; - } - - void Output(void* ptr) override { - if (!stream) { - return; - } - // AudioOut library manages timing, but we still need to guard against the SDL - // audio queue stalling, which may happen during device changes, for example. - // Otherwise, latency may grow over time unbounded. - if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) { - LOG_INFO(Lib_AudioOut, "SDL audio queue backed up ({} queued, {} threshold), clearing.", - queued, queue_threshold); - SDL_ClearAudioStream(stream); - // Recalculate the threshold in case this happened because of a device change. - CalculateQueueThreshold(); - } - if (!SDL_PutAudioStreamData(stream, ptr, static_cast(guest_buffer_size))) { - LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError()); - } - } - - void SetVolume(const std::array& ch_volumes) override { - if (!stream) { - return; - } - // SDL does not have per-channel volumes, for now just take the maximum of the channels. - const auto vol = *std::ranges::max_element(ch_volumes); - if (!SDL_SetAudioStreamGain(stream, static_cast(vol) / SCE_AUDIO_OUT_VOLUME_0DB * - Config::getVolumeSlider() / 100.0f)) { - LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}", - SDL_GetError()); - } - } - -private: - void CalculateQueueThreshold() { - SDL_AudioSpec discard; - int sdl_buffer_frames; - if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard, - &sdl_buffer_frames)) { - LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}", - SDL_GetError()); - sdl_buffer_frames = 0; - } - const auto sdl_buffer_size = sdl_buffer_frames * frame_size; - const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4; - if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) { - host_buffer_size = sdl_buffer_size; - queue_threshold = new_threshold; - LOG_INFO(Lib_AudioOut, - "SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes", - guest_buffer_size, host_buffer_size, queue_threshold); - } - } - - u32 frame_size; - u32 guest_buffer_size; - u32 host_buffer_size{}; - u32 queue_threshold{}; - SDL_AudioStream* stream{}; -}; - -std::unique_ptr SDLAudioOut::Open(PortOut& port) { - return std::make_unique(port); -} - -} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio_in.cpp b/src/core/libraries/audio/sdl_audio_in.cpp new file mode 100644 index 000000000..d36811175 --- /dev/null +++ b/src/core/libraries/audio/sdl_audio_in.cpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "audioin.h" +#include "audioin_backend.h" + +namespace Libraries::AudioIn { + +class SDLInPortBackend : public PortInBackend { +public: + explicit SDLInPortBackend(const PortIn& port) : port(port) { + SDL_AudioFormat sampleFormat = SDL_AUDIO_S16; // PS4 uses S16 format + + SDL_AudioSpec fmt; + SDL_zero(fmt); + fmt.format = sampleFormat; + fmt.channels = static_cast(port.channels_num); + fmt.freq = static_cast(port.freq); + + std::string micDevStr = Config::getMicDevice(); + uint32_t devId = 0; + if (micDevStr == "None") { + nullDevice = true; + LOG_INFO(Lib_AudioIn, "Audio input disabled by configuration"); + } else if (micDevStr == "Default Device") { + devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING; + LOG_INFO(Lib_AudioIn, "Using default audio input device"); + } else { + try { + devId = static_cast(std::stoul(micDevStr)); + LOG_INFO(Lib_AudioIn, "Using audio input device ID: {}", devId); + } catch (const std::exception& e) { + nullDevice = true; + LOG_WARNING(Lib_AudioIn, "Invalid device ID '{}', disabling input", micDevStr); + } + } + + if (!nullDevice) { + stream = SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr); + if (stream) { + if (SDL_ResumeAudioStreamDevice(stream)) { + LOG_INFO(Lib_AudioIn, "Audio input opened: {} Hz, {} channels, format {}", + port.freq, port.channels_num, static_cast(port.format)); + } else { + SDL_DestroyAudioStream(stream); + stream = nullptr; + LOG_ERROR(Lib_AudioIn, "Failed to resume audio input stream"); + } + } else { + LOG_ERROR(Lib_AudioIn, "Failed to open audio input device: {}", SDL_GetError()); + } + } + + // Allocate internal buffer for null device simulation + if (!stream) { + const size_t bufferSize = port.samples_num * port.sample_size * port.channels_num; + internal_buffer = std::malloc(bufferSize); + if (internal_buffer) { + // Fill with silence + std::memset(internal_buffer, 0, bufferSize); + LOG_INFO(Lib_AudioIn, "Created null audio input buffer of {} bytes", bufferSize); + } + } + } + + ~SDLInPortBackend() override { + if (stream) { + SDL_DestroyAudioStream(stream); + } + if (internal_buffer) { + std::free(internal_buffer); + internal_buffer = nullptr; + } + } + + int Read(void* out_buffer) override { + const int bytesToRead = port.samples_num * port.sample_size * port.channels_num; + + if (stream) { + // Read from actual audio device + int attempts = 0; + while (SDL_GetAudioStreamAvailable(stream) < bytesToRead) { + SDL_Delay(1); + if (++attempts > 1000) { + return 0; + } + } + + const int bytesRead = SDL_GetAudioStreamData(stream, out_buffer, bytesToRead); + if (bytesRead < 0) { + LOG_ERROR(Lib_AudioIn, "Audio input read error: {}", SDL_GetError()); + return 0; + } + + const int framesRead = bytesRead / (port.sample_size * port.channels_num); + return framesRead; + } else if (internal_buffer) { + // Return silence from null device buffer + std::memcpy(out_buffer, internal_buffer, bytesToRead); + return port.samples_num; + } else { + // No device available + return 0; + } + } + + void Clear() override { + if (stream) { + SDL_ClearAudioStream(stream); + } + } + bool IsAvailable() override { + if (nullDevice) { + return false; + } else { + return true; + } + } + +private: + const PortIn& port; + SDL_AudioStream* stream = nullptr; + void* internal_buffer = nullptr; + bool nullDevice = false; +}; + +std::unique_ptr SDLAudioIn::Open(PortIn& port) { + return std::make_unique(port); +} + +} // namespace Libraries::AudioIn \ No newline at end of file diff --git a/src/core/libraries/audio/sdl_audio_out.cpp b/src/core/libraries/audio/sdl_audio_out.cpp new file mode 100644 index 000000000..ce2598759 --- /dev/null +++ b/src/core/libraries/audio/sdl_audio_out.cpp @@ -0,0 +1,600 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include + +#include "common/config.h" +#include "common/logging/log.h" +#include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" +#include "core/libraries/kernel/threads.h" + +// SIMD support detection +#if defined(__x86_64__) || defined(_M_X64) +#include +#define HAS_SSE2 +#endif + +#define SDL_INVALID_AUDIODEVICEID 0 + +namespace Libraries::AudioOut { + +// Volume constants +constexpr float VOLUME_0DB = 32768.0f; // 1 << 15 +constexpr float INV_VOLUME_0DB = 1.0f / VOLUME_0DB; +constexpr float VOLUME_EPSILON = 0.001f; +// Timing constants +constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms +constexpr u64 MIN_SLEEP_THRESHOLD_US = 10; +constexpr u64 TIMING_RESYNC_THRESHOLD_US = 100000; // Resync if >100ms behind + +// Queue management +constexpr u32 QUEUE_MULTIPLIER = 4; +// Memory alignment for SIMD +constexpr size_t AUDIO_BUFFER_ALIGNMENT = 32; + +// Channel positions +enum ChannelPos : u8 { + FL = 0, + FR = 1, + FC = 2, + LF = 3, + SL = 4, + SR = 5, + BL = 6, + BR = 7, + STD_SL = 6, + STD_SR = 7, + STD_BL = 4, + STD_BR = 5 +}; + +class SDLPortBackend : public PortBackend { +public: + explicit SDLPortBackend(const PortOut& port) + : frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()), + buffer_frames(port.buffer_frames), sample_rate(port.sample_rate), + num_channels(port.format_info.num_channels), is_float(port.format_info.is_float), + is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout) { + + if (!Initialize(port.type)) { + LOG_ERROR(Lib_AudioOut, "Failed to initialize SDL audio backend"); + } + } + + ~SDLPortBackend() override { + Cleanup(); + } + + void Output(void* ptr) override { + if (!stream || !internal_buffer || !convert) [[unlikely]] { + return; + } + + if (ptr == nullptr) [[unlikely]] { + return; + } + + UpdateVolumeIfChanged(); + const u64 current_time = Kernel::sceKernelGetProcessTime(); + convert(ptr, internal_buffer, buffer_frames, nullptr); + HandleTiming(current_time); + + if ((output_count++ & 0xF) == 0) { // Check every 16 outputs + ManageAudioQueue(); + } + + if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) [[unlikely]] { + LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError()); + } + + last_output_time.store(current_time, std::memory_order_release); + } + + void SetVolume(const std::array& ch_volumes) override { + if (!stream) [[unlikely]] { + return; + } + + float max_channel_gain = 0.0f; + const u32 channels_to_check = std::min(num_channels, 8u); + + for (u32 i = 0; i < channels_to_check; i++) { + const float channel_gain = static_cast(ch_volumes[i]) * INV_VOLUME_0DB; + max_channel_gain = std::max(max_channel_gain, channel_gain); + } + + const float slider_gain = Config::getVolumeSlider() * 0.01f; // Faster than /100.0f + const float total_gain = max_channel_gain * slider_gain; + + const float current = current_gain.load(std::memory_order_acquire); + if (std::abs(total_gain - current) < VOLUME_EPSILON) { + return; + } + + // Apply volume change + if (SDL_SetAudioStreamGain(stream, total_gain)) { + current_gain.store(total_gain, std::memory_order_release); + LOG_DEBUG(Lib_AudioOut, + "Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})", + total_gain, max_channel_gain, slider_gain); + } else { + LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); + } + } + + u64 GetLastOutputTime() const { + return last_output_time.load(std::memory_order_acquire); + } + +private: + bool Initialize(OrbisAudioOutPort type) { + // Calculate timing parameters + period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate; + + // Allocate aligned internal buffer for SIMD operations + internal_buffer_size = buffer_frames * sizeof(float) * num_channels; + +#ifdef _WIN32 + internal_buffer = _aligned_malloc(internal_buffer_size, AUDIO_BUFFER_ALIGNMENT); +#else + if (posix_memalign(&internal_buffer, AUDIO_BUFFER_ALIGNMENT, internal_buffer_size) != 0) { + internal_buffer = nullptr; + } +#endif + + if (!internal_buffer) { + LOG_ERROR(Lib_AudioOut, "Failed to allocate aligned audio buffer of size {}", + internal_buffer_size); + return false; + } + + // Initialize current gain + current_gain.store(Config::getVolumeSlider() * 0.01f, std::memory_order_relaxed); + + if (!SelectConverter()) { + FreeAlignedBuffer(); + return false; + } + + // Open SDL device + if (!OpenDevice(type)) { + FreeAlignedBuffer(); + return false; + } + + CalculateQueueThreshold(); + return true; + } + + void Cleanup() { + if (stream) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + } + FreeAlignedBuffer(); + } + + void FreeAlignedBuffer() { + if (internal_buffer) { +#ifdef _WIN32 + _aligned_free(internal_buffer); +#else + free(internal_buffer); +#endif + internal_buffer = nullptr; + } + } + + void UpdateVolumeIfChanged() { + const u64 current_time = Kernel::sceKernelGetProcessTime(); + + if (current_time - last_volume_check_time < VOLUME_CHECK_INTERVAL_US) { + return; + } + + last_volume_check_time = current_time; + + const float config_volume = Config::getVolumeSlider() * 0.01f; + const float stored_gain = current_gain.load(std::memory_order_acquire); + + // Only update if the difference is significant + if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) { + if (SDL_SetAudioStreamGain(stream, config_volume)) { + current_gain.store(config_volume, std::memory_order_release); + LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume); + } else { + LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); + } + } + } + + void HandleTiming(u64 current_time) { + if (next_output_time == 0) [[unlikely]] { + // First output - set initial timing + next_output_time = current_time + period_us; + return; + } + + const s64 time_diff = static_cast(current_time - next_output_time); + + if (time_diff > static_cast(TIMING_RESYNC_THRESHOLD_US)) [[unlikely]] { + // We're far behind - resync + next_output_time = current_time + period_us; + } else if (time_diff < 0) { + // We're ahead of schedule - wait + const u64 time_to_wait = static_cast(-time_diff); + next_output_time += period_us; + + if (time_to_wait > MIN_SLEEP_THRESHOLD_US) { + // Sleep for most of the wait period + const u64 sleep_duration = time_to_wait - MIN_SLEEP_THRESHOLD_US; + std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration)); + } + } else { + // Slightly behind or on time - just advance + next_output_time += period_us; + } + } + + void ManageAudioQueue() { + const auto queued = SDL_GetAudioStreamQueued(stream); + + if (queued >= queue_threshold) [[unlikely]] { + LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued, + queue_threshold); + SDL_ClearAudioStream(stream); + CalculateQueueThreshold(); + } + } + + bool OpenDevice(OrbisAudioOutPort type) { + const SDL_AudioSpec fmt = { + .format = SDL_AUDIO_F32LE, + .channels = static_cast(num_channels), + .freq = static_cast(sample_rate), + }; + + // Determine device + const std::string device_name = GetDeviceName(type); + const SDL_AudioDeviceID dev_id = SelectAudioDevice(device_name, type); + + if (dev_id == SDL_INVALID_AUDIODEVICEID) { + return false; + } + + // Create audio stream + stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr); + if (!stream) { + LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); + return false; + } + + // Configure channel mapping + if (!ConfigureChannelMap()) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + return false; + } + + // Set initial volume + const float initial_gain = current_gain.load(std::memory_order_relaxed); + if (!SDL_SetAudioStreamGain(stream, initial_gain)) { + LOG_WARNING(Lib_AudioOut, "Failed to set initial audio gain: {}", SDL_GetError()); + } + + // Start playback + if (!SDL_ResumeAudioStreamDevice(stream)) { + LOG_ERROR(Lib_AudioOut, "Failed to resume audio stream: {}", SDL_GetError()); + SDL_DestroyAudioStream(stream); + stream = nullptr; + return false; + } + + LOG_INFO(Lib_AudioOut, "Opened audio device: {} ({} Hz, {} ch, gain: {:.3f})", device_name, + sample_rate, num_channels, initial_gain); + return true; + } + + SDL_AudioDeviceID SelectAudioDevice(const std::string& device_name, OrbisAudioOutPort type) { + if (device_name == "None") { + LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}", + static_cast(type)); + return SDL_INVALID_AUDIODEVICEID; + } + + if (device_name.empty() || device_name == "Default Device") { + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + // Search for specific device + int num_devices = 0; + SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices); + + if (!dev_array) { + LOG_WARNING(Lib_AudioOut, "No audio devices found, using default"); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + SDL_AudioDeviceID selected_device = SDL_INVALID_AUDIODEVICEID; + + for (int i = 0; i < num_devices; i++) { + const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]); + if (dev_name && device_name == dev_name) { + selected_device = dev_array[i]; + break; + } + } + + SDL_free(dev_array); + + if (selected_device == SDL_INVALID_AUDIODEVICEID) { + LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default", device_name); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + return selected_device; + } + + bool ConfigureChannelMap() { + if (num_channels == 0) { + return true; + } + + std::vector channel_map(num_channels); + + if (is_std && num_channels == 8) { + // Standard 8CH layout requires remapping + channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR}; + } else { + std::copy_n(channel_layout.begin(), num_channels, channel_map.begin()); + } + + if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) { + LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError()); + return false; + } + + return true; + } + + std::string GetDeviceName(OrbisAudioOutPort type) const { + switch (type) { + case OrbisAudioOutPort::Main: + case OrbisAudioOutPort::Bgm: + return Config::getMainOutputDevice(); + case OrbisAudioOutPort::PadSpk: + return Config::getPadSpkOutputDevice(); + default: + return Config::getMainOutputDevice(); + } + } + + bool SelectConverter() { + if (is_float) { + switch (num_channels) { + case 1: + convert = &ConvertF32Mono; + break; + case 2: + convert = &ConvertF32Stereo; + break; + case 8: + convert = is_std ? &ConvertF32Std8CH : &ConvertF32_8CH; + break; + default: + LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels); + return false; + } + } else { + switch (num_channels) { + case 1: + convert = &ConvertS16Mono; + break; + case 2: +#if defined(HAS_SSE2) + convert = &ConvertS16StereoSIMD; +#else + convert = &ConvertS16Stereo; +#endif + break; + case 8: +#if defined(HAS_SSE2) + convert = &ConvertS16_8CH_SIMD; +#else + convert = &ConvertS16_8CH; +#endif + break; + default: + LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels); + return false; + } + } + + return true; + } + + void CalculateQueueThreshold() { + if (!stream) { + return; + } + + SDL_AudioSpec discard; + int sdl_buffer_frames = 0; + + if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard, + &sdl_buffer_frames)) { + LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError()); + } + + const u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels; + queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * QUEUE_MULTIPLIER; + + LOG_DEBUG(Lib_AudioOut, "Audio queue threshold: {} bytes (SDL buffer: {} frames)", + queue_threshold, sdl_buffer_frames); + } + + using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes); + + static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + for (u32 i = 0; i < frames; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + + static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const u32 num_samples = frames << 1; // * 2 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + +#ifdef HAS_SSE2 + static void ConvertS16StereoSIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 1; + u32 i = 0; + + // Process 8 samples at a time (4 stereo frames) + for (; i + 8 <= num_samples; i += 8) { + // Load 8 s16 values + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + + // Convert to 32-bit integers + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + + // Convert to float and scale + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + + // Store results + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + // Handle remaining samples + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + + static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const u32 num_samples = frames << 3; // * 8 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + +#ifdef HAS_SSE2 + static void ConvertS16_8CH_SIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 3; + u32 i = 0; + + // Process 8 samples at a time + for (; i + 8 <= num_samples; i += 8) { + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + + static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) { + std::memcpy(dst, src, frames * sizeof(float)); + } + + static void ConvertF32Stereo(const void* src, void* dst, u32 frames, const float*) { + std::memcpy(dst, src, frames * 2 * sizeof(float)); + } + + static void ConvertF32_8CH(const void* src, void* dst, u32 frames, const float*) { + std::memcpy(dst, src, frames * 8 * sizeof(float)); + } + + static void ConvertF32Std8CH(const void* src, void* dst, u32 frames, const float*) { + const float* s = static_cast(src); + float* d = static_cast(dst); + + // Channel remapping for standard 8CH layout + for (u32 i = 0; i < frames; i++) { + const u32 offset = i << 3; // * 8 + + d[offset + FL] = s[offset + FL]; + d[offset + FR] = s[offset + FR]; + d[offset + FC] = s[offset + FC]; + d[offset + LF] = s[offset + LF]; + d[offset + SL] = s[offset + STD_SL]; + d[offset + SR] = s[offset + STD_SR]; + d[offset + BL] = s[offset + STD_BL]; + d[offset + BR] = s[offset + STD_BR]; + } + } + + // Audio format parameters + const u32 frame_size; + const u32 guest_buffer_size; + const u32 buffer_frames; + const u32 sample_rate; + const u32 num_channels; + const bool is_float; + const bool is_std; + const std::array channel_layout; + + alignas(64) u64 period_us{0}; + alignas(64) std::atomic last_output_time{0}; + u64 next_output_time{0}; + u64 last_volume_check_time{0}; + u32 output_count{0}; + + // Buffers + u32 internal_buffer_size{0}; + void* internal_buffer{nullptr}; + + // Converter function pointer + ConverterFunc convert{nullptr}; + + // Volume management + alignas(64) std::atomic current_gain{1.0f}; + + // SDL audio stream + SDL_AudioStream* stream{nullptr}; + u32 queue_threshold{0}; +}; + +std::unique_ptr SDLAudioOut::Open(PortOut& port) { + return std::make_unique(port); +} + +} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_in.cpp b/src/core/libraries/audio/sdl_in.cpp deleted file mode 100644 index 30bc0c578..000000000 --- a/src/core/libraries/audio/sdl_in.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include "sdl_in.h" - -int SDLAudioIn::AudioInit() { - return SDL_InitSubSystem(SDL_INIT_AUDIO); -} - -int SDLAudioIn::AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format) { - std::scoped_lock lock{m_mutex}; - - for (int id = 0; id < static_cast(portsIn.size()); ++id) { - auto& port = portsIn[id]; - if (!port.isOpen) { - port.isOpen = true; - port.type = type; - port.samples_num = samples_num; - port.freq = freq; - port.format = format; - - SDL_AudioFormat sampleFormat; - switch (format) { - case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO: - sampleFormat = SDL_AUDIO_S16; - port.channels_num = 1; - port.sample_size = 2; - break; - case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO: - sampleFormat = SDL_AUDIO_S16; - port.channels_num = 2; - port.sample_size = 2; - break; - default: - port.isOpen = false; - return ORBIS_AUDIO_IN_ERROR_INVALID_PORT; - } - - SDL_AudioSpec fmt; - SDL_zero(fmt); - fmt.format = sampleFormat; - fmt.channels = port.channels_num; - fmt.freq = port.freq; - - std::string micDevStr = Config::getMicDevice(); - uint32_t devId; - - bool nullDevice = false; - if (micDevStr == "None") { - nullDevice = true; - } else if (micDevStr == "Default Device") { - devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING; - } else { - try { - devId = static_cast(std::stoul(micDevStr)); - } catch (const std::exception& e) { - nullDevice = true; - } - } - - port.stream = - nullDevice ? nullptr : SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr); - - if (!port.stream) { - // if stream is null, either due to configuration disabling the input, - // or no input devices present in the system, still return a valid id - // as some games require that (e.g. L.A. Noire) - return id + 1; - } - - if (SDL_ResumeAudioStreamDevice(port.stream) == false) { - SDL_DestroyAudioStream(port.stream); - port = {}; - return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL; - } - - return id + 1; - } - } - - return ORBIS_AUDIO_IN_ERROR_INVALID_PORT; -} - -int SDLAudioIn::AudioInInput(int handle, void* out_buffer) { - std::scoped_lock lock{m_mutex}; - - if (handle < 1 || handle > static_cast(portsIn.size()) || !out_buffer) - return ORBIS_AUDIO_IN_ERROR_INVALID_PORT; - - auto& port = portsIn[handle - 1]; - if (!port.isOpen) - return ORBIS_AUDIO_IN_ERROR_INVALID_PORT; - - const int bytesToRead = port.samples_num * port.sample_size * port.channels_num; - - if (out_buffer == nullptr) { - int attempts = 0; - while (SDL_GetAudioStreamAvailable(port.stream) > 0) { - SDL_Delay(1); - if (++attempts > 1000) { - return ORBIS_AUDIO_IN_ERROR_TIMEOUT; - } - } - return 0; // done - } - - int attempts = 0; - while (SDL_GetAudioStreamAvailable(port.stream) < bytesToRead) { - SDL_Delay(1); - if (++attempts > 1000) { - return ORBIS_AUDIO_IN_ERROR_TIMEOUT; - } - } - - const int bytesRead = SDL_GetAudioStreamData(port.stream, out_buffer, bytesToRead); - if (bytesRead < 0) { - // SDL_GetAudioStreamData failed - LOG_ERROR(Lib_AudioIn, "AudioInInput error: {}", SDL_GetError()); - return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL; - } - const int framesRead = bytesRead / (port.sample_size * port.channels_num); - return framesRead; -} - -void SDLAudioIn::AudioInClose(int handle) { - std::scoped_lock lock{m_mutex}; - if (handle < 1 || handle > (int)portsIn.size()) - return; - - auto& port = portsIn[handle - 1]; - if (!port.isOpen) - return; - - SDL_DestroyAudioStream(port.stream); - port = {}; -} \ No newline at end of file diff --git a/src/core/libraries/audio/sdl_in.h b/src/core/libraries/audio/sdl_in.h deleted file mode 100644 index e1b2a4682..000000000 --- a/src/core/libraries/audio/sdl_in.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -namespace Libraries::AudioIn { -enum OrbisAudioInParam { - ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO = 0, - ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO = 2 -}; -} - -#define ORBIS_AUDIO_IN_ERROR_INVALID_PORT -1 -#define ORBIS_AUDIO_IN_ERROR_TIMEOUT -2 -#define ORBIS_AUDIO_IN_ERROR_STREAM_FAIL -3 - -class SDLAudioIn { -public: - int AudioInit(); - int AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format); - int AudioInInput(int handle, void* out_buffer); - void AudioInClose(int handle); - -private: - struct AudioInPort { - bool isOpen = false; - int type = 0; - uint32_t samples_num = 0; - uint32_t freq = 0; - int channels_num = 0; - int sample_size = 0; - uint32_t format = 0; - SDL_AudioStream* stream = nullptr; - }; - - std::array portsIn; - std::mutex m_mutex; -}; diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index 2ddbcd890..3f5fdcf78 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) { return AudioOut::sceAudioOutClose(handle); } -s32 PS4_SYSV_ABI -sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id, - s32 type, const s32 index, const u32 len, const u32 freq, - const AudioOut::OrbisAudioOutParamExtendedInformation param) { +s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen( + const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id, + s32 type, const s32 index, const u32 len, const u32 freq, + const AudioOut::OrbisAudioOutParamExtendedInformation param) { LOG_INFO(Lib_Audio3d, "called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}", port_id, user_id, type, index, len, freq); @@ -422,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id, +s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id, const OrbisAudio3dOpenParameters* parameters, OrbisAudio3dPortId* port_id) { LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id, diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h index 1057c1f31..ae20e39a8 100644 --- a/src/core/libraries/audio3d/audio3d.h +++ b/src/core/libraries/audio3d/audio3d.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -15,8 +15,6 @@ class SymbolsResolver; namespace Libraries::Audio3d { -using OrbisUserServiceUserId = s32; - enum class OrbisAudio3dRate : u32 { ORBIS_AUDIO3D_RATE_48000 = 0, }; @@ -91,7 +89,8 @@ struct Audio3dState { }; s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle); -s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, OrbisUserServiceUserId user_id, +s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, + Libraries::UserService::OrbisUserServiceUserId user_id, s32 type, s32 index, u32 len, u32 freq, AudioOut::OrbisAudioOutParamExtendedInformation param); s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, void* ptr); @@ -127,7 +126,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* qu u32* queue_available); s32 PS4_SYSV_ABI sceAudio3dPortGetState(); s32 PS4_SYSV_ABI sceAudio3dPortGetStatus(); -s32 PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId user_id, +s32 PS4_SYSV_ABI sceAudio3dPortOpen(Libraries::UserService::OrbisUserServiceUserId user_id, const OrbisAudio3dOpenParameters* parameters, OrbisAudio3dPortId* port_id); s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking); diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp index f42f690ed..4b42fc8c2 100644 --- a/src/core/libraries/avplayer/avplayer_common.cpp +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -30,7 +30,15 @@ AvPlayerSourceType GetSourceType(std::string_view path) { } // schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond - auto ext = name.substr(name.rfind('.')); + + // Find extension dot + auto dot_pos = name.rfind('.'); + if (dot_pos == std::string_view::npos) { + return AvPlayerSourceType::Unknown; + } + + // Extract extension (".ext/anything" or ".ext") + auto ext = name.substr(dot_pos); if (ext.empty()) { return AvPlayerSourceType::Unknown; } diff --git a/src/core/libraries/companion/companion_httpd.cpp b/src/core/libraries/companion/companion_httpd.cpp index a8756c4cd..6ffc8c052 100644 --- a/src/core/libraries/companion/companion_httpd.cpp +++ b/src/core/libraries/companion/companion_httpd.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -16,7 +16,7 @@ s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value, } s32 PS4_SYSV_ABI -sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId) { +sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId userId) { LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/companion/companion_util.cpp b/src/core/libraries/companion/companion_util.cpp index 758cfface..2794c49af 100644 --- a/src/core/libraries/companion/companion_util.cpp +++ b/src/core/libraries/companion/companion_util.cpp @@ -29,10 +29,9 @@ u32 PS4_SYSV_ABI getEvent(sceCompanionUtilContext* ctx, sceCompanionUtilEvent* o } s32 PS4_SYSV_ABI sceCompanionUtilGetEvent(sceCompanionUtilEvent* outEvent) { - sceCompanionUtilContext* ctx = nullptr; - u32 ret = getEvent(ctx, outEvent, 1); + u32 ret = ORBIS_COMPANION_UTIL_NO_EVENT; - LOG_DEBUG(Lib_CompanionUtil, "(STUBBED) called ret: {}", ret); + LOG_DEBUG(Lib_CompanionUtil, "(STUBBED) called ret: {:#x}", ret); return ret; } diff --git a/src/core/libraries/game_live_streaming/gamelivestreaming.h b/src/core/libraries/game_live_streaming/gamelivestreaming.h index 0bab969bd..5ceee8ff5 100644 --- a/src/core/libraries/game_live_streaming/gamelivestreaming.h +++ b/src/core/libraries/game_live_streaming/gamelivestreaming.h @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/types.h" +#include "core/libraries/system/userservice.h" namespace Core::Loader { class SymbolsResolver; @@ -15,11 +16,11 @@ struct OrbisGameLiveStreamingStatus { bool isOnAir; u8 align[3]; u32 spectatorCounts; - s32 userId; + Libraries::UserService::OrbisUserServiceUserId userId; u8 reserved[60]; }; struct OrbisGameLiveStreamingStatus2 { - s32 userId; + Libraries::UserService::OrbisUserServiceUserId userId; bool isOnAir; u8 align[3]; u32 spectatorCounts; diff --git a/src/core/libraries/ime/ime_dialog.cpp b/src/core/libraries/ime/ime_dialog.cpp index 8ee4a0407..226570bd6 100644 --- a/src/core/libraries/ime/ime_dialog.cpp +++ b/src/core/libraries/ime/ime_dialog.cpp @@ -115,9 +115,199 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u3 return Error::OK; } -int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param, + const OrbisImeParamExtended* extended, + u32* width, u32* height) { + if (!param || !width || !height) { + return Error::INVALID_ADDRESS; + } + + // Check parameter bounds + if (static_cast(param->type) > 4) { + return Error::INVALID_ARG; + } + + if (extended) { + // Check panel priority for full panel mode (Accent = 3) + if (extended->priority == OrbisImePanelPriority::Accent) { + // Full panel mode - return maximum size + if ((param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) != + OrbisImeOption::DEFAULT) { + *width = 2560; // For 4K/5K displays + *height = 1440; + } else { + *width = 1920; + *height = 1080; + } + LOG_DEBUG(Lib_ImeDialog, "Full panel mode: width={}, height={}", *width, *height); + return Error::OK; + } + } + + // First get the base panel size from the basic function + Error result = sceImeDialogGetPanelSize(param, width, height); + if (result != Error::OK) { + return result; + } + + // Adjust based on IME type + switch (param->type) { + case OrbisImeType::Default: + case OrbisImeType::BasicLatin: + case OrbisImeType::Url: + case OrbisImeType::Mail: + // Standard IME types + if ((param->option & OrbisImeOption::PASSWORD) != OrbisImeOption::DEFAULT) { + *height = *height + 20; + } + if ((param->option & OrbisImeOption::MULTILINE) != OrbisImeOption::DEFAULT) { + *height = *height * 3 / 2; + } + break; + + case OrbisImeType::Number: + *width = *width * 3 / 4; + *height = *height * 2 / 3; + break; + + default: + // Unknown type, use default size + break; + } + + // Apply extended options if provided + if (extended) { + // Handle extended option flags + if ((extended->option & OrbisImeExtOption::PRIORITY_FULL_WIDTH) != + OrbisImeExtOption::DEFAULT) { + // Full width priority + bool use_2k = (param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) != + OrbisImeOption::DEFAULT; + *width = use_2k ? 1200 : 800; + LOG_DEBUG(Lib_ImeDialog, "Full width priority: width={}", *width); + } + + if ((extended->option & OrbisImeExtOption::PRIORITY_FIXED_PANEL) != + OrbisImeExtOption::DEFAULT) { + // Fixed panel size + *width = 600; + *height = 400; + LOG_DEBUG(Lib_ImeDialog, "Fixed panel: width={}, height={}", *width, *height); + } + + switch (extended->priority) { + case OrbisImePanelPriority::Alphabet: + *width = 600; + *height = 400; + break; + + case OrbisImePanelPriority::Symbol: + *width = 500; + *height = 300; + break; + + case OrbisImePanelPriority::Accent: + // Already handled + break; + + case OrbisImePanelPriority::Default: + default: + // Use calculated sizes + break; + } + + if ((extended->option & OrbisImeExtOption::INIT_EXT_KEYBOARD_MODE) != + OrbisImeExtOption::DEFAULT) { + if (extended->ext_keyboard_mode != 0) { + // Check for high-res mode flags + if ((extended->ext_keyboard_mode & + static_cast( + OrbisImeInitExtKeyboardMode::INPUT_METHOD_STATE_FULL_WIDTH)) != 0) { + *width = *width * 5 / 4; + } + + // Check for format characters enabled + if ((extended->ext_keyboard_mode & + static_cast( + OrbisImeInitExtKeyboardMode::ENABLE_FORMAT_CHARACTERS)) != 0) { + *height = *height + 30; + } + } + } + + // Check for accessibility mode + if ((extended->option & OrbisImeExtOption::ENABLE_ACCESSIBILITY) != + OrbisImeExtOption::DEFAULT) { + *width = *width * 5 / 4; // 25% larger for accessibility + *height = *height * 5 / 4; + LOG_DEBUG(Lib_ImeDialog, "Accessibility mode: width={}, height={}", *width, *height); + } + + // Check for forced accessibility panel + if ((extended->option & OrbisImeExtOption::ACCESSIBILITY_PANEL_FORCED) != + OrbisImeExtOption::DEFAULT) { + *width = 800; + *height = 600; + LOG_DEBUG(Lib_ImeDialog, "Forced accessibility panel: width={}, height={}", *width, + *height); + } + } + + if ((param->option & static_cast(0x8)) != OrbisImeOption::DEFAULT) { //? + *width *= 2; + *height *= 2; + LOG_DEBUG(Lib_ImeDialog, "Size mode: width={}, height={}", *width, *height); + } + + // Adjust for supported languages if specified + if (param->supported_languages != static_cast(0)) { + // Check if CJK languages are supported (need larger panel) + OrbisImeLanguage cjk_mask = OrbisImeLanguage::JAPANESE | OrbisImeLanguage::KOREAN | + OrbisImeLanguage::SIMPLIFIED_CHINESE | + OrbisImeLanguage::TRADITIONAL_CHINESE; + + if ((param->supported_languages & cjk_mask) != static_cast(0)) { + *width = *width * 5 / 4; // 25% wider for CJK input + *height = *height * 6 / 5; // 20% taller + LOG_DEBUG(Lib_ImeDialog, "CJK language support: width={}, height={}", *width, *height); + } + + // Check if Arabic is supported (right-to-left layout) + if ((param->supported_languages & OrbisImeLanguage::ARABIC) != + static_cast(0)) { + *width = *width * 11 / 10; // 10% wider for Arabic + LOG_DEBUG(Lib_ImeDialog, "Arabic language support: width={}", *width); + } + } + + // Ensure minimum sizes + const uint32_t min_width = 200; + const uint32_t min_height = 100; + if (*width < min_width) + *width = min_width; + if (*height < min_height) + *height = min_height; + + // Ensure maximum sizes (don't exceed screen bounds) + bool use_2k_coords = + (param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) != OrbisImeOption::DEFAULT; + const uint32_t max_width = use_2k_coords ? 2560 : 1920; + const uint32_t max_height = use_2k_coords ? 1440 : 1080; + if (*width > max_width) + *width = max_width; + if (*height > max_height) + *height = max_height; + + // Check for fixed position option + if ((param->option & OrbisImeOption::FIXED_POSITION) != OrbisImeOption::DEFAULT) { + if (*width > 800) + *width = 800; + if (*height > 600) + *height = 600; + } + + LOG_DEBUG(Lib_ImeDialog, "Final panel size: width={}, height={}", *width, *height); + return Error::OK; } Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { diff --git a/src/core/libraries/ime/ime_dialog.h b/src/core/libraries/ime/ime_dialog.h index 569bdf3c0..532762ccc 100644 --- a/src/core/libraries/ime/ime_dialog.h +++ b/src/core/libraries/ime/ime_dialog.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -37,7 +37,9 @@ int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(); int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(); Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width, u32* height); -int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(); +Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param, + const OrbisImeParamExtended* extended, + u32* width, u32* height); Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus(); Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 5330b90fd..bc4e2def6 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -34,6 +34,7 @@ #include #else #include +#include #endif namespace D = Core::Devices; @@ -751,6 +752,30 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) { sb->st_size = file->f.GetSize(); sb->st_blksize = 512; sb->st_blocks = (sb->st_size + 511) / 512; +#if defined(__linux__) || defined(__FreeBSD__) + struct stat filestat = {}; + stat(file->f.GetPath().c_str(), &filestat); + sb->st_atim = *reinterpret_cast(&filestat.st_atim); + sb->st_mtim = *reinterpret_cast(&filestat.st_mtim); + sb->st_ctim = *reinterpret_cast(&filestat.st_ctim); +#elif defined(__APPLE__) + struct stat filestat = {}; + stat(file->f.GetPath().c_str(), &filestat); + sb->st_atim = *reinterpret_cast(&filestat.st_atimespec); + sb->st_mtim = *reinterpret_cast(&filestat.st_mtimespec); + sb->st_ctim = *reinterpret_cast(&filestat.st_ctimespec); +#else + const auto ft = std::filesystem::last_write_time(file->f.GetPath()); + const auto sctp = std::chrono::time_point_cast( + ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); + const auto secs = std::chrono::time_point_cast(sctp); + const auto nsecs = std::chrono::duration_cast(sctp - secs); + + sb->st_mtim.tv_sec = static_cast(secs.time_since_epoch().count()); + sb->st_mtim.tv_nsec = static_cast(nsecs.count()); + sb->st_atim = sb->st_mtim; + sb->st_ctim = sb->st_mtim; +#endif // TODO incomplete break; } @@ -1262,7 +1287,8 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri if (file->type == Core::FileSys::FileType::Regular || file->type == Core::FileSys::FileType::Device) { // Disk files always ready - if (want_read) { + // For devices, stdin (fd 0) is never read-ready. + if (want_read && i != 0) { FD_SET_POSIX(i, &read_ready); } if (want_write) { diff --git a/src/core/libraries/kernel/file_system.h b/src/core/libraries/kernel/file_system.h index 507f0952c..d8989828a 100644 --- a/src/core/libraries/kernel/file_system.h +++ b/src/core/libraries/kernel/file_system.h @@ -65,6 +65,9 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000; constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000; constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000; +s32 PS4_SYSV_ABI posix_open(const char* path, s32 flags, u16 mode); +s32 PS4_SYSV_ABI posix_close(s32 fd); +s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence); s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, u64 nbytes); s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes); s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, u64 nbytes, s64 offset); diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 62903ff72..378064e44 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -89,21 +89,31 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 m } s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) { + LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len); + if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) { + LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start, + len); + return ORBIS_KERNEL_ERROR_EINVAL; + } if (len == 0) { return ORBIS_OK; } - LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len); auto* memory = Core::Memory::Instance(); - memory->Free(start, len); - return ORBIS_OK; + return memory->Free(start, len, true); } s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) { + LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len); + if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) { + LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start, + len); + return ORBIS_KERNEL_ERROR_EINVAL; + } if (len == 0) { return ORBIS_OK; } auto* memory = Core::Memory::Instance(); - memory->Free(start, len); + memory->Free(start, len, false); return ORBIS_OK; } diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 4ea7fa062..31cc9a81b 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -191,6 +191,26 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfo(s32 handle, Core::OrbisKernelModuleInfo* return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleInfo2(s32 handle, Core::OrbisKernelModuleInfo* info) { + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (info->st_size != sizeof(Core::OrbisKernelModuleInfo)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + if (module == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + if (module->IsSystemLib()) { + return ORBIS_KERNEL_ERROR_EPERM; + } + *info = module->GetModuleInfo(); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceKernelGetModuleInfoInternal(s32 handle, Core::OrbisKernelModuleInfoEx* info) { if (info == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; @@ -230,6 +250,31 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList(s32* handles, u64 num_array, u64* out_co return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_count) { + if (handles == nullptr || out_count == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + + auto* linker = Common::Singleton::Instance(); + u64 id = 0; + u64 index = 0; + auto* module = linker->GetModule(id); + while (module != nullptr && index < num_array) { + if (!module->IsSystemLib()) { + handles[index++] = id; + } + id++; + module = linker->GetModule(id); + } + + if (index == num_array && module != nullptr) { + return ORBIS_KERNEL_ERROR_ENOMEM; + } + + *out_count = index; + return ORBIS_OK; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -249,8 +294,11 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", sceKernelGetModuleInfoForUnwind); LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", sceKernelGetModuleInfoFromAddr); LIB_FUNCTION("kUpgrXIrz7Q", "libkernel", 1, "libkernel", sceKernelGetModuleInfo); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel", 1, "libkernel", sceKernelGetModuleInfo2); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel_module_info", 1, "libkernel", sceKernelGetModuleInfo2); LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal); LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList); + LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit); } diff --git a/src/core/libraries/kernel/threads.h b/src/core/libraries/kernel/threads.h index bcccf1695..42ab0b13f 100644 --- a/src/core/libraries/kernel/threads.h +++ b/src/core/libraries/kernel/threads.h @@ -28,6 +28,16 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return); +int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr); +int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type); +int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr); + +int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, + const char* name); +int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex); +int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex); +int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex); + void RegisterThreads(Core::Loader::SymbolsResolver* sym); class Thread { diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 5d97c5dc1..31e8b900b 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -261,7 +261,6 @@ int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime) { CHECK_AND_INIT_MUTEX - UNREACHABLE(); return (*mutex)->Lock(abstime); } @@ -378,7 +377,8 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { } int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) { - if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) { + if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck || + type >= PthreadMutexType::Max) { return POSIX_EINVAL; } (*attr)->m_type = type; diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 6c11eebc2..20bd20f4b 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" @@ -14,12 +14,6 @@ namespace Libraries::Kernel { -constexpr int PthreadInheritSched = 4; - -constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700; -constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256; -constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767; - extern PthreadAttr PthreadAttrDefault; void _thread_cleanupspecific(); @@ -231,7 +225,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt new_thread->attr = *(*attr); new_thread->attr.cpusetsize = 0; } - if (new_thread->attr.sched_inherit == PthreadInheritSched) { + if (curthread != nullptr && new_thread->attr.sched_inherit == PthreadInheritSched) { if (True(curthread->attr.flags & PthreadAttrFlags::ScopeSystem)) { new_thread->attr.flags |= PthreadAttrFlags::ScopeSystem; } else { @@ -325,6 +319,7 @@ PthreadT PS4_SYSV_ABI posix_pthread_self() { } void PS4_SYSV_ABI posix_pthread_set_name_np(PthreadT thread, const char* name) { + LOG_INFO(Kernel_Pthread, "called, new name: {}", name); Common::SetCurrentThreadName(name); } diff --git a/src/core/libraries/kernel/threads/pthread.h b/src/core/libraries/kernel/threads/pthread.h index cc1cb3829..fed3b96fe 100644 --- a/src/core/libraries/kernel/threads/pthread.h +++ b/src/core/libraries/kernel/threads/pthread.h @@ -24,6 +24,12 @@ class SymbolsResolver; namespace Libraries::Kernel { +constexpr int PthreadInheritSched = 4; + +constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700; +constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256; +constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767; + struct Pthread; enum class PthreadMutexFlags : u32 { diff --git a/src/core/libraries/kernel/threads/pthread_attr.cpp b/src/core/libraries/kernel/threads/pthread_attr.cpp index 4fe9e72f2..aa5781f4f 100644 --- a/src/core/libraries/kernel/threads/pthread_attr.cpp +++ b/src/core/libraries/kernel/threads/pthread_attr.cpp @@ -24,9 +24,9 @@ static constexpr std::array ThrPriorities = {{ }}; PthreadAttr PthreadAttrDefault = { - .sched_policy = SchedPolicy::Fifo, - .sched_inherit = 0, - .prio = 0, + .sched_policy = SchedPolicy::Other, + .sched_inherit = PthreadInheritSched, + .prio = ORBIS_KERNEL_PRIO_FIFO_DEFAULT, .suspend = false, .flags = PthreadAttrFlags::ScopeSystem, .stackaddr_attr = nullptr, diff --git a/src/core/libraries/libc_internal/libc_internal.cpp b/src/core/libraries/libc_internal/libc_internal.cpp index df35680a4..6a92d2317 100644 --- a/src/core/libraries/libc_internal/libc_internal.cpp +++ b/src/core/libraries/libc_internal/libc_internal.cpp @@ -11,6 +11,7 @@ #include "libc_internal_math.h" #include "libc_internal_memory.h" #include "libc_internal_str.h" +#include "libc_internal_threads.h" #include "printf.h" namespace Libraries::LibcInternal { @@ -20,5 +21,11 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { RegisterlibSceLibcInternalStr(sym); RegisterlibSceLibcInternalMemory(sym); RegisterlibSceLibcInternalIo(sym); + RegisterlibSceLibcInternalThreads(sym); +} + +void ForceRegisterLib(Core::Loader::SymbolsResolver* sym) { + // Used to forcibly enable HLEs for broken LLE functions. + ForceRegisterlibSceLibcInternalIo(sym); } } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal.h b/src/core/libraries/libc_internal/libc_internal.h index 9b00b72d8..1494a2ab2 100644 --- a/src/core/libraries/libc_internal/libc_internal.h +++ b/src/core/libraries/libc_internal/libc_internal.h @@ -15,4 +15,5 @@ namespace Libraries::LibcInternal { // so everything is just in the .cpp file void RegisterLib(Core::Loader::SymbolsResolver* sym); +void ForceRegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_io.cpp b/src/core/libraries/libc_internal/libc_internal_io.cpp index 8105b66cc..504ba5b48 100644 --- a/src/core/libraries/libc_internal/libc_internal_io.cpp +++ b/src/core/libraries/libc_internal/libc_internal_io.cpp @@ -3,21 +3,484 @@ #include #include +#include #include +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/file_system.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/kernel/posix_error.h" +#include "core/libraries/libc_internal/libc_internal_io.h" +#include "core/libraries/libc_internal/libc_internal_threads.h" #include "core/libraries/libs.h" -#include "libc_internal_io.h" #include "printf.h" namespace Libraries::LibcInternal { -int PS4_SYSV_ABI internal_snprintf(char* s, size_t n, VA_ARGS) { + +s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS) { VA_CTX(ctx); return snprintf_ctx(s, n, &ctx); } + +std::map g_files{}; +// Constants for tracking accurate file indexes. +// Since the file struct is exposed to the application, accuracy is important. +static constexpr s32 g_initial_files = 5; +static constexpr s32 g_max_files = 0x100; + +OrbisFILE* PS4_SYSV_ABI internal__Fofind() { + u64 index = g_initial_files; + while (index != g_max_files) { + OrbisFILE* file = g_files[index]; + // If file doesn't exist, create it. + if (file == nullptr) { + file = new OrbisFILE(); + if (file == nullptr) { + return nullptr; + } + // Store new file in the array, initialize default values, and return it. + g_files[index] = file; + file->_Mode = 0x80; + file->_Idx = index; + return file; + } + // Special case, files with mode 0 are returned? + if (file->_Mode == 0) { + file->_Mode = 0xff7f; + return file; + } + index++; + } + return nullptr; +} + +void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file) { + if (file != nullptr && file->_Mutex != nullptr) { + internal__Mtxlock(&file->_Mutex); + } +} + +void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file) { + if (file != nullptr && file->_Mutex != nullptr) { + internal__Mtxunlock(&file->_Mutex); + } +} + +OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file, + s32 fd, s32 s_mode, s32 flag) { + if (file == nullptr) { + *Kernel::__Error() = POSIX_ENOMEM; + } + + // Preserve mode and index + Libraries::Kernel::PthreadMutexT mtx = file->_Mutex; + Libraries::Kernel::PthreadMutexT* mtx_ptr = &file->_Mutex; + u8 file_index = file->_Idx; + u16 file_mode = file->_Mode & 0x80; + + // Real library does a memcpy using a static global FILE object. + // This stored file is just zeros, with the only exception being a handle of -1. + memset(file, 0, sizeof(OrbisFILE)); + file->_Handle = -1; + + // Not sure what this magic is for, but I'll replicate it. + u8* ptr = &file->_Cbuf; + // Note: this field is supposed to be a pthread mutex. + // Since we don't export pthread HLEs for other functions, I'll avoid handling this for now. + file->_Mutex = nullptr; + file->_Idx = file_index; + file->_Buf = ptr; + file->_Bend = &file->unk2; + file->_Next = ptr; + file->_Rend = ptr; + file->_WRend = ptr; + file->_Wend = ptr; + file->_WWend = ptr; + file->_Rback = ptr; + file->_WRback = &file->unk1; + + // Parse inputted mode string + const char* mode_str = mode; + u16 calc_mode = 0; + u16 access_mode = 0; + if (mode_str[0] == 'r') { + calc_mode = 1 | file_mode; + } else if (mode_str[0] == 'w') { + calc_mode = 0x1a | file_mode; + } else if (mode_str[0] == 'a') { + calc_mode = 0x16 | file_mode; + } else { + // Closes the file and returns EINVAL. + file->_Mode = file_mode; + if (flag == 0) { + internal__Mtxinit(mtx_ptr, nullptr); + } else { + file->_Mutex = mtx; + internal__Unlockfilelock(file); + } + internal_fclose(file); + *Kernel::__Error() = POSIX_EINVAL; + return nullptr; + } + file->_Mode = calc_mode; + + do { + // This is all basically straight from decomp, need to cleanup at some point. + if (mode_str[1] == '+') { + file_mode = 3; + if ((~calc_mode & 3) == 0) { + break; + } + } else if (mode_str[1] != 'b') { + file_mode = 0x20; + if ((calc_mode & 0x20) != 0) { + break; + } + } + mode_str++; + calc_mode = file_mode | calc_mode; + file->_Mode = calc_mode; + } while (true); + + if (path == nullptr && fd >= 0) { + // I guess this is for some internal behavior? + file->_Handle = fd; + } else { + fd = internal__Fopen(path, calc_mode, s_mode == 0x55); + file->_Handle = fd; + } + + // Error case + if (fd < 0) { + // Closes the file, but ensures errno is unchanged. + if (flag == 0) { + internal__Mtxinit(mtx_ptr, nullptr); + } else { + file->_Mutex = mtx; + internal__Unlockfilelock(file); + } + s32 old_errno = *Kernel::__Error(); + internal_fclose(file); + *Kernel::__Error() = old_errno; + return nullptr; + } + + if (flag == 0) { + char mtx_name[0x20]; + std::snprintf(mtx_name, 0x20, "FileFD:0x%08X", fd); + internal__Mtxinit(mtx_ptr, mtx_name); + } else { + file->_Mutex = mtx; + } + return file; +} + +s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag) { + u32 large_mode = mode; + u16 open_mode = 0600; + if (!flag) { + open_mode = 0666; + } + // Straight from decomp, should probably get cleaned up at some point. + s32 creat_flag = large_mode << 5 & 0x200; + s32 excl_flag = large_mode << 5 & 0x800; + s32 misc_flags = (large_mode & 8) * 0x80 + (large_mode & 4) * 2; + // Real library has an array for this, where large_mode & 3 is used as an index. + // That array has values [0, 0, 1, 2], so this call should match the result. + s32 access_flag = std::max((large_mode & 3) - 1, 0); + s32 open_flags = creat_flag | misc_flags | excl_flag | access_flag; + return Libraries::Kernel::posix_open(path, open_flags, open_mode); +} + +OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode) { + std::scoped_lock lk{g_file_mtx}; + LOG_INFO(Lib_LibcInternal, "called, path {}, mode {}", path, mode); + OrbisFILE* file = internal__Fofind(); + return internal__Foprep(path, mode, file, -1, 0, 0); +} + +s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file) { + if (file == nullptr) { + std::scoped_lock lk{g_file_mtx}; + s32 fflush_result = 0; + for (auto& file : g_files) { + s32 res = internal_fflush(file.second); + if (res < 0) { + fflush_result = -1; + } + } + return fflush_result; + } + if ((file->_Mode & 0x2000) != 0) { + internal__Lockfilelock(file); + u16 file_mode = file->_Mode; + u8* file_buf_start = file->_Buf; + u8* file_buf_end = file->_Next; + while (file_buf_start < file_buf_end) { + u64 size_to_write = static_cast(file_buf_end - file_buf_start); + s32 write_bytes = + Libraries::Kernel::sceKernelWrite(file->_Handle, file_buf_start, size_to_write); + if (write_bytes < 1) { + file_buf_start = file->_Buf; + file->_Next = file_buf_start; + file->_Wend = file_buf_start; + file->_WWend = file_buf_start; + u8* off_mode = reinterpret_cast(&file->_Mode) + 1; + *off_mode = *off_mode | 2; + internal__Unlockfilelock(file); + return -1; + } + file_buf_end = file->_Next; + file_buf_start += write_bytes; + } + file->_Next = file_buf_start; + file->_Wend = file_buf_start; + file->_WWend = file_buf_start; + file->_Mode = file_mode & 0xdfff; + internal__Unlockfilelock(file); + } + return 0; +} + +s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2) { + if (val1 < val2) { + return val2 - val1; + } + return 0; +} + +s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence) { + if ((file->_Mode & 3) == 0) { + return -1; + } + if (internal_fflush(file) != 0) { + return -1; + } + if (whence >= 3) { + *Libraries::Kernel::__Error() = POSIX_EINVAL; + return -1; + } + if (file_pos != nullptr) { + offset = offset + file_pos->_Off; + } + if (whence == 1 && (file->_Mode & 0x1000) != 0) { + s64 val1 = internal__Nnl(file, file->_Rback, &file->_Cbuf); + u8* rsave_ptr = file->_Rsave; + if (rsave_ptr == nullptr) { + rsave_ptr = file->_Rend; + } + s64 val2 = internal__Nnl(file, file->_Next, rsave_ptr); + s64 val3 = internal__Nnl(file, file->_Next, file->_WRend); + offset = offset - (val1 + val2 + val3); + } + s64 result = 0; + if (whence == 2 || (whence == 1 && offset != 0) || (whence == 0 && offset != -1)) { + result = Libraries::Kernel::posix_lseek(file->_Handle, offset, whence); + } + if (result == -1) { + return -1; + } + + u16 file_mode = file->_Mode; + if ((file_mode & 0x3000) != 0) { + u8* file_buf = file->_Buf; + file->_Next = file_buf; + file->_Rend = file_buf; + file->_WRend = file_buf; + file->_Wend = file_buf; + file->_WWend = file_buf; + file->_Rback = &file->_Cbuf; + file->_WRback = &file->unk1; + file->_Rsave = nullptr; + } + if (file_pos != nullptr) { + std::memcpy(&file->_Wstate, &file_pos->_Wstate, sizeof(Orbis_Mbstatet)); + } + file->_Mode = file_mode & 0xceff; + return 0; +} + +s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence) { + internal__Lockfilelock(file); + LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, offset {:#x}, whence {:#x}", + file->_Handle, offset, whence); + s32 result = internal__Fspos(file, nullptr, offset, whence); + internal__Unlockfilelock(file); + return result; +} + +s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file) { + if (file->_Rend > file->_Next) { + return 1; + } + if ((file->_Mode & 0x100) != 0) { + return 0; + } + u16 mode = file->_Mode; + if ((mode & 0xa001) != 1) { + // Lot of magic here, might be valuable to figure out what this does. + file->_Mode = (((mode ^ 0x8000) >> 0xf) << 0xe) | mode | 0x200; + return -1; + } + + u8* file_buf = file->_Buf; + if ((mode & 0x800) == 0 && file_buf == &file->_Cbuf) { + // Allocate a new file buffer, for now, we'll use host malloc to create it. + // When we have an HLE for malloc, that should be used instead. + u8* new_buffer = std::bit_cast(std::malloc(0x10000)); + if (new_buffer == nullptr) { + file->_Buf = file_buf; + file->_Bend = file_buf + 1; + } else { + file->_Mode = file->_Mode | 0x40; + file->_Buf = new_buffer; + file->_Bend = new_buffer + 0x10000; + file->_WRend = new_buffer; + file->_WWend = new_buffer; + file_buf = new_buffer; + } + } + file->_Next = file_buf; + file->_Rend = file_buf; + file->_Wend = file_buf; + // Intentional shrinking here, library treats value as 32-bit. + s32 read_result = + Libraries::Kernel::sceKernelRead(file->_Handle, file_buf, file->_Bend - file_buf); + if (read_result < 0) { + u8* off_mode = reinterpret_cast(&file->_Mode) + 1; + *off_mode = *off_mode | 0x42; + return -1; + } else if (read_result != 0) { + file->_Mode = file->_Mode | 0x5000; + file->_Rend = file->_Rend + read_result; + return 1; + } + file->_Mode = (file->_Mode & 0xaeff) | 0x4100; + return 0; +} + +u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file) { + if (size == 0 || nmemb == 0) { + return 0; + } + internal__Lockfilelock(file); + LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, size {:#x}, nmemb {:#x}", file->_Handle, + size, nmemb); + s64 total_size = size * nmemb; + s64 remaining_size = total_size; + if ((file->_Mode & 0x4000) != 0) { + while (remaining_size != 0) { + u8* rback_ptr = file->_Rback; + if (&file->_Cbuf <= rback_ptr) { + break; + } + file->_Rback = rback_ptr + 1; + *ptr = *rback_ptr; + ptr++; + remaining_size--; + } + } + + while (remaining_size != 0) { + u8* file_ptr = file->_Rsave; + if (file_ptr == nullptr) { + file_ptr = file->_Rend; + } else { + file->_Rend = file_ptr; + file->_Rsave = nullptr; + } + u8* src = file->_Next; + if (file_ptr <= src) { + s32 res = internal__Frprep(file); + if (res < 1) { + internal__Unlockfilelock(file); + return (total_size - remaining_size) / size; + } + src = file->_Next; + file_ptr = file->_Rend; + } + u64 copy_bytes = std::min(file_ptr - src, remaining_size); + std::memcpy(ptr, src, copy_bytes); + file->_Next += copy_bytes; + ptr += copy_bytes; + remaining_size -= copy_bytes; + } + internal__Unlockfilelock(file); + return (total_size - remaining_size) / size; +} + +void PS4_SYSV_ABI internal__Fofree(OrbisFILE* file) { + u8* cbuf_ptr = &file->_Cbuf; + s8 trunc_mode = static_cast(file->_Mode); + + file->_Mode = 0; + file->_Handle = -1; + file->_Buf = cbuf_ptr; + file->_Next = cbuf_ptr; + file->_Rend = cbuf_ptr; + file->_WRend = cbuf_ptr; + file->_Wend = cbuf_ptr; + file->_WWend = cbuf_ptr; + file->_Rback = cbuf_ptr; + file->_WRback = &file->unk1; + if (trunc_mode < 0) { + // Remove file from vector + g_files.erase(file->_Idx); + internal__Mtxdst(&file->_Mutex); + free(file); + } +} + +s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file) { + if (file == nullptr) { + return -1; + } + + LOG_INFO(Lib_LibcInternal, "called, file handle {:#x}", file->_Handle); + if ((file->_Mode & 3) == 0 || file->_Handle < 0) { + std::scoped_lock lk{g_file_mtx}; + internal__Fofree(file); + *Libraries::Kernel::__Error() = POSIX_EBADF; + } else { + s32 fflush_result = internal_fflush(file); + std::scoped_lock lk{g_file_mtx}; + if ((file->_Mode & 0x40) != 0) { + std::free(file->_Buf); + } + file->_Buf = nullptr; + s32 close_result = Libraries::Kernel::posix_close(file->_Handle); + internal__Fofree(file); + // Need to figure out what exactly this means. + return ~-(close_result == 0) | fflush_result; + } + return 0; +} + void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("eLdDw6l0-bU", "libSceLibcInternal", 1, "libSceLibcInternal", internal_snprintf); + LIB_FUNCTION("MUjC4lbHrK4", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fflush); + LIB_FUNCTION("xGT4Mc55ViQ", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofind); + LIB_FUNCTION("dREVnZkAKRE", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Foprep); + LIB_FUNCTION("sQL8D-jio7U", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fopen); + LIB_FUNCTION("A+Y3xfrWLLo", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fspos); + LIB_FUNCTION("Ss3108pBuZY", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Nnl); + LIB_FUNCTION("9s3P+LCvWP8", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Frprep); + LIB_FUNCTION("jVDuvE3s5Bs", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofree); + LIB_FUNCTION("vZkmJmvqueY", "libSceLibcInternal", 1, "libSceLibcInternal", + internal__Lockfilelock); + LIB_FUNCTION("0x7rx8TKy2Y", "libSceLibcInternal", 1, "libSceLibcInternal", + internal__Unlockfilelock); } + +void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) { + // Goal is to be minimally intrusive here to allow LLE for printf/stdout writes. + LIB_FUNCTION("xeYO4u7uyJ0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fopen); + LIB_FUNCTION("rQFVBXp-Cxg", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fseek); + LIB_FUNCTION("lbB+UlZqVG0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fread); + LIB_FUNCTION("uodLYyUip20", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fclose); +} + } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_io.h b/src/core/libraries/libc_internal/libc_internal_io.h index f5291526b..27915d8fa 100644 --- a/src/core/libraries/libc_internal/libc_internal_io.h +++ b/src/core/libraries/libc_internal/libc_internal_io.h @@ -3,12 +3,96 @@ #pragma once +#include #include "common/types.h" +#include "core/libraries/kernel/threads.h" namespace Core::Loader { class SymbolsResolver; } namespace Libraries::LibcInternal { + +static std::recursive_mutex g_file_mtx{}; + +union Orbis__mbstate_t { + u8 __mbstate8[128]; + s64 _mbstateL; +}; + +struct Orbis_Mbstatet { + u64 _Wchar; + u16 _Byte, _State; + s32 : 32; +}; + +struct Orbisfpos_t { + s64 _Off; + Orbis_Mbstatet _Wstate; +}; + +struct Orbis__sbuf { + u8* _base; + s32 _size; +}; + +struct OrbisFILE { + u16 _Mode; + u8 _Idx; + s32 _Handle; + u8 *_Buf, *_Bend, *_Next; + u8 *_Rend, *_Wend, *_Rback; + u16 *_WRback, _WBack[2]; + u16 unk1; + u8 *_Rsave, *_WRend, *_WWend; + Orbis_Mbstatet _Wstate; + u8* _Tmpnam; + u8 _Back[6], _Cbuf; + u8 unk2; + Libraries::Kernel::PthreadMutexT _Mutex; + u8* _p; + s32 _r; + s32 _w; + s16 _flags; + s16 _file; + Orbis__sbuf _bf; + s32 _lbfsize; + void* _cookie; + s32 PS4_SYSV_ABI (*_close)(void*); + s32 PS4_SYSV_ABI (*_read)(void*, char*, s32); + Orbisfpos_t PS4_SYSV_ABI (*_seek)(void*, Orbisfpos_t, s32); + s32 (*_write)(void*, const char*, s32); + Orbis__sbuf _ub; + u8* _up; + s32 _ur; + u8 _ubuf[3]; + u8 _nbuf[1]; + Orbis__sbuf _lb; + s32 _blksize; + Orbisfpos_t _offset; + void* _fl_mutex; + void* _fl_owner; + s32 _fl_count; + s32 _orientation; + Orbis__mbstate_t _mbstate; +}; + +s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS); +void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file); +void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file); +OrbisFILE* PS4_SYSV_ABI internal__Fofind(); +OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file, + s32 fd, s32 flag1, s32 flag2); +s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag); +OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode); +s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2); +s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence); +s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file); +s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence); +s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file); +u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file); +s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file); + void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym); +void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_threads.cpp b/src/core/libraries/libc_internal/libc_internal_threads.cpp new file mode 100644 index 000000000..2d8ddaccb --- /dev/null +++ b/src/core/libraries/libc_internal/libc_internal_threads.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/kernel/threads.h" +#include "core/libraries/libc_internal/libc_internal_threads.h" +#include "core/libraries/libs.h" + +namespace Libraries::LibcInternal { + +void getMutexName(char* buf, u64 size, const char* name) { + if (name != nullptr) { + std::snprintf(buf, size, "SceLibcI_%s", name); + } else { + std::snprintf(buf, size, "SceLibcI"); + } +} + +s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name) { + char mtx_name[0x20]; + getMutexName(mtx_name, sizeof(mtx_name), name); + + Libraries::Kernel::PthreadMutexAttrT attr{}; + s32 result = Libraries::Kernel::posix_pthread_mutexattr_init(&attr); + if (result != 0) { + return 1; + } + + result = Libraries::Kernel::posix_pthread_mutexattr_settype( + &attr, Libraries::Kernel::PthreadMutexType::Recursive); + if (result == 0) { + s32 mtx_init_result = Libraries::Kernel::scePthreadMutexInit(mtx, &attr, mtx_name); + result = Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr); + if (mtx_init_result == 0 && result == 0) { + return 0; + } + } else { + Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr); + } + + return 1; +} + +s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_lock(mtx); + return result != 0; +} + +s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_unlock(mtx); + return result != 0; +} + +s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx) { + s32 result = Libraries::Kernel::posix_pthread_mutex_destroy(mtx); + return result != 0; +} + +void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("z7STeF6abuU", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxinit); + LIB_FUNCTION("pE4Ot3CffW0", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxlock); + LIB_FUNCTION("cMwgSSmpE5o", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxunlock); + LIB_FUNCTION("LaPaA6mYA38", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxdst); +} + +} // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libc_internal/libc_internal_threads.h b/src/core/libraries/libc_internal/libc_internal_threads.h new file mode 100644 index 000000000..74e6d41b1 --- /dev/null +++ b/src/core/libraries/libc_internal/libc_internal_threads.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" +#include "core/libraries/kernel/threads.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::LibcInternal { + +s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name); +s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx); +s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx); +s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx); + +void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::LibcInternal \ No newline at end of file diff --git a/src/core/libraries/libpng/pngenc.cpp b/src/core/libraries/libpng/pngenc.cpp new file mode 100644 index 000000000..a17adbf71 --- /dev/null +++ b/src/core/libraries/libpng/pngenc.cpp @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#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(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 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 \ No newline at end of file diff --git a/src/core/libraries/libpng/pngenc.h b/src/core/libraries/libpng/pngenc.h new file mode 100644 index 000000000..1e6549340 --- /dev/null +++ b/src/core/libraries/libpng/pngenc.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/libpng/pngenc_error.h b/src/core/libraries/libpng/pngenc_error.h new file mode 100644 index 000000000..a07b1317f --- /dev/null +++ b/src/core/libraries/libpng/pngenc_error.h @@ -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; \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 6cf2b1a6c..410b629cc 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -35,11 +35,14 @@ #include "core/libraries/np/np_commerce.h" #include "core/libraries/np/np_common.h" #include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_matching2.h" +#include "core/libraries/np/np_partner.h" #include "core/libraries/np/np_party.h" #include "core/libraries/np/np_profile_dialog.h" #include "core/libraries/np/np_score.h" #include "core/libraries/np/np_sns_facebook_dialog.h" #include "core/libraries/np/np_trophy.h" +#include "core/libraries/np/np_tus.h" #include "core/libraries/np/np_web_api.h" #include "core/libraries/np/np_web_api2.h" #include "core/libraries/pad/pad.h" @@ -77,6 +80,7 @@ namespace Libraries { void InitHLELibs(Core::Loader::SymbolsResolver* sym) { LOG_INFO(Lib_Kernel, "Initializing HLE libraries"); Libraries::Kernel::RegisterLib(sym); + Libraries::LibcInternal::ForceRegisterLib(sym); Libraries::GnmDriver::RegisterLib(sym); Libraries::VideoOut::RegisterLib(sym); Libraries::UserService::RegisterLib(sym); @@ -97,6 +101,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Np::NpCommerce::RegisterLib(sym); Libraries::Np::NpCommon::RegisterLib(sym); Libraries::Np::NpManager::RegisterLib(sym); + Libraries::Np::NpMatching2::RegisterLib(sym); Libraries::Np::NpScore::RegisterLib(sym); Libraries::Np::NpTrophy::RegisterLib(sym); Libraries::Np::NpWebApi::RegisterLib(sym); @@ -105,6 +110,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym); Libraries::Np::NpAuth::RegisterLib(sym); Libraries::Np::NpParty::RegisterLib(sym); + Libraries::Np::NpPartner::RegisterLib(sym); + Libraries::Np::NpTus::RegisterLib(sym); Libraries::ScreenShot::RegisterLib(sym); Libraries::AppContent::RegisterLib(sym); Libraries::PngDec::RegisterLib(sym); diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index ebb10db68..8bc9b51f0 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -430,8 +430,8 @@ int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int return index + 1; } -int PS4_SYSV_ABI sceHttpReadData() { - LOG_ERROR(Lib_Http, "(STUBBED) called"); +int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size) { + LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size); return ORBIS_OK; } diff --git a/src/core/libraries/network/http.h b/src/core/libraries/network/http.h index 2ad5e171f..d373fd290 100644 --- a/src/core/libraries/network/http.h +++ b/src/core/libraries/network/http.h @@ -91,7 +91,7 @@ int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, c int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, int32_t* responseCode, const char** reasonPhrase, u64* phraseLen); -int PS4_SYSV_ABI sceHttpReadData(); +int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size); int PS4_SYSV_ABI sceHttpRedirectCacheFlush(); int PS4_SYSV_ABI sceHttpRemoveRequestHeader(); int PS4_SYSV_ABI sceHttpRequestGetAllHeaders(); diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index a365d407b..102447952 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -655,10 +655,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or switch (file->type) { case Core::FileSys::FileType::Socket: { + auto native_handle = file->socket->Native(); + if (!native_handle) { + // P2P socket, cannot be added to epoll + LOG_ERROR(Lib_Net, "P2P socket cannot be added to epoll (unimplemented)"); + *sceNetErrnoLoc() = ORBIS_NET_EBADF; + return ORBIS_NET_ERROR_EBADF; + } + epoll_event native_event = {.events = ConvertEpollEventsIn(event->events), .data = {.fd = id}}; - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *file->socket->Native(), - &native_event) == 0); + ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *native_handle, &native_event) == 0); epoll->events.emplace_back(id, *event); break; } @@ -696,10 +703,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or switch (file->type) { case Core::FileSys::FileType::Socket: { + auto native_handle = file->socket->Native(); + if (!native_handle) { + // P2P socket, cannot be modified in epoll + LOG_ERROR(Lib_Net, "P2P socket cannot be modified in epoll (unimplemented)"); + *sceNetErrnoLoc() = ORBIS_NET_EBADF; + return ORBIS_NET_ERROR_EBADF; + } + epoll_event native_event = {.events = ConvertEpollEventsIn(event->events), .data = {.fd = id}}; - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *file->socket->Native(), - &native_event) == 0); + ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *native_handle, &native_event) == 0); *it = {id, *event}; break; } @@ -731,8 +745,15 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or switch (file->type) { case Core::FileSys::FileType::Socket: { - ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *file->socket->Native(), nullptr) == - 0); + auto native_handle = file->socket->Native(); + if (!native_handle) { + // P2P socket, cannot be removed from epoll + LOG_ERROR(Lib_Net, "P2P socket cannot be removed from epoll (unimplemented)"); + *sceNetErrnoLoc() = ORBIS_NET_EBADF; + return ORBIS_NET_ERROR_EBADF; + } + + ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *native_handle, nullptr) == 0); epoll->events.erase(it); break; } @@ -782,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; } diff --git a/src/core/libraries/network/sys_net.cpp b/src/core/libraries/network/sys_net.cpp index 6d7876c06..76107d323 100644 --- a/src/core/libraries/network/sys_net.cpp +++ b/src/core/libraries/network/sys_net.cpp @@ -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()); diff --git a/src/core/libraries/np/np_auth.cpp b/src/core/libraries/np/np_auth.cpp index 0c855546c..b6091723c 100644 --- a/src/core/libraries/np/np_auth.cpp +++ b/src/core/libraries/np/np_auth.cpp @@ -123,7 +123,9 @@ s32 GetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParame // Not sure what values are expected here, so zeroing these for now. std::memset(auth_code, 0, sizeof(OrbisNpAuthorizationCode)); - *issuer_id = 0; + if (issuer_id != nullptr) { + *issuer_id = 0; + } return ORBIS_OK; } diff --git a/src/core/libraries/np/np_auth.h b/src/core/libraries/np/np_auth.h index 636210772..0894bd85d 100644 --- a/src/core/libraries/np/np_auth.h +++ b/src/core/libraries/np/np_auth.h @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/types.h" #include "core/libraries/np/np_types.h" +#include "core/libraries/system/userservice.h" namespace Core::Loader { class SymbolsResolver; @@ -31,7 +32,7 @@ struct OrbisNpAuthGetAuthorizationCodeParameter { struct OrbisNpAuthGetAuthorizationCodeParameterA { u64 size; - s32 user_id; + Libraries::UserService::OrbisUserServiceUserId user_id; u8 padding[4]; const OrbisNpClientId* client_id; const char* scope; @@ -47,7 +48,7 @@ struct OrbisNpAuthGetIdTokenParameter { struct OrbisNpAuthGetIdTokenParameterA { u64 size; - s32 user_id; + Libraries::UserService::OrbisUserServiceUserId user_id; u8 padding[4]; const OrbisNpClientId* client_id; const OrbisNpClientSecret* client_secret; diff --git a/src/core/libraries/np/np_common.h b/src/core/libraries/np/np_common.h index 2fd4ecd7c..a130f9c1d 100644 --- a/src/core/libraries/np/np_common.h +++ b/src/core/libraries/np/np_common.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/libraries/np/np_error.h b/src/core/libraries/np/np_error.h index 444625559..f806d4829 100644 --- a/src/core/libraries/np/np_error.h +++ b/src/core/libraries/np/np_error.h @@ -14,4 +14,12 @@ constexpr int ORBIS_NP_ERROR_INVALID_SIZE = 0x80550011; constexpr int ORBIS_NP_ERROR_ABORTED = 0x80550012; constexpr int ORBIS_NP_ERROR_REQUEST_MAX = 0x80550013; constexpr int ORBIS_NP_ERROR_REQUEST_NOT_FOUND = 0x80550014; -constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015; \ No newline at end of file +constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015; + +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = 0x80550704; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = 0x80550706; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070c; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070e; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = 0x80550714; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = 0x80550718; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719; \ No newline at end of file diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 9bfe26fa7..942d8b655 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include +#include #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> 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,11 +700,25 @@ struct NpStateCallbackForNpToolkit { NpStateCallbackForNpToolkit NpStateCbForNp; +struct NpStateCallback { + std::variant func; + void* userdata; +}; + +NpStateCallback NpStateCb; + s32 PS4_SYSV_ABI sceNpCheckCallback() { if (!g_signed_in) { return ORBIS_NP_ERROR_NOT_INITIALIZED; } 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; } @@ -695,6 +727,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; @@ -704,6 +770,22 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT return id; } +void RegisterNpCallback(std::string key, std::function 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(); @@ -742,9 +824,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", diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 61a283ba7..49250db03 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -3,6 +3,8 @@ #pragma once +#include + #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 (*)(s32 userId, OrbisNpState state, - void* userdata); - -enum class OrbisNpGamePresenseStatus { - Offline = 0, - Online = 1, -}; - enum class OrbisNpReachabilityState { Unavailable = 0, Available = 1, Reachable = 2, }; +using OrbisNpStateCallback = + PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, + OrbisNpId* npId, void* userdata); +using OrbisNpStateCallbackA = PS4_SYSV_ABI void (*)( + Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata); +using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)( + Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata); +using OrbisNpReachabilityStateCallback = + PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId, + OrbisNpReachabilityState state, void* userdata); + +enum class OrbisNpGamePresenseStatus { + Offline = 0, + Online = 1, +}; + struct OrbisNpCountryCode { char country_code[2]; char end; @@ -80,5 +90,13 @@ struct OrbisNpCreateAsyncRequestParameter { u8 padding[4]; }; +void RegisterNpCallback(std::string key, std::function cb); +void DeregisterNpCallback(std::string key); + +s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpId* np_id); +s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpOnlineId* online_id); + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpManager diff --git a/src/core/libraries/np/np_matching2.cpp b/src/core/libraries/np/np_matching2.cpp new file mode 100644 index 000000000..423b84257 --- /dev/null +++ b/src/core/libraries/np/np_matching2.cpp @@ -0,0 +1,812 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#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 g_ctx_events; +static std::deque g_lobby_events; +static std::deque g_room_events; +static std::mutex g_responses_mutex; +static std::deque> 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 defaultRequestOptParam = std::nullopt; + +auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) { + return requestOpt ? *requestOpt + : (defaultRequestOptParam ? defaultRequestOptParam + : std::optional{}); +} + +struct OrbisNpMatching2CreateJoinRoomRequestA { + u16 maxSlot; + OrbisNpMatching2TeamId teamId; + u8 pad[5]; + OrbisNpMatching2Flags flags; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + void* roomPasswd; + void* passwdSlotMask; + void* groupConfig; + u64 groupConfigs; + void* joinGroupLabel; + Libraries::Np::OrbisNpAccountId* allowedUser; + u64 allowedUsers; + Libraries::Np::OrbisNpAccountId* blockedUser; + u64 blockedUsers; + void* internalBinAttr; + u64 internalBinAttrs; + void* externalSearchIntAttr; + u64 externalSearchIntAttrs; + void* externalSearchBinAttr; + u64 externalSearchBinAttrs; + void* externalBinAttr; + u64 externalBinAttrs; + void* memberInternalBinAttr; + u64 memberInternalBinAttrs; + void* signalingParam; +}; + +static_assert(sizeof(OrbisNpMatching2CreateJoinRoomRequestA) == 184); + +struct OrbisNpMatching2RoomDataInternal { + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + u16 maxSlot; + OrbisNpMatching2ServerId serverId; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + void* roomGroup; + u64 roomGroups; + OrbisNpMatching2Flags flags; + u8 pad[4]; + void* internalBinAttr; + u64 internalBinAttrs; +}; + +struct OrbisNpMatching2RoomMemberDataInternalA { + OrbisNpMatching2RoomMemberDataInternalA* next; + u64 joinDateTicks; + Libraries::Np::OrbisNpPeerAddressA user; + Libraries::Np::OrbisNpOnlineId onlineId; + u8 pad[4]; + OrbisNpMatching2RoomMemberId memberId; + OrbisNpMatching2TeamId teamId; + OrbisNpMatching2NatType natType; + OrbisNpMatching2Flags flags; + void* roomGroup; + void* roomMemberInternalBinAttr; + u64 roomMemberInternalBinAttrs; +}; + +struct OrbisNpMatching2RoomMemberDataInternalListA { + OrbisNpMatching2RoomMemberDataInternalA* members; + u64 membersNum; + OrbisNpMatching2RoomMemberDataInternalA* me; + OrbisNpMatching2RoomMemberDataInternalA* owner; +}; + +struct OrbisNpMatching2CreateJoinRoomResponseA { + OrbisNpMatching2RoomDataInternal* roomData; + OrbisNpMatching2RoomMemberDataInternalListA members; +}; + +int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2CreateJoinRoomRequestA* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId) { + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + LOG_DEBUG(Lib_NpMatching2, + "maxSlot = {}, teamId = {}, worldId = {}, lobbyId = {}, groupConfig = {}, " + "joinGroupLabel = {}", + request->maxSlot, request->teamId, request->worldId, request->lobbyId, + request->groupConfig, request->joinGroupLabel); + + static OrbisNpMatching2RequestId id = 10; + *requestId = id++; + + if (auto optParam = GetOptParam(requestOpt); optParam) { + LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, + optParam->appId); + std::scoped_lock lk{g_responses_mutex}; + auto reqIdCopy = *requestId; + auto requestCopy = *request; + g_responses.emplace_back([=]() { + Libraries::Np::OrbisNpOnlineId onlineId{}; + if (NpManager::sceNpGetOnlineId(1, &onlineId) != ORBIS_OK) { + return; + } + + OrbisNpMatching2RoomMemberDataInternalA me{ + nullptr, + 0, + {0xace104e, Libraries::Np::OrbisNpPlatformType::PS4}, + onlineId, + {0, 0, 0, 0}, + 1, + requestCopy.teamId, + 1, + 0, + nullptr, + nullptr, + 0}; + OrbisNpMatching2RoomDataInternal room{requestCopy.maxSlot, + 0, + static_cast(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 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 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 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 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 \ No newline at end of file diff --git a/src/core/libraries/np/np_matching2.h b/src/core/libraries/np/np_matching2.h new file mode 100644 index 000000000..6f7fca900 --- /dev/null +++ b/src/core/libraries/np/np_matching2.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_partner.cpp b/src/core/libraries/np/np_partner.cpp new file mode 100644 index 000000000..447144344 --- /dev/null +++ b/src/core/libraries/np/np_partner.cpp @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#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 \ No newline at end of file diff --git a/src/core/libraries/np/np_partner.h b/src/core/libraries/np/np_partner.h new file mode 100644 index 000000000..4cb2a6f5f --- /dev/null +++ b/src/core/libraries/np/np_partner.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/np/np_partner_error.h b/src/core/libraries/np/np_partner_error.h new file mode 100644 index 000000000..be1c7c594 --- /dev/null +++ b/src/core/libraries/np/np_partner_error.h @@ -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; \ No newline at end of file diff --git a/src/core/libraries/np/np_trophy.cpp b/src/core/libraries/np/np_trophy.cpp index 7468de13b..976d614c0 100644 --- a/src/core/libraries/np/np_trophy.cpp +++ b/src/core/libraries/np/np_trophy.cpp @@ -149,7 +149,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id, +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, + Libraries::UserService::OrbisUserServiceUserId user_id, uint32_t service_label, u64 options) { ASSERT(options == 0ull); if (!context) { diff --git a/src/core/libraries/np/np_trophy.h b/src/core/libraries/np/np_trophy.h index 36e59e537..ab187ae13 100644 --- a/src/core/libraries/np/np_trophy.h +++ b/src/core/libraries/np/np_trophy.h @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include "common/types.h" #include "core/libraries/rtc/rtc.h" @@ -132,7 +133,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails(); int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature(); -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id, +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, + Libraries::UserService::OrbisUserServiceUserId user_id, u32 service_label, u64 options); s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle); int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context); diff --git a/src/core/libraries/np/np_tus.cpp b/src/core/libraries/np/np_tus.cpp new file mode 100644 index 000000000..d18cf4ec0 --- /dev/null +++ b/src/core/libraries/np/np_tus.cpp @@ -0,0 +1,1445 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_tus.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/np/object_manager.h" +#include "core/libraries/system/userservice.h" + +namespace Libraries::Np::NpTus { + +int PackReqId(int libCtxId, int reqId) { + return ((libCtxId & 0xFFFF) << 16) | (reqId & 0xFFFF); +} + +std::pair UnpackReqId(int reqId) { + return {reqId >> 16, reqId & 0xFFFF}; +} + +bool IsReqId(int id) { + return id > (1 << 16); +} + +struct NpTusRequest { + std::future task; + + void Start(auto lambda) { + this->task = std::async(std::launch::async, lambda); + } +}; + +using NpTusRequestsManager = + ObjectManager; + +struct NpTusTitleContext { + u32 serviceLabel; + OrbisNpId npId; + NpTusRequestsManager requestsManager; + + s32 GetRequest(int reqId, NpTusRequest** out) { + NpTusRequest* req = nullptr; + if (auto ret = requestsManager.GetObject(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + + return ORBIS_OK; + } + + s32 DeleteRequest(int reqId) { + return requestsManager.DeleteObject(reqId); + } +}; + +using NpTusContextManager = + ObjectManager; + +static NpTusContextManager ctxManager; + +s32 GetRequest(int requestId, NpTusRequest** out) { + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + NpTusRequest* req = nullptr; + if (auto ret = ctx->GetRequest(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId) { + if (!npId) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (serviceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpTus, "serviceLabel = {}, npId->data = {}", serviceLabel, npId->handle.data); + + return ctxManager.CreateObject(serviceLabel, *npId); +} + +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA(OrbisNpServiceLabel serviceLabel, + Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_ERROR(Lib_NpTus, "serviceLabel = {}, userId = {}", serviceLabel, userId); + OrbisNpId npId; + auto ret = NpManager::sceNpGetNpId(userId, &npId); + + if (ret < 0) { + return ret; + } + + return sceNpTusCreateNpTitleCtx(serviceLabel, &npId); +} + +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId) { + LOG_INFO(Lib_NpTus, "redirecting"); + return sceNpTusCreateNpTitleCtx(serviceLabel, npId); +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* dataStatus, u64 dataStatusSize, + void* data, u64 dataSize, void* option) { + LOG_INFO( + Lib_NpTus, + "reqId = {:#x}, slotId = {}, dataStatusSize = {}, data = {}, dataSize = {}, option = {}", + reqId, slotId, dataStatusSize, data, dataSize, fmt::ptr(option)); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetData(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, void* option) { + LOG_ERROR( + Lib_NpTus, + "reqId = {:#x}, slotId = {}, dataStatusSize = {}, data = {}, dataSize = {}, option = {}", + reqId, slotId, dataStatusSize, data, dataSize, fmt::ptr(option)); + + auto ret = sceNpTusGetDataAsync(reqId, npId, slotId, dataStatus, dataStatusSize, data, dataSize, + option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ret; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatus() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatus() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync(int reqId, OrbisNpId* npId, + OrbisNpTusSlotId* slotIds, s64* variables, + u64 variablesSize, int arrayLen, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, variablesSize = {}, arrayLen = " + "{}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), + variablesSize, arrayLen, fmt::ptr(option)); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable(int reqId, OrbisNpId* npId, OrbisNpTusSlotId* slotIds, + s64* variables, u64 variablesSize, int arrayLen, + void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, variablesSize = {}, arrayLen = " + "{}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), + variablesSize, arrayLen, fmt::ptr(option)); + + auto ret = sceNpTusGetMultiSlotVariableAsync(reqId, npId, slotIds, variables, variablesSize, + arrayLen, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync(int reqId, OrbisNpId* npIds, + OrbisNpTusSlotId slotId, + OrbisNpTusDataStatus* statuses, + u64 statusesBytes, int arrayLen, + void* option) { + LOG_ERROR(Lib_NpTus, "(STUBBED) reqId = {:#x}, slotId = {}, arrayLen = {}, option = {}", reqId, + slotId, arrayLen, fmt::ptr(option)); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAsync(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, + u64 totalSize, u64 sendSize, const void* data, + const OrbisNpTusDataInfo* info, u64 infoSize, + const OrbisNpId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, npId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, npId ? npId->handle.data : "", slotId, totalSize, sendSize, + info ? info->size : 0, infoSize, + lastChangedAuthor ? lastChangedAuthor->handle.data : ""); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetData(int reqId, OrbisNpId* npId, OrbisNpTusSlotId slotId, u64 totalSize, + u64 sendSize, const void* data, const OrbisNpTusDataInfo* info, + u64 infoSize, const OrbisNpId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, npId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, npId ? npId->handle.data : "", slotId, totalSize, sendSize, + info ? info->size : 0, infoSize, + lastChangedAuthor ? lastChangedAuthor->handle.data : ""); + + auto ret = sceNpTusSetDataAsync(reqId, npId, slotId, totalSize, sendSize, data, info, infoSize, + lastChangedAuthor, lastChanged, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync(int reqId, OrbisNpId* npId, + OrbisNpTusSlotId* slotIds, s64* variables, + int arrayLen, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {}, npId = {}, slotIds = {}, variables = {}, arrayLen = {}, option = {}", + reqId, npId ? npId->handle.data : "", fmt::ptr(slotIds), fmt::ptr(variables), arrayLen, + fmt::ptr(option)); + + if (!slotIds || !variables) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayLen < 1 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayLen > 64) { + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + } + if (std::ranges::any_of( + std::vector>(slotIds, slotIds + arrayLen), + [](auto id) { return id < 0; })) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA(OrbisNpServiceLabel serviceLabel, + Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_DEBUG(Lib_NpTus, "redirecting"); + return sceNpTusCreateNpTitleCtxA(serviceLabel, userId); +} + +s32 PS4_SYSV_ABI sceNpTssGetDataAsync(int reqId, OrbisNpTssSlotId slotId, + OrbisNpTssDataStatus* dataStatus, u64 dataStatusSize, + void* data, u64 dataSize, OrbisNpTssGetDataOptParam* option) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}, slotId = {}, dataStatusSize = {}, dataSize = {}", reqId, + slotId, dataStatusSize, dataSize); + + if (option && option->size != 0x20) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (dataStatus && dataStatusSize != 0x18) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (slotId < 0 || slotId > 15) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + if (dataStatus) { + dataStatus->status = OrbisNpTssStatus::Ok; + dataStatus->contentLength = 0; + } + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetData(int reqId, OrbisNpTssSlotId slotId, + OrbisNpTssDataStatus* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, OrbisNpTssGetDataOptParam* option) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}, slotId = {}, dataStatusSize = {}, dataSize = {}", reqId, + slotId, dataStatusSize, dataSize); + + auto ret = + sceNpTssGetDataAsync(reqId, slotId, dataStatus, dataStatusSize, data, dataSize, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ret; +} + +s32 PS4_SYSV_ABI sceNpTssGetSmallStorage() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetStorage() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetStorageAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAbortRequest() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusCreateRequest(int libCtxId) { + LOG_INFO(Lib_NpTus, "libCtxId = {}", libCtxId); + + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(libCtxId, &ctx); ret < 0) { + return ret; + } + + auto req = ctx->requestsManager.CreateObject(); + if (req < 0) { + return req; + } + + return PackReqId(libCtxId, req); +} + +s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx(int ctxId) { + LOG_INFO(Lib_NpTus, "ctxId = {}", ctxId); + + return ctxManager.DeleteObject(ctxId); +} + +s32 PS4_SYSV_ABI sceNpTusDeleteRequest(int requestId) { + LOG_INFO(Lib_NpTus, "requestId = {:#x}", requestId); + + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpTusTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + return ctx->DeleteRequest(reqId); +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId slotId, OrbisNpTusDataStatusA* dataStatus, + u64 dataStatusSize, void* data, u64 dataSize, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {:#x}, slotId = {}, dataStatus = {}, dataStatusSize = {}, " + "dataSize = {}", + reqId, accountId, slotId, fmt::ptr(dataStatus), dataStatusSize, dataSize); + + if (slotId < 0 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (dataStatusSize != sizeof(OrbisNpTusDataStatusA)) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataA(int reqId, OrbisNpAccountId accountId, OrbisNpTusSlotId slotId, + OrbisNpTusDataStatusA* dataStatus, u64 dataStatusSize, void* data, + u64 dataSize, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {:#x}, slotId = {}, dataStatus = {}, dataStatusSize = {}, " + "dataSize = {}", + reqId, accountId, slotId, fmt::ptr(dataStatus), dataStatusSize, dataSize); + + auto ret = sceNpTusGetDataAAsync(reqId, accountId, slotId, dataStatus, dataStatusSize, data, + dataSize, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ret; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId* slotIds, + OrbisNpTusDataStatusA* statuses, + u64 statusesSize, int arrayLen, + void* option) { + LOG_ERROR(Lib_NpTus, "reqId = {:#x}, accountId = {}, arrayLen = {}, option = {}", reqId, + accountId, arrayLen, fmt::ptr(option)); + + if (!slotIds || !statuses) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayLen < 1 || option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayLen * sizeof(OrbisNpTusDataStatusA) != statusesSize) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (arrayLen > 64) { + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + } + if (std::ranges::any_of( + std::vector>(slotIds, slotIds + arrayLen), + [](auto id) { return id < 0; })) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + // if sdk_ver >= 5.50 clear the statuses array + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId* slotIds, + OrbisNpTusDataStatusA* statuses, u64 statusesSize, + int arrayLen, void* option) { + LOG_ERROR(Lib_NpTus, "reqId = {:#x}, accountId = {}, arrayLen = {}, option = {}", reqId, + accountId, arrayLen, fmt::ptr(option)); + + auto ret = sceNpTusGetMultiSlotDataStatusAAsync(reqId, accountId, slotIds, statuses, + statusesSize, arrayLen, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusPollAsync(int reqId, int* result) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}", reqId); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->task.valid()) { + LOG_ERROR(Lib_NpTus, "request not started"); + return 1; + } + if (req->task.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + LOG_DEBUG(Lib_NpTus, "request finished"); + if (result) { + *result = req->task.get(); + } + return 0; + } + + return 1; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAAsync(int reqId, OrbisNpAccountId accountId, + OrbisNpTusSlotId slotId, u64 totalSize, u64 sendSize, + const void* data, const OrbisNpTusDataInfo* info, + u64 infoSize, const OrbisNpAccountId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick* lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, accountId, slotId, totalSize, sendSize, info ? info->size : 0, infoSize, + lastChangedAuthor ? *lastChangedAuthor : 0); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->Start([=]() { + // + return 0; + }); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataA(int reqId, OrbisNpAccountId accountId, OrbisNpTusSlotId slotId, + u64 totalSize, u64 sendSize, const void* data, + const OrbisNpTusDataInfo* info, u64 infoSize, + const OrbisNpAccountId* lastChangedAuthor, + Libraries::Rtc::OrbisRtcTick* lastChanged, void* option) { + LOG_INFO(Lib_NpTus, + "reqId = {:#x}, accountId = {}, slotId = {}, totalSize = {}, sendSize = {}, " + "info->size = {}, infoSize = {}, lastChangedAuthor = {}", + reqId, accountId, slotId, totalSize, sendSize, info ? info->size : 0, infoSize, + lastChangedAuthor ? *lastChangedAuthor : 0); + + auto ret = sceNpTusSetDataAAsync(reqId, accountId, slotId, totalSize, sendSize, data, info, + infoSize, lastChangedAuthor, lastChanged, option); + if (ret < 0) { + return ret; + } + + sceNpTusWaitAsync(reqId, &ret); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetThreadParam() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetTimeout() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSave() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUser() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result) { + LOG_INFO(Lib_NpTus, "reqId = {:#x}", reqId); + + NpTusRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->task.valid()) { + LOG_ERROR(Lib_NpTus, "request not started"); + return 1; + } + + req->task.wait(); + + LOG_DEBUG(Lib_NpTus, "request finished"); + if (result) { + *result = req->task.get(); + } + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("sRVb2Cf0GHg", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTssCreateNpTitleCtx); + LIB_FUNCTION("cRVmNrJDbG8", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusAddAndGetVariable); + LIB_FUNCTION("Q2UmHdK04c8", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusAddAndGetVariableAsync); + LIB_FUNCTION("ukr6FBSrkJw", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusAddAndGetVariableVUser); + LIB_FUNCTION("lliK9T6ylJg", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusAddAndGetVariableVUserAsync); + LIB_FUNCTION("BIkMmUfNKWM", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusCreateNpTitleCtx); + LIB_FUNCTION("0DT5bP6YzBo", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusDeleteMultiSlotData); + LIB_FUNCTION("OCozl1ZtxRY", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotDataAsync); + LIB_FUNCTION("mYhbiRtkE1Y", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariable); + LIB_FUNCTION("0nDVqcYECoM", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariableAsync); + LIB_FUNCTION("XOzszO4ONWU", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusGetData); + LIB_FUNCTION("uHtKS5V1T5k", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusGetDataAsync); + LIB_FUNCTION("GQHCksS7aLs", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusGetDataVUser); + LIB_FUNCTION("5R6kI-8f+Hk", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusGetDataVUserAsync); + LIB_FUNCTION("DXigwIBTjWE", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetFriendsDataStatus); + LIB_FUNCTION("LUwvy0MOSqw", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetFriendsDataStatusAsync); + LIB_FUNCTION("cy+pAALkHp8", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusGetFriendsVariable); + LIB_FUNCTION("YFYWOwYI6DY", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetFriendsVariableAsync); + LIB_FUNCTION("pgcNwFHoOL4", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatus); + LIB_FUNCTION("Qyek420uZmM", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusAsync); + LIB_FUNCTION("NGCeFUl5ckM", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusVUser); + LIB_FUNCTION("bHWFSg6jvXc", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusVUserAsync); + LIB_FUNCTION("F+eQlfcka98", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariable); + LIB_FUNCTION("bcPB2rnhQqo", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableAsync); + LIB_FUNCTION("uFxVYJEkcmc", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableVUser); + LIB_FUNCTION("qp-rTrq1klk", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableVUserAsync); + LIB_FUNCTION("NvHjFkx2rnU", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatus); + LIB_FUNCTION("0zkr0T+NYvI", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusAsync); + LIB_FUNCTION("xwJIlK0bHgA", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusVUser); + LIB_FUNCTION("I5dlIKkHNkQ", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusVUserAsync); + LIB_FUNCTION("6G9+4eIb+cY", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserVariable); + LIB_FUNCTION("YRje5yEXS0U", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableAsync); + LIB_FUNCTION("zB0vaHTzA6g", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableVUser); + LIB_FUNCTION("xZXQuNSTC6o", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableVUserAsync); + LIB_FUNCTION("4NrufkNCkiE", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusSetData); + LIB_FUNCTION("G68xdfQuiyU", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusSetDataAsync); + LIB_FUNCTION("+RhzSuuXwxo", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusSetDataVUser); + LIB_FUNCTION("E4BCVfx-YfM", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusSetDataVUserAsync); + LIB_FUNCTION("c6aYoa47YgI", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusSetMultiSlotVariable); + LIB_FUNCTION("5J9GGMludxY", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusSetMultiSlotVariableAsync); + LIB_FUNCTION("ukC55HsotJ4", "libSceNpTusCompat", 1, "libSceNpTus", sceNpTusTryAndSetVariable); + LIB_FUNCTION("xQfR51i4kck", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusTryAndSetVariableAsync); + LIB_FUNCTION("ZbitD262GhY", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusTryAndSetVariableVUser); + LIB_FUNCTION("trZ6QGW6jHs", "libSceNpTusCompat", 1, "libSceNpTus", + sceNpTusTryAndSetVariableVUserAsync); + LIB_FUNCTION("sRVb2Cf0GHg", "libSceNpTus", 1, "libSceNpTus", sceNpTssCreateNpTitleCtx); + LIB_FUNCTION("lBtrk+7lk14", "libSceNpTus", 1, "libSceNpTus", sceNpTssCreateNpTitleCtxA); + LIB_FUNCTION("-SUR+UoLS6c", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetData); + LIB_FUNCTION("DS2yu3Sjj1o", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetDataAsync); + LIB_FUNCTION("lL+Z3zCKNTs", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetSmallStorage); + LIB_FUNCTION("f2Pe4LGS2II", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetSmallStorageAsync); + LIB_FUNCTION("IVSbAEOxJ6I", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetStorage); + LIB_FUNCTION("k5NZIzggbuk", "libSceNpTus", 1, "libSceNpTus", sceNpTssGetStorageAsync); + LIB_FUNCTION("2eq1bMwgZYo", "libSceNpTus", 1, "libSceNpTus", sceNpTusAbortRequest); + LIB_FUNCTION("cRVmNrJDbG8", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariable); + LIB_FUNCTION("wPFah4-5Xec", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariableA); + LIB_FUNCTION("2dB427dT3Iw", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariableAAsync); + LIB_FUNCTION("Q2UmHdK04c8", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariableAsync); + LIB_FUNCTION("Nt1runsPVJc", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariableAVUser); + LIB_FUNCTION("GjlEgLCh4DY", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableAVUserAsync); + LIB_FUNCTION("EPeq43CQKxY", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableForCrossSave); + LIB_FUNCTION("mXZi1D2xwZE", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableForCrossSaveAsync); + LIB_FUNCTION("4VLlu7EIjzk", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableForCrossSaveVUser); + LIB_FUNCTION("6Lu9geO5TiA", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableForCrossSaveVUserAsync); + LIB_FUNCTION("ukr6FBSrkJw", "libSceNpTus", 1, "libSceNpTus", sceNpTusAddAndGetVariableVUser); + LIB_FUNCTION("lliK9T6ylJg", "libSceNpTus", 1, "libSceNpTus", + sceNpTusAddAndGetVariableVUserAsync); + LIB_FUNCTION("wjNhItL2wzg", "libSceNpTus", 1, "libSceNpTus", + sceNpTusChangeModeForOtherSaveDataOwners); + LIB_FUNCTION("BIkMmUfNKWM", "libSceNpTus", 1, "libSceNpTus", sceNpTusCreateNpTitleCtx); + LIB_FUNCTION("1n-dGukBgnY", "libSceNpTus", 1, "libSceNpTus", sceNpTusCreateNpTitleCtxA); + LIB_FUNCTION("3bh2aBvvmvM", "libSceNpTus", 1, "libSceNpTus", sceNpTusCreateRequest); + LIB_FUNCTION("hhy8+oecGac", "libSceNpTus", 1, "libSceNpTus", sceNpTusCreateTitleCtx); + LIB_FUNCTION("0DT5bP6YzBo", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotData); + LIB_FUNCTION("iXzUOM9sXU0", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotDataA); + LIB_FUNCTION("6-+Yqc-NppQ", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotDataAAsync); + LIB_FUNCTION("OCozl1ZtxRY", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotDataAsync); + LIB_FUNCTION("xutwCvsydkk", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotDataVUser); + LIB_FUNCTION("zDeH4tr+0cQ", "libSceNpTus", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotDataVUserAsync); + LIB_FUNCTION("mYhbiRtkE1Y", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotVariable); + LIB_FUNCTION("pwnE9Oa1uF8", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteMultiSlotVariableA); + LIB_FUNCTION("NQIw7tzo0Ow", "libSceNpTus", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariableAAsync); + LIB_FUNCTION("0nDVqcYECoM", "libSceNpTus", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariableAsync); + LIB_FUNCTION("o02Mtf8G6V0", "libSceNpTus", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariableVUser); + LIB_FUNCTION("WCzd3cxhubo", "libSceNpTus", 1, "libSceNpTus", + sceNpTusDeleteMultiSlotVariableVUserAsync); + LIB_FUNCTION("H3uq7x0sZOI", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteNpTitleCtx); + LIB_FUNCTION("CcIH40dYS88", "libSceNpTus", 1, "libSceNpTus", sceNpTusDeleteRequest); + LIB_FUNCTION("XOzszO4ONWU", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetData); + LIB_FUNCTION("yWEHUFkY1qI", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataA); + LIB_FUNCTION("xzG8mG9YlKY", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataAAsync); + LIB_FUNCTION("uHtKS5V1T5k", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataAsync); + LIB_FUNCTION("iaH+Sxlw32k", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataAVUser); + LIB_FUNCTION("uoFvgzwawAY", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataAVUserAsync); + LIB_FUNCTION("1TE3OvH61qo", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataForCrossSave); + LIB_FUNCTION("CFPx3eyaT34", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataForCrossSaveAsync); + LIB_FUNCTION("-LxFGYCJwww", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataForCrossSaveVUser); + LIB_FUNCTION("B7rBR0CoYLI", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetDataForCrossSaveVUserAsync); + LIB_FUNCTION("GQHCksS7aLs", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataVUser); + LIB_FUNCTION("5R6kI-8f+Hk", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetDataVUserAsync); + LIB_FUNCTION("DXigwIBTjWE", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsDataStatus); + LIB_FUNCTION("yixh7HDKWfk", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsDataStatusA); + LIB_FUNCTION("OheijxY5RYE", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetFriendsDataStatusAAsync); + LIB_FUNCTION("LUwvy0MOSqw", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsDataStatusAsync); + LIB_FUNCTION("TDoqRD+CE+M", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetFriendsDataStatusForCrossSave); + LIB_FUNCTION("68B6XDgSANk", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetFriendsDataStatusForCrossSaveAsync); + LIB_FUNCTION("cy+pAALkHp8", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsVariable); + LIB_FUNCTION("C8TY-UnQoXg", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsVariableA); + LIB_FUNCTION("wrImtTqUSGM", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsVariableAAsync); + LIB_FUNCTION("YFYWOwYI6DY", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetFriendsVariableAsync); + LIB_FUNCTION("mD6s8HtMdpk", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetFriendsVariableForCrossSave); + LIB_FUNCTION("FabW3QpY3gQ", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetFriendsVariableForCrossSaveAsync); + LIB_FUNCTION("pgcNwFHoOL4", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotDataStatus); + LIB_FUNCTION("833Y2TnyonE", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotDataStatusA); + LIB_FUNCTION("7uLPqiNvNLc", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusAAsync); + LIB_FUNCTION("Qyek420uZmM", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusAsync); + LIB_FUNCTION("azmjx3jBAZA", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusAVUser); + LIB_FUNCTION("668Ij9MYKEU", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusAVUserAsync); + LIB_FUNCTION("DgpRToHWN40", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusForCrossSave); + LIB_FUNCTION("LQ6CoHcp+ug", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusForCrossSaveAsync); + LIB_FUNCTION("KBfBmtxCdmI", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusForCrossSaveVUser); + LIB_FUNCTION("4UF2uu2eDCo", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync); + LIB_FUNCTION("NGCeFUl5ckM", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusVUser); + LIB_FUNCTION("bHWFSg6jvXc", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotDataStatusVUserAsync); + LIB_FUNCTION("F+eQlfcka98", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotVariable); + LIB_FUNCTION("GDXlRTxgd+M", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotVariableA); + LIB_FUNCTION("2BnPSY1Oxd8", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableAAsync); + LIB_FUNCTION("bcPB2rnhQqo", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotVariableAsync); + LIB_FUNCTION("AsziNQ9X2uk", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableAVUser); + LIB_FUNCTION("y-DJK+d+leg", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableAVUserAsync); + LIB_FUNCTION("m9XZnxw9AmE", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableForCrossSave); + LIB_FUNCTION("DFlBYT+Lm2I", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableForCrossSaveAsync); + LIB_FUNCTION("wTuuw4-6HI8", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableForCrossSaveVUser); + LIB_FUNCTION("DPcu0qWsd7Q", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync); + LIB_FUNCTION("uFxVYJEkcmc", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiSlotVariableVUser); + LIB_FUNCTION("qp-rTrq1klk", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiSlotVariableVUserAsync); + LIB_FUNCTION("NvHjFkx2rnU", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserDataStatus); + LIB_FUNCTION("lxNDPDnWfMc", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserDataStatusA); + LIB_FUNCTION("kt+k6jegYZ8", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusAAsync); + LIB_FUNCTION("0zkr0T+NYvI", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusAsync); + LIB_FUNCTION("fJU2TZId210", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusAVUser); + LIB_FUNCTION("WBh3zfrjS38", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusAVUserAsync); + LIB_FUNCTION("cVeBif6zdZ4", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusForCrossSave); + LIB_FUNCTION("lq0Anwhj0wY", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusForCrossSaveAsync); + LIB_FUNCTION("w-c7U0MW2KY", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusForCrossSaveVUser); + LIB_FUNCTION("H6sQJ99usfE", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync); + LIB_FUNCTION("xwJIlK0bHgA", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusVUser); + LIB_FUNCTION("I5dlIKkHNkQ", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserDataStatusVUserAsync); + LIB_FUNCTION("6G9+4eIb+cY", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserVariable); + LIB_FUNCTION("Gjixv5hqRVY", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserVariableA); + LIB_FUNCTION("eGunerNP9n0", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableAAsync); + LIB_FUNCTION("YRje5yEXS0U", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserVariableAsync); + LIB_FUNCTION("fVvocpq4mG4", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableAVUser); + LIB_FUNCTION("V8ZA3hHrAbw", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableAVUserAsync); + LIB_FUNCTION("Q5uQeScvTPE", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableForCrossSave); + LIB_FUNCTION("oZ8DMeTU-50", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableForCrossSaveAsync); + LIB_FUNCTION("Djuj2+1VNL0", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableForCrossSaveVUser); + LIB_FUNCTION("82RP7itI-zI", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableForCrossSaveVUserAsync); + LIB_FUNCTION("zB0vaHTzA6g", "libSceNpTus", 1, "libSceNpTus", sceNpTusGetMultiUserVariableVUser); + LIB_FUNCTION("xZXQuNSTC6o", "libSceNpTus", 1, "libSceNpTus", + sceNpTusGetMultiUserVariableVUserAsync); + LIB_FUNCTION("t7b6dmpQNiI", "libSceNpTus", 1, "libSceNpTus", sceNpTusPollAsync); + LIB_FUNCTION("4NrufkNCkiE", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetData); + LIB_FUNCTION("VzxN3tOouj8", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataA); + LIB_FUNCTION("4u58d6g6uwU", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataAAsync); + LIB_FUNCTION("G68xdfQuiyU", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataAsync); + LIB_FUNCTION("kbWqOt3QjKU", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataAVUser); + LIB_FUNCTION("Fmx4tapJGzo", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataAVUserAsync); + LIB_FUNCTION("+RhzSuuXwxo", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataVUser); + LIB_FUNCTION("E4BCVfx-YfM", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetDataVUserAsync); + LIB_FUNCTION("c6aYoa47YgI", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetMultiSlotVariable); + LIB_FUNCTION("cf-WMA0jYCc", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetMultiSlotVariableA); + LIB_FUNCTION("ypMObSwfcns", "libSceNpTus", 1, "libSceNpTus", + sceNpTusSetMultiSlotVariableAAsync); + LIB_FUNCTION("5J9GGMludxY", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetMultiSlotVariableAsync); + LIB_FUNCTION("1Cz0hTJFyh4", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetMultiSlotVariableVUser); + LIB_FUNCTION("CJAxTxQdwHM", "libSceNpTus", 1, "libSceNpTus", + sceNpTusSetMultiSlotVariableVUserAsync); + LIB_FUNCTION("6GKDdRCFx8c", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetThreadParam); + LIB_FUNCTION("KMlHj+tgfdQ", "libSceNpTus", 1, "libSceNpTus", sceNpTusSetTimeout); + LIB_FUNCTION("ukC55HsotJ4", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariable); + LIB_FUNCTION("0up4MP1wNtc", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariableA); + LIB_FUNCTION("bGTjTkHPHTE", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariableAAsync); + LIB_FUNCTION("xQfR51i4kck", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariableAsync); + LIB_FUNCTION("oGIcxlUabSA", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariableAVUser); + LIB_FUNCTION("uf77muc5Bog", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableAVUserAsync); + LIB_FUNCTION("MGvSJEHwyL8", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableForCrossSave); + LIB_FUNCTION("JKGYZ2F1yT8", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableForCrossSaveAsync); + LIB_FUNCTION("fcCwKpi4CbU", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableForCrossSaveVUser); + LIB_FUNCTION("CjVIpztpTNc", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableForCrossSaveVUserAsync); + LIB_FUNCTION("ZbitD262GhY", "libSceNpTus", 1, "libSceNpTus", sceNpTusTryAndSetVariableVUser); + LIB_FUNCTION("trZ6QGW6jHs", "libSceNpTus", 1, "libSceNpTus", + sceNpTusTryAndSetVariableVUserAsync); + LIB_FUNCTION("hYPJFWzFPjA", "libSceNpTus", 1, "libSceNpTus", sceNpTusWaitAsync); +}; + +} // namespace Libraries::Np::NpTus \ No newline at end of file diff --git a/src/core/libraries/np/np_tus.h b/src/core/libraries/np/np_tus.h new file mode 100644 index 000000000..ef4554ec3 --- /dev/null +++ b/src/core/libraries/np/np_tus.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/rtc/rtc.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Np::NpTus { + +using OrbisNpTssSlotId = s32; +using OrbisNpTusSlotId = s32; + +struct OrbisNpTusVariable { + OrbisNpId npId; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + u8 pad1[4]; + OrbisNpId lastChangedAuthor; + s64 variable; + s64 oldVariable; + OrbisNpAccountId owner; + OrbisNpAccountId lastChangedAuthorId; +}; + +struct OrbisNpTusDataInfo { + u64 size; + u8 data[384]; +}; + +struct OrbisNpTusDataStatus { + OrbisNpId npId; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + OrbisNpId lastChangedAuthor; + u8 pad2[4]; + void* data; + u64 dataSize; + OrbisNpTusDataInfo info; +}; + +static_assert(sizeof(OrbisNpTusDataStatus) == 0x1F0); + +struct OrbisNpTusDataStatusA { + OrbisNpOnlineId onlineId; + u8 pad[16]; + int set; + Libraries::Rtc::OrbisRtcTick lastChanged; + OrbisNpOnlineId lastChangedAuthor; + u8 pad2[20]; + void* data; + u64 dataSize; + OrbisNpTusDataInfo info; + OrbisNpAccountId owner; + OrbisNpAccountId lastChangedAuthorId; + u8 pad3[16]; +}; + +static_assert(sizeof(OrbisNpTusDataStatusA) == 0x210); + +enum class OrbisNpTssStatus : int { + Ok = 0, + Partial = 1, + NotModified = 2, +}; + +struct OrbisNpTssDataStatus { + Libraries::Rtc::OrbisRtcTick modified; + OrbisNpTssStatus status; + u64 contentLength; +}; + +struct OrbisNpTssGetDataOptParam { + u64 size; + u64* offset; + u64* last; + void* param; +}; + +s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Np::NpTus \ No newline at end of file diff --git a/src/core/libraries/np/np_types.h b/src/core/libraries/np/np_types.h index 8388ae47f..58c119bec 100644 --- a/src/core/libraries/np/np_types.h +++ b/src/core/libraries/np/np_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -9,6 +9,8 @@ // For structs and constants shared between multiple Np libraries. namespace Libraries::Np { +using OrbisNpAccountId = u64; + constexpr s32 ORBIS_NP_ONLINEID_MAX_LENGTH = 16; struct OrbisNpOnlineId { @@ -43,4 +45,21 @@ struct OrbisNpIdToken { u8 padding[7]; }; +using OrbisNpServiceLabel = u32; +constexpr s32 ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF; + +using OrbisNpAccountId = u64; +enum OrbisNpPlatformType : s32 { + None = 0, + PS3 = 1, + Vita = 2, + PS4 = 3, +}; + +struct OrbisNpPeerAddressA { + OrbisNpAccountId accountId; + OrbisNpPlatformType platform; + char padding[4]; +}; + }; // namespace Libraries::Np \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api.cpp b/src/core/libraries/np/np_web_api.cpp index db9d2f42a..0c633e0d1 100644 --- a/src/core/libraries/np/np_web_api.cpp +++ b/src/core/libraries/np/np_web_api.cpp @@ -1,155 +1,293 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/elf_info.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_web_api.h" +#include "core/libraries/np/np_web_api_error.h" +#include "core/libraries/np/np_web_api_internal.h" + +#include namespace Libraries::Np::NpWebApi { -s32 PS4_SYSV_ABI sceNpWebApiCreateContext() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); +static bool g_is_initialized = false; +static s32 g_active_library_contexts = 0; + +s32 PS4_SYSV_ABI sceNpWebApiCreateContext(s32 libCtxId, OrbisNpOnlineId* onlineId) { + if (libCtxId >= 0x8000) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID; + } + if (onlineId == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + return createUserContextWithOnlineId(libCtxId, onlineId); +} + +s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter( + s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam, u64 filterParamNum) { + if (pFilterParam == nullptr || filterParamNum == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_WARNING(Lib_NpWebApi, "called, libCtxId = {:#x}", libCtxId); + return createPushEventFilter(libCtxId, pFilterParam, filterParamNum); +} + +s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter( + s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) { + if (pNpServiceName == nullptr || pFilterParam == nullptr || filterParamNum == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + if (getCompiledSdkVersion() >= Common::ElfInfo::FW_20 && + npServiceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_WARNING(Lib_NpWebApi, + "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', " + "npServiceLabel = {:#x}", + libCtxId, handleId, pNpServiceName, npServiceLabel); + return createServicePushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel, + pFilterParam, filterParamNum); +} + +s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter(s32 libCtxId, s32 filterId) { + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId); + return deletePushEventFilter(libCtxId, filterId); +} + +s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter(s32 libCtxId, s32 filterId) { + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId); + return deleteServicePushEventFilter(libCtxId, filterId); +} + +s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + void* pUserArg) { + if (cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}", + titleUserCtxId, filterId, fmt::ptr(cbFunc)); + return registerExtdPushEventCallback(titleUserCtxId, filterId, cbFunc, nullptr, pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback(s32 titleUserCtxId, + OrbisNpWebApiNotificationCallback cbFunc, + void* pUserArg) { + if (cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId, + fmt::ptr(cbFunc)); + return registerNotificationCallback(titleUserCtxId, cbFunc, pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, + void* pUserArg) { + if (getCompiledSdkVersion() >= Common::ElfInfo::FW_10 && cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}", + titleUserCtxId, filterId, fmt::ptr(cbFunc)); + return registerPushEventCallback(titleUserCtxId, filterId, cbFunc, pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback( + s32 titleUserCtxId, s32 filterId, OrbisNpWebApiServicePushEventCallback cbFunc, + void* pUserArg) { + if (getCompiledSdkVersion() >= Common::ElfInfo::FW_10 && cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}", + titleUserCtxId, filterId, fmt::ptr(cbFunc)); + return registerServicePushEventCallback(titleUserCtxId, filterId, cbFunc, nullptr, nullptr, + pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback(s32 titleUserCtxId) { + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}", titleUserCtxId); + return unregisterNotificationCallback(titleUserCtxId); +} + +s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId) { + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId, + callbackId); + return unregisterPushEventCallback(titleUserCtxId, callbackId); +} + +s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId) { + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId, + callbackId); + return unregisterServicePushEventCallback(titleUserCtxId, callbackId); +} + +s32 PS4_SYSV_ABI sceNpWebApiAbortHandle(s32 libCtxId, s32 handleId) { + LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId); + return abortHandle(libCtxId, handleId); +} + +s32 PS4_SYSV_ABI sceNpWebApiAbortRequest(s64 requestId) { + LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}", requestId); + return abortRequest(requestId); +} + +s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(s64 requestId, const char* pFieldName, + const char* pValue) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : requestId = {:#x}, " + "pFieldName = '{}', pValue = '{}'", + requestId, (pFieldName ? pFieldName : "null"), (pValue ? pValue : "null")); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart(s64 requestId, + const OrbisNpWebApiMultipartPartParameter* pParam, + s32* pIndex) { + LOG_INFO(Lib_NpWebApi, + "called (STUBBED) : requestId = {:#x}, " + "pParam = {}, pIndex = {}", + requestId, fmt::ptr(pParam), fmt::ptr(pIndex)); + if (pParam) { + LOG_ERROR(Lib_NpWebApi, " Part params: headerNum = {}, contentLength = {}", + pParam->headerNum, pParam->contentLength); + } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); +void PS4_SYSV_ABI sceNpWebApiCheckTimeout() { + LOG_TRACE(Lib_NpWebApi, "called"); + if (!g_is_initialized) { + return; + } + return checkTimeout(); +} + +s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection(s32 userCtxId, + bool bRemainKeepAliveConnection) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : userCtxId = {:#x}, " + "bRemainKeepAliveConnection = {}", + userCtxId, bRemainKeepAliveConnection); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection(s32 userCtxId, const char* pApiGroup, + bool bRemainKeepAliveConnection) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : userCtxId = {:#x}, " + "pApiGroup = '{}', bRemainKeepAliveConnection = {}", + userCtxId, (pApiGroup ? pApiGroup : "null"), bRemainKeepAliveConnection); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiCreateContextA(s32 libCtxId, + Libraries::UserService::OrbisUserServiceUserId userId) { + if (libCtxId >= 0x8000) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID; + } + if (userId == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + return createUserContext(libCtxId, userId); } -s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter( + s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum) { + if ((pNpServiceName != nullptr && npServiceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) || + pFilterParam == nullptr || filterParamNum == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO( + Lib_NpWebApi, + "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', npServiceLabel = {:#x}", + libCtxId, handleId, (pNpServiceName ? pNpServiceName : "null"), npServiceLabel); + return createExtendedPushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel, + pFilterParam, filterParamNum, false); } -s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiCreateHandle(s32 libCtxId) { + return createHandle(libCtxId); } -s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest(s32 titleUserCtxId, const char* pApiGroup, + const char* pPath, + OrbisNpWebApiHttpMethod method, + s64* pRequestId) { + if (pApiGroup == nullptr || pPath == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + if (getCompiledSdkVersion() >= Common::ElfInfo::FW_25 && + method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO(Lib_NpWebApi, + "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}", + titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method)); + + return createRequest(titleUserCtxId, pApiGroup, pPath, method, nullptr, nullptr, pRequestId, + true); } -s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(s32 titleUserCtxId, const char* pApiGroup, + const char* pPath, OrbisNpWebApiHttpMethod method, + const OrbisNpWebApiContentParameter* pContentParameter, + s64* pRequestId) { + if (pApiGroup == nullptr || pPath == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + if (pContentParameter != nullptr && pContentParameter->contentLength != 0 && + pContentParameter->pContentType == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER; + } + + if (getCompiledSdkVersion() >= Common::ElfInfo::FW_25 && + method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO(Lib_NpWebApi, + "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}", + titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method)); + + return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, nullptr, + pRequestId, false); } -s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiDeleteContext(s32 titleUserCtxId) { + LOG_INFO(Lib_NpWebApi, "called titleUserCtxId = {:#x}", titleUserCtxId); + return deleteUserContext(titleUserCtxId); } -s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter(s32 libCtxId, s32 filterId) { + LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId); + return deleteExtendedPushEventFilter(libCtxId, filterId); } -s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle(s32 libCtxId, s32 handleId) { + LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId); + return deleteHandle(libCtxId, handleId); } -s32 PS4_SYSV_ABI sceNpWebApiAbortHandle() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(s64 requestId) { + LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}", requestId); + return deleteRequest(requestId); } -s32 PS4_SYSV_ABI sceNpWebApiAbortRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCheckTimeout() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCreateContextA() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCreateHandle() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiCreateRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiDeleteContext() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats(s32 userCtxId, const char* pApiGroup, + OrbisNpWebApiConnectionStats* pStats) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : userCtxId = {:#x}, " + "pApiGroup = '{}', pStats = {}", + userCtxId, (pApiGroup ? pApiGroup : "null"), fmt::ptr(pStats)); return ORBIS_OK; } @@ -158,135 +296,300 @@ s32 PS4_SYSV_ABI sceNpWebApiGetErrorCode() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue() { +s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue(s64 requestId, const char* pFieldName, + char* pValue, u64 valueSize) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : requestId = {:#x}, " + "pFieldName = '{}', pValue = {}, valueSize = {}", + requestId, (pFieldName ? pFieldName : "null"), fmt::ptr(pValue), valueSize); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength(s64 requestId, const char* pFieldName, + u64* pValueLength) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : requestId = {:#x}, " + "pFieldName = '{}', pValueLength = {}", + requestId, (pFieldName ? pFieldName : "null"), fmt::ptr(pValueLength)); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode(s64 requestId, s32* out_status_code) { + LOG_ERROR(Lib_NpWebApi, "called : requestId = {:#x}", requestId); + // On newer SDKs, NULL output pointer is invalid + if (getCompiledSdkVersion() > Common::ElfInfo::FW_10 && out_status_code == nullptr) + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + s32 returncode = getHttpStatusCodeInternal(requestId, out_status_code); + return returncode; +} + +s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats(s32 libCtxId, + OrbisNpWebApiMemoryPoolStats* pCurrentStat) { + LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, pCurrentStat = {}", libCtxId, + fmt::ptr(pCurrentStat)); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize) { + LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId, + poolSize); + if (!g_is_initialized) { + g_is_initialized = true; + s32 result = initializeLibrary(); + if (result < ORBIS_OK) { + return result; + } + } + + s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 0); + if (result >= ORBIS_OK) { + g_active_library_contexts++; + } + return result; +} + +s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence(s32 libHttpCtxId, u64 poolSize) { + LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId, + poolSize); + if (!g_is_initialized) { + g_is_initialized = true; + s32 result = initializeLibrary(); + if (result < ORBIS_OK) { + return result; + } + } + + s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 3); + if (result >= ORBIS_OK) { + g_active_library_contexts++; + } + return result; +} + +s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter( + s32 libCtxId, s32 handleId, const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, + u64 filterParamNum) { + if (pFilterParam == nullptr || filterParamNum == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId); + return createExtendedPushEventFilter(libCtxId, handleId, nullptr, + ORBIS_NP_INVALID_SERVICE_LABEL, pFilterParam, + filterParamNum, true); +} + +s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest( + s32 titleUserCtxId, const char* pApiGroup, const char* pPath, OrbisNpWebApiHttpMethod method, + const OrbisNpWebApiContentParameter* pContentParameter, + const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId) { + LOG_INFO(Lib_NpWebApi, "called"); + if (pApiGroup == nullptr || pPath == nullptr || + method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_PATCH) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + if (pContentParameter != nullptr && pContentParameter->contentLength != 0 && + pContentParameter->pContentType == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER; + } + + LOG_INFO(Lib_NpWebApi, + "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}", + titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method)); + + return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, pInternalArgs, + pRequestId, false); +} + +s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter( + s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) { + if (pFilterParam == nullptr || filterParamNum == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_WARNING(Lib_NpWebApi, + "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', " + "npServiceLabel = {:#x}", + libCtxId, handleId, (pNpServiceName ? pNpServiceName : "null"), npServiceLabel); + return createServicePushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel, + pFilterParam, filterParamNum); +} + +s32 PS4_SYSV_ABI sceNpWebApiIntInitialize(const OrbisNpWebApiIntInitializeArgs* args) { + LOG_INFO(Lib_NpWebApi, "called"); + if (args == nullptr || args->structSize != sizeof(OrbisNpWebApiIntInitializeArgs)) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + if (!g_is_initialized) { + g_is_initialized = true; + s32 result = initializeLibrary(); + if (result < ORBIS_OK) { + return result; + } + } + + s32 result = createLibraryContext(args->libHttpCtxId, args->poolSize, args->name, 2); + if (result >= ORBIS_OK) { + g_active_library_contexts++; + } + return result; +} + +s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback( + s32 titleUserCtxId, s32 filterId, OrbisNpWebApiInternalServicePushEventCallback cbFunc, + void* pUserArg) { + if (cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId, + fmt::ptr(cbFunc)); + return registerServicePushEventCallback(titleUserCtxId, filterId, nullptr, cbFunc, nullptr, + pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA( + s32 titleUserCtxId, s32 filterId, OrbisNpWebApiInternalServicePushEventCallbackA cbFunc, + void* pUserArg) { + if (cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId, + fmt::ptr(cbFunc)); + return registerServicePushEventCallback(titleUserCtxId, filterId, nullptr, nullptr, cbFunc, + pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiReadData(s64 requestId, void* pData, u64 size) { + LOG_ERROR(Lib_NpWebApi, "called : requestId = {:#x}, pData = {}, size = {:#x}", requestId, + fmt::ptr(pData), size); + if (pData == nullptr || size == 0) + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + + return readDataInternal(requestId, pData, size); +} + +s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA( + s32 titleUserCtxId, s32 filterId, OrbisNpWebApiExtdPushEventCallbackA cbFunc, void* pUserArg) { + if (cbFunc == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId, + fmt::ptr(cbFunc)); + return registerExtdPushEventCallbackA(titleUserCtxId, filterId, cbFunc, pUserArg); +} + +s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest(s64 requestId, s32 partIndex, const void* pData, + u64 dataSize) { + if (partIndex <= 0 || pData == nullptr || dataSize == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO(Lib_NpWebApi, + "called, requestId = {:#x}, " + "partIndex = {:#x}, pData = {}, dataSize = {:#x}", + requestId, partIndex, fmt::ptr(pData), dataSize); + return sendRequest(requestId, partIndex, pData, dataSize, 0, nullptr); +} + +s32 PS4_SYSV_ABI +sceNpWebApiSendMultipartRequest2(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, + OrbisNpWebApiResponseInformationOption* pRespInfoOption) { + if (partIndex <= 0 || pData == nullptr || dataSize == 0) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + LOG_INFO(Lib_NpWebApi, + "called, requestId = {:#x}, " + "partIndex = {:#x}, pData = {}, dataSize = {:#x}, pRespInfoOption = {}", + requestId, partIndex, fmt::ptr(pData), dataSize, fmt::ptr(pRespInfoOption)); + return sendRequest(requestId, partIndex, pData, dataSize, 1, pRespInfoOption); +} + +s32 PS4_SYSV_ABI sceNpWebApiSendRequest(s64 requestId, const void* pData, u64 dataSize) { + LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pData = {}, dataSize = {:#x}", requestId, + fmt::ptr(pData), dataSize); + return sendRequest(requestId, 0, pData, dataSize, 0, nullptr); +} + +s32 PS4_SYSV_ABI sceNpWebApiSendRequest2(s64 requestId, const void* pData, u64 dataSize, + OrbisNpWebApiResponseInformationOption* pRespInfoOption) { + LOG_INFO(Lib_NpWebApi, + "called, requestId = {:#x}, " + "pData = {}, dataSize = {:#x}, pRespInfoOption = {}", + requestId, fmt::ptr(pData), dataSize, fmt::ptr(pRespInfoOption)); + return sendRequest(requestId, 0, pData, dataSize, 1, pRespInfoOption); +} + +s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout) { + LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, handleId = {:#x}, timeout = {} ms", libCtxId, + handleId, timeout); + return setHandleTimeout(libCtxId, handleId, timeout); +} + +s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection(s32 libCtxId, s32 maxConnection) { + LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, maxConnection = {}", libCtxId, + maxConnection); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType(s64 requestId, const char* pTypeName, + const char* pBoundary) { + LOG_ERROR(Lib_NpWebApi, + "called (STUBBED) : requestId = {:#x}, " + "pTypeName = '{}', pBoundary = '{}'", + requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null")); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout(s64 requestId, u32 timeout) { + LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}, timeout = {} ms", requestId, timeout); + return setRequestTimeout(requestId, timeout); +} + +s32 PS4_SYSV_ABI sceNpWebApiTerminate(s32 libCtxId) { + LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}", libCtxId); + s32 result = terminateContext(libCtxId); + if (result != ORBIS_OK) { + return result; + } + + g_active_library_contexts--; + if (g_active_library_contexts == 0) { + g_is_initialized = false; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId) { + LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId, + callbackId); + return unregisterExtdPushEventCallback(titleUserCtxId, callbackId); +} + +s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId(const char* pJsonNpId, + Libraries::Np::OrbisNpId* pNpId) { LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceNpWebApiVshInitialize(s32 libHttpCtxId, u64 poolSize) { + LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId, + poolSize); + if (!g_is_initialized) { + g_is_initialized = true; + s32 result = initializeLibrary(); + if (result < ORBIS_OK) { + return result; + } + } -s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiInitialize() { - LOG_ERROR(Lib_NpWebApi, "(DUMMY) called"); - static s32 id = 0; - return ++id; -} - -s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntInitialize() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiReadData() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest2() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSendRequest() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSendRequest2() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiTerminate() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpWebApiVshInitialize() { - LOG_ERROR(Lib_NpWebApi, "(STUBBED) called"); - return ORBIS_OK; + s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 4); + if (result >= ORBIS_OK) { + g_active_library_contexts++; + } + return result; } s32 PS4_SYSV_ABI Func_064C4ED1EDBEB9E8() { diff --git a/src/core/libraries/np/np_web_api.h b/src/core/libraries/np/np_web_api.h index 6679662cb..8dd9441e0 100644 --- a/src/core/libraries/np/np_web_api.h +++ b/src/core/libraries/np/np_web_api.h @@ -1,9 +1,12 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/types.h" +#include "core/libraries/np/np_common.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/system/userservice.h" namespace Core::Loader { class SymbolsResolver; @@ -11,106 +14,115 @@ class SymbolsResolver; namespace Libraries::Np::NpWebApi { -s32 PS4_SYSV_ABI sceNpWebApiCreateContext(); -s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback(); -s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback(); -s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiAbortHandle(); -s32 PS4_SYSV_ABI sceNpWebApiAbortRequest(); -s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(); -s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart(); -s32 PS4_SYSV_ABI sceNpWebApiCheckTimeout(); -s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection(); -s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection(); -s32 PS4_SYSV_ABI sceNpWebApiCreateContextA(); -s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiCreateHandle(); -s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest(); -s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(); -s32 PS4_SYSV_ABI sceNpWebApiDeleteContext(); -s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle(); -s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(); -s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats(); -s32 PS4_SYSV_ABI sceNpWebApiGetErrorCode(); -s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue(); -s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength(); -s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode(); -s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats(); -s32 PS4_SYSV_ABI sceNpWebApiInitialize(); -s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence(); -s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest(); -s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter(); -s32 PS4_SYSV_ABI sceNpWebApiIntInitialize(); -s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA(); -s32 PS4_SYSV_ABI sceNpWebApiReadData(); -s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA(); -s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest(); -s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest2(); -s32 PS4_SYSV_ABI sceNpWebApiSendRequest(); -s32 PS4_SYSV_ABI sceNpWebApiSendRequest2(); -s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout(); -s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection(); -s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType(); -s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout(); -s32 PS4_SYSV_ABI sceNpWebApiTerminate(); -s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback(); -s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId(); -s32 PS4_SYSV_ABI sceNpWebApiVshInitialize(); -s32 PS4_SYSV_ABI Func_064C4ED1EDBEB9E8(); -s32 PS4_SYSV_ABI Func_0783955D4E9563DA(); -s32 PS4_SYSV_ABI Func_1A6D77F3FD8323A8(); -s32 PS4_SYSV_ABI Func_1E0693A26FE0F954(); -s32 PS4_SYSV_ABI Func_24A9B5F1D77000CF(); -s32 PS4_SYSV_ABI Func_24AAA6F50E4C2361(); -s32 PS4_SYSV_ABI Func_24D8853D6B47FC79(); -s32 PS4_SYSV_ABI Func_279B3E9C7C4A9DC5(); -s32 PS4_SYSV_ABI Func_28461E29E9F8D697(); -s32 PS4_SYSV_ABI Func_3C29624704FAB9E0(); -s32 PS4_SYSV_ABI Func_3F027804ED2EC11E(); -s32 PS4_SYSV_ABI Func_4066C94E782997CD(); -s32 PS4_SYSV_ABI Func_47C85356815DBE90(); -s32 PS4_SYSV_ABI Func_4FCE8065437E3B87(); -s32 PS4_SYSV_ABI Func_536280BE3DABB521(); -s32 PS4_SYSV_ABI Func_57A0E1BC724219F3(); -s32 PS4_SYSV_ABI Func_5819749C040B6637(); -s32 PS4_SYSV_ABI Func_6198D0C825E86319(); -s32 PS4_SYSV_ABI Func_61F2B9E8AB093743(); -s32 PS4_SYSV_ABI Func_6BC388E6113F0D44(); -s32 PS4_SYSV_ABI Func_7500F0C4F8DC2D16(); -s32 PS4_SYSV_ABI Func_75A03814C7E9039F(); -s32 PS4_SYSV_ABI Func_789D6026C521416E(); -s32 PS4_SYSV_ABI Func_7DED63D06399EFFF(); -s32 PS4_SYSV_ABI Func_7E55A2DCC03D395A(); -s32 PS4_SYSV_ABI Func_7E6C8F9FB86967F4(); -s32 PS4_SYSV_ABI Func_7F04B7D4A7D41E80(); -s32 PS4_SYSV_ABI Func_8E167252DFA5C957(); -s32 PS4_SYSV_ABI Func_95D0046E504E3B09(); -s32 PS4_SYSV_ABI Func_97284BFDA4F18FDF(); -s32 PS4_SYSV_ABI Func_99E32C1F4737EAB4(); -s32 PS4_SYSV_ABI Func_9CFF661EA0BCBF83(); -s32 PS4_SYSV_ABI Func_9EB0E1F467AC3B29(); -s32 PS4_SYSV_ABI Func_A2318FE6FBABFAA3(); -s32 PS4_SYSV_ABI Func_BA07A2E1BF7B3971(); -s32 PS4_SYSV_ABI Func_BD0803EEE0CC29A0(); -s32 PS4_SYSV_ABI Func_BE6F4E5524BB135F(); -s32 PS4_SYSV_ABI Func_C0D490EB481EA4D0(); -s32 PS4_SYSV_ABI Func_C175D392CA6D084A(); -s32 PS4_SYSV_ABI Func_CD0136AF165D2F2F(); -s32 PS4_SYSV_ABI Func_D1C0ADB7B52FEAB5(); -s32 PS4_SYSV_ABI Func_E324765D18EE4D12(); -s32 PS4_SYSV_ABI Func_E789F980D907B653(); -s32 PS4_SYSV_ABI Func_F9A32E8685627436(); +#define ORBIS_NP_WEBAPI_DEFAULT_CONNECTION_NUM 1 +#define ORBIS_NP_WEBAPI_MAX_CONNECTION_NUM 16 +#define ORBIS_NP_WEBAPI_PUSH_EVENT_DATA_TYPE_LEN_MAX 64 +#define ORBIS_NP_WEBAPI_EXTD_PUSH_EVENT_EXTD_DATA_KEY_LEN_MAX 32 + +struct OrbisNpWebApiPushEventDataType { + char val[ORBIS_NP_WEBAPI_PUSH_EVENT_DATA_TYPE_LEN_MAX + 1]; +}; + +struct OrbisNpWebApiExtdPushEventExtdDataKey { + char val[ORBIS_NP_WEBAPI_EXTD_PUSH_EVENT_EXTD_DATA_KEY_LEN_MAX + 1]; +}; + +struct OrbisNpWebApiPushEventFilterParameter { + OrbisNpWebApiPushEventDataType dataType; +}; + +struct OrbisNpWebApiServicePushEventFilterParameter { + OrbisNpWebApiPushEventDataType dataType; +}; + +struct OrbisNpWebApiExtdPushEventFilterParameter { + OrbisNpWebApiPushEventDataType dataType; + OrbisNpWebApiExtdPushEventExtdDataKey* pExtdDataKey; + u64 extdDataKeyNum; +}; + +struct OrbisNpWebApiExtdPushEventExtdData { + OrbisNpWebApiExtdPushEventExtdDataKey extdDataKey; + char* pData; + u64 dataLen; +}; + +struct OrbisNpWebApiHttpHeader { + char* pName; + char* pValue; +}; + +struct OrbisNpWebApiMultipartPartParameter { + OrbisNpWebApiHttpHeader* pHeaders; + u64 headerNum; + u64 contentLength; +}; + +enum OrbisNpWebApiHttpMethod : s32 { + ORBIS_NP_WEBAPI_HTTP_METHOD_GET, + ORBIS_NP_WEBAPI_HTTP_METHOD_POST, + ORBIS_NP_WEBAPI_HTTP_METHOD_PUT, + ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE, + ORBIS_NP_WEBAPI_HTTP_METHOD_PATCH +}; + +struct OrbisNpWebApiContentParameter { + u64 contentLength; + const char* pContentType; + u8 reserved[16]; +}; + +struct OrbisNpWebApiResponseInformationOption { + s32 httpStatus; + char* pErrorObject; + u64 errorObjectSize; + u64 responseDataSize; +}; + +struct OrbisNpWebApiMemoryPoolStats { + u64 poolSize; + u64 maxInuseSize; + u64 currentInuseSize; + s32 reserved; +}; + +struct OrbisNpWebApiConnectionStats { + u32 max; + u32 used; + u32 unused; + u32 keepAlive; + u64 reserved; +}; + +struct OrbisNpWebApiIntInitializeArgs { + u32 libHttpCtxId; + u8 reserved[4]; + u64 poolSize; + const char* name; + u64 structSize; +}; + +struct OrbisNpWebApiIntCreateRequestExtraArgs { + void* unk_0; + void* unk_1; + void* unk_2; +}; + +using OrbisNpWebApiPushEventCallback = PS4_SYSV_ABI void (*)(); // dummy + +using OrbisNpWebApiExtdPushEventCallback = PS4_SYSV_ABI void (*)(); // dummy +using OrbisNpWebApiExtdPushEventCallbackA = PS4_SYSV_ABI void (*)( + s32 userCtxId, s32 callbackId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel, + const OrbisNpPeerAddressA* pTo, const OrbisNpOnlineId* pToOnlineId, + const OrbisNpPeerAddressA* pFrom, const OrbisNpOnlineId* pFromOnlineId, + const OrbisNpWebApiPushEventDataType* pDataType, const char* pData, u64 dataLen, + const OrbisNpWebApiExtdPushEventExtdData* pExtdData, u64 extdDataNum, void* pUserArg); + +using OrbisNpWebApiServicePushEventCallback = PS4_SYSV_ABI void (*)(); // dummy +using OrbisNpWebApiInternalServicePushEventCallback = PS4_SYSV_ABI void (*)(); // dummy +using OrbisNpWebApiInternalServicePushEventCallbackA = PS4_SYSV_ABI void (*)(); // dummy + +using OrbisNpWebApiNotificationCallback = PS4_SYSV_ABI void (*)(); // dummy void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpWebApi \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api_error.h b/src/core/libraries/np/np_web_api_error.h new file mode 100644 index 000000000..c7f08224f --- /dev/null +++ b/src/core/libraries/np/np_web_api_error.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +constexpr int ORBIS_NP_WEBAPI_ERROR_OUT_OF_MEMORY = 0x80552901; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT = 0x80552902; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = 0x80552903; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80552904; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = 0x80552905; +constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = 0x80552906; +constexpr int ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN = 0x80552907; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = 0x80552908; +constexpr int ORBIS_NP_WEBAPI_ERROR_ABORTED = 0x80552909; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055290a; +constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290b; +constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290c; +constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = 0x8055290d; +constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290e; +constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290f; +constexpr int ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x80552910; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY = 0x80552911; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY = 0x80552912; +constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY = 0x80552913; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_HTTP_STATUS_CODE = 0x80552914; +constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_HTTP_HEADER = 0x80552915; +constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_FUNCTION_CALL = 0x80552916; +constexpr int ORBIS_NP_WEBAPI_ERROR_MULTIPART_PART_NOT_FOUND = 0x80552917; +constexpr int ORBIS_NP_WEBAPI_ERROR_PARAMETER_TOO_LONG = 0x80552918; +constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY = 0x80552919; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = 0x8055291a; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX = 0x8055291b; +constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055291c; +constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055291d; +constexpr int ORBIS_NP_WEBAPI_ERROR_AFTER_SEND = 0x8055291e; +constexpr int ORBIS_NP_WEBAPI_ERROR_TIMEOUT = 0x8055291f; diff --git a/src/core/libraries/np/np_web_api_internal.cpp b/src/core/libraries/np/np_web_api_internal.cpp new file mode 100644 index 000000000..3d6c7de86 --- /dev/null +++ b/src/core/libraries/np/np_web_api_internal.cpp @@ -0,0 +1,1534 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/config.h" +#include "common/elf_info.h" +#include "core/libraries/kernel/process.h" +#include "core/libraries/kernel/time.h" +#include "core/libraries/network/http.h" +#include "np_web_api_internal.h" + +#include + +namespace Libraries::Np::NpWebApi { + +static std::mutex g_global_mutex; +static std::map g_contexts; +static s32 g_library_context_count = 0; +static s32 g_user_context_count = 0; +static s32 g_handle_count = 0; +static s32 g_push_event_filter_count = 0; +static s32 g_service_push_event_filter_count = 0; +static s32 g_extended_push_event_filter_count = 0; +static s32 g_registered_callback_count = 0; +static s64 g_request_count = 0; +static u64 g_last_timeout_check = 0; +static s32 g_sdk_ver = 0; + +s32 initializeLibrary() { + return Kernel::sceKernelGetCompiledSdkVersion(&g_sdk_ver); +} + +s32 getCompiledSdkVersion() { + return g_sdk_ver; +} + +s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name, s32 type) { + std::scoped_lock lk{g_global_mutex}; + + g_library_context_count++; + if (g_library_context_count >= 0x8000) { + g_library_context_count = 1; + } + s32 ctx_id = g_library_context_count; + while (g_contexts.contains(ctx_id)) { + ctx_id--; + } + if (ctx_id <= 0) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX; + } + + // Create new context + g_contexts[ctx_id] = new OrbisNpWebApiContext{}; + auto& new_context = g_contexts.at(ctx_id); + new_context->libCtxId = ctx_id; + new_context->libHttpCtxId = libHttpCtxId; + new_context->type = type; + new_context->userCount = 0; + new_context->terminated = false; + if (name != nullptr) { + new_context->name = std::string(name); + } + + return ctx_id; +} + +OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag) { + std::scoped_lock lk{g_global_mutex}; + if (libCtxId < 1 || libCtxId >= 0x8000) { + return nullptr; + } + auto& context = g_contexts[libCtxId]; + std::scoped_lock lk2{context->contextLock}; + if (flag == 0 && context->terminated) { + return nullptr; + } + context->userCount++; + return context; +} + +void releaseContext(OrbisNpWebApiContext* context) { + std::scoped_lock lk{context->contextLock}; + context->userCount--; +} + +bool isContextTerminated(OrbisNpWebApiContext* context) { + std::scoped_lock lk{context->contextLock}; + return context->terminated; +} + +bool isContextBusy(OrbisNpWebApiContext* context) { + std::scoped_lock lk{context->contextLock}; + return context->userCount > 1; +} + +bool areContextHandlesBusy(OrbisNpWebApiContext* context) { + std::scoped_lock lk{context->contextLock}; + bool is_busy = false; + for (auto& handle : context->handles) { + if (handle.second->userCount > 0) { + return true; + } + } + return false; +} + +void lockContext(OrbisNpWebApiContext* context) { + context->contextLock.lock(); +} + +void unlockContext(OrbisNpWebApiContext* context) { + context->contextLock.unlock(); +} + +void markContextAsTerminated(OrbisNpWebApiContext* context) { + std::scoped_lock lk{context->contextLock}; + context->terminated = true; +} + +void checkContextTimeout(OrbisNpWebApiContext* context) { + u64 time = Kernel::sceKernelGetProcessTime(); + std::scoped_lock lk{context->contextLock}; + + for (auto& user_context : context->userContexts) { + checkUserContextTimeout(user_context.second); + } + + for (auto& value : context->timerHandles) { + auto& timer_handle = value.second; + if (!timer_handle->timedOut && timer_handle->handleTimeout != 0 && + timer_handle->handleEndTime < time) { + timer_handle->timedOut = true; + abortHandle(context->libCtxId, timer_handle->handleId); + } + } +} + +void checkTimeout() { + u64 time = Kernel::sceKernelGetProcessTime(); + if (time < g_last_timeout_check + 1000) { + return; + } + g_last_timeout_check = time; + std::scoped_lock lk{g_global_mutex}; + + for (auto& context : g_contexts) { + checkContextTimeout(context.second); + } +} + +s32 deleteContext(s32 libCtxId) { + std::scoped_lock lk{g_global_mutex}; + if (!g_contexts.contains(libCtxId)) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + auto& context = g_contexts[libCtxId]; + context->handles.clear(); + context->timerHandles.clear(); + context->pushEventFilters.clear(); + context->servicePushEventFilters.clear(); + context->extendedPushEventFilters.clear(); + + g_contexts.erase(libCtxId); + return ORBIS_OK; +} + +s32 terminateContext(s32 libCtxId) { + OrbisNpWebApiContext* ctx = findAndValidateContext(libCtxId); + if (ctx == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + if (g_sdk_ver < Common::ElfInfo::FW_40 && isContextBusy(ctx)) { + releaseContext(ctx); + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY; + } + + std::vector user_context_ids; + for (auto& user_context : ctx->userContexts) { + user_context_ids.emplace_back(user_context.first); + } + for (s32 user_context_id : user_context_ids) { + s32 result = deleteUserContext(user_context_id); + if (result != ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND || + g_sdk_ver < Common::ElfInfo::FW_40) { + return result; + } + } + + lockContext(ctx); + if (g_sdk_ver >= Common::ElfInfo::FW_40) { + for (auto& handle : ctx->handles) { + abortHandle(libCtxId, handle.first); + } + if (isContextTerminated(ctx)) { + unlockContext(ctx); + releaseContext(ctx); + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + markContextAsTerminated(ctx); + while (isContextBusy(ctx) || areContextHandlesBusy(ctx)) { + unlockContext(ctx); + Kernel::sceKernelUsleep(50000); + lockContext(ctx); + } + } + + unlockContext(ctx); + releaseContext(ctx); + return deleteContext(libCtxId); +} + +OrbisNpWebApiUserContext* findUserContextByUserId( + OrbisNpWebApiContext* context, Libraries::UserService::OrbisUserServiceUserId userId) { + if (userId == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return nullptr; + } + + std::scoped_lock lk{context->contextLock}; + for (auto& user_context : context->userContexts) { + if (user_context.second->userId == userId) { + user_context.second->userCount++; + return user_context.second; + } + } + return nullptr; +} + +OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context, s32 titleUserCtxId) { + std::scoped_lock lk{context->contextLock}; + if (!context->userContexts.contains(titleUserCtxId)) { + return nullptr; + } + OrbisNpWebApiUserContext* user_context = context->userContexts[titleUserCtxId]; + if (user_context->deleted) { + return nullptr; + } + user_context->userCount++; + return user_context; +} + +s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId) { + LOG_WARNING(Lib_NpWebApi, "called libCtxId = {}", libCtxId); + + Libraries::UserService::OrbisUserServiceUserId user_id = 0; + Libraries::UserService::sceUserServiceGetInitialUser(&user_id); + return createUserContext(libCtxId, user_id); +} + +s32 createUserContext(s32 libCtxId, Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_INFO(Lib_NpWebApi, "libCtxId = {}, userId = {}", libCtxId, userId); + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContextByUserId(context, userId); + if (user_context != nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST; + } + + std::scoped_lock lk{context->contextLock}; + + // Create new user context + g_user_context_count++; + if (g_user_context_count >= 0x10000) { + g_user_context_count = 1; + } + s32 user_ctx_id = (libCtxId << 0x10) | g_user_context_count; + while (context->userContexts.contains(user_ctx_id)) { + user_ctx_id--; + } + if (user_ctx_id <= (libCtxId << 0x10)) { + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX; + } + + context->userContexts[user_ctx_id] = new OrbisNpWebApiUserContext{}; + user_context = context->userContexts.at(user_ctx_id); + user_context->userCount = 0; + user_context->parentContext = context; + user_context->userId = userId; + user_context->userCtxId = user_ctx_id; + user_context->deleted = false; + + // TODO: Internal structs related to libSceHttp use are initialized here. + releaseContext(context); + return user_ctx_id; +} + +s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc, + void* pUserArg) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + lockContext(context); + user_context->notificationCallbackFunction = cbFunc; + user_context->pNotificationCallbackUserArgs = pUserArg; + unlockContext(context); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +s32 unregisterNotificationCallback(s32 titleUserCtxId) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + lockContext(context); + user_context->notificationCallbackFunction = nullptr; + user_context->pNotificationCallbackUserArgs = nullptr; + unlockContext(context); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +bool isUserContextBusy(OrbisNpWebApiUserContext* userContext) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + return userContext->userCount > 1; +} + +bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + bool is_busy = false; + for (auto& request : userContext->requests) { + request.second->userCount++; + bool req_busy = isRequestBusy(request.second); + request.second->userCount--; + if (req_busy) { + return true; + } + } + return false; +} + +void releaseUserContext(OrbisNpWebApiUserContext* userContext) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + userContext->userCount--; +} + +void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + for (auto& request : userContext->requests) { + checkRequestTimeout(request.second); + } +} + +s32 deleteUserContext(s32 titleUserCtxId) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + lockContext(context); + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + unlockContext(context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (g_sdk_ver < Common::ElfInfo::FW_40) { + if (isUserContextBusy(user_context)) { + releaseUserContext(user_context); + unlockContext(context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY; + } + + if (areUserContextRequestsBusy(user_context)) { + releaseUserContext(user_context); + unlockContext(context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY; + } + } else { + for (auto& request : user_context->requests) { + abortRequestInternal(context, user_context, request.second); + } + + if (user_context->deleted) { + releaseUserContext(user_context); + unlockContext(context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + user_context->deleted = true; + while (isUserContextBusy(user_context) || areUserContextRequestsBusy(user_context)) { + unlockContext(context); + Kernel::sceKernelUsleep(50000); + lockContext(context); + } + } + + user_context->extendedPushEventCallbacks.clear(); + user_context->servicePushEventCallbacks.clear(); + user_context->pushEventCallbacks.clear(); + user_context->requests.clear(); + context->userContexts.erase(titleUserCtxId); + + unlockContext(context); + releaseContext(context); + return ORBIS_OK; +} + +s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath, + OrbisNpWebApiHttpMethod method, + const OrbisNpWebApiContentParameter* pContentParameter, + const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId, + bool isMultipart) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + lockContext(user_context->parentContext); + if (g_sdk_ver >= Common::ElfInfo::FW_40 && user_context->deleted) { + unlockContext(user_context->parentContext); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + g_request_count++; + if (g_request_count >> 0x20 != 0) { + g_request_count = 1; + } + + s64 user_ctx_id = static_cast(titleUserCtxId); + s32 request_id = (user_ctx_id << 0x20) | g_request_count; + while (user_context->requests.contains(request_id)) { + request_id--; + } + // Real library would hang if this assert fails. + ASSERT_MSG(request_id <= (user_ctx_id << 0x20), "Too many requests!"); + user_context->requests[request_id] = new OrbisNpWebApiRequest{}; + + auto& request = user_context->requests[request_id]; + request->parentContext = context; + request->userCount = 0; + request->requestId = request_id; + request->userMethod = method; + request->multipart = isMultipart; + request->aborted = false; + + if (pApiGroup != nullptr) { + request->userApiGroup = std::string(pApiGroup); + } + + if (pPath != nullptr) { + request->userPath = std::string(pPath); + } + + if (pContentParameter != nullptr) { + request->userContentLength = pContentParameter->contentLength; + if (pContentParameter->pContentType != nullptr) { + request->userContentType = std::string(pContentParameter->pContentType); + } + } + + if (pInternalArgs != nullptr) { + ASSERT_MSG(pInternalArgs->unk_0 == nullptr && pInternalArgs->unk_1 == nullptr && + pInternalArgs->unk_2 == nullptr, + "Internal arguments for requests not supported"); + } + + unlockContext(user_context->parentContext); + + if (pRequestId != nullptr) { + *pRequestId = request->requestId; + } + + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext, s64 requestId) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + if (userContext->requests.contains(requestId)) { + return userContext->requests[requestId]; + } + + return nullptr; +} + +OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext, s64 requestId) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + if (userContext->requests.contains(requestId)) { + auto& request = userContext->requests[requestId]; + request->userCount++; + return request; + } + + return nullptr; +} + +bool isRequestBusy(OrbisNpWebApiRequest* request) { + std::scoped_lock lk{request->parentContext->contextLock}; + return request->userCount > 1; +} + +s32 setRequestTimeout(s64 requestId, u32 timeout) { + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + request->requestTimeout = timeout; + + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +void startRequestTimer(OrbisNpWebApiRequest* request) { + if (request->requestTimeout != 0 && request->requestEndTime == 0) { + request->requestEndTime = Kernel::sceKernelGetProcessTime() + request->requestTimeout; + } +} + +void checkRequestTimeout(OrbisNpWebApiRequest* request) { + u64 time = Kernel::sceKernelGetProcessTime(); + if (!request->timedOut && request->requestEndTime != 0 && request->requestEndTime < time) { + request->timedOut = true; + abortRequest(request->requestId); + } +} + +s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s8 flag, + const OrbisNpWebApiResponseInformationOption* pRespInfoOption) { + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + startRequestTimer(request); + + // TODO: multipart logic + + if (g_sdk_ver >= Common::ElfInfo::FW_25 && !request->sent) { + request->sent = true; + } + + lockContext(context); + if (!request->timedOut && request->aborted) { + unlockContext(context); + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_ABORTED; + } + + unlockContext(context); + + // Stubbing sceNpManagerIntGetSigninState call with a config check. + if (!Config::getPSNSignedIn()) { + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN; + } + + LOG_ERROR(Lib_NpWebApi, + "(STUBBED) called, requestId = {:#x}, pApiGroup = '{}', pPath = '{}', pContentType = " + "'{}', method = {}, multipart = {}", + requestId, request->userApiGroup, request->userPath, request->userContentType, + magic_enum::enum_name(request->userMethod), request->multipart); + + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext, + OrbisNpWebApiRequest* request) { + if (context == nullptr || userContext == nullptr || request == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + std::scoped_lock lk{context->contextLock}; + if (request->aborted) { + return ORBIS_OK; + } + + request->aborted = true; + + // TODO: Should also abort any Np requests and Http requests tied to this request. + + return ORBIS_OK; +} + +s32 abortRequest(s64 requestId) { + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequest(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + s32 result = abortRequestInternal(context, user_context, request); + + releaseUserContext(user_context); + releaseContext(context); + return result; +} + +void releaseRequest(OrbisNpWebApiRequest* request) { + std::scoped_lock lk{request->parentContext->contextLock}; + request->userCount--; +} + +s32 deleteRequest(s64 requestId) { + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + lockContext(context); + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + if (g_sdk_ver < Common::ElfInfo::FW_40 && isRequestBusy(request)) { + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY; + } + + abortRequestInternal(context, user_context, request); + while (isRequestBusy(request)) { + unlockContext(context); + Kernel::sceKernelUsleep(50000); + lockContext(context); + } + + releaseRequest(request); + user_context->requests.erase(request->requestId); + + releaseUserContext(user_context); + unlockContext(context); + releaseContext(context); + return ORBIS_OK; +} + +s32 createHandleInternal(OrbisNpWebApiContext* context) { + g_handle_count++; + if (g_handle_count >= 0xf0000000) { + g_handle_count = 1; + } + + std::scoped_lock lk{context->contextLock}; + + s32 handle_id = g_handle_count; + context->handles[handle_id] = new OrbisNpWebApiHandle{}; + auto& handle = context->handles[handle_id]; + handle->handleId = handle_id; + handle->userCount = 0; + handle->aborted = false; + handle->deleted = false; + + if (g_sdk_ver >= Common::ElfInfo::FW_30) { + context->timerHandles[handle_id] = new OrbisNpWebApiTimerHandle{}; + auto& timer_handle = context->timerHandles[handle_id]; + timer_handle->handleId = handle_id; + timer_handle->timedOut = false; + timer_handle->handleTimeout = 0; + timer_handle->handleEndTime = 0; + } + + return handle_id; +} + +s32 createHandle(s32 libCtxId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = createHandleInternal(context); + releaseContext(context); + return result; +} + +s32 setHandleTimeoutInternal(OrbisNpWebApiContext* context, s32 handleId, u32 timeout) { + std::scoped_lock lk{context->contextLock}; + if (!context->timerHandles.contains(handleId)) { + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + auto& handle = context->handles[handleId]; + handle->userCount++; + + auto& timer_handle = context->timerHandles[handleId]; + timer_handle->handleTimeout = timeout; + + handle->userCount--; + return ORBIS_OK; +} + +s32 setHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = setHandleTimeoutInternal(context, handleId, timeout); + releaseContext(context); + return result; +} + +void startHandleTimer(OrbisNpWebApiContext* context, s32 handleId) { + std::scoped_lock lk{context->contextLock}; + if (!context->timerHandles.contains(handleId)) { + return; + } + auto& timer_handle = context->timerHandles[handleId]; + if (timer_handle->handleTimeout == 0) { + return; + } + timer_handle->handleEndTime = Kernel::sceKernelGetProcessTime() + timer_handle->handleTimeout; +} + +void releaseHandle(OrbisNpWebApiContext* context, OrbisNpWebApiHandle* handle) { + if (handle != nullptr) { + std::scoped_lock lk{context->contextLock}; + handle->userCount--; + } +} + +s32 getHandle(OrbisNpWebApiContext* context, s32 handleId, OrbisNpWebApiHandle** handleOut) { + std::scoped_lock lk{context->contextLock}; + if (!context->handles.contains(handleId)) { + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + auto& handle = context->handles[handleId]; + handle->userCount++; + if (handleOut != nullptr) { + *handleOut = handle; + } + return ORBIS_OK; +} + +s32 abortHandle(s32 libCtxId, s32 handleId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiHandle* handle; + s32 result = getHandle(context, handleId, &handle); + if (result == ORBIS_OK) { + std::scoped_lock lk{context->contextLock}; + handle->aborted = true; + // TODO: sceNpAsmClientAbortRequest call + releaseHandle(context, handle); + } + + releaseContext(context); + return result; +} + +s32 deleteHandleInternal(OrbisNpWebApiContext* context, s32 handleId) { + lockContext(context); + if (!context->handles.contains(handleId)) { + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + + auto& handle = context->handles[handleId]; + if (g_sdk_ver >= Common::ElfInfo::FW_40) { + if (handle->deleted) { + unlockContext(context); + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + handle->deleted = true; + unlockContext(context); + abortHandle(context->libCtxId, handleId); + lockContext(context); + handle->userCount++; + while (handle->userCount > 1) { + handle->userCount--; + unlockContext(context); + Kernel::sceKernelUsleep(50000); + lockContext(context); + handle->userCount++; + } + handle->userCount--; + } else if (handle->userCount > 0) { + unlockContext(context); + return ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY; + } + + context->handles.erase(handleId); + + if (g_sdk_ver >= Common::ElfInfo::FW_30 && context->timerHandles.contains(handleId)) { + auto& timer_handle = context->timerHandles[handleId]; + context->timerHandles.erase(handleId); + } + + unlockContext(context); + return ORBIS_OK; +} + +s32 deleteHandle(s32 libCtxId, s32 handleId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = deleteHandleInternal(context, handleId); + releaseContext(context); + return result; +} + +s32 createPushEventFilterInternal(OrbisNpWebApiContext* context, + const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum) { + std::scoped_lock lk{context->contextLock}; + g_push_event_filter_count++; + if (g_push_event_filter_count >= 0xf0000000) { + g_push_event_filter_count = 1; + } + s32 filterId = g_push_event_filter_count; + + context->pushEventFilters[filterId] = new OrbisNpWebApiPushEventFilter{}; + auto& filter = context->pushEventFilters[filterId]; + filter->parentContext = context; + filter->filterId = filterId; + + if (pFilterParam != nullptr && filterParamNum != 0) { + for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) { + OrbisNpWebApiPushEventFilterParameter copy = OrbisNpWebApiPushEventFilterParameter{}; + memcpy(©, &pFilterParam[param_idx], sizeof(OrbisNpWebApiPushEventFilterParameter)); + filter->filterParams.emplace_back(copy); + } + } + return filterId; +} + +s32 createPushEventFilter(s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = createPushEventFilterInternal(context, pFilterParam, filterParamNum); + releaseContext(context); + return result; +} + +s32 deletePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) { + std::scoped_lock lk{context->contextLock}; + if (!context->pushEventFilters.contains(filterId)) { + return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND; + } + + context->pushEventFilters[filterId]->filterParams.clear(); + context->pushEventFilters.erase(filterId); + return ORBIS_OK; +} + +s32 deletePushEventFilter(s32 libCtxId, s32 filterId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = deletePushEventFilterInternal(context, filterId); + releaseContext(context); + return result; +} + +s32 registerPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, void* pUserArg) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + g_registered_callback_count++; + if (g_registered_callback_count >= 0xf0000000) { + g_registered_callback_count = 1; + } + s32 cbId = g_registered_callback_count; + + userContext->pushEventCallbacks[cbId] = new OrbisNpWebApiRegisteredPushEventCallback{}; + auto& cb = userContext->pushEventCallbacks[cbId]; + cb->callbackId = cbId; + cb->filterId = filterId; + cb->cbFunc = cbFunc; + cb->pUserArg = pUserArg; + + return cbId; +} + +s32 registerPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, void* pUserArg) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (g_sdk_ver >= Common::ElfInfo::FW_25 && !context->pushEventFilters.contains(filterId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND; + } + + s32 result = registerPushEventCallbackInternal(user_context, filterId, cbFunc, pUserArg); + releaseUserContext(user_context); + releaseContext(context); + return result; +} + +s32 unregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (!user_context->pushEventCallbacks.contains(callbackId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND; + } + + lockContext(context); + user_context->pushEventCallbacks.erase(callbackId); + unlockContext(context); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +s32 createServicePushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) { + std::scoped_lock lk{context->contextLock}; + if (!context->handles.contains(handleId)) { + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + auto& handle = context->handles[handleId]; + handle->userCount++; + + if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) { + // Seems sceNpManagerIntGetUserList fails? + LOG_DEBUG(Lib_NpWebApi, "Cannot create service push event while PSN is disabled"); + handle->userCount--; + return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND; + } + + g_service_push_event_filter_count++; + if (g_service_push_event_filter_count >= 0xf0000000) { + g_service_push_event_filter_count = 1; + } + s32 filterId = g_service_push_event_filter_count; + + context->servicePushEventFilters[filterId] = new OrbisNpWebApiServicePushEventFilter{}; + auto& filter = context->servicePushEventFilters[filterId]; + filter->parentContext = context; + filter->filterId = filterId; + + if (pNpServiceName == nullptr) { + filter->internal = true; + } else { + // TODO: if pNpServiceName is non-null, create an np request for this filter. + LOG_ERROR(Lib_NpWebApi, "Np behavior not handled"); + filter->npServiceName = std::string(pNpServiceName); + } + + filter->npServiceLabel = npServiceLabel; + + if (pFilterParam != nullptr && filterParamNum != 0) { + for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) { + OrbisNpWebApiServicePushEventFilterParameter copy = + OrbisNpWebApiServicePushEventFilterParameter{}; + memcpy(©, &pFilterParam[param_idx], + sizeof(OrbisNpWebApiServicePushEventFilterParameter)); + filter->filterParams.emplace_back(copy); + } + } + + handle->userCount--; + return filterId; +} + +s32 createServicePushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, + u64 filterParamNum) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + startHandleTimer(context, handleId); + s32 result = createServicePushEventFilterInternal(context, handleId, pNpServiceName, + npServiceLabel, pFilterParam, filterParamNum); + releaseContext(context); + return result; +} + +s32 deleteServicePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) { + std::scoped_lock lk{context->contextLock}; + if (!context->servicePushEventFilters.contains(filterId)) { + return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND; + } + + context->servicePushEventFilters[filterId]->filterParams.clear(); + context->servicePushEventFilters.erase(filterId); + return ORBIS_OK; +} + +s32 deleteServicePushEventFilter(s32 libCtxId, s32 filterId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = deleteServicePushEventFilterInternal(context, filterId); + releaseContext(context); + return result; +} + +s32 registerServicePushEventCallbackInternal( + OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, void* pUserArg) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + if (cbFunc == nullptr && intCbFunc == nullptr && intCbFuncA == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + + g_registered_callback_count++; + if (g_registered_callback_count >= 0xf0000000) { + g_registered_callback_count = 1; + } + s32 cbId = g_registered_callback_count; + + userContext->servicePushEventCallbacks[cbId] = + new OrbisNpWebApiRegisteredServicePushEventCallback{}; + auto& cb = userContext->servicePushEventCallbacks[cbId]; + cb->callbackId = cbId; + cb->filterId = filterId; + cb->cbFunc = cbFunc; + cb->internalCbFunc = intCbFunc; + cb->internalCbFuncA = intCbFuncA; + cb->pUserArg = pUserArg; + + return cbId; +} + +s32 registerServicePushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, + void* pUserArg) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (g_sdk_ver >= Common::ElfInfo::FW_25 && + !context->servicePushEventFilters.contains(filterId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND; + } + + s32 result = registerServicePushEventCallbackInternal(user_context, filterId, cbFunc, intCbFunc, + intCbFuncA, pUserArg); + releaseUserContext(user_context); + releaseContext(context); + return result; +} + +s32 unregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (!user_context->servicePushEventCallbacks.contains(callbackId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND; + } + + lockContext(context); + user_context->servicePushEventCallbacks.erase(callbackId); + unlockContext(context); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +s32 createExtendedPushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum, + bool internal) { + std::scoped_lock lk{context->contextLock}; + if (!context->handles.contains(handleId)) { + return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND; + } + auto& handle = context->handles[handleId]; + handle->userCount++; + + if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) { + // Seems sceNpManagerIntGetUserList fails? + LOG_DEBUG(Lib_NpWebApi, "Cannot create extended push event while PSN is disabled"); + handle->userCount--; + return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND; + } + + g_extended_push_event_filter_count++; + if (g_extended_push_event_filter_count >= 0xf0000000) { + g_extended_push_event_filter_count = 1; + } + s32 filterId = g_extended_push_event_filter_count; + + context->extendedPushEventFilters[filterId] = new OrbisNpWebApiExtendedPushEventFilter{}; + auto& filter = context->extendedPushEventFilters[filterId]; + filter->internal = internal; + filter->parentContext = context; + filter->filterId = filterId; + + if (pNpServiceName == nullptr) { + npServiceLabel = ORBIS_NP_INVALID_SERVICE_LABEL; + } else { + // TODO: if pNpServiceName is non-null, create an np request for this filter. + LOG_ERROR(Lib_NpWebApi, "Np behavior not handled"); + filter->npServiceName = std::string(pNpServiceName); + } + + filter->npServiceLabel = npServiceLabel; + + if (pFilterParam != nullptr && filterParamNum != 0) { + for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) { + OrbisNpWebApiExtdPushEventFilterParameter copy = + OrbisNpWebApiExtdPushEventFilterParameter{}; + memcpy(©, &pFilterParam[param_idx], + sizeof(OrbisNpWebApiExtdPushEventFilterParameter)); + filter->filterParams.emplace_back(copy); + // TODO: Every parameter is registered with an extended data filter through + // sceNpPushRegisterExtendedDataFilter + } + } + + handle->userCount--; + return filterId; +} + +s32 createExtendedPushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, + u64 filterParamNum, bool internal) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + startHandleTimer(context, handleId); + s32 result = createExtendedPushEventFilterInternal( + context, handleId, pNpServiceName, npServiceLabel, pFilterParam, filterParamNum, internal); + releaseContext(context); + return result; +} + +s32 deleteExtendedPushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) { + std::scoped_lock lk{context->contextLock}; + if (!context->extendedPushEventFilters.contains(filterId)) { + return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND; + } + + context->extendedPushEventFilters[filterId]->filterParams.clear(); + context->extendedPushEventFilters.erase(filterId); + return ORBIS_OK; +} + +s32 deleteExtendedPushEventFilter(s32 libCtxId, s32 filterId) { + OrbisNpWebApiContext* context = findAndValidateContext(libCtxId); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + s32 result = deleteExtendedPushEventFilterInternal(context, filterId); + releaseContext(context); + return result; +} + +s32 registerExtdPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, + void* pUserArg) { + std::scoped_lock lk{userContext->parentContext->contextLock}; + + g_registered_callback_count++; + if (g_registered_callback_count >= 0xf0000000) { + g_registered_callback_count = 1; + } + + if (cbFunc == nullptr && cbFuncA == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + } + s32 cbId = g_registered_callback_count; + + userContext->extendedPushEventCallbacks[cbId] = + new OrbisNpWebApiRegisteredExtendedPushEventCallback{}; + auto& cb = userContext->extendedPushEventCallbacks[cbId]; + cb->callbackId = cbId; + cb->filterId = filterId; + cb->cbFunc = cbFunc; + cb->cbFuncA = cbFuncA; + cb->pUserArg = pUserArg; + + return cbId; +} + +s32 registerExtdPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, void* pUserArg) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (!context->extendedPushEventFilters.contains(filterId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND; + } + + s32 result = + registerExtdPushEventCallbackInternal(user_context, filterId, cbFunc, cbFuncA, pUserArg); + releaseUserContext(user_context); + releaseContext(context); + return result; +} + +s32 registerExtdPushEventCallbackA(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallbackA cbFunc, void* pUserArg) { + return registerExtdPushEventCallback(titleUserCtxId, filterId, nullptr, cbFunc, pUserArg); +} + +s32 unregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId) { + OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + if (!user_context->extendedPushEventCallbacks.contains(callbackId)) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND; + } + + lockContext(context); + user_context->extendedPushEventCallbacks.erase(callbackId); + unlockContext(context); + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI getHttpRequestIdFromRequest(OrbisNpWebApiRequest* request) + +{ + return request->requestId; +} + +s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code) { + s32 status_code; + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequest(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + // Query HTTP layer + { + int32_t httpReqId = getHttpRequestIdFromRequest(request); + s32 err = Libraries::Http::sceHttpGetStatusCode(httpReqId, &status_code); + + if (out_status_code != nullptr) + *out_status_code = status_code; + + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + + return err; + } +} + +void PS4_SYSV_ABI setRequestEndTime(OrbisNpWebApiRequest* request) { + u64 time; + if ((request->requestTimeout != 0) && (request->requestEndTime == 0)) { + time = Libraries::Kernel::sceKernelGetProcessTime(); + request->requestEndTime = (u64)request->requestTimeout + time; + } +} + +void PS4_SYSV_ABI clearRequestEndTime(OrbisNpWebApiRequest* req) { + req->requestEndTime = 0; + return; +} + +bool PS4_SYSV_ABI hasRequestTimedOut(OrbisNpWebApiRequest* request) { + return request->timedOut; +} + +bool PS4_SYSV_ABI isRequestAborted(OrbisNpWebApiRequest* request) { + return request->aborted; +} + +void PS4_SYSV_ABI setRequestState(OrbisNpWebApiRequest* request, u8 state) { + request->requestState = state; +} + +u64 PS4_SYSV_ABI copyRequestData(OrbisNpWebApiRequest* request, void* data, u64 size) { + u64 readSize = 0; + + if (request->remainingData != 0) { + u64 remainingSize = request->remainingData - request->readOffset; + + if (remainingSize != 0) { + if (remainingSize < size) { + size = remainingSize; + } + memcpy(data, request->data + request->readOffset, size); + request->readOffset += static_cast(size); + readSize = size; + } + } + return readSize; +} + +s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size) { + u32 offset; + s32 result; + u64 remainingSize; + u64 bytesCopied; + + OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30); + if (context == nullptr) { + return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20); + if (user_context == nullptr) { + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND; + } + + OrbisNpWebApiRequest* request = findRequest(user_context, requestId); + if (request == nullptr) { + releaseUserContext(user_context); + releaseContext(context); + return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND; + } + + setRequestEndTime(request); + + bytesCopied = copyRequestData(request, pData, size); + offset = (u32)bytesCopied; + remainingSize = size - offset; + + // If caller wants more data than buffered + if (remainingSize != 0) { + lockContext(context); + setRequestState(request, 5); // TODO add request states? + + if (!hasRequestTimedOut(request) && isRequestAborted(request)) { + unlockContext(context); + offset = ORBIS_NP_WEBAPI_ERROR_ABORTED; + } else { + unlockContext(context); + + int32_t httpReqId = getHttpRequestIdFromRequest(request); + int32_t httpRead = + Libraries::Http::sceHttpReadData(httpReqId, (u8*)pData + offset, remainingSize); + + if (httpRead < 0) + httpRead = 0; + + offset += httpRead; + } + } + + // Final state resolution + lockContext(context); + setRequestState(request, 0); + + if (hasRequestTimedOut(request)) { + result = ORBIS_NP_WEBAPI_ERROR_TIMEOUT; + } else if (isRequestAborted(request)) { + result = ORBIS_NP_WEBAPI_ERROR_ABORTED; + } else { + result = offset; + } + + unlockContext(context); + + // Cleanup + clearRequestEndTime(request); + releaseRequest(request); + releaseUserContext(user_context); + releaseContext(context); + + return result; +} + +}; // namespace Libraries::Np::NpWebApi \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api_internal.h b/src/core/libraries/np/np_web_api_internal.h new file mode 100644 index 000000000..571df0ab9 --- /dev/null +++ b/src/core/libraries/np/np_web_api_internal.h @@ -0,0 +1,301 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_web_api.h" +#include "core/libraries/np/np_web_api_error.h" + +namespace Libraries::Np::NpWebApi { + +// Structs reference each other, so declare them before their contents. +struct OrbisNpWebApiContext; +struct OrbisNpWebApiUserContext; +struct OrbisNpWebApiRequest; +struct OrbisNpWebApiHandle; +struct OrbisNpWebApiTimerHandle; +struct OrbisNpWebApiPushEventFilter; +struct OrbisNpWebApiServicePushEventFilter; +struct OrbisNpWebApiExtendedPushEventFilter; +struct OrbisNpWebApiRegisteredPushEventCallback; +struct OrbisNpWebApiRegisteredServicePushEventCallback; +struct OrbisNpWebApiRegisteredExtendedPushEventCallback; + +struct OrbisNpWebApiContext { + s32 type; + s32 userCount; + s32 libCtxId; + s32 libHttpCtxId; + std::recursive_mutex contextLock; + std::map userContexts; + std::map handles; + std::map timerHandles; + std::map pushEventFilters; + std::map servicePushEventFilters; + std::map extendedPushEventFilters; + std::string name; + bool terminated; +}; + +struct OrbisNpWebApiUserContext { + OrbisNpWebApiContext* parentContext; + s32 userCount; + s32 userCtxId; + Libraries::UserService::OrbisUserServiceUserId userId; + std::map requests; + std::map pushEventCallbacks; + std::map servicePushEventCallbacks; + std::map extendedPushEventCallbacks; + bool deleted; + OrbisNpWebApiNotificationCallback notificationCallbackFunction; + void* pNotificationCallbackUserArgs; +}; + +struct OrbisNpWebApiRequest { + OrbisNpWebApiContext* parentContext; + s32 userCount; + s64 requestId; + std::string userApiGroup; + std::string userPath; + OrbisNpWebApiHttpMethod userMethod; + u64 userContentLength; + std::string userContentType; + bool multipart; + bool aborted; + bool sent; + u32 requestTimeout; + u64 requestEndTime; + bool timedOut; + // not sure Stephen + u8 requestState; + u64 remainingData; + u32 readOffset; + char data[64]; +}; + +struct OrbisNpWebApiHandle { + s32 handleId; + bool aborted; + bool deleted; + s32 userCount; +}; + +struct OrbisNpWebApiTimerHandle { + s32 handleId; + u32 handleTimeout; + u64 handleEndTime; + bool timedOut; +}; + +struct OrbisNpWebApiPushEventFilter { + s32 filterId; + std::vector filterParams; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiServicePushEventFilter { + s32 filterId; + bool internal; + std::vector filterParams; + std::string npServiceName; + OrbisNpServiceLabel npServiceLabel; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiExtendedPushEventFilter { + s32 filterId; + bool internal; + std::vector filterParams; + std::string npServiceName; + OrbisNpServiceLabel npServiceLabel; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiRegisteredPushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiPushEventCallback cbFunc; + void* pUserArg; +}; + +struct OrbisNpWebApiRegisteredServicePushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiServicePushEventCallback cbFunc; + OrbisNpWebApiInternalServicePushEventCallback internalCbFunc; + // Note: real struct stores both internal callbacks in one field + OrbisNpWebApiInternalServicePushEventCallbackA internalCbFuncA; + void* pUserArg; +}; + +struct OrbisNpWebApiRegisteredExtendedPushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiExtdPushEventCallback cbFunc; + // Note: real struct stores both callbacks in one field + OrbisNpWebApiExtdPushEventCallbackA cbFuncA; + void* pUserArg; +}; + +// General functions +s32 initializeLibrary(); // FUN_01001450 +s32 getCompiledSdkVersion(); // FUN_01001440 + +// Library context functions +s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name, + s32 type); // FUN_01006970 +OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag = 0); // FUN_01006860 +void releaseContext(OrbisNpWebApiContext* context); // FUN_01006fc0 +bool isContextTerminated(OrbisNpWebApiContext* context); // FUN_01006910 +bool isContextBusy(OrbisNpWebApiContext* context); // FUN_01008a50 +bool areContextHandlesBusy(OrbisNpWebApiContext* context); // FUN_01008c20 +void lockContext(OrbisNpWebApiContext* context); // FUN_010072e0 +void unlockContext(OrbisNpWebApiContext* context); // FUN_010072f0 +void markContextAsTerminated(OrbisNpWebApiContext* context); // FUN_01008bf0 +void checkContextTimeout(OrbisNpWebApiContext* context); // FUN_01008ad0 +void checkTimeout(); // FUN_01003700 +s32 deleteContext(s32 libCtxId); // FUN_01006c70 +s32 terminateContext(s32 libCtxId); // FUN_010014b0 + +// User context functions +OrbisNpWebApiUserContext* findUserContextByUserId( + OrbisNpWebApiContext* context, + Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010075c0 +OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context, + s32 userCtxId); // FUN_01007530 +s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId); // FUN_010016a0 +s32 createUserContext(s32 libCtxId, + Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010015c0 +s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc, + void* pUserArg); // FUN_01003770 +s32 unregisterNotificationCallback(s32 titleUserCtxId); // FUN_01003800 +bool isUserContextBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100ea40 +bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100d1f0 +void releaseUserContext(OrbisNpWebApiUserContext* userContext); // FUN_0100caa0 +void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext); // FUN_0100ea90 +s32 deleteUserContext(s32 userCtxId); // FUN_01001710 + +// Request functions +s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath, + OrbisNpWebApiHttpMethod method, + const OrbisNpWebApiContentParameter* pContentParameter, + const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId, + bool isMultipart); // FUN_01001850 +OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext, + s64 requestId); // FUN_0100d3a0 +OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext, + s64 requestId); // FUN_0100d330 +bool isRequestBusy(OrbisNpWebApiRequest* request); // FUN_0100c1b0 +s32 setRequestTimeout(s64 requestId, u32 timeout); // FUN_01003610 +void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0 +void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130 +s32 sendRequest( + s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag, + const OrbisNpWebApiResponseInformationOption* pResponseInformationOption); // FUN_01001c50 +s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext, + OrbisNpWebApiRequest* request); // FUN_01001b70 +s32 abortRequest(s64 requestId); // FUN_01002c70 +void releaseRequest(OrbisNpWebApiRequest* request); // FUN_01009fb0 +s32 deleteRequest(s64 requestId); // FUN_010019a0 + +// Handle functions +s32 createHandleInternal(OrbisNpWebApiContext* context); // FUN_01007730 +s32 createHandle(s32 libCtxId); // FUN_01002ee0 +s32 setHandleTimeoutInternal(OrbisNpWebApiContext* context, s32 handleId, + u32 timeout); // FUN_01007ed0 +s32 setHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout); // FUN_010036b0 +void startHandleTimer(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007fd0 +void releaseHandle(OrbisNpWebApiContext* context, OrbisNpWebApiHandle* handle); // FUN_01007ea0 +s32 getHandle(OrbisNpWebApiContext* context, s32 handleId, + OrbisNpWebApiHandle** handleOut); // FUN_01007e20 +s32 abortHandle(s32 libCtxId, s32 handleId); // FUN_01003390 +s32 deleteHandleInternal(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007a00 +s32 deleteHandle(s32 libCtxId, s32 handleId); // FUN_01002f20 + +// Push event filter functions +s32 createPushEventFilterInternal(OrbisNpWebApiContext* context, + const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01008040 +s32 createPushEventFilter(s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01002d10 +s32 deletePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId); // FUN_01008180 +s32 deletePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002d60 + +// Push event callback functions +s32 registerPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, + void* userArg); // FUN_0100d450 +s32 registerPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, + void* pUserArg); // FUN_01002da0 +s32 unregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01002e50 + +// Service push event filter functions +s32 createServicePushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_010082f0 +s32 createServicePushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01002f60 +s32 deleteServicePushEventFilterInternal(OrbisNpWebApiContext* context, + s32 filterId); // FUN_010084f0 +s32 deleteServicePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002fe0 + +// Service push event callback functions +s32 registerServicePushEventCallbackInternal( + OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, void* pUserArg); // FUN_0100d8c0 +s32 registerServicePushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, + void* pUserArg); // FUN_01003030 +s32 unregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_010030f0 + +// Extended push event filter functions +s32 createExtendedPushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum, + bool internal); // FUN_01008680 +s32 createExtendedPushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, + u64 filterParamNum, bool internal); // FUN_01003180 +s32 deleteExtendedPushEventFilterInternal(OrbisNpWebApiContext* context, + s32 filterId); // FUN_01008880 +s32 deleteExtendedPushEventFilter(s32 libCtxId, s32 filterId); // FUN_01003200 + +// Extended push event callback functions +s32 registerExtdPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, + void* pUserArg); // FUN_0100df60 +s32 registerExtdPushEventCallback(s32 userCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, + void* pUserArg); // FUN_01003250 +s32 registerExtdPushEventCallbackA(s32 userCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallbackA cbFunc, + void* pUserArg); // FUN_01003240 +s32 unregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01003300 + +s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code); +s32 PS4_SYSV_ABI getHttpRequestIdFromRequest(OrbisNpWebApiRequest* request); +s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size); +void PS4_SYSV_ABI setRequestEndTime(OrbisNpWebApiRequest* request); +void PS4_SYSV_ABI clearRequestEndTime(OrbisNpWebApiRequest* req); +bool PS4_SYSV_ABI hasRequestTimedOut(OrbisNpWebApiRequest* request); +void PS4_SYSV_ABI setRequestState(OrbisNpWebApiRequest* request, u8 state); +u64 PS4_SYSV_ABI copyRequestData(OrbisNpWebApiRequest* request, void* data, u64 size); + +}; // namespace Libraries::Np::NpWebApi \ No newline at end of file diff --git a/src/core/libraries/np/object_manager.h b/src/core/libraries/np/object_manager.h new file mode 100644 index 000000000..5f6ed9663 --- /dev/null +++ b/src/core/libraries/np/object_manager.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +template +struct ObjectManager { + s32 GetObject(int objectId, T** out) { + std::scoped_lock lk{mutex}; + if (objectId < 1 || objectId > N) { + return INVALID_OBJECT_ID_ERROR; + } + auto obj = objects[objectId - 1]; + if (!obj) { + return OBJECT_NOT_FOUND_ERROR; + } + *out = obj; + return ORBIS_OK; + } + + template + s32 CreateObject(Args&&... args) { + std::scoped_lock lk{mutex}; + + if (auto slot = std::ranges::find(objects, nullptr); slot != objects.end()) { + *slot = new T{args...}; + + return std::ranges::distance(objects.begin(), slot) + 1; + } + + return MAX_OBJECTS_ERROR; + } + + s32 DeleteObject(int objectId) { + std::scoped_lock lk{mutex}; + + if (objectId < 1 || objectId > N) { + return INVALID_OBJECT_ID_ERROR; + } + auto obj = objects[objectId - 1]; + if (!obj) { + return OBJECT_NOT_FOUND_ERROR; + } + + delete obj; + objects[objectId - 1] = nullptr; + + return ORBIS_OK; + } + +private: + std::mutex mutex; + std::array objects = {nullptr}; +}; \ No newline at end of file diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 09f404969..5f50b8a7d 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" @@ -94,18 +94,6 @@ int PS4_SYSV_ABI scePadGetCapability() { int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) { LOG_DEBUG(Lib_Pad, "called handle = {}", handle); - if (handle < 0) { - pInfo->touchPadInfo.pixelDensity = 1; - pInfo->touchPadInfo.resolution.x = 1920; - pInfo->touchPadInfo.resolution.y = 950; - pInfo->stickInfo.deadZoneLeft = 1; - pInfo->stickInfo.deadZoneRight = 1; - pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; - pInfo->connectedCount = 1; - pInfo->connected = false; - pInfo->deviceClass = OrbisPadDeviceClass::Standard; - return ORBIS_OK; - } pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; @@ -113,8 +101,12 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->stickInfo.deadZoneRight = 1; pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; pInfo->connectedCount = 1; - pInfo->connected = true; pInfo->deviceClass = OrbisPadDeviceClass::Standard; + if (handle < 0) { + pInfo->connected = false; + return ORBIS_OK; + } + pInfo->connected = true; if (Config::getUseSpecialPad()) { pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL; pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); @@ -159,7 +151,8 @@ int PS4_SYSV_ABI scePadGetFeatureReport() { return ORBIS_OK; } -int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index) { +int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index) { if (!g_initialized) { return ORBIS_PAD_ERROR_NOT_INITIALIZED; } @@ -256,7 +249,8 @@ int PS4_SYSV_ABI scePadMbusTerm() { return ORBIS_OK; } -int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam) { +int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index, const OrbisPadOpenParam* pParam) { if (!g_initialized) { return ORBIS_PAD_ERROR_NOT_INITIALIZED; } @@ -277,8 +271,8 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP return 1; // dummy } -int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, - const OrbisPadOpenExtParam* pParam) { +int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index, const OrbisPadOpenExtParam* pParam) { LOG_ERROR(Lib_Pad, "(STUBBED) called"); if (Config::getUseSpecialPad()) { if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) @@ -300,20 +294,16 @@ int PS4_SYSV_ABI scePadOutputReport() { return ORBIS_OK; } -int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - LOG_TRACE(Lib_Pad, "called"); - int connected_count = 0; - bool connected = false; - Input::State states[64]; - auto* controller = Common::Singleton::Instance(); - const auto* engine = controller->GetEngine(); - int ret_num = controller->ReadStates(states, num, &connected, &connected_count); - +int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected, + u32 connected_count) { if (!connected) { - ret_num = 1; + pData[0] = {}; + pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + pData[0].connected = false; + return 1; } - for (int i = 0; i < ret_num; i++) { + for (int i = 0; i < num; i++) { pData[i].buttons = states[i].buttonsState; pData[i].leftStick.x = states[i].axes[static_cast(Input::Axis::LeftX)]; pData[i].leftStick.y = states[i].axes[static_cast(Input::Axis::LeftY)]; @@ -321,20 +311,16 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].rightStick.y = states[i].axes[static_cast(Input::Axis::RightY)]; pData[i].analogButtons.l2 = states[i].axes[static_cast(Input::Axis::TriggerLeft)]; pData[i].analogButtons.r2 = states[i].axes[static_cast(Input::Axis::TriggerRight)]; - pData[i].acceleration.x = states[i].acceleration.x; - pData[i].acceleration.y = states[i].acceleration.y; - pData[i].acceleration.z = states[i].acceleration.z; - pData[i].angularVelocity.x = states[i].angularVelocity.x; - pData[i].angularVelocity.y = states[i].angularVelocity.y; - pData[i].angularVelocity.z = states[i].angularVelocity.z; - pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; pData[i].acceleration.x = states[i].acceleration.x * 0.098; pData[i].acceleration.y = states[i].acceleration.y * 0.098; pData[i].acceleration.z = states[i].acceleration.z * 0.098; pData[i].angularVelocity.x = states[i].angularVelocity.x; pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; + pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); if (engine && handle == 1) { const auto gyro_poll_rate = engine->GetAccelPollRate(); if (gyro_poll_rate != 0.0f) { @@ -352,7 +338,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { controller->SetLastOrientation(outputOrientation); } } - pData[i].touchData.touchNum = (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); @@ -407,7 +392,18 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].deviceUniqueDataLen = 0; } - return ret_num; + return num; +} + +int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { + LOG_TRACE(Lib_Pad, "called"); + int connected_count = 0; + bool connected = false; + std::vector states(64); + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); + int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count); + return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count); } int PS4_SYSV_ABI scePadReadBlasterForTracker() { @@ -437,95 +433,11 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { } auto* controller = Common::Singleton::Instance(); const auto* engine = controller->GetEngine(); - int connectedCount = 0; - bool isConnected = false; + int connected_count = 0; + bool connected = false; Input::State state; - controller->ReadState(&state, &isConnected, &connectedCount); - pData->buttons = state.buttonsState; - pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; - pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; - pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; - pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; - pData->acceleration.x = state.acceleration.x * 0.098; - pData->acceleration.y = state.acceleration.y * 0.098; - pData->acceleration.z = state.acceleration.z * 0.098; - pData->angularVelocity.x = state.angularVelocity.x; - pData->angularVelocity.y = state.angularVelocity.y; - pData->angularVelocity.z = state.angularVelocity.z; - pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - - // Only do this on handle 1 for now - if (engine && handle == 1) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = - std::chrono::duration_cast(now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, - lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } - pData->touchData.touchNum = - (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); - - // Only do this on handle 1 for now - if (handle == 1) { - if (controller->GetTouchCount() >= 127) { - controller->SetTouchCount(0); - } - - if (controller->GetSecondaryTouchCount() >= 127) { - controller->SetSecondaryTouchCount(0); - } - - if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) { - controller->SetTouchCount(controller->GetTouchCount() + 1); - controller->SetSecondaryTouchCount(controller->GetTouchCount()); - } else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) { - controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1); - } else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) { - if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) { - controller->SetTouchCount(controller->GetSecondaryTouchCount()); - } else { - if (controller->WasSecondaryTouchReset()) { - controller->SetTouchCount(controller->GetSecondaryTouchCount()); - controller->UnsetSecondaryTouchResetBool(); - } - } - } - - controller->SetPreviousTouchNum(pData->touchData.touchNum); - - if (pData->touchData.touchNum == 1) { - state.touchpad[0].ID = controller->GetTouchCount(); - state.touchpad[1].ID = 0; - } else if (pData->touchData.touchNum == 2) { - state.touchpad[0].ID = controller->GetTouchCount(); - state.touchpad[1].ID = controller->GetSecondaryTouchCount(); - } - } else { - state.touchpad[0].ID = 1; - state.touchpad[1].ID = 2; - } - - pData->touchData.touch[0].x = state.touchpad[0].x; - pData->touchData.touch[0].y = state.touchpad[0].y; - pData->touchData.touch[0].id = state.touchpad[0].ID; - pData->touchData.touch[1].x = state.touchpad[1].x; - pData->touchData.touch[1].y = state.touchpad[1].y; - pData->touchData.touch[1].id = state.touchpad[1].ID; - pData->timestamp = state.time; - pData->connected = true; // isConnected; //TODO fix me proper - pData->connectedCount = 1; // connectedCount; - pData->deviceUniqueDataLen = 0; - + controller->ReadState(&state, &connected, &connected_count); + ProcessStates(handle, pData, &state, 1, connected, connected_count); return ORBIS_OK; } diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index ca6e8a73f..02ceaf3d9 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include "common/enum.h" #include "common/types.h" @@ -276,7 +277,8 @@ int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle, OrbisPadExtendedControllerInformation* pInfo); int PS4_SYSV_ABI scePadGetExtensionUnitInfo(); int PS4_SYSV_ABI scePadGetFeatureReport(); -int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index); +int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index); int PS4_SYSV_ABI scePadGetIdleCount(); int PS4_SYSV_ABI scePadGetInfo(); int PS4_SYSV_ABI scePadGetInfoByPortType(); @@ -294,8 +296,10 @@ int PS4_SYSV_ABI scePadIsMoveReproductionModel(); int PS4_SYSV_ABI scePadIsValidHandle(); int PS4_SYSV_ABI scePadMbusInit(); int PS4_SYSV_ABI scePadMbusTerm(); -int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam); -int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, const OrbisPadOpenExtParam* pParam); +int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index, const OrbisPadOpenParam* pParam); +int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, + s32 index, const OrbisPadOpenExtParam* pParam); int PS4_SYSV_ABI scePadOpenExt2(); int PS4_SYSV_ABI scePadOutputReport(); int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num); diff --git a/src/core/libraries/remote_play/remoteplay.cpp b/src/core/libraries/remote_play/remoteplay.cpp index 06d9fccfb..775450d26 100644 --- a/src/core/libraries/remote_play/remoteplay.cpp +++ b/src/core/libraries/remote_play/remoteplay.cpp @@ -54,7 +54,8 @@ int PS4_SYSV_ABI sceRemoteplayGetConnectHistory() { return ORBIS_OK; } -int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus) { +int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus( + Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus) { *pStatus = ORBIS_REMOTEPLAY_CONNECTION_STATUS_DISCONNECT; return ORBIS_OK; } diff --git a/src/core/libraries/remote_play/remoteplay.h b/src/core/libraries/remote_play/remoteplay.h index 35465d6df..b4614dca0 100644 --- a/src/core/libraries/remote_play/remoteplay.h +++ b/src/core/libraries/remote_play/remoteplay.h @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include "common/types.h" namespace Core::Loader { @@ -24,7 +25,8 @@ int PS4_SYSV_ABI sceRemoteplayDisconnect(); int PS4_SYSV_ABI sceRemoteplayGeneratePinCode(); int PS4_SYSV_ABI sceRemoteplayGetApMode(); int PS4_SYSV_ABI sceRemoteplayGetConnectHistory(); -int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus); +int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus( + Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus); int PS4_SYSV_ABI sceRemoteplayGetConnectUserId(); int PS4_SYSV_ABI sceRemoteplayGetMbusDeviceInfo(); int PS4_SYSV_ABI sceRemoteplayGetOperationStatus(); diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index f85845f70..c5f66d883 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -167,7 +167,7 @@ void StopThread() { g_backup_thread_semaphore.release(); } -bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, +bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id, std::string_view dir_name, OrbisSaveDataEventType origin) { auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h index 83a263c9b..2a5e54d49 100644 --- a/src/core/libraries/save_data/save_backup.h +++ b/src/core/libraries/save_data/save_backup.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -6,12 +6,11 @@ #include #include +#include #include "common/types.h" namespace Libraries::SaveData { -using OrbisUserServiceUserId = s32; - namespace Backup { enum class WorkerStatus { @@ -32,7 +31,7 @@ enum class OrbisSaveDataEventType : u32 { struct BackupRequest { bool done{}; - OrbisUserServiceUserId user_id{}; + Libraries::UserService::OrbisUserServiceUserId user_id{}; std::string title_id{}; std::string dir_name{}; OrbisSaveDataEventType origin{}; @@ -45,7 +44,7 @@ void StartThread(); void StopThread(); -bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, +bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id, std::string_view dir_name, OrbisSaveDataEventType origin); bool Restore(const std::filesystem::path& save_path); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index 75a644fdb..bc6bbfd72 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -46,13 +46,13 @@ static const std::unordered_map default_title = { namespace Libraries::SaveData { -fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, +fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial) { return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; } -fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial, - std::string_view dir_name) { +fs::path SaveInstance::MakeDirSavePath(Libraries::UserService::OrbisUserServiceUserId user_id, + std::string_view game_serial, std::string_view dir_name) { return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; } @@ -89,8 +89,8 @@ void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, #undef P } -SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, - std::string_view _dir_name, int max_blocks) +SaveInstance::SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id, + std::string _game_serial, std::string_view _dir_name, int max_blocks) : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), dir_name(_dir_name), max_blocks(std::clamp(max_blocks, OrbisSaveDataBlocksMin2, OrbisSaveDataBlocksMax)) { diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h index 6e7ac8f66..b758649b2 100644 --- a/src/core/libraries/save_data/save_instance.h +++ b/src/core/libraries/save_data/save_instance.h @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include +#include #include "common/io_file.h" #include "core/file_format/psf.h" @@ -52,13 +53,13 @@ class SaveInstance { public: // Location of all save data for a title - static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id, - std::string_view game_serial); + static std::filesystem::path MakeTitleSavePath( + Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial); // Location of a specific save data directory - static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id, - std::string_view game_serial, - std::string_view dir_name); + static std::filesystem::path MakeDirSavePath( + Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial, + std::string_view dir_name); static uint64_t GetMaxBlockFromSFO(const PSF& psf); @@ -67,8 +68,8 @@ public: static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial); - explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial, - std::string_view dir_name, int max_blocks = 0); + explicit SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id, + std::string game_serial, std::string_view dir_name, int max_blocks = 0); ~SaveInstance(); diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp index 5f5ba8fea..0a16d4756 100644 --- a/src/core/libraries/save_data/save_memory.cpp +++ b/src/core/libraries/save_data/save_memory.cpp @@ -88,8 +88,8 @@ std::string GetSaveDir(u32 slot_id) { return dir; } -std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id, - std::string_view game_serial) { +std::filesystem::path GetSavePath(Libraries::UserService::OrbisUserServiceUserId user_id, + u32 slot_id, std::string_view game_serial) { std::string dir(StandardDirnameSaveDataMemory); if (slot_id > 0) { dir += std::to_string(slot_id); @@ -97,8 +97,8 @@ std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id, return SaveInstance::MakeDirSavePath(user_id, game_serial, dir); } -size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial, - size_t memory_size) { +size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id, + std::string_view game_serial, size_t memory_size) { std::lock_guard lck{g_slot_mtx}; const auto save_dir = GetSavePath(user_id, slot_id, game_serial); diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h index 7765b04cd..b524de8bc 100644 --- a/src/core/libraries/save_data/save_memory.h +++ b/src/core/libraries/save_data/save_memory.h @@ -4,26 +4,24 @@ #pragma once #include +#include #include "core/libraries/save_data/save_backup.h" class PSF; -namespace Libraries::SaveData { -using OrbisUserServiceUserId = s32; -} // namespace Libraries::SaveData - namespace Libraries::SaveData::SaveMemory { void PersistMemory(u32 slot_id, bool lock = true); [[nodiscard]] std::string GetSaveDir(u32 slot_id); -[[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id, - std::string_view game_serial); +[[nodiscard]] std::filesystem::path GetSavePath( + Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id, + std::string_view game_serial); // returns the size of the save memory if exists -size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial, - size_t memory_size); +size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id, + std::string_view game_serial, size_t memory_size); // Write the icon. Set buf to null to read the standard icon. void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 7fba8ed21..a5199c297 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -42,7 +42,6 @@ enum class OrbisSaveDataSaveDataMemoryOption : u32 { UNLOCK_LIMITATIONS = 1 << 2, }; -using OrbisUserServiceUserId = s32; using OrbisSaveDataBlocks = u64; constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB @@ -97,7 +96,7 @@ struct OrbisSaveDataFingerprint { }; struct OrbisSaveDataBackup { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -106,7 +105,7 @@ struct OrbisSaveDataBackup { }; struct OrbisSaveDataCheckBackupData { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -116,7 +115,7 @@ struct OrbisSaveDataCheckBackupData { }; struct OrbisSaveDataDelete { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -153,7 +152,7 @@ struct OrbisSaveDataMemoryData { }; struct OrbisSaveDataMemoryGet2 { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; std::array _pad; OrbisSaveDataMemoryData* data; OrbisSaveDataParam* param; @@ -163,7 +162,7 @@ struct OrbisSaveDataMemoryGet2 { }; struct OrbisSaveDataMemorySet2 { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; std::array _pad; const OrbisSaveDataMemoryData* data; const OrbisSaveDataParam* param; @@ -175,7 +174,7 @@ struct OrbisSaveDataMemorySet2 { struct OrbisSaveDataMemorySetup2 { OrbisSaveDataSaveDataMemoryOption option; - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; size_t memorySize; size_t iconMemorySize; // +4.5 @@ -197,14 +196,14 @@ enum OrbisSaveDataMemorySyncOption : u32 { }; struct OrbisSaveDataMemorySync { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; u32 slotId; OrbisSaveDataMemorySyncOption option; std::array _reserved; }; struct OrbisSaveDataMount2 { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataDirName* dirName; OrbisSaveDataBlocks blocks; @@ -214,7 +213,7 @@ struct OrbisSaveDataMount2 { }; struct OrbisSaveDataMount { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -245,7 +244,7 @@ struct OrbisSaveDataMountResult { }; struct OrbisSaveDataRestoreBackupData { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; s32 : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -256,7 +255,7 @@ struct OrbisSaveDataRestoreBackupData { }; struct OrbisSaveDataTransferringMount { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; const OrbisSaveDataFingerprint* fingerprint; @@ -264,7 +263,7 @@ struct OrbisSaveDataTransferringMount { }; struct OrbisSaveDataDirNameSearchCond { - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; int : 32; const OrbisSaveDataTitleId* titleId; const OrbisSaveDataDirName* dirName; @@ -303,7 +302,7 @@ using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType; struct OrbisSaveDataEvent { OrbisSaveDataEventType type; s32 errorCode; - OrbisUserServiceUserId userId; + Libraries::UserService::OrbisUserServiceUserId userId; std::array _pad; OrbisSaveDataTitleId titleId; OrbisSaveDataDirName dirName; @@ -1106,8 +1105,9 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { return ORBIS_OK; } -Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, - const size_t bufSize, const int64_t offset) { +Error PS4_SYSV_ABI +sceSaveDataGetSaveDataMemory(const Libraries::UserService::OrbisUserServiceUserId userId, void* buf, + const size_t bufSize, const int64_t offset) { LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); OrbisSaveDataMemoryData data{}; data.buf = buf; @@ -1469,8 +1469,9 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { return ORBIS_OK; } -Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, - size_t bufSize, int64_t offset) { +Error PS4_SYSV_ABI +sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset) { LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); OrbisSaveDataMemoryData data{}; data.buf = buf; @@ -1527,8 +1528,9 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* return Error::OK; } -Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, - OrbisSaveDataParam* param) { +Error PS4_SYSV_ABI +sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, + size_t memorySize, OrbisSaveDataParam* param) { LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize); OrbisSaveDataMemorySetup2 setupParam{}; setupParam.userId = userId; diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index d1c625980..37a21dbc7 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -3,6 +3,7 @@ #pragma once +#include #include "common/cstring.h" #include "common/types.h" @@ -21,8 +22,6 @@ constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size enum class Error : u32; enum class OrbisSaveDataParamType : u32; -using OrbisUserServiceUserId = s32; - // Maximum size for a title ID (4 uppercase letters + 5 digits) constexpr int OrbisSaveDataTitleIdDataSize = 10; // Maximum save directory name size @@ -126,8 +125,9 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint size_t paramBufSize, size_t* gotSize); Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress); int PS4_SYSV_ABI sceSaveDataGetSaveDataCount(); -Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, - size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI +sceSaveDataGetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath(); @@ -163,11 +163,13 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint OrbisSaveDataParamType paramType, const void* paramBuf, size_t paramBufSize); int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser(); -Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, - size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI +sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); -Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, - OrbisSaveDataParam* param); +Error PS4_SYSV_ABI +sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, + size_t memorySize, OrbisSaveDataParam* param); Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, OrbisSaveDataMemorySetupResult* result); int PS4_SYSV_ABI sceSaveDataShutdownStart(); diff --git a/src/core/libraries/share_play/shareplay.h b/src/core/libraries/share_play/shareplay.h index ca65c9a9d..b67b01e93 100644 --- a/src/core/libraries/share_play/shareplay.h +++ b/src/core/libraries/share_play/shareplay.h @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include "common/types.h" #include "core/libraries/np/np_types.h" @@ -21,8 +22,8 @@ struct OrbisSharePlayConnectionInfo { int mode; Libraries::Np::OrbisNpOnlineId hostOnlineId; Libraries::Np::OrbisNpOnlineId visitorOnlineId; - s32 hostUserId; - s32 visitorUserId; + Libraries::UserService::OrbisUserServiceUserId hostUserId; + Libraries::UserService::OrbisUserServiceUserId visitorUserId; }; int PS4_SYSV_ABI sceSharePlayCrashDaemon(); diff --git a/src/core/libraries/system/systemservice.h b/src/core/libraries/system/systemservice.h index b8bdf0b5f..e3eeb21dc 100644 --- a/src/core/libraries/system/systemservice.h +++ b/src/core/libraries/system/systemservice.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // reference // https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/sys_service.h @@ -7,6 +7,7 @@ #include #include #include "common/types.h" +#include "userservice.h" namespace Core::Loader { class SymbolsResolver; @@ -119,12 +120,12 @@ struct OrbisSystemServiceEvent { char boot_argument[7169]; } join_event; struct { - s32 user_id; + Libraries::UserService::OrbisUserServiceUserId userId; u32 np_service_label; u8 reserved[8184]; } service_entitlement_update; struct { - s32 user_id; + Libraries::UserService::OrbisUserServiceUserId userId; u32 np_service_label; u8 reserved[8184]; } unified_entitlement_update; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index ece2640c9..e9176afdc 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -291,8 +291,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, - const void* param) { +s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType, + s32 index, const void* param) { LOG_INFO(Lib_VideoOut, "called"); ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index 7db09530b..ba2732ff7 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include "core/libraries/kernel/equeue.h" #include "core/libraries/videoout/buffer.h" @@ -12,8 +13,6 @@ class SymbolsResolver; namespace Libraries::VideoOut { -using SceUserServiceUserId = s32; // TODO move it to proper place - // SceVideoOutBusType constexpr int SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; // Main output constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_SOCIAL_SCREEN = 5; // Aux output for social @@ -131,8 +130,8 @@ s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle); s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg); s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status); s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status); -s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, - const void* param); +s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType, + s32 index, const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev); s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data); diff --git a/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp b/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp index 444eaa765..5844affa2 100644 --- a/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp +++ b/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp @@ -5,9 +5,12 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/web_browser_dialog/webbrowserdialog.h" +#include "magic_enum/magic_enum.hpp" namespace Libraries::WebBrowserDialog { +static auto g_status = Libraries::CommonDialog::Status::NONE; + s32 PS4_SYSV_ABI sceWebBrowserDialogClose() { LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called"); return ORBIS_OK; @@ -23,14 +26,19 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus() { - LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called"); - return ORBIS_OK; +Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } -s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize() { - LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called"); - return ORBIS_OK; +Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize() { + if (CommonDialog::g_isInitialized) { + LOG_INFO(Lib_WebBrowserDialog, "already initialized"); + return Libraries::CommonDialog::Error::ALREADY_SYSTEM_INITIALIZED; + } + LOG_DEBUG(Lib_WebBrowserDialog, "initialized"); + CommonDialog::g_isInitialized = true; + return Libraries::CommonDialog::Error::OK; } s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate() { @@ -63,14 +71,22 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate() { - LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called"); - return ORBIS_OK; +Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate() { + if (g_status == Libraries::CommonDialog::Status::RUNNING) { + LOG_ERROR(Lib_WebBrowserDialog, + "CloseWebBrowser Dialog unimplemented"); // sceWebBrowserDialogClose(); + } + if (g_status == Libraries::CommonDialog::Status::NONE) { + return Libraries::CommonDialog::Error::NOT_INITIALIZED; + } + g_status = Libraries::CommonDialog::Status::NONE; + CommonDialog::g_isUsed = false; + return Libraries::CommonDialog::Error::OK; } -s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() { - LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called"); - return ORBIS_OK; +Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } s32 PS4_SYSV_ABI Func_F2BE042771625F8C() { diff --git a/src/core/libraries/web_browser_dialog/webbrowserdialog.h b/src/core/libraries/web_browser_dialog/webbrowserdialog.h index 08f76a4fe..3dad7e1e9 100644 --- a/src/core/libraries/web_browser_dialog/webbrowserdialog.h +++ b/src/core/libraries/web_browser_dialog/webbrowserdialog.h @@ -3,6 +3,7 @@ #pragma once +#include #include "common/types.h" namespace Core::Loader { @@ -14,16 +15,16 @@ namespace Libraries::WebBrowserDialog { s32 PS4_SYSV_ABI sceWebBrowserDialogClose(); s32 PS4_SYSV_ABI sceWebBrowserDialogGetEvent(); s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult(); -s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus(); -s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize(); +Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus(); +Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize(); s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate(); s32 PS4_SYSV_ABI sceWebBrowserDialogOpen(); s32 PS4_SYSV_ABI sceWebBrowserDialogOpenForPredeterminedContent(); s32 PS4_SYSV_ABI sceWebBrowserDialogResetCookie(); s32 PS4_SYSV_ABI sceWebBrowserDialogSetCookie(); s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom(); -s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate(); -s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus(); +Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate(); +Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus(); s32 PS4_SYSV_ABI Func_F2BE042771625F8C(); void RegisterLib(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 97d766a38..7a0653e9f 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -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& 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(); } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 3a4a16933..9d26142ce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,10 +1,11 @@ -// 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" #include "common/assert.h" #include "common/config.h" #include "common/debug.h" +#include "common/elf_info.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/orbis_error.h" @@ -25,6 +26,9 @@ MemoryManager::MemoryManager() { VirtualMemoryArea{region.lower(), region.upper() - region.lower()}); LOG_INFO(Kernel_Vmm, "{:#x} - {:#x}", region.lower(), region.upper()); } + + ASSERT_MSG(Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_version) == 0, + "Failed to get compiled SDK version"); } MemoryManager::~MemoryManager() = default; @@ -55,26 +59,27 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1 // Insert an area that covers the direct memory physical address block. // Note that this should never be called after direct memory allocations have been made. dmem_map.clear(); - dmem_map.emplace(0, DirectMemoryArea{0, total_direct_size}); + dmem_map.emplace(0, PhysicalMemoryArea{0, total_direct_size}); // Insert an area that covers the flexible memory physical address block. // Note that this should never be called after flexible memory allocations have been made. const auto remaining_physical_space = total_size - total_direct_size; fmem_map.clear(); fmem_map.emplace(total_direct_size, - FlexibleMemoryArea{total_direct_size, remaining_physical_space}); + PhysicalMemoryArea{total_direct_size, remaining_physical_space}); LOG_INFO(Kernel_Vmm, "Configured memory regions: flexible size = {:#x}, direct size = {:#x}", total_flexible_size, total_direct_size); } u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { - static constexpr u64 MinSizeToClamp = 3_GB; + static constexpr u64 MinSizeToClamp = 1_GB; // Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer. if (size < MinSizeToClamp) { return size; } + std::shared_lock lk{mutex}; ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}", virtual_addr); @@ -113,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); @@ -131,21 +137,47 @@ void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) { } } -bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) { +bool MemoryManager::TryWriteBacking(void* address, const void* data, u64 size) { const VAddr virtual_addr = std::bit_cast(address); - ASSERT_MSG(IsValidMapping(virtual_addr, num_bytes), "Attempted to access invalid address {:#x}", + std::shared_lock lk{mutex}; + ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}", virtual_addr); - const auto& vma = FindVMA(virtual_addr)->second; - if (!HasPhysicalBacking(vma)) { + + std::vector vmas_to_write; + auto current_vma = FindVMA(virtual_addr); + while (current_vma->second.Overlaps(virtual_addr, size)) { + if (!HasPhysicalBacking(current_vma->second)) { + break; + } + vmas_to_write.emplace_back(current_vma->second); + current_vma++; + } + + if (vmas_to_write.empty()) { return false; } - u8* backing = impl.BackingBase() + vma.phys_base + (virtual_addr - vma.base); - memcpy(backing, data, num_bytes); + + for (auto& vma : vmas_to_write) { + auto start_in_vma = std::max(virtual_addr, vma.base) - vma.base; + auto phys_handle = std::prev(vma.phys_areas.upper_bound(start_in_vma)); + for (; phys_handle != vma.phys_areas.end(); phys_handle++) { + if (!size) { + break; + } + const u64 start_in_dma = + std::max(start_in_vma, phys_handle->first) - phys_handle->first; + u8* backing = impl.BackingBase() + phys_handle->second.base + start_in_dma; + u64 copy_size = std::min(size, phys_handle->second.size - start_in_dma); + memcpy(backing, data, copy_size); + size -= copy_size; + } + } + return true; } 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); @@ -155,7 +187,7 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, auto mapping_end = mapping_start + size; // Find the first free, large enough dmem area in the range. - while (dmem_area->second.dma_type != DMAType::Free || + while (dmem_area->second.dma_type != PhysicalMemoryType::Free || dmem_area->second.GetEnd() < mapping_end) { // The current dmem_area isn't suitable, move to the next one. dmem_area++; @@ -175,8 +207,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, } // Add the allocated region to the list and commit its pages. - auto& area = CarveDmemArea(mapping_start, size)->second; - area.dma_type = DMAType::Pooled; + auto& area = CarvePhysArea(dmem_map, mapping_start, size)->second; + area.dma_type = PhysicalMemoryType::Pooled; area.memory_type = 3; // Track how much dmem was allocated for pools. @@ -187,17 +219,16 @@ 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); - auto mapping_start = search_start > dmem_area->second.base - ? Common::AlignUp(search_start, alignment) - : Common::AlignUp(dmem_area->second.base, alignment); + auto mapping_start = + Common::AlignUp(std::max(search_start, dmem_area->second.base), alignment); auto mapping_end = mapping_start + size; // Find the first free, large enough dmem area in the range. - while (dmem_area->second.dma_type != DMAType::Free || + while (dmem_area->second.dma_type != PhysicalMemoryType::Free || dmem_area->second.GetEnd() < mapping_end) { // The current dmem_area isn't suitable, move to the next one. dmem_area++; @@ -217,15 +248,55 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u6 } // Add the allocated region to the list and commit its pages. - auto& area = CarveDmemArea(mapping_start, size)->second; + auto& area = CarvePhysArea(dmem_map, mapping_start, size)->second; area.memory_type = memory_type; - area.dma_type = DMAType::Allocated; + area.dma_type = PhysicalMemoryType::Allocated; MergeAdjacent(dmem_map, dmem_area); + return mapping_start; } -void MemoryManager::Free(PAddr phys_addr, u64 size) { - std::scoped_lock lk{mutex}; +s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) { + // Basic bounds checking + if (phys_addr > total_direct_size || (is_checked && phys_addr + size > total_direct_size)) { + LOG_ERROR(Kernel_Vmm, "phys_addr {:#x}, size {:#x} goes outside dmem map", phys_addr, size); + if (is_checked) { + return ORBIS_KERNEL_ERROR_ENOENT; + } + return ORBIS_OK; + } + + std::scoped_lock lk{unmap_mutex}; + // If this is a checked free, then all direct memory in range must be allocated. + std::vector> free_list; + u64 remaining_size = size; + auto phys_handle = FindDmemArea(phys_addr); + for (; phys_handle != dmem_map.end(); phys_handle++) { + if (remaining_size == 0) { + // Done searching + break; + } + auto& dmem_area = phys_handle->second; + if (dmem_area.dma_type == PhysicalMemoryType::Free) { + if (is_checked) { + // Checked frees will error if anything in the area isn't allocated. + // Unchecked frees will just ignore free areas. + LOG_ERROR(Kernel_Vmm, "Attempting to release a free dmem area"); + return ORBIS_KERNEL_ERROR_ENOENT; + } + continue; + } + + // Store physical address and size to release + const PAddr current_phys_addr = std::max(phys_addr, phys_handle->first); + const u64 start_in_dma = current_phys_addr - phys_handle->first; + const u64 size_in_dma = + std::min(remaining_size, phys_handle->second.size - start_in_dma); + free_list.emplace_back(current_phys_addr, size_in_dma); + + // Track remaining size to free + remaining_size -= size_in_dma; + } // Release any dmem mappings that reference this physical block. std::vector> remove_list; @@ -233,53 +304,55 @@ void MemoryManager::Free(PAddr phys_addr, u64 size) { if (mapping.type != VMAType::Direct) { continue; } - if (mapping.phys_base <= phys_addr && phys_addr < mapping.phys_base + mapping.size) { - const auto vma_start_offset = phys_addr - mapping.phys_base; - const auto addr_in_vma = mapping.base + vma_start_offset; - const auto size_in_vma = - mapping.size - vma_start_offset > size ? size : mapping.size - vma_start_offset; + for (auto& [offset_in_vma, phys_mapping] : mapping.phys_areas) { + if (phys_addr + size > phys_mapping.base && + phys_addr < phys_mapping.base + phys_mapping.size) { + const u64 phys_offset = + std::max(phys_mapping.base, phys_addr) - phys_mapping.base; + const VAddr addr_in_vma = mapping.base + offset_in_vma + phys_offset; + const u64 unmap_size = std::min(phys_mapping.size - phys_offset, size); - LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", addr_in_vma, - size_in_vma); - // Unmaping might erase from vma_map. We can't do it here. - remove_list.emplace_back(addr_in_vma, size_in_vma); + // Unmapping might erase from vma_map. We can't do it here. + remove_list.emplace_back(addr_in_vma, unmap_size); + } } } + + // 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); } // Unmap all dmem areas within this area. - auto phys_addr_to_search = phys_addr; - auto remaining_size = size; - auto dmem_area = FindDmemArea(phys_addr); - while (dmem_area != dmem_map.end() && remaining_size > 0) { + for (auto& [phys_addr, size] : free_list) { // Carve a free dmem area in place of this one. - const auto start_phys_addr = - phys_addr > dmem_area->second.base ? phys_addr : dmem_area->second.base; - const auto offset_in_dma = start_phys_addr - dmem_area->second.base; - const auto size_in_dma = dmem_area->second.size - offset_in_dma > remaining_size - ? remaining_size - : dmem_area->second.size - offset_in_dma; - const auto dmem_handle = CarveDmemArea(start_phys_addr, size_in_dma); + const auto dmem_handle = CarvePhysArea(dmem_map, phys_addr, size); auto& new_dmem_area = dmem_handle->second; - new_dmem_area.dma_type = DMAType::Free; + new_dmem_area.dma_type = PhysicalMemoryType::Free; new_dmem_area.memory_type = 0; // Merge the new dmem_area with dmem_map MergeAdjacent(dmem_map, dmem_handle); - - // Get the next relevant dmem area. - phys_addr_to_search = phys_addr + size_in_dma; - remaining_size -= size_in_dma; - dmem_area = FindDmemArea(phys_addr_to_search); } + + return ORBIS_OK; } s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) { + std::scoped_lock lk{unmap_mutex}; + std::unique_lock lk2{mutex}; ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}", virtual_addr); - std::scoped_lock lk{mutex}; // Input addresses to PoolCommit are treated as fixed, and have a constant alignment. const u64 alignment = 64_KB; @@ -314,48 +387,55 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 prot |= MemoryProt::CpuRead; } + // Create the virtual mapping for the commit + const auto new_vma_handle = CarveVMA(virtual_addr, size); + auto& new_vma = new_vma_handle->second; + new_vma.disallow_merge = false; + new_vma.prot = prot; + new_vma.name = "anon"; + new_vma.type = Core::VMAType::Pooled; + new_vma.phys_areas.clear(); + // Find suitable physical addresses auto handle = dmem_map.begin(); u64 remaining_size = size; VAddr current_addr = mapped_addr; - while (handle != dmem_map.end() && remaining_size != 0) { - if (handle->second.dma_type != DMAType::Pooled) { + while (handle != dmem_map.end() && remaining_size > 0) { + if (handle->second.dma_type != PhysicalMemoryType::Pooled) { // Non-pooled means it's either not for pool use, or already committed. handle++; continue; } // On PS4, commits can make sparse physical mappings. - // For now, it's easier to create separate memory mappings for each physical mapping. u64 size_to_map = std::min(remaining_size, handle->second.size); - // Carve out the new VMA representing this mapping - const auto new_vma_handle = CarveVMA(current_addr, size_to_map); - auto& new_vma = new_vma_handle->second; - new_vma.disallow_merge = false; - new_vma.prot = prot; - new_vma.name = "anon"; - new_vma.type = Core::VMAType::Pooled; - new_vma.is_exec = false; - // Use the start of this area as the physical backing for this mapping. - const auto new_dmem_handle = CarveDmemArea(handle->second.base, size_to_map); + const auto new_dmem_handle = CarvePhysArea(dmem_map, handle->second.base, size_to_map); auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Committed; + new_dmem_area.dma_type = PhysicalMemoryType::Committed; new_dmem_area.memory_type = mtype; - new_vma.phys_base = new_dmem_area.base; + + // Add the dmem area to this vma, merge it with any similar tracked areas. + new_vma.phys_areas[current_addr - mapped_addr] = new_dmem_handle->second; + MergeAdjacent(new_vma.phys_areas, new_vma.phys_areas.find(current_addr - mapped_addr)); + + // Perform an address space mapping for each physical area + void* out_addr = impl.Map(current_addr, size_to_map, new_dmem_area.base); + // Tracy memory tracking breaks from merging memory areas. Disabled for now. + // TRACK_ALLOC(out_addr, size_to_map, "VMEM"); + handle = MergeAdjacent(dmem_map, new_dmem_handle); - - // Perform the mapping - void* out_addr = impl.Map(current_addr, size_to_map, alignment, new_vma.phys_base, false); - TRACK_ALLOC(out_addr, size_to_map, "VMEM"); - current_addr += size_to_map; remaining_size -= size_to_map; handle++; } - ASSERT_MSG(remaining_size == 0, "Unable to map physical memory"); + ASSERT_MSG(remaining_size == 0, "Failed to commit pooled memory"); + // Merge this VMA with similar nearby areas + MergeAdjacent(vma_map, new_vma_handle); + + lk2.unlock(); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); } @@ -363,6 +443,47 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 return ORBIS_OK; } +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; + 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(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(virtual_addr, size); + auto& new_vma = new_vma_handle->second; + const bool is_exec = True(prot & MemoryProt::CpuExec); + if (True(prot & MemoryProt::CpuWrite)) { + // On PS4, read is appended to write mappings. + 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 new_vma_handle; +} + s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name, bool validate_dmem, PAddr phys_addr, u64 alignment) { @@ -375,9 +496,9 @@ 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{unmap_mutex}; - std::scoped_lock lk{mutex}; - + PhysHandle dmem_area; // Validate the requested physical address range if (phys_addr != -1) { if (total_direct_size < phys_addr + size) { @@ -390,15 +511,15 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo auto dmem_area = FindDmemArea(phys_addr); while (dmem_area != dmem_map.end() && dmem_area->second.base < phys_addr + size) { // If any requested dmem area is not allocated, return an error. - if (dmem_area->second.dma_type != DMAType::Allocated && - dmem_area->second.dma_type != DMAType::Mapped) { + if (dmem_area->second.dma_type != PhysicalMemoryType::Allocated && + dmem_area->second.dma_type != PhysicalMemoryType::Mapped) { LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, phys_addr); return ORBIS_KERNEL_ERROR_ENOMEM; } // If we need to perform extra validation, then check for Mapped dmem areas too. - if (validate_dmem && dmem_area->second.dma_type == DMAType::Mapped) { + if (validate_dmem && dmem_area->second.dma_type == PhysicalMemoryType::Mapped) { LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, phys_addr); return ORBIS_KERNEL_ERROR_EBUSY; @@ -406,136 +527,137 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo dmem_area++; } - - // If the prior loop succeeds, we need to loop through again and carve out mapped dmas. - // This needs to be a separate loop to avoid modifying dmem map during failed calls. - auto phys_addr_to_search = phys_addr; - auto remaining_size = size; - dmem_area = FindDmemArea(phys_addr); - while (dmem_area != dmem_map.end() && remaining_size > 0) { - // Carve a new dmem area in place of this one with the appropriate type. - // Ensure the carved area only covers the current dmem area. - const auto start_phys_addr = - phys_addr > dmem_area->second.base ? phys_addr : dmem_area->second.base; - const auto offset_in_dma = start_phys_addr - dmem_area->second.base; - const auto size_in_dma = dmem_area->second.size - offset_in_dma > remaining_size - ? remaining_size - : dmem_area->second.size - offset_in_dma; - const auto dmem_handle = CarveDmemArea(start_phys_addr, size_in_dma); - auto& new_dmem_area = dmem_handle->second; - new_dmem_area.dma_type = DMAType::Mapped; - - // Merge the new dmem_area with dmem_map - MergeAdjacent(dmem_map, dmem_handle); - - // Get the next relevant dmem area. - phys_addr_to_search = phys_addr + size_in_dma; - remaining_size -= size_in_dma; - dmem_area = FindDmemArea(phys_addr_to_search); - } } - // 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; - 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) { - 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 (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, mapped_addr); + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, virtual_addr); return ORBIS_KERNEL_ERROR_ENOMEM; } - } else { - // When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0, - // search from address 0x200000000 instead. + } else if (False(flags & MemoryMapFlags::Fixed)) { + // Find a free virtual addr to map 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) { + 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; } } - // Create a memory area representing this mapping. - const auto new_vma_handle = CarveVMA(mapped_addr, size); + // Perform early GPU unmap to avoid potential deadlocks + if (IsValidGpuMapping(virtual_addr, size)) { + rasterizer->UnmapMemory(virtual_addr, size); + } + + // Acquire writer lock. + std::unique_lock lk2{mutex}; + + // Create VMA representing this mapping. + auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment); auto& new_vma = new_vma_handle->second; + auto mapped_addr = new_vma.base; + bool is_exec = True(prot & MemoryProt::CpuExec); // If type is Flexible, we need to track how much flexible memory is used here. // We also need to determine a reasonable physical base to perform this mapping at. if (type == VMAType::Flexible) { - flexible_usage += size; - - // Find a suitable physical address + // Find suitable physical addresses auto handle = fmem_map.begin(); - while (handle != fmem_map.end() && - (!handle->second.is_free || handle->second.size < size)) { + u64 remaining_size = size; + VAddr current_addr = mapped_addr; + while (handle != fmem_map.end() && remaining_size != 0) { + if (handle->second.dma_type != PhysicalMemoryType::Free) { + // If the handle isn't free, we cannot use it. + handle++; + continue; + } + + // Determine the size we can map here. + u64 size_to_map = std::min(remaining_size, handle->second.size); + + // Create a physical area + const auto new_fmem_handle = CarvePhysArea(fmem_map, handle->second.base, size_to_map); + auto& new_fmem_area = new_fmem_handle->second; + new_fmem_area.dma_type = PhysicalMemoryType::Flexible; + + // Add the new area to the vma, merge it with any similar tracked areas. + new_vma.phys_areas[current_addr - mapped_addr] = new_fmem_handle->second; + MergeAdjacent(new_vma.phys_areas, new_vma.phys_areas.find(current_addr - mapped_addr)); + + // Perform an address space mapping for each physical area + void* out_addr = impl.Map(current_addr, size_to_map, new_fmem_area.base, is_exec); + // Tracy memory tracking breaks from merging memory areas. Disabled for now. + // TRACK_ALLOC(out_addr, size_to_map, "VMEM"); + + // Merge this handle with adjacent areas + handle = MergeAdjacent(fmem_map, new_fmem_handle); + + // Get the next flexible area. + current_addr += size_to_map; + remaining_size -= size_to_map; + flexible_usage += size_to_map; handle++; } + ASSERT_MSG(remaining_size == 0, "Failed to map physical memory"); + } else if (type == VMAType::Direct) { + // Map the physical memory for this direct memory mapping. + auto current_phys_addr = phys_addr; + u64 remaining_size = size; + auto dmem_area = FindDmemArea(phys_addr); + while (dmem_area != dmem_map.end() && remaining_size > 0) { + // Carve a new dmem area in place of this one with the appropriate type. + // Ensure the carved area only covers the current dmem area. + const auto start_phys_addr = std::max(current_phys_addr, dmem_area->second.base); + const auto offset_in_dma = start_phys_addr - dmem_area->second.base; + const auto size_in_dma = + std::min(dmem_area->second.size - offset_in_dma, remaining_size); + const auto dmem_handle = CarvePhysArea(dmem_map, start_phys_addr, size_in_dma); + auto& new_dmem_area = dmem_handle->second; + new_dmem_area.dma_type = PhysicalMemoryType::Mapped; - // Some games will end up fragmenting the flexible address space. - ASSERT_MSG(handle != fmem_map.end() && handle->second.is_free, - "No suitable physical memory areas to map"); + // Add the dmem area to this vma, merge it with any similar tracked areas. + const u64 offset_in_vma = current_phys_addr - phys_addr; + new_vma.phys_areas[offset_in_vma] = dmem_handle->second; + MergeAdjacent(new_vma.phys_areas, new_vma.phys_areas.find(offset_in_vma)); - // We'll use the start of this area as the physical backing for this mapping. - const auto new_fmem_handle = CarveFmemArea(handle->second.base, size); - auto& new_fmem_area = new_fmem_handle->second; - new_fmem_area.is_free = false; - phys_addr = new_fmem_area.base; - MergeAdjacent(fmem_map, new_fmem_handle); + // Merge the new dmem_area with dmem_map + MergeAdjacent(dmem_map, dmem_handle); + + // Get the next relevant dmem area. + current_phys_addr += size_in_dma; + remaining_size -= size_in_dma; + dmem_area = FindDmemArea(current_phys_addr); + } + ASSERT_MSG(remaining_size == 0, "Failed to map physical memory"); } - if (True(prot & MemoryProt::CpuWrite)) { - // On PS4, read is appended to write mappings. - prot |= MemoryProt::CpuRead; - } - - const bool is_exec = True(prot & MemoryProt::CpuExec); - new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); - new_vma.prot = prot; - new_vma.name = name; - new_vma.type = type; - new_vma.phys_base = phys_addr == -1 ? 0 : phys_addr; - new_vma.is_exec = is_exec; - - if (type == VMAType::Reserved) { - // Technically this should be done for direct and flexible mappings too, - // But some Windows-specific limitations make that hard to accomplish. + if (new_vma.type != VMAType::Direct || sdk_version >= Common::ElfInfo::FW_20) { + // Merge this VMA with similar nearby areas + // Direct memory mappings only coalesce on SDK version 2.00 or later. MergeAdjacent(vma_map, new_vma_handle); } - if (type == VMAType::Reserved || type == VMAType::PoolReserved) { - // For Reserved/PoolReserved mappings, we don't perform any address space allocations. - // Just set out_addr to mapped_addr instead. - *out_addr = std::bit_cast(mapped_addr); - } else { + *out_addr = std::bit_cast(mapped_addr); + if (type != VMAType::Reserved && type != VMAType::PoolReserved) { + // Flexible address space mappings were performed while finding direct memory areas. + if (type != VMAType::Flexible) { + impl.Map(mapped_addr, size, phys_addr, is_exec); + // Tracy memory tracking breaks from merging memory areas. Disabled for now. + // TRACK_ALLOC(mapped_addr, size, "VMEM"); + } + + lk2.unlock(); + // If this is not a reservation, then map to GPU and address space if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); } - *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec); - - TRACK_ALLOC(*out_addr, size, "VMEM"); } return ORBIS_OK; @@ -543,29 +665,8 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, s32 fd, s64 phys_addr) { - VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; - ASSERT_MSG(IsValidMapping(mapped_addr, size), "Attempted to access invalid address {:#x}", - mapped_addr); - - std::scoped_lock lk{mutex}; - - // Find first free area to map the file. - if (False(flags & MemoryMapFlags::Fixed)) { - mapped_addr = SearchFree(mapped_addr, size, 1); - if (mapped_addr == -1) { - // No suitable memory areas to map to - return ORBIS_KERNEL_ERROR_ENOMEM; - } - } - - if (True(flags & MemoryMapFlags::Fixed)) { - const auto& vma = FindVMA(mapped_addr)->second; - const u64 remaining_size = vma.base + vma.size - virtual_addr; - ASSERT_MSG(!vma.IsMapped() && remaining_size >= size, - "Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}", - vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size); - } - + uintptr_t handle = 0; + std::scoped_lock lk{unmap_mutex}; // Get the file to map auto* h = Common::Singleton::Instance(); auto file = h->GetFile(fd); @@ -584,42 +685,71 @@ 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; } - impl.MapFile(mapped_addr, size, phys_addr, std::bit_cast(prot), handle); - if (prot >= MemoryProt::GpuRead) { // On real hardware, GPU file mmaps cause a full system crash due to an internal error. ASSERT_MSG(false, "Files cannot be mapped to GPU memory"); } + if (True(prot & MemoryProt::CpuExec)) { // On real hardware, execute permissions are silently removed. prot &= ~MemoryProt::CpuExec; } - // Add virtual memory area - auto& new_vma = CarveVMA(mapped_addr, size)->second; - new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); - new_vma.prot = prot; - new_vma.name = "File"; + 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; - new_vma.type = VMAType::File; + auto mapped_addr = new_vma.base; + bool is_exec = True(prot & MemoryProt::CpuExec); + + impl.MapFile(mapped_addr, size, phys_addr, std::bit_cast(prot), handle); *out_addr = std::bit_cast(mapped_addr); return ORBIS_OK; } s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { + std::scoped_lock lk{unmap_mutex}; ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}", virtual_addr); - std::scoped_lock lk{mutex}; // Do an initial search to ensure this decommit is valid. auto it = FindVMA(virtual_addr); @@ -631,40 +761,50 @@ 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; while (remaining_size != 0) { - const auto it = FindVMA(current_addr); - const auto& vma_base = it->second; - const bool is_exec = vma_base.is_exec; + const auto handle = FindVMA(current_addr); + const auto& vma_base = handle->second; const auto start_in_vma = current_addr - vma_base.base; const auto size_in_vma = std::min(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; // Re-pool the direct memory used by this mapping - const auto unmap_phys_base = vma_base.phys_base + start_in_vma; - const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size_in_vma); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Pooled; + u64 size_to_free = size_in_vma; + auto phys_handle = std::prev(vma_base.phys_areas.upper_bound(start_in_vma)); + while (phys_handle != vma_base.phys_areas.end() && size_to_free > 0) { + // Calculate physical memory offset, address, and size + u64 dma_offset = + std::max(phys_handle->first, start_in_vma) - phys_handle->first; + PAddr phys_addr = phys_handle->second.base + dma_offset; + u64 size_in_dma = + std::min(size_to_free, phys_handle->second.size - dma_offset); - // Coalesce with nearby direct memory areas. - MergeAdjacent(dmem_map, new_dmem_handle); - } + // Create a new dmem area reflecting the pooled region + const auto new_dmem_handle = CarvePhysArea(dmem_map, phys_addr, size_in_dma); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = PhysicalMemoryType::Pooled; - if (vma_base.type != VMAType::PoolReserved) { - // Unmap the memory region. - impl.Unmap(vma_base.base, vma_base.size, start_in_vma, start_in_vma + size_in_vma, - vma_base.phys_base, vma_base.is_exec, true, false); - TRACK_FREE(virtual_addr, "VMEM"); + // Coalesce with nearby direct memory areas. + MergeAdjacent(dmem_map, new_dmem_handle); + + // Increment loop + size_to_free -= size_in_dma; + phys_handle++; + } + ASSERT_MSG(size_to_free == 0, "Failed to decommit pooled memory"); } // Mark region as pool reserved and attempt to coalesce it with neighbours. @@ -672,108 +812,112 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { auto& vma = new_it->second; vma.type = VMAType::PoolReserved; vma.prot = MemoryProt::NoAccess; - vma.phys_base = 0; vma.disallow_merge = false; vma.name = "anon"; + vma.phys_areas.clear(); MergeAdjacent(vma_map, new_it); current_addr += size_in_vma; remaining_size -= size_in_vma; } + // Unmap from address space + impl.Unmap(virtual_addr, size); + // Tracy memory tracking breaks from merging memory areas. Disabled for now. + // TRACK_FREE(virtual_addr, "VMEM"); + return ORBIS_OK; } s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) { - std::scoped_lock lk{mutex}; if (size == 0) { return ORBIS_OK; } + + 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); + + // 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 vma_base_addr = vma_base.base; - const auto vma_base_size = vma_base.size; - const auto type = vma_base.type; - const auto phys_base = vma_base.phys_base; - const bool is_exec = vma_base.is_exec; - const auto start_in_vma = virtual_addr - vma_base_addr; - const auto adjusted_size = - vma_base_size - start_in_vma < size ? vma_base_size - start_in_vma : size; - const bool has_backing = HasPhysicalBacking(vma_base) || type == VMAType::File; - const auto prot = vma_base.prot; - const bool readonly_file = prot == MemoryProt::CpuRead && type == VMAType::File; - - if (type == VMAType::Free) { - return adjusted_size; + const auto start_in_vma = virtual_addr - vma_base.base; + const auto size_in_vma = std::min(vma_base.size - start_in_vma, size); + const auto vma_type = vma_base.type; + if (vma_base.type == VMAType::Free || vma_base.type == VMAType::Pooled) { + return size_in_vma; } - if (type == VMAType::Direct) { - // Unmap all direct memory areas covered by this unmap. - auto phys_addr = phys_base + start_in_vma; - auto remaining_size = adjusted_size; - DMemHandle dmem_handle = FindDmemArea(phys_addr); - while (dmem_handle != dmem_map.end() && remaining_size > 0) { - const auto start_in_dma = phys_addr - dmem_handle->second.base; - const auto size_in_dma = dmem_handle->second.size - start_in_dma > remaining_size - ? remaining_size - : dmem_handle->second.size - start_in_dma; - dmem_handle = CarveDmemArea(phys_addr, size_in_dma); - auto& dmem_area = dmem_handle->second; - dmem_area.dma_type = DMAType::Allocated; - remaining_size -= dmem_area.size; - phys_addr += dmem_area.size; + VAddr current_addr = virtual_addr; + if (vma_base.phys_areas.size() > 0) { + u64 size_to_free = size_in_vma; + auto phys_handle = std::prev(vma_base.phys_areas.upper_bound(start_in_vma)); + while (phys_handle != vma_base.phys_areas.end() && size_to_free > 0) { + // Calculate physical memory offset, address, and size + u64 dma_offset = std::max(phys_handle->first, start_in_vma) - phys_handle->first; + PAddr phys_addr = phys_handle->second.base + dma_offset; + u64 size_in_dma = std::min(size_to_free, phys_handle->second.size - dma_offset); - // Check if we can coalesce any dmem areas. - MergeAdjacent(dmem_map, dmem_handle); - dmem_handle = FindDmemArea(phys_addr); + // Create a new dmem area reflecting the pooled region + if (vma_type == VMAType::Direct) { + const auto new_dmem_handle = CarvePhysArea(dmem_map, phys_addr, size_in_dma); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = PhysicalMemoryType::Allocated; + + // Coalesce with nearby direct memory areas. + MergeAdjacent(dmem_map, new_dmem_handle); + } else if (vma_type == VMAType::Flexible) { + // Update fmem_map + const auto new_fmem_handle = CarvePhysArea(fmem_map, phys_addr, size_in_dma); + auto& new_fmem_area = new_fmem_handle->second; + new_fmem_area.dma_type = PhysicalMemoryType::Free; + + // Coalesce with nearby flexible memory areas. + MergeAdjacent(fmem_map, new_fmem_handle); + + // Zero out the old memory data + const auto unmap_hardware_address = impl.BackingBase() + phys_addr; + std::memset(unmap_hardware_address, 0, size_in_dma); + + // Update flexible usage + flexible_usage -= size_in_dma; + } + + // Increment through loop + size_to_free -= size_in_dma; + phys_handle++; } - } - - if (type == VMAType::Flexible) { - flexible_usage -= adjusted_size; - - // Now that there is a physical backing used for flexible memory, - // manually erase the contents before unmapping to prevent possible issues. - const auto unmap_hardware_address = impl.BackingBase() + phys_base + start_in_vma; - std::memset(unmap_hardware_address, 0, adjusted_size); - - // Address space unmap needs the physical_base from the start of the vma, - // so calculate the phys_base to unmap from here. - const auto unmap_phys_base = phys_base + start_in_vma; - const auto new_fmem_handle = CarveFmemArea(unmap_phys_base, adjusted_size); - auto& new_fmem_area = new_fmem_handle->second; - new_fmem_area.is_free = true; - MergeAdjacent(fmem_map, new_fmem_handle); + ASSERT_MSG(size_to_free == 0, "Failed to unmap physical memory"); } // Mark region as free and attempt to coalesce it with neighbours. - const auto new_it = CarveVMA(virtual_addr, adjusted_size); + const auto new_it = CarveVMA(virtual_addr, size_in_vma); auto& vma = new_it->second; vma.type = VMAType::Free; vma.prot = MemoryProt::NoAccess; - vma.phys_base = 0; + vma.phys_areas.clear(); vma.disallow_merge = false; vma.name = ""; MergeAdjacent(vma_map, new_it); - if (type != VMAType::Reserved && type != VMAType::PoolReserved) { - // If this mapping has GPU access, unmap from GPU. - if (IsValidGpuMapping(virtual_addr, size)) { - rasterizer->UnmapMemory(virtual_addr, size); - } - + if (vma_type != VMAType::Reserved && vma_type != VMAType::PoolReserved) { // Unmap the memory region. - impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + adjusted_size, - phys_base, is_exec, has_backing, readonly_file); - TRACK_FREE(virtual_addr, "VMEM"); + impl.Unmap(virtual_addr, size_in_vma); + // Tracy memory tracking breaks from merging memory areas. Disabled for now. + // TRACK_FREE(virtual_addr, "VMEM"); } - return adjusted_size; + return size_in_vma; } s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, u64 size) { @@ -791,8 +935,8 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, u64 size) { } s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { + std::shared_lock lk{mutex}; ASSERT_MSG(IsValidMapping(addr), "Attempted to access invalid address {:#x}", addr); - std::scoped_lock lk{mutex}; const auto it = FindVMA(addr); const auto& vma = it->second; @@ -810,6 +954,7 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr if (prot != nullptr) { *prot = static_cast(vma.prot); } + return ORBIS_OK; } @@ -817,6 +962,8 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz MemoryProt prot) { const auto start_in_vma = addr - vma_base.base; const auto adjusted_size = std::min(vma_base.size - start_in_vma, size); + const MemoryProt old_prot = vma_base.prot; + const MemoryProt new_prot = prot; if (vma_base.type == VMAType::Free || vma_base.type == VMAType::PoolReserved) { // On PS4, protecting freed memory does nothing. @@ -857,8 +1004,11 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz prot &= ~MemoryProt::CpuExec; } - // Change protection - vma_base.prot = prot; + // Split VMAs and apply protection change. + const auto new_it = CarveVMA(addr, adjusted_size); + auto& new_vma = new_it->second; + new_vma.prot = prot; + MergeAdjacent(vma_map, new_it); if (vma_base.type == VMAType::Reserved) { // On PS4, protections change vma_map, but don't apply. @@ -866,20 +1016,22 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz return adjusted_size; } - impl.Protect(addr, size, perms); + // Perform address-space memory protections if needed. + if (new_prot != old_prot) { + impl.Protect(addr, adjusted_size, perms); + } return adjusted_size; } s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { - std::scoped_lock lk{mutex}; - // If size is zero, then there's nothing to protect if (size == 0) { return ORBIS_OK; } // Ensure the range to modify is valid + std::scoped_lock lk{mutex, unmap_mutex}; ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr); // Appropriately restrict flags. @@ -909,8 +1061,6 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { - std::scoped_lock lk{mutex}; - // FindVMA on addresses before the vma_map return garbage data. auto query_addr = addr < impl.SystemManagedVirtualBase() ? impl.SystemManagedVirtualBase() : addr; @@ -918,6 +1068,8 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region"); return ORBIS_KERNEL_ERROR_EACCES; } + + std::shared_lock lk{mutex}; auto it = FindVMA(query_addr); while (it != vma_map.end() && it->second.type == VMAType::Free && flags == 1) { @@ -938,9 +1090,12 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, info->is_stack = vma.type == VMAType::Stack ? 1 : 0; info->is_pooled = vma.type == VMAType::PoolReserved || vma.type == VMAType::Pooled ? 1 : 0; info->is_committed = vma.IsMapped() ? 1 : 0; - if (vma.type == VMAType::Direct || vma.type == VMAType::Pooled) { - // Offset is only assigned for direct and pooled mappings. - info->offset = vma.phys_base; + info->memory_type = 0; + if (vma.type == VMAType::Direct) { + // Offset is only assigned for direct mappings. + ASSERT_MSG(vma.phys_areas.size() > 0, "No physical backing for direct mapping?"); + info->offset = vma.phys_areas.begin()->second.base; + info->memory_type = vma.phys_areas.begin()->second.memory_type; } if (vma.type == VMAType::Reserved || vma.type == VMAType::PoolReserved) { // Protection is hidden from reserved mappings. @@ -949,33 +1104,24 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, strncpy(info->name, vma.name.data(), ::Libraries::Kernel::ORBIS_KERNEL_MAXIMUM_NAME_LENGTH); - if (vma.type == VMAType::Direct) { - const auto dmem_it = FindDmemArea(vma.phys_base); - ASSERT_MSG(vma.phys_base <= dmem_it->second.GetEnd(), "vma.phys_base is not in dmem_map!"); - info->memory_type = dmem_it->second.memory_type; - } else { - info->memory_type = ::Libraries::Kernel::ORBIS_KERNEL_WB_ONION; - } - return ORBIS_OK; } s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, ::Libraries::Kernel::OrbisQueryInfo* out_info) { - std::scoped_lock lk{mutex}; - if (addr >= total_direct_size) { LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!"); return ORBIS_KERNEL_ERROR_EACCES; } + std::shared_lock lk{mutex}; auto dmem_area = FindDmemArea(addr); - while (dmem_area != dmem_map.end() && dmem_area->second.dma_type == DMAType::Free && + while (dmem_area != dmem_map.end() && dmem_area->second.dma_type == PhysicalMemoryType::Free && find_next) { dmem_area++; } - if (dmem_area == dmem_map.end() || dmem_area->second.dma_type == DMAType::Free) { + if (dmem_area == dmem_map.end() || dmem_area->second.dma_type == PhysicalMemoryType::Free) { LOG_WARNING(Kernel_Vmm, "Unable to find allocated direct memory region to query!"); return ORBIS_KERNEL_ERROR_EACCES; } @@ -986,8 +1132,8 @@ s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, // Loop through all sequential mapped or allocated dmem areas // to determine the hardware accurate end. while (dmem_area != dmem_map.end() && dmem_area->second.memory_type == out_info->memoryType && - (dmem_area->second.dma_type == DMAType::Mapped || - dmem_area->second.dma_type == DMAType::Allocated)) { + (dmem_area->second.dma_type == PhysicalMemoryType::Mapped || + dmem_area->second.dma_type == PhysicalMemoryType::Allocated)) { out_info->end = dmem_area->second.GetEnd(); dmem_area++; } @@ -997,14 +1143,14 @@ s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment, PAddr* phys_addr_out, u64* size_out) { - std::scoped_lock lk{mutex}; + std::shared_lock lk{mutex}; auto dmem_area = FindDmemArea(search_start); PAddr paddr{}; u64 max_size{}; while (dmem_area != dmem_map.end()) { - if (dmem_area->second.dma_type != DMAType::Free) { + if (dmem_area->second.dma_type != PhysicalMemoryType::Free) { dmem_area++; continue; } @@ -1043,45 +1189,46 @@ 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); // Search through all VMAs covered by the provided range. // We aren't modifying these VMAs, so it's safe to iterate through them. - auto remaining_size = size; + VAddr current_addr = addr; + u64 remaining_size = size; auto vma_handle = FindVMA(addr); - while (vma_handle != vma_map.end() && vma_handle->second.base < addr + size) { + while (vma_handle != vma_map.end() && remaining_size > 0) { + // Calculate position in vma + const VAddr start_in_vma = current_addr - vma_handle->second.base; + const u64 size_in_vma = + std::min(remaining_size, vma_handle->second.size - start_in_vma); + // Direct and Pooled mappings are the only ones with a memory type. if (vma_handle->second.type == VMAType::Direct || vma_handle->second.type == VMAType::Pooled) { - // Calculate position in vma - const auto start_in_vma = addr - vma_handle->second.base; - const auto size_in_vma = vma_handle->second.size - start_in_vma; - auto phys_addr = vma_handle->second.phys_base + start_in_vma; - auto size_to_modify = remaining_size > size_in_vma ? size_in_vma : remaining_size; + // Split area to modify into a new VMA. + vma_handle = CarveVMA(current_addr, size_in_vma); + auto phys_handle = vma_handle->second.phys_areas.begin(); + while (phys_handle != vma_handle->second.phys_areas.end()) { + // Update internal physical areas + phys_handle->second.memory_type = memory_type; - // Loop through remaining dmem areas until the physical addresses represented - // are all adjusted. - DMemHandle dmem_handle = FindDmemArea(phys_addr); - while (dmem_handle != dmem_map.end() && size_in_vma >= size_to_modify && - size_to_modify > 0) { - const auto start_in_dma = phys_addr - dmem_handle->second.base; - const auto size_in_dma = dmem_handle->second.size - start_in_dma > size_to_modify - ? size_to_modify - : dmem_handle->second.size - start_in_dma; - dmem_handle = CarveDmemArea(phys_addr, size_in_dma); + // Carve a new dmem area in dmem_map, update memory type there + auto dmem_handle = + CarvePhysArea(dmem_map, phys_handle->second.base, phys_handle->second.size); auto& dmem_area = dmem_handle->second; dmem_area.memory_type = memory_type; - size_to_modify -= dmem_area.size; - phys_addr += dmem_area.size; - // Check if we can coalesce any dmem areas now that the types are different. - MergeAdjacent(dmem_map, dmem_handle); - dmem_handle = FindDmemArea(phys_addr); + // Increment phys_handle + phys_handle++; } + + // Check if VMA can be merged with adjacent areas after physical area modifications. + vma_handle = MergeAdjacent(vma_map, vma_handle); } - remaining_size -= vma_handle->second.size; + current_addr += size_in_vma; + remaining_size -= size_in_vma; vma_handle++; } @@ -1089,32 +1236,36 @@ 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 - auto aligned_size = Common::AlignUp(size, 16_KB); + u64 aligned_size = Common::AlignUp(size, 16_KB); // Addresses are aligned down to the nearest 16_KB - auto aligned_addr = Common::AlignDown(virtual_addr, 16_KB); + VAddr aligned_addr = Common::AlignDown(virtual_addr, 16_KB); ASSERT_MSG(IsValidMapping(aligned_addr, aligned_size), "Attempted to access invalid address {:#x}", aligned_addr); auto it = FindVMA(aligned_addr); - s64 remaining_size = aligned_size; - auto current_addr = aligned_addr; - while (remaining_size > 0) { + u64 remaining_size = aligned_size; + VAddr current_addr = aligned_addr; + while (remaining_size > 0 && it != vma_map.end()) { + const u64 start_in_vma = current_addr - it->second.base; + const u64 size_in_vma = std::min(remaining_size, it->second.size - start_in_vma); // Nothing needs to be done to free VMAs if (!it->second.IsFree()) { - if (remaining_size < it->second.size) { - // We should split VMAs here, but this could cause trouble for Windows. - // Instead log a warning and name the whole VMA. - LOG_WARNING(Kernel_Vmm, "Trying to partially name a range"); + if (size_in_vma < it->second.size) { + it = CarveVMA(current_addr, size_in_vma); + auto& new_vma = it->second; + new_vma.name = name; + } else { + auto& vma = it->second; + vma.name = name; } - auto& vma = it->second; - vma.name = name; } - remaining_size -= it->second.size; - current_addr += it->second.size; - it = FindVMA(current_addr); + it = MergeAdjacent(vma_map, it); + remaining_size -= size_in_vma; + current_addr += size_in_vma; + it++; } } @@ -1125,8 +1276,9 @@ s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, return ORBIS_KERNEL_ERROR_ENOENT; } + std::shared_lock lk{mutex}; const auto& dmem_area = FindDmemArea(addr)->second; - if (dmem_area.dma_type == DMAType::Free) { + if (dmem_area.dma_type == PhysicalMemoryType::Free) { LOG_ERROR(Kernel_Vmm, "Unable to find allocated direct memory region to check type!"); return ORBIS_KERNEL_ERROR_ENOENT; } @@ -1138,6 +1290,7 @@ s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, } s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { + std::shared_lock lk{mutex}; ASSERT_MSG(IsValidMapping(addr), "Attempted to access invalid address {:#x}", addr); const auto& vma = FindVMA(addr)->second; if (vma.IsFree()) { @@ -1158,12 +1311,11 @@ s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { if (end != nullptr) { *end = reinterpret_cast(stack_end); } - return ORBIS_OK; } s32 MemoryManager::GetMemoryPoolStats(::Libraries::Kernel::OrbisKernelMemoryPoolBlockStats* stats) { - std::scoped_lock lk{mutex}; + std::shared_lock lk{mutex}; // Run through dmem_map, determine how much physical memory is currently committed constexpr u64 block_size = 64_KB; @@ -1171,7 +1323,7 @@ s32 MemoryManager::GetMemoryPoolStats(::Libraries::Kernel::OrbisKernelMemoryPool auto dma_handle = dmem_map.begin(); while (dma_handle != dmem_map.end()) { - if (dma_handle->second.dma_type == DMAType::Committed) { + if (dma_handle->second.dma_type == PhysicalMemoryType::Committed) { committed_size += dma_handle->second.size; } dma_handle++; @@ -1182,6 +1334,7 @@ s32 MemoryManager::GetMemoryPoolStats(::Libraries::Kernel::OrbisKernelMemoryPool // TODO: Determine how "cached blocks" work stats->allocated_cached_blocks = 0; stats->available_cached_blocks = 0; + return ORBIS_OK; } @@ -1251,6 +1404,52 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, u64 size, u32 alignment) { return -1; } +MemoryManager::VMAHandle MemoryManager::MergeAdjacent(VMAMap& handle_map, VMAHandle iter) { + const auto next_vma = std::next(iter); + if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) { + u64 base_offset = iter->second.size; + iter->second.size += next_vma->second.size; + for (auto& area : next_vma->second.phys_areas) { + iter->second.phys_areas[base_offset + area.first] = area.second; + } + handle_map.erase(next_vma); + } + + if (iter != handle_map.begin()) { + auto prev_vma = std::prev(iter); + if (prev_vma->second.CanMergeWith(iter->second)) { + u64 base_offset = prev_vma->second.size; + prev_vma->second.size += iter->second.size; + for (auto& area : iter->second.phys_areas) { + prev_vma->second.phys_areas[base_offset + area.first] = area.second; + } + handle_map.erase(iter); + iter = prev_vma; + } + } + + return iter; +} + +MemoryManager::PhysHandle MemoryManager::MergeAdjacent(PhysMap& handle_map, PhysHandle iter) { + const auto next_vma = std::next(iter); + if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) { + iter->second.size += next_vma->second.size; + handle_map.erase(next_vma); + } + + if (iter != handle_map.begin()) { + auto prev_vma = std::prev(iter); + if (prev_vma->second.CanMergeWith(iter->second)) { + prev_vma->second.size += iter->second.size; + handle_map.erase(iter); + iter = prev_vma; + } + } + + return iter; +} + MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, u64 size) { auto vma_handle = FindVMA(virtual_addr); @@ -1279,11 +1478,11 @@ MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, u64 size) { return vma_handle; } -MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, u64 size) { - auto dmem_handle = FindDmemArea(addr); - ASSERT_MSG(addr <= dmem_handle->second.GetEnd(), "Physical address not in dmem_map"); +MemoryManager::PhysHandle MemoryManager::CarvePhysArea(PhysMap& map, PAddr addr, u64 size) { + auto pmem_handle = std::prev(map.upper_bound(addr)); + ASSERT_MSG(addr <= pmem_handle->second.GetEnd(), "Physical address not in map"); - const DirectMemoryArea& area = dmem_handle->second; + const PhysicalMemoryArea& area = pmem_handle->second; ASSERT_MSG(area.base <= addr, "Adding an allocation to already allocated region"); const PAddr start_in_area = addr - area.base; @@ -1293,38 +1492,14 @@ MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, u64 size) { if (end_in_vma != area.size) { // Split VMA at the end of the allocated region - Split(dmem_handle, end_in_vma); + Split(map, pmem_handle, end_in_vma); } if (start_in_area != 0) { // Split VMA at the start of the allocated region - dmem_handle = Split(dmem_handle, start_in_area); + pmem_handle = Split(map, pmem_handle, start_in_area); } - return dmem_handle; -} - -MemoryManager::FMemHandle MemoryManager::CarveFmemArea(PAddr addr, u64 size) { - auto fmem_handle = FindFmemArea(addr); - ASSERT_MSG(addr <= fmem_handle->second.GetEnd(), "Physical address not in fmem_map"); - - const FlexibleMemoryArea& area = fmem_handle->second; - ASSERT_MSG(area.base <= addr, "Adding an allocation to already allocated region"); - - const PAddr start_in_area = addr - area.base; - const PAddr end_in_vma = start_in_area + size; - ASSERT_MSG(end_in_vma <= area.size, "Mapping cannot fit inside free region: size = {:#x}", - size); - - if (end_in_vma != area.size) { - // Split VMA at the end of the allocated region - Split(fmem_handle, end_in_vma); - } - if (start_in_area != 0) { - // Split VMA at the start of the allocated region - fmem_handle = Split(fmem_handle, start_in_area); - } - - return fmem_handle; + return pmem_handle; } MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, u64 offset_in_vma) { @@ -1337,13 +1512,43 @@ MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, u64 offset_i new_vma.size -= offset_in_vma; if (HasPhysicalBacking(new_vma)) { - new_vma.phys_base += offset_in_vma; + // Update physical areas map for both areas + new_vma.phys_areas.clear(); + + std::map old_vma_phys_areas; + for (auto& [offset, region] : old_vma.phys_areas) { + // Fully contained in first VMA + if (offset + region.size <= offset_in_vma) { + old_vma_phys_areas[offset] = region; + } + // Split between both VMAs + if (offset < offset_in_vma && offset + region.size > offset_in_vma) { + // Create region in old VMA + u64 size_in_old = offset_in_vma - offset; + old_vma_phys_areas[offset] = PhysicalMemoryArea{ + region.base, size_in_old, region.memory_type, region.dma_type}; + // Create region in new VMA + PAddr new_base = region.base + size_in_old; + u64 size_in_new = region.size - size_in_old; + new_vma.phys_areas[0] = + PhysicalMemoryArea{new_base, size_in_new, region.memory_type, region.dma_type}; + } + // Fully contained in new VMA + if (offset >= offset_in_vma) { + new_vma.phys_areas[offset - offset_in_vma] = region; + } + } + + // Set old_vma's physical areas map to the newly created map + old_vma.phys_areas = old_vma_phys_areas; } + return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); } -MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offset_in_area) { - auto& old_area = dmem_handle->second; +MemoryManager::PhysHandle MemoryManager::Split(PhysMap& map, PhysHandle phys_handle, + u64 offset_in_area) { + auto& old_area = phys_handle->second; ASSERT(offset_in_area < old_area.size && offset_in_area > 0); auto new_area = old_area; @@ -1352,19 +1557,7 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offse new_area.base += offset_in_area; new_area.size -= offset_in_area; - return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area); -} - -MemoryManager::FMemHandle MemoryManager::Split(FMemHandle fmem_handle, u64 offset_in_area) { - auto& old_area = fmem_handle->second; - ASSERT(offset_in_area < old_area.size && offset_in_area > 0); - - auto new_area = old_area; - old_area.size = offset_in_area; - new_area.base += offset_in_area; - new_area.size -= offset_in_area; - - return fmem_map.emplace_hint(std::next(fmem_handle), new_area.base, new_area); + return map.emplace_hint(std::next(phys_handle), new_area.base, new_area); } } // namespace Core diff --git a/src/core/memory.h b/src/core/memory.h index 7ebf9d34c..f9ae64942 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -8,6 +8,7 @@ #include #include #include "common/enum.h" +#include "common/shared_first_mutex.h" #include "common/singleton.h" #include "common/types.h" #include "core/address_space.h" @@ -27,6 +28,8 @@ class MemoryMapViewer; namespace Core { +constexpr u64 DEFAULT_MAPPING_BASE = 0x200000000; + enum class MemoryProt : u32 { NoAccess = 0, CpuRead = 1, @@ -54,12 +57,37 @@ enum class MemoryMapFlags : u32 { }; DECLARE_ENUM_FLAG_OPERATORS(MemoryMapFlags) -enum class DMAType : u32 { +enum class PhysicalMemoryType : u32 { Free = 0, Allocated = 1, Mapped = 2, Pooled = 3, Committed = 4, + Flexible = 5, +}; + +struct PhysicalMemoryArea { + PAddr base = 0; + u64 size = 0; + s32 memory_type = 0; + PhysicalMemoryType dma_type = PhysicalMemoryType::Free; + + PAddr GetEnd() const { + return base + size; + } + + bool CanMergeWith(const PhysicalMemoryArea& next) const { + if (base + size != next.base) { + return false; + } + if (memory_type != next.memory_type) { + return false; + } + if (dma_type != next.dma_type) { + return false; + } + return true; + } }; enum class VMAType : u32 { @@ -74,65 +102,24 @@ enum class VMAType : u32 { File = 8, }; -struct DirectMemoryArea { - PAddr base = 0; - u64 size = 0; - s32 memory_type = 0; - DMAType dma_type = DMAType::Free; - - PAddr GetEnd() const { - return base + size; - } - - bool CanMergeWith(const DirectMemoryArea& next) const { - if (base + size != next.base) { - return false; - } - if (memory_type != next.memory_type) { - return false; - } - if (dma_type != next.dma_type) { - return false; - } - return true; - } -}; - -struct FlexibleMemoryArea { - PAddr base = 0; - u64 size = 0; - bool is_free = true; - - PAddr GetEnd() const { - return base + size; - } - - bool CanMergeWith(const FlexibleMemoryArea& next) const { - if (base + size != next.base) { - return false; - } - if (is_free != next.is_free) { - return false; - } - return true; - } -}; - struct VirtualMemoryArea { VAddr base = 0; u64 size = 0; - PAddr phys_base = 0; + std::map phys_areas; VMAType type = VMAType::Free; MemoryProt prot = MemoryProt::NoAccess; - bool disallow_merge = false; std::string name = ""; - uintptr_t fd = 0; - bool is_exec = false; + s32 fd = 0; + bool disallow_merge = false; bool Contains(VAddr addr, u64 size) const { return addr >= base && (addr + size) <= (base + this->size); } + bool Overlaps(VAddr addr, u64 size) const { + return addr <= (base + this->size) && (addr + size) >= base; + } + bool IsFree() const noexcept { return type == VMAType::Free; } @@ -141,30 +128,35 @@ struct VirtualMemoryArea { return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved; } - bool CanMergeWith(const VirtualMemoryArea& next) const { + bool CanMergeWith(VirtualMemoryArea& next) { if (disallow_merge || next.disallow_merge) { return false; } if (base + size != next.base) { return false; } - if ((type == VMAType::Direct || type == VMAType::Flexible || type == VMAType::Pooled) && - phys_base + size != next.phys_base) { - return false; + if (type == VMAType::Direct && next.type == VMAType::Direct) { + auto& last_phys = std::prev(phys_areas.end())->second; + auto& first_next_phys = next.phys_areas.begin()->second; + if (last_phys.base + last_phys.size != first_next_phys.base || + last_phys.memory_type != first_next_phys.memory_type) { + return false; + } } if (prot != next.prot || type != next.type) { return false; } + if (name.compare(next.name) != 0) { + return false; + } + return true; } }; class MemoryManager { - using DMemMap = std::map; - using DMemHandle = DMemMap::iterator; - - using FMemMap = std::map; - using FMemHandle = FMemMap::iterator; + using PhysMap = std::map; + using PhysHandle = PhysMap::iterator; using VMAMap = std::map; using VMAHandle = VMAMap::iterator; @@ -220,10 +212,11 @@ public: // Now make sure the full address range is contained in vma_map. auto vma_handle = FindVMA(virtual_addr); auto addr_to_check = virtual_addr; - s64 size_to_validate = size; + u64 size_to_validate = size; while (vma_handle != vma_map.end() && size_to_validate > 0) { const auto offset_in_vma = addr_to_check - vma_handle->second.base; - const auto size_in_vma = vma_handle->second.size - offset_in_vma; + const auto size_in_vma = + std::min(vma_handle->second.size - offset_in_vma, size_to_validate); size_to_validate -= size_in_vma; addr_to_check += size_in_vma; vma_handle++; @@ -245,7 +238,7 @@ public: void CopySparseMemory(VAddr source, u8* dest, u64 size); - bool TryWriteBacking(void* address, const void* data, u32 num_bytes); + bool TryWriteBacking(void* address, const void* data, u64 size); void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); @@ -253,7 +246,7 @@ public: PAddr Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, s32 memory_type); - void Free(PAddr phys_addr, u64 size); + s32 Free(PAddr phys_addr, u64 size, bool is_checked); s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype); @@ -300,52 +293,35 @@ private: return std::prev(vma_map.upper_bound(target)); } - DMemHandle FindDmemArea(PAddr target) { + PhysHandle FindDmemArea(PAddr target) { return std::prev(dmem_map.upper_bound(target)); } - FMemHandle FindFmemArea(PAddr target) { + PhysHandle FindFmemArea(PAddr target) { return std::prev(fmem_map.upper_bound(target)); } - template - Handle MergeAdjacent(auto& handle_map, Handle iter) { - const auto next_vma = std::next(iter); - if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) { - iter->second.size += next_vma->second.size; - handle_map.erase(next_vma); - } - - if (iter != handle_map.begin()) { - auto prev_vma = std::prev(iter); - if (prev_vma->second.CanMergeWith(iter->second)) { - prev_vma->second.size += iter->second.size; - handle_map.erase(iter); - iter = prev_vma; - } - } - - return iter; - } - bool HasPhysicalBacking(VirtualMemoryArea vma) { return vma.type == VMAType::Direct || vma.type == VMAType::Flexible || vma.type == VMAType::Pooled; } + 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); + VMAHandle MergeAdjacent(VMAMap& map, VMAHandle iter); + + PhysHandle MergeAdjacent(PhysMap& map, PhysHandle iter); + VMAHandle CarveVMA(VAddr virtual_addr, u64 size); - DMemHandle CarveDmemArea(PAddr addr, u64 size); - - FMemHandle CarveFmemArea(PAddr addr, u64 size); + PhysHandle CarvePhysArea(PhysMap& map, PAddr addr, u64 size); VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma); - DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area); - - FMemHandle Split(FMemHandle fmem_handle, u64 offset_in_area); + PhysHandle Split(PhysMap& map, PhysHandle dmem_handle, u64 offset_in_area); u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size); @@ -353,14 +329,16 @@ private: private: AddressSpace impl; - DMemMap dmem_map; - FMemMap fmem_map; + PhysMap dmem_map; + PhysMap fmem_map; VMAMap vma_map; - std::mutex mutex; + Common::SharedFirstMutex mutex{}; + std::mutex unmap_mutex{}; u64 total_direct_size{}; u64 total_flexible_size{}; u64 flexible_usage{}; u64 pool_budget{}; + s32 sdk_version{}; Vulkan::Rasterizer* rasterizer{}; struct PrtArea { diff --git a/src/core/module.h b/src/core/module.h index c39310406..778344e33 100644 --- a/src/core/module.h +++ b/src/core/module.h @@ -5,6 +5,7 @@ #include #include +#include "common/config.h" #include "common/types.h" #include "core/loader/elf.h" #include "core/loader/symbols_resolver.h" @@ -164,6 +165,14 @@ public: return elf.IsSharedLib(); } + bool IsSystemLib() { + auto system_path = Config::getSysModulesPath(); + if (file.string().starts_with(system_path.string().c_str())) { + return true; + } + return false; + } + template T GetProcParam() const noexcept { return reinterpret_cast(proc_param_virtual_addr); diff --git a/src/emulator.cpp b/src/emulator.cpp index 44f8b0e72..0dde0b7fa 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -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 @@ -28,6 +28,7 @@ #include "common/singleton.h" #include "core/debugger.h" #include "core/devtools/widget/module_list.h" +#include "core/emulator_state.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -36,6 +37,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 +98,7 @@ s32 ReadCompiledSdkVersion(const std::filesystem::path& file) { void Emulator::Run(std::filesystem::path file, std::vector args, std::optional p_game_folder) { - Common::SetCurrentThreadName("Main Thread"); + Common::SetCurrentThreadName("shadPS4:Main"); if (waitForDebuggerBeforeRun) { Debugger::WaitForDebuggerAttach(); } @@ -205,6 +207,13 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"), true); + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (id + ".toml"))) { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true); + } else { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false); + } + // Initialize logging as soon as possible if (!id.empty() && Config::getSeparateLogFilesEnabled()) { Common::Log::Initialize(id + ".log"); @@ -527,11 +536,12 @@ 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}, {"libSceCesCs.sprx", nullptr}, + {"libSceAudiodec.sprx", nullptr}, {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont}, {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt}, {"libSceFreeTypeOt.sprx", nullptr}}); diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 6657f4036..3606ad5d2 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -61,110 +61,51 @@ void State::OnAccel(const float accel[3]) { acceleration.z = accel[2]; } -GameController::GameController() { - m_states_num = 0; - m_last_state = State(); -} +GameController::GameController() : m_states_queue(64) {} void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) { - std::scoped_lock lock{m_mutex}; - *isConnected = m_connected; *connectedCount = m_connected_count; - *state = GetLastState(); + *state = m_state; } int GameController::ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount) { - std::scoped_lock lock{m_mutex}; - *isConnected = m_connected; *connectedCount = m_connected_count; int ret_num = 0; - if (m_connected) { - if (m_states_num == 0) { - ret_num = 1; - states[0] = m_last_state; - } else { - for (uint32_t i = 0; i < m_states_num; i++) { - if (ret_num >= states_num) { - break; - } - auto index = (m_first_state + i) % MAX_STATES; - if (!m_private[index].obtained) { - m_private[index].obtained = true; - - states[ret_num++] = m_states[index]; - } + std::lock_guard lg(m_states_queue_mutex); + for (int i = 0; i < states_num; i++) { + auto o_state = m_states_queue.Pop(); + if (!o_state) { + break; } + states[ret_num++] = *o_state; } } - return ret_num; } -State GameController::GetLastState() const { - if (m_states_num == 0) { - return m_last_state; - } - const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES; - return m_states[last]; -} - -void GameController::AddState(const State& state) { - if (m_states_num >= MAX_STATES) { - m_states_num = MAX_STATES - 1; - m_first_state = (m_first_state + 1) % MAX_STATES; - } - - const u32 index = (m_first_state + m_states_num) % MAX_STATES; - m_states[index] = state; - m_last_state = state; - m_private[index].obtained = false; - m_states_num++; -} - -void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnButton(button, is_pressed); - - AddState(state); +void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) { + m_state.OnButton(button, is_pressed); + PushState(); } void GameController::Axis(int id, Input::Axis axis, int value) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnAxis(axis, value); - - AddState(state); + m_state.OnAxis(axis, value); + PushState(); } void GameController::Gyro(int id, const float gyro[3]) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Update the angular velocity (gyro data) - state.OnGyro(gyro); - - AddState(state); + m_state.OnGyro(gyro); + PushState(); } + void GameController::Acceleration(int id, const float acceleration[3]) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Update the acceleration values - state.OnAccel(acceleration); - - AddState(state); + m_state.OnAccel(acceleration); + PushState(); } void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, @@ -206,7 +147,6 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { if (!m_engine) { return; } - std::scoped_lock _{m_mutex}; m_engine->SetLightBarRGB(r, g, b); } @@ -214,39 +154,29 @@ void GameController::SetVibration(u8 smallMotor, u8 largeMotor) { if (!m_engine) { return; } - std::scoped_lock _{m_mutex}; m_engine->SetVibration(smallMotor, largeMotor); } void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) { if (touchIndex < 2) { - std::scoped_lock lock{m_mutex}; - auto state = GetLastState(); - - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnTouchpad(touchIndex, touchDown, x, y); - - AddState(state); + m_state.OnTouchpad(touchIndex, touchDown, x, y); + PushState(); } } u8 GameController::GetTouchCount() { - std::scoped_lock lock{m_mutex}; return m_touch_count; } void GameController::SetTouchCount(u8 touchCount) { - std::scoped_lock lock{m_mutex}; m_touch_count = touchCount; } u8 GameController::GetSecondaryTouchCount() { - std::scoped_lock lock{m_mutex}; return m_secondary_touch_count; } void GameController::SetSecondaryTouchCount(u8 touchCount) { - std::scoped_lock lock{m_mutex}; m_secondary_touch_count = touchCount; if (touchCount == 0) { m_was_secondary_reset = true; @@ -254,47 +184,38 @@ void GameController::SetSecondaryTouchCount(u8 touchCount) { } u8 GameController::GetPreviousTouchNum() { - std::scoped_lock lock{m_mutex}; return m_previous_touchnum; } void GameController::SetPreviousTouchNum(u8 touchNum) { - std::scoped_lock lock{m_mutex}; m_previous_touchnum = touchNum; } bool GameController::WasSecondaryTouchReset() { - std::scoped_lock lock{m_mutex}; return m_was_secondary_reset; } void GameController::UnsetSecondaryTouchResetBool() { - std::scoped_lock lock{m_mutex}; m_was_secondary_reset = false; } void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) { - std::scoped_lock lock{m_mutex}; m_orientation = orientation; } Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() { - std::scoped_lock lock{m_mutex}; return m_orientation; } std::chrono::steady_clock::time_point GameController::GetLastUpdate() { - std::scoped_lock lock{m_mutex}; return m_last_update; } void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) { - std::scoped_lock lock{m_mutex}; m_last_update = lastUpdate; } void GameController::SetEngine(std::unique_ptr engine) { - std::scoped_lock _{m_mutex}; m_engine = std::move(engine); if (m_engine) { m_engine->Init(); @@ -305,24 +226,17 @@ Engine* GameController::GetEngine() { return m_engine.get(); } +void GameController::PushState() { + std::lock_guard lg(m_states_queue_mutex); + m_state.time = Libraries::Kernel::sceKernelGetProcessTime(); + m_states_queue.Push(m_state); +} + u32 GameController::Poll() { if (m_connected) { - std::scoped_lock lock{m_mutex}; - auto time = Libraries::Kernel::sceKernelGetProcessTime(); - if (m_states_num == 0) { - auto diff = (time - m_last_state.time) / 1000; - if (diff >= 100) { - AddState(GetLastState()); - } - } else { - auto index = (m_first_state - 1 + m_states_num) % MAX_STATES; - auto diff = (time - m_states[index].time) / 1000; - if (m_private[index].obtained && diff >= 100) { - AddState(GetLastState()); - } - } + PushState(); } - return 100; + return 33; } } // namespace Input diff --git a/src/input/controller.h b/src/input/controller.h index dfde521be..6c13fdf99 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -6,7 +6,10 @@ #include #include #include +#include + #include + #include "common/types.h" #include "core/libraries/pad/pad.h" @@ -63,7 +66,36 @@ inline int GetAxis(int min, int max, int value) { return std::clamp((255 * (value - min)) / (max - min), 0, 255); } -constexpr u32 MAX_STATES = 32; +template +class RingBufferQueue { +public: + RingBufferQueue(size_t size) : m_storage(size) {} + + void Push(T item) { + const size_t index = (m_begin + m_size) % m_storage.size(); + m_storage[index] = std::move(item); + if (m_size < m_storage.size()) { + m_size += 1; + } else { + m_begin = (m_begin + 1) % m_storage.size(); + } + } + + std::optional Pop() { + if (m_size == 0) { + return {}; + } + const size_t index = m_begin; + m_begin = (m_begin + 1) % m_storage.size(); + m_size -= 1; + return std::move(m_storage[index]); + } + +private: + size_t m_begin = 0; + size_t m_size = 0; + std::vector m_storage; +}; class GameController { public: @@ -72,9 +104,8 @@ public: void ReadState(State* state, bool* isConnected, int* connectedCount); int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount); - State GetLastState() const; - void CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed); - void AddState(const State& state); + + void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed); void Axis(int id, Input::Axis axis, int value); void Gyro(int id, const float gyro[3]); void Acceleration(int id, const float acceleration[3]); @@ -105,26 +136,22 @@ public: Libraries::Pad::OrbisFQuaternion& orientation); private: - struct StateInternal { - bool obtained = false; - }; + void PushState(); - std::mutex m_mutex; bool m_connected = true; - State m_last_state; - int m_connected_count = 0; - u32 m_states_num = 0; - u32 m_first_state = 0; + int m_connected_count = 1; u8 m_touch_count = 0; u8 m_secondary_touch_count = 0; - u8 m_previous_touch_count = 0; u8 m_previous_touchnum = 0; bool m_was_secondary_reset = false; - std::array m_states; - std::array m_private; std::chrono::steady_clock::time_point m_last_update = {}; Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f}; + State m_state; + + std::mutex m_states_queue_mutex; + RingBufferQueue m_states_queue; + std::unique_ptr m_engine = nullptr; }; diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index e74569737..e44693fbf 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "input_handler.h" @@ -22,6 +22,8 @@ #include "common/elf_info.h" #include "common/io_file.h" #include "common/path_util.h" +#include "core/devtools/layer.h" +#include "core/emulator_state.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -108,6 +110,8 @@ auto output_array = std::array{ ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO), ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD), ControllerOutput(HOTKEY_RENDERDOC), + ControllerOutput(HOTKEY_VOLUME_UP), + ControllerOutput(HOTKEY_VOLUME_DOWN), ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), }; @@ -198,6 +202,8 @@ InputBinding GetBindingFromString(std::string& line) { input = InputID(InputType::Axis, string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); + } else if (string_to_hotkey_map.find(t) != string_to_hotkey_map.end()) { + input = InputID(InputType::Controller, string_to_hotkey_map.at(t)); } else { // Invalid token found; return default binding LOG_DEBUG(Input, "Invalid token found: {}", t); @@ -218,8 +224,8 @@ InputBinding GetBindingFromString(std::string& line) { void ParseInputConfig(const std::string game_id = "") { std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; - const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default); - const auto global_config_file = Config::GetFoolproofInputConfigFile("global"); + const auto config_file = Config::GetInputConfigFile(game_id_or_default); + const auto global_config_file = Config::GetInputConfigFile("global"); // we reset these here so in case the user fucks up or doesn't include some of these, // we can fall back to default @@ -392,19 +398,23 @@ void ParseInputConfig(const std::string game_id = "") { // normal cases InputBinding binding = GetBindingFromString(input_string); - BindingConnection connection(InputID(), nullptr); - auto button_it = string_to_cbutton_map.find(output_string); - auto axis_it = string_to_axis_map.find(output_string); if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); return; } + BindingConnection connection(InputID(), nullptr); + auto button_it = string_to_cbutton_map.find(output_string); + auto hotkey_it = string_to_hotkey_map.find(output_string); + auto axis_it = string_to_axis_map.find(output_string); if (button_it != string_to_cbutton_map.end()) { connection = BindingConnection( binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); connections.insert(connections.end(), connection); - + } else if (hotkey_it != string_to_hotkey_map.end()) { + connection = BindingConnection( + binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second))); + connections.insert(connections.end(), connection); } else if (axis_it != string_to_axis_map.end()) { int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( @@ -542,19 +552,20 @@ void ControllerOutput::FinalizeUpdate() { } old_button_state = new_button_state; old_param = *new_param; + bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed(); if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: leftjoystick_halfmode = new_button_state; @@ -562,6 +573,9 @@ void ControllerOutput::FinalizeUpdate() { case RIGHTJOYSTICK_HALFMODE: rightjoystick_halfmode = new_button_state; break; + case HOTKEY_RELOAD_INPUTS: + ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); + break; case HOTKEY_FULLSCREEN: PushSDLEvent(SDL_EVENT_TOGGLE_FULLSCREEN); break; @@ -571,9 +585,6 @@ void ControllerOutput::FinalizeUpdate() { case HOTKEY_SIMPLE_FPS: PushSDLEvent(SDL_EVENT_TOGGLE_SIMPLE_FPS); break; - case HOTKEY_RELOAD_INPUTS: - PushSDLEvent(SDL_EVENT_RELOAD_INPUTS); - break; case HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK: PushSDLEvent(SDL_EVENT_MOUSE_TO_JOYSTICK); break; @@ -586,6 +597,16 @@ void ControllerOutput::FinalizeUpdate() { case HOTKEY_RENDERDOC: PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); break; + case HOTKEY_VOLUME_UP: + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() + 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); + break; + case HOTKEY_VOLUME_DOWN: + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() - 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); + break; case HOTKEY_QUIT: PushSDLEvent(SDL_EVENT_QUIT_DIALOG); break; @@ -596,7 +617,7 @@ void ControllerOutput::FinalizeUpdate() { SetMouseGyroRollMode(new_button_state); break; default: // is a normal key (hopefully) - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state); break; } } else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) { @@ -627,12 +648,12 @@ void ControllerOutput::FinalizeUpdate() { case Axis::TriggerLeft: ApplyDeadzone(new_param, lefttrigger_deadzone); controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); + controller->Button(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); return; case Axis::TriggerRight: ApplyDeadzone(new_param, righttrigger_deadzone); controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); + controller->Button(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); return; default: break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index eaadd164e..844870b5d 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -55,6 +55,8 @@ #define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007 #define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008 #define HOTKEY_RENDERDOC 0xf0000009 +#define HOTKEY_VOLUME_UP 0xf000000a +#define HOTKEY_VOLUME_DOWN 0xf000000b #define SDL_UNMAPPED UINT32_MAX - 1 @@ -136,6 +138,8 @@ const std::map string_to_cbutton_map = { {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, +}; +const std::map string_to_hotkey_map = { {"hotkey_pause", HOTKEY_PAUSE}, {"hotkey_fullscreen", HOTKEY_FULLSCREEN}, {"hotkey_show_fps", HOTKEY_SIMPLE_FPS}, @@ -145,6 +149,8 @@ const std::map string_to_cbutton_map = { {"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO}, {"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD}, {"hotkey_renderdoc_capture", HOTKEY_RENDERDOC}, + {"hotkey_volume_up", HOTKEY_VOLUME_UP}, + {"hotkey_volume_down", HOTKEY_VOLUME_DOWN}, }; const std::map string_to_axis_map = { diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 996c35ef9..0dc44608b 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -104,8 +104,8 @@ void EmulateTouchpad(GameController* controller, u32 interval) { controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0, std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f), std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f)); - controller->CheckButton(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad, - (mouse_buttons & SDL_BUTTON_RMASK) != 0); + controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad, + (mouse_buttons & SDL_BUTTON_RMASK) != 0); } void ApplyMouseInputBlockers() { diff --git a/src/main.cpp b/src/main.cpp index aa3f4de45..d3799e2ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include #include -#include "functional" -#include "iostream" -#include "string" -#include "system_error" -#include "unordered_map" #include -#include #include "common/config.h" +#include "common/key_manager.h" #include "common/logging/backend.h" #include "common/memory_patcher.h" #include "common/path_util.h" @@ -27,249 +28,176 @@ int main(int argc, char* argv[]) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); #endif + IPC::Instance().Init(); - // Init emulator state - std::shared_ptr m_emu_state = std::make_shared(); - EmulatorState::SetInstance(m_emu_state); - // Load configurations + + auto emu_state = std::make_shared(); + EmulatorState::SetInstance(emu_state); + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); - bool has_game_argument = false; - std::string game_path; - std::vector game_args{}; - std::optional game_folder; + // ---- Trophy key migration ---- + auto key_manager = KeyManager::GetInstance(); + key_manager->LoadFromFile(); + if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() && + !Config::getTrophyKey().empty()) { + 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(); + } + } - bool waitForDebugger = false; + CLI::App app{"shadPS4 Emulator CLI"}; + + // ---- CLI state ---- + std::optional gamePath; + std::vector gameArgs; + std::optional overrideRoot; std::optional waitPid; + bool waitForDebugger = false; - // Map of argument strings to lambda functions - std::unordered_map> arg_map = { - {"-h", - [&](int&) { - std::cout - << "Usage: shadps4 [options] \n" - "Options:\n" - " -g, --game Specify game path to launch\n" - " -- ... Parameters passed to the game ELF. " - "Needs to be at the end of the line, and everything after \"--\" is a " - "game argument.\n" - " -p, --patch Apply specified patch file\n" - " -i, --ignore-game-patch Disable automatic loading of game patch\n" - " -f, --fullscreen Specify window initial fullscreen " - "state. Does not overwrite the config file.\n" - " --add-game-folder Adds a new game folder to the config.\n" - " --set-addon-folder Sets the addon folder to the config.\n" - " --log-append Append log output to file instead of " - "overwriting it.\n" - " --override-root Override the game root folder. Default is the " - "parent of game path\n" - " --wait-for-debugger Wait for debugger to attach\n" - " --wait-for-pid Wait for process with specified PID to stop\n" - " --config-clean Run the emulator with the default config " - "values, ignores the config file(s) entirely.\n" - " --config-global Run the emulator with the base config file " - "only, ignores game specific configs.\n" - " --show-fps Enable FPS counter display at startup\n" - " -h, --help Display this help message\n"; - exit(0); - }}, - {"--help", [&](int& i) { arg_map["-h"](i); }}, + std::optional fullscreenStr; + bool ignoreGamePatch = false; + bool showFps = false; + bool configClean = false; + bool configGlobal = false; + bool logAppend = false; - {"-g", - [&](int& i) { - if (i + 1 < argc) { - game_path = argv[++i]; - has_game_argument = true; - } else { - std::cerr << "Error: Missing argument for -g/--game\n"; - exit(1); - } - }}, - {"--game", [&](int& i) { arg_map["-g"](i); }}, + std::optional addGameFolder; + std::optional setAddonFolder; + std::optional patchFile; - {"-p", - [&](int& i) { - if (i + 1 < argc) { - MemoryPatcher::patch_file = argv[++i]; - } else { - std::cerr << "Error: Missing argument for -p/--patch\n"; - exit(1); - } - }}, - {"--patch", [&](int& i) { arg_map["-p"](i); }}, + // ---- Options ---- + app.add_option("-g,--game", gamePath, "Game path or ID"); + app.add_option("-p,--patch", patchFile, "Patch file to apply"); + app.add_flag("-i,--ignore-game-patch", ignoreGamePatch, + "Disable automatic loading of game patches"); - {"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }}, - {"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }}, - {"-f", - [&](int& i) { - if (++i >= argc) { - std::cerr << "Error: Missing argument for -f/--fullscreen\n"; - exit(1); - } - std::string f_param(argv[i]); - bool is_fullscreen; - if (f_param == "true") { - is_fullscreen = true; - } else if (f_param == "false") { - is_fullscreen = false; - } else { - std::cerr - << "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n"; - exit(1); - } - // Set fullscreen mode without saving it to config file - Config::setIsFullscreen(is_fullscreen); - }}, - {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, - {"--add-game-folder", - [&](int& i) { - if (++i >= argc) { - std::cerr << "Error: Missing argument for --add-game-folder\n"; - exit(1); - } - std::string config_dir(argv[i]); - std::filesystem::path config_path = std::filesystem::path(config_dir); - std::error_code discard; - if (!std::filesystem::exists(config_path, discard)) { - std::cerr << "Error: File does not exist: " << config_path << "\n"; - exit(1); - } + // FULLSCREEN: behavior-identical + app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)"); - Config::addGameInstallDir(config_path); - Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); - std::cout << "Game folder successfully saved.\n"; - exit(0); - }}, - {"--set-addon-folder", - [&](int& i) { - if (++i >= argc) { - std::cerr << "Error: Missing argument for --add-addon-folder\n"; - exit(1); - } - std::string config_dir(argv[i]); - std::filesystem::path config_path = std::filesystem::path(config_dir); - std::error_code discard; - if (!std::filesystem::exists(config_path, discard)) { - std::cerr << "Error: File does not exist: " << config_path << "\n"; - exit(1); - } + app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory); - Config::setAddonInstallDir(config_path); - Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); - std::cout << "Addon folder successfully saved.\n"; - exit(0); - }}, - {"--log-append", [&](int& i) { Common::Log::SetAppend(); }}, - {"--config-clean", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Clean); }}, - {"--config-global", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Global); }}, - {"--override-root", - [&](int& i) { - if (++i >= argc) { - std::cerr << "Error: Missing argument for --override-root\n"; - exit(1); - } - std::string folder_str{argv[i]}; - std::filesystem::path folder{folder_str}; - if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) { - std::cerr << "Error: Folder does not exist: " << folder_str << "\n"; - exit(1); - } - game_folder = folder; - }}, - {"--wait-for-debugger", [&](int& i) { waitForDebugger = true; }}, - {"--wait-for-pid", - [&](int& i) { - if (++i >= argc) { - std::cerr << "Error: Missing argument for --wait-for-pid\n"; - exit(1); - } - waitPid = std::stoi(argv[i]); - }}, - {"--show-fps", [&](int& i) { Config::setShowFpsCounter(true); }}}; + app.add_flag("--wait-for-debugger", waitForDebugger); + app.add_option("--wait-for-pid", waitPid); + app.add_flag("--show-fps", showFps); + app.add_flag("--config-clean", configClean); + app.add_flag("--config-global", configGlobal); + app.add_flag("--log-append", logAppend); + + app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); + app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); + + // ---- Capture args after `--` verbatim ---- + app.allow_extras(); + app.parse_complete_callback([&]() { + const auto& extras = app.remaining(); + if (!extras.empty()) { + gameArgs = extras; + } + }); + + // ---- No-args behavior ---- if (argc == 1) { - if (!SDL_ShowSimpleMessageBox( - SDL_MESSAGEBOX_INFORMATION, "shadPS4", - "This is a CLI application. Please use the QTLauncher for a GUI: " - "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", - nullptr)) - std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n"; - int dummy = 0; // one does not simply pass 0 directly - arg_map.at("-h")(dummy); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4", + "This is a CLI application. Please use the QTLauncher for a GUI:\n" + "https://github.com/shadps4-emu/shadps4-qtlauncher/releases", + nullptr); + std::cout << app.help(); return -1; } - // Parse command-line arguments using the map - for (int i = 1; i < argc; ++i) { - std::string cur_arg = argv[i]; - auto it = arg_map.find(cur_arg); - if (it != arg_map.end()) { - it->second(i); // Call the associated lambda function - } else if (i == argc - 1 && !has_game_argument) { - // Assume the last argument is the game file if not specified via -g/--game - game_path = argv[i]; - has_game_argument = true; - } else if (std::string(argv[i]) == "--") { - if (i + 1 == argc) { - std::cerr << "Warning: -- is set, but no game arguments are added!\n"; - break; - } - for (int j = i + 1; j < argc; j++) { - game_args.push_back(argv[j]); - } - break; - } else if (i + 1 < argc && std::string(argv[i + 1]) == "--") { - if (!has_game_argument) { - game_path = argv[i]; - has_game_argument = true; - } + try { + app.parse(argc, argv); + } catch (const CLI::ParseError& e) { + return app.exit(e); + } + + // ---- Utility commands ---- + if (addGameFolder) { + Config::addGameInstallDir(*addGameFolder); + Config::save(user_dir / "config.toml"); + std::cout << "Game folder successfully saved.\n"; + return 0; + } + + if (setAddonFolder) { + Config::setAddonInstallDir(*setAddonFolder); + Config::save(user_dir / "config.toml"); + std::cout << "Addon folder successfully saved.\n"; + return 0; + } + + if (!gamePath.has_value()) { + if (!gameArgs.empty()) { + gamePath = gameArgs.front(); + gameArgs.erase(gameArgs.begin()); } else { - std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; - } - } - - // If no game directory is set and no command line argument, prompt for it - if (Config::getGameInstallDirs().empty()) { - std::cerr << "Warning: No game folder set, please set it by calling shadps4" - " with the --add-game-folder argument\n"; - } - - if (!has_game_argument) { - std::cerr << "Error: Please provide a game path or ID.\n"; - exit(1); - } - - // Check if the game path or ID exists - std::filesystem::path eboot_path(game_path); - - // Check if the provided path is a valid file - if (!std::filesystem::exists(eboot_path)) { - // If not a file, treat it as a game ID and search in install directories recursively - bool game_found = false; - const int max_depth = 5; - for (const auto& install_dir : Config::getGameInstallDirs()) { - if (auto found_path = Common::FS::FindGameByID(install_dir, game_path, max_depth)) { - eboot_path = *found_path; - game_found = true; - break; - } - } - if (!game_found) { - std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl; + std::cerr << "Error: Please provide a game path or ID.\n"; return 1; } } - if (waitPid.has_value()) { - Core::Debugger::WaitForPid(waitPid.value()); + // ---- Apply flags ---- + if (patchFile) + MemoryPatcher::patch_file = *patchFile; + + if (ignoreGamePatch) + Core::FileSys::MntPoints::ignore_game_patches = true; + + if (fullscreenStr) { + if (*fullscreenStr == "true") { + Config::setIsFullscreen(true); + } else if (*fullscreenStr == "false") { + Config::setIsFullscreen(false); + } else { + std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n"; + return 1; + } } - // Run the emulator with the resolved eboot path - Core::Emulator* emulator = Common::Singleton::Instance(); + if (showFps) + Config::setShowFpsCounter(true); + + if (configClean) + Config::setConfigMode(Config::ConfigMode::Clean); + + if (configGlobal) + Config::setConfigMode(Config::ConfigMode::Global); + + if (logAppend) + Common::Log::SetAppend(); + + // ---- Resolve game path or ID ---- + std::filesystem::path ebootPath(*gamePath); + if (!std::filesystem::exists(ebootPath)) { + bool found = false; + constexpr int maxDepth = 5; + for (const auto& installDir : Config::getGameInstallDirs()) { + if (auto foundPath = Common::FS::FindGameByID(installDir, *gamePath, maxDepth)) { + ebootPath = *foundPath; + found = true; + break; + } + } + if (!found) { + std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n"; + return 1; + } + } + + if (waitPid) + Core::Debugger::WaitForPid(*waitPid); + + auto* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0]; emulator->waitForDebuggerBeforeRun = waitForDebugger; - emulator->Run(eboot_path, game_args, game_folder); + emulator->Run(ebootPath, gameArgs, overrideRoot); return 0; } diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 476a56b52..c9183f301 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "SDL3/SDL_events.h" @@ -471,7 +471,7 @@ void WindowSDL::WaitEvent() { } void WindowSDL::InitTimers() { - SDL_AddTimer(100, &PollController, controller); + SDL_AddTimer(33, &PollController, controller); SDL_AddTimer(33, Input::MousePolling, (void*)controller); } @@ -540,7 +540,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { // as it would break the entire touchpad handling // You can still bind other things to it though if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) { - controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down); + controller->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down); return; } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index cc6d19075..261155ab5 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -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,7 +561,11 @@ void EmitContext::DefineVertexBlock() { const std::array 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 bool needs_clip_distance_emulation = l_stage == LogicalStage::Vertex && + stage == Stage::Vertex && + profile.needs_clip_distance_emulation; + const auto has_clip_distance_outputs = info.stores.GetAny(IR::Attribute::ClipDistance); + if (has_clip_distance_outputs && !needs_clip_distance_emulation) { const Id type{TypeArray(F32[1], ConstU32(8U))}; const Id initializer{ConstantComposite(type, zero)}; clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance, spv::StorageClass::Output, @@ -583,16 +602,29 @@ void EmitContext::DefineOutputs() { Name(output_attr_array, "out_attrs"); } } else { + const bool needs_clip_distance_emulation = + stage == Stage::Vertex && profile.needs_clip_distance_emulation && + 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 + (needs_clip_distance_emulation ? 1 : 0))}; Name(id, fmt::format("out_attr{}", i)); output_params[i] = GetAttributeInfo(AmdGpu::NumberFormat::Float, id, num_components, true); + ++num_attrs; + } + + if (needs_clip_distance_emulation) { + 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; diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index b2e981d6a..8931e8dde 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -583,6 +583,14 @@ void Translator::S_MULK_I32(const GcnInst& inst) { // SOP1 void Translator::S_MOV(const GcnInst& inst) { + if (inst.dst[0].field == OperandField::ScalarGPR) { + if (inst.src[0].field == OperandField::ExecLo) { + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), ir.GetExec()); + return; + } else if (inst.src[0].field == OperandField::ExecHi) { + return; + } + } SetDst(inst.dst[0], GetSrc(inst.src[0])); } diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index f999a3e3e..08b0192f5 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -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); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 0803647a2..08a0f6527 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -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(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(inst.src[0])}; + const IR::F64 src1{GetSrc64(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])}; diff --git a/src/shader_recompiler/ir/attribute.cpp b/src/shader_recompiler/ir/attribute.cpp index 382f9b1d9..e74b62817 100644 --- a/src/shader_recompiler/ir/attribute.cpp +++ b/src/shader_recompiler/ir/attribute.cpp @@ -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: @@ -153,9 +153,9 @@ std::string NameOf(Attribute attribute) { case Attribute::TessellationEvaluationPointV: return "TessellationEvaluationPointV"; case Attribute::PackedHullInvocationInfo: - return "OffChipLdsBase"; - case Attribute::OffChipLdsBase: return "PackedHullInvocationInfo"; + case Attribute::OffChipLdsBase: + return "OffChipLdsBase"; case Attribute::TessFactorsBufferBase: return "TessFactorsBufferBase"; case Attribute::PointSize: diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index 48b496727..d975c47ea 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "common/assert.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/attribute.h" @@ -189,7 +190,7 @@ std::optional FindTessConstantSharp(IR::Inst* read_const_buff // Walker that helps deduce what type of attribute a DS instruction is reading // or writing, which could be an input control point, output control point, // or per-patch constant (PatchConst). -// For certain ReadConstBuffer instructions using the tess constants V#,, we visit the users +// For certain ReadConstBuffer instructions using the tess constants V#, we visit the users // recursively and increment a counter on the Load/WriteShared users. // Namely NumPatch (from m_hsNumPatch), HsOutputBase (m_hsOutputBase), // and PatchConstBase (m_patchConstBase). @@ -200,9 +201,11 @@ std::optional FindTessConstantSharp(IR::Inst* read_const_buff // // TODO: this will break if AMD compiler used distributive property like // TcsNumPatches * (ls_stride * #input_cp_in_patch + hs_cp_stride * #output_cp_in_patch) +// +// Assert if the region is ambiguous due to phi nodes in the addr calculation for a DS instruction, class TessConstantUseWalker { public: - void MarkTessAttributeUsers(IR::Inst* read_const_buffer, TessConstantAttribute attr) { + void WalkUsersOfTessConstant(IR::Inst* read_const_buffer, TessConstantAttribute attr) { u32 inc; switch (attr) { case TessConstantAttribute::HsNumPatch: @@ -217,14 +220,19 @@ public: } for (IR::Use use : read_const_buffer->Uses()) { - MarkTessAttributeUsersHelper(use, inc); + WalkUsersOfTessConstantHelper(use, inc, false); } ++seq_num; } private: - void MarkTessAttributeUsersHelper(IR::Use use, u32 inc) { + struct PhiInfo { + u32 seq_num; + u32 unique_edge; + }; + + void WalkUsersOfTessConstantHelper(IR::Use use, u32 inc, bool propagateError) { IR::Inst* inst = use.user; switch (use.user->GetOpcode()) { @@ -232,38 +240,37 @@ private: case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: { - u32 counter = inst->Flags(); - inst->SetFlags(counter + inc); - // Stop here - return; + bool is_addr_operand = use.operand == 0; + if (is_addr_operand) { + u32 counter = inst->Flags(); + inst->SetFlags(counter + inc); + ASSERT_MSG(!propagateError, "LDS instruction {} accesses ambiguous attribute type", + fmt::ptr(use.user)); + // Stop here + return; + } } case IR::Opcode::Phi: { - struct PhiCounter { - u16 seq_num; - u16 unique_edge; - }; - - PhiCounter count = inst->Flags(); - ASSERT_MSG(count.seq_num == 0 || count.unique_edge == use.operand); + auto it = phi_infos.find(use.user); // the point of seq_num is to tell us if we've already traversed this - // phi on the current walk. Alternatively we could keep a set of phi's - // seen on the current walk. This is to handle phi cycles - if (count.seq_num == 0) { + // phi on the current walk to handle phi cycles + if (it == phi_infos.end()) { // First time we've encountered this phi - count.seq_num = seq_num; // Mark the phi as having been traversed originally through this edge - count.unique_edge = use.operand; - } else if (count.seq_num < seq_num) { - count.seq_num = seq_num; + phi_infos[inst] = {.seq_num = seq_num, + .unique_edge = static_cast(use.operand)}; + } else if (it->second.seq_num < seq_num) { + it->second.seq_num = seq_num; // For now, assume we are visiting this phi via the same edge // as on other walks. If not, some dataflow analysis might be necessary - ASSERT(count.unique_edge == use.operand); + if (it->second.unique_edge != use.operand) { + propagateError = true; + } } else { - // count.seq_num == seq_num + ASSERT(it->second.seq_num == seq_num); // there's a cycle, and we've already been here on this walk return; } - inst->SetFlags(count); break; } default: @@ -271,10 +278,11 @@ private: } for (IR::Use use : inst->Uses()) { - MarkTessAttributeUsersHelper(use, inc); + WalkUsersOfTessConstantHelper(use, inc, propagateError); } } + std::unordered_map phi_infos; u32 seq_num{1u}; }; @@ -690,7 +698,7 @@ void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info) { case TessConstantAttribute::HsNumPatch: case TessConstantAttribute::HsOutputBase: case TessConstantAttribute::PatchConstBase: - walker.MarkTessAttributeUsers(&inst, tess_const_attr); + walker.WalkUsersOfTessConstant(&inst, tess_const_attr); // We should be able to safely set these to 0 so that indexing happens only // within the local patch in the recompiled Vulkan shader. This assumes // these values only contribute to address calculations for in/out diff --git a/src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp b/src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp new file mode 100644 index 000000000..cf93142a1 --- /dev/null +++ b/src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp @@ -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 diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index 5bf362284..f103b6736 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -8,7 +8,8 @@ namespace Shader { struct Profile; -} +void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info); +} // namespace Shader namespace Shader::Optimization { diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 53b161149..93129ac0e 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -660,6 +660,7 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info, inst.SetArg(1, ir.Imm32(binding)); } else { // Convert shared memory opcode to storage buffer atomic to GDS buffer. + auto& buffer = info.buffers[binding]; const IR::U32 offset = IR::U32{inst.Arg(0)}; const IR::U32 address_words = ir.ShiftRightLogical(offset, ir.Imm32(1)); const IR::U32 address_dwords = ir.ShiftRightLogical(offset, ir.Imm32(2)); @@ -705,27 +706,35 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info, case IR::Opcode::SharedAtomicXor32: inst.ReplaceUsesWith(ir.BufferAtomicXor(handle, address_dwords, inst.Arg(1), {})); break; - case IR::Opcode::LoadSharedU16: + case IR::Opcode::LoadSharedU16: { inst.ReplaceUsesWith(ir.LoadBufferU16(handle, address_words, {})); + buffer.used_types |= IR::Type::U16; break; + } case IR::Opcode::LoadSharedU32: inst.ReplaceUsesWith(ir.LoadBufferU32(1, handle, address_dwords, {})); break; - case IR::Opcode::LoadSharedU64: + case IR::Opcode::LoadSharedU64: { inst.ReplaceUsesWith(ir.LoadBufferU64(handle, address_qwords, {})); + buffer.used_types |= IR::Type::U64; break; - case IR::Opcode::WriteSharedU16: + } + case IR::Opcode::WriteSharedU16: { ir.StoreBufferU16(handle, address_words, IR::U16{inst.Arg(1)}, {}); inst.Invalidate(); + buffer.used_types |= IR::Type::U16; break; + } case IR::Opcode::WriteSharedU32: ir.StoreBufferU32(1, handle, address_dwords, inst.Arg(1), {}); inst.Invalidate(); break; - case IR::Opcode::WriteSharedU64: + case IR::Opcode::WriteSharedU64: { ir.StoreBufferU64(handle, address_qwords, IR::U64{inst.Arg(1)}, {}); inst.Invalidate(); + buffer.used_types |= IR::Type::U64; break; + } default: UNREACHABLE(); } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 52e37bbf0..038a80733 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -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 diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 4764ddbec..f4fa45afc 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -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& 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); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 8620ab970..04e176765 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -34,6 +34,7 @@ enum class LogicalStage : u32 { }; constexpr u32 MaxStageTypes = static_cast(LogicalStage::NumLogicalStages); +constexpr auto MaxEmulatedClipDistances = 4u; constexpr Stage StageFromIndex(size_t index) noexcept { return static_cast(index); @@ -201,14 +202,16 @@ struct FragmentRuntimeInfo { std::array inputs; std::array 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); } diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 3f307c51b..b2a4d7a61 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -830,7 +830,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); auto& queue = asc_queues[{vqid}]; + struct IndirectPatch { + const PM4Header* header; + VAddr indirect_addr; + }; + boost::container::small_vector indirect_patches; + auto base_addr = reinterpret_cast(acb.data()); + size_t acb_size = acb.size_bytes(); while (!acb.empty()) { ProcessCommands(); @@ -919,8 +926,18 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { - rasterizer->CopyBuffer(dma_data->DstAddress(), dma_data->SrcAddress(), - dma_data->NumBytes(), false, false); + const u32 num_bytes = dma_data->NumBytes(); + const VAddr src_addr = dma_data->SrcAddress(); + const VAddr dst_addr = dma_data->DstAddress(); + const PM4Header* header = + reinterpret_cast(dst_addr - sizeof(PM4Header)); + if (dst_addr >= base_addr && dst_addr < base_addr + acb_size && + num_bytes == sizeof(PM4CmdDispatchIndirect::GroupDimensions) && + header->type == 3 && header->type3.opcode == PM4ItOpcode::DispatchDirect) { + indirect_patches.emplace_back(header, src_addr); + } else { + rasterizer->CopyBuffer(dst_addr, src_addr, num_bytes, false, false); + } } else { UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); @@ -964,6 +981,12 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } case PM4ItOpcode::DispatchDirect: { const auto* dispatch_direct = reinterpret_cast(header); + if (auto it = std::ranges::find(indirect_patches, header, &IndirectPatch::header); + it != indirect_patches.end()) { + const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions); + rasterizer->DispatchIndirect(it->indirect_addr, 0, size); + break; + } auto& cs_program = GetCsRegs(); cs_program.dim_x = dispatch_direct->dim_x; cs_program.dim_y = dispatch_direct->dim_y; @@ -1034,9 +1057,13 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } case PM4ItOpcode::ReleaseMem: { const auto* release_mem = reinterpret_cast(header); - release_mem->SignalFence([pipe_id = queue.pipe_id] { - Platform::IrqC::Instance()->Signal(static_cast(pipe_id)); - }); + release_mem->SignalFence( + [pipe_id = queue.pipe_id] { + Platform::IrqC::Instance()->Signal(static_cast(pipe_id)); + }, + [this](VAddr dst, u16 gds_index, u16 num_dwords) { + rasterizer->CopyBuffer(dst, gds_index, num_dwords * sizeof(u32), false, true); + }); break; } case PM4ItOpcode::EventWrite: { diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index eb48f3568..17511d0a2 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -50,7 +50,7 @@ union PM4Type3Header { } u32 NumWords() const { - return count + 1; + return (count + 1) & 0x3fff; } u32 raw; @@ -327,6 +327,7 @@ enum class DataSelect : u32 { Data64 = 2, GpuClock64 = 3, PerfCounter = 4, + GdsMemStore = 5, }; enum class InterruptSelect : u32 { @@ -920,8 +921,9 @@ struct PM4CmdReleaseMem { u32 data_hi; template - T* Address() const { - return reinterpret_cast(address_lo | u64(address_hi) << 32); + T Address() const { + u64 full_address = address_lo | (u64(address_hi) << 32); + return std::bit_cast(full_address); } u32 DataDWord() const { @@ -932,22 +934,26 @@ struct PM4CmdReleaseMem { return data_lo | u64(data_hi) << 32; } - void SignalFence(auto&& signal_irq) const { + void SignalFence(auto&& signal_irq, auto&& gds_to_mem) const { switch (data_sel.Value()) { case DataSelect::Data32Low: { - *Address() = DataDWord(); + *Address() = DataDWord(); break; } case DataSelect::Data64: { - *Address() = DataQWord(); + *Address() = DataQWord(); break; } case DataSelect::GpuClock64: { - *Address() = GetGpuClock64(); + *Address() = GetGpuClock64(); break; } case DataSelect::PerfCounter: { - *Address() = GetGpuPerfCounter(); + *Address() = GetGpuPerfCounter(); + break; + } + case DataSelect::GdsMemStore: { + gds_to_mem(Address(), gds_index, num_dw); break; } default: { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index a0ea58817..1b0af1d17 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -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(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(LogicalStage::Fragment)]; key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 214d6d697..737c9feed 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1015,6 +1015,10 @@ bool Rasterizer::IsMapped(VAddr addr, u64 size) { // There is no memory, so not mapped. return false; } + if (static_cast(addr) > std::numeric_limits::max() - size) { + // Memory range wrapped the address space, cannot be mapped. + return false; + } const auto range = decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); Common::RecursiveSharedLock lock{mapped_ranges_mutex};