diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1fab6354..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 }} @@ -160,7 +169,7 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -216,7 +225,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev - name: Cache CMake Configuration uses: actions/cache@v4 diff --git a/.gitmodules b/.gitmodules index c5d05edd3..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 @@ -120,3 +116,13 @@ [submodule "externals/miniz"] path = externals/miniz url = https://github.com/richgel999/miniz +[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 04534ec26..2afe3a49e 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,8 +202,8 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "12") -set(EMULATOR_VERSION_PATCH "6") +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}") @@ -262,6 +262,8 @@ include_directories(src) set(AJM_LIB src/core/libraries/ajm/ajm.cpp src/core/libraries/ajm/ajm.h + src/core/libraries/ajm/ajm_aac.cpp + src/core/libraries/ajm/ajm_aac.h src/core/libraries/ajm/ajm_at9.cpp src/core/libraries/ajm/ajm_at9.h src/core/libraries/ajm/ajm_batch.cpp @@ -422,6 +424,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/rtc/rtc.cpp src/core/libraries/rtc/rtc.h src/core/libraries/rtc/rtc_error.h + src/core/libraries/rudp/rudp.cpp + src/core/libraries/rudp/rudp.h src/core/libraries/disc_map/disc_map.cpp src/core/libraries/disc_map/disc_map.h src/core/libraries/disc_map/disc_map_codes.h @@ -520,6 +524,9 @@ set(SYSTEM_GESTURE_LIB set(PNG_LIB src/core/libraries/libpng/pngdec.cpp src/core/libraries/libpng/pngdec.h src/core/libraries/libpng/pngdec_error.h + src/core/libraries/libpng/pngenc.cpp + src/core/libraries/libpng/pngenc.h + src/core/libraries/libpng/pngenc_error.h ) set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h @@ -577,14 +584,20 @@ 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_api2.cpp + src/core/libraries/np/np_web_api2.h src/core/libraries/np/np_party.cpp src/core/libraries/np/np_party.h src/core/libraries/np/np_auth.cpp @@ -593,6 +606,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_profile_dialog.h src/core/libraries/np/np_sns_facebook_dialog.cpp src/core/libraries/np/np_sns_facebook_dialog.h + src/core/libraries/np/np_partner.cpp + src/core/libraries/np/np_partner.h ) set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp @@ -738,6 +753,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) @@ -781,6 +798,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 @@ -836,6 +855,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/thread.h src/core/tls.cpp src/core/tls.h + src/core/emulator_state.cpp + src/core/emulator_state.h ) if (ARCHITECTURE STREQUAL "x86_64") @@ -904,6 +925,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 @@ -1085,7 +1107,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) +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 dbb890f87..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). @@ -148,9 +153,9 @@ The following firmware modules are supported and must be placed in shadPS4's `sy | Modules | Modules | Modules | Modules | |-------------------------|-------------------------|-------------------------|-------------------------| | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | -| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx | -| libSceUlt.sprx | | | | - +| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx | +| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.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 c85fcf003..210ca1c5e 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -11,6 +11,7 @@ GPL-2.0 net.shadps4.shadPS4.desktop https://shadps4.net/ + https://github.com/shadps4-emu/shadPS4

shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.

The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.

@@ -37,6 +38,12 @@ 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 + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5 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 eb3723f2c..db03e7679 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() @@ -258,9 +259,19 @@ if (WIN32) add_subdirectory(ext-wepoll) endif() +if (NOT TARGET fdk-aac) +add_subdirectory(aacdec) +endif() + #nlohmann json set(JSON_BuildTests OFF CACHE INTERNAL "") add_subdirectory(json) # miniz add_subdirectory(miniz) + +# cli11 +set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + +add_subdirectory(CLI11) \ No newline at end of file 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/externals/aacdec/CMakeLists.txt b/externals/aacdec/CMakeLists.txt new file mode 100644 index 000000000..2adfa032b --- /dev/null +++ b/externals/aacdec/CMakeLists.txt @@ -0,0 +1,154 @@ +# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +set(AACDEC_SRC + fdk-aac/libAACdec/src/FDK_delay.cpp + fdk-aac/libAACdec/src/aac_ram.cpp + fdk-aac/libAACdec/src/aac_rom.cpp + fdk-aac/libAACdec/src/aacdec_drc.cpp + fdk-aac/libAACdec/src/aacdec_hcr.cpp + fdk-aac/libAACdec/src/aacdec_hcr_bit.cpp + fdk-aac/libAACdec/src/aacdec_hcrs.cpp + fdk-aac/libAACdec/src/aacdec_pns.cpp + fdk-aac/libAACdec/src/aacdec_tns.cpp + fdk-aac/libAACdec/src/aacdecoder.cpp + fdk-aac/libAACdec/src/aacdecoder_lib.cpp + fdk-aac/libAACdec/src/block.cpp + fdk-aac/libAACdec/src/channel.cpp + fdk-aac/libAACdec/src/channelinfo.cpp + fdk-aac/libAACdec/src/conceal.cpp + fdk-aac/libAACdec/src/ldfiltbank.cpp + fdk-aac/libAACdec/src/pulsedata.cpp + fdk-aac/libAACdec/src/rvlc.cpp + fdk-aac/libAACdec/src/rvlcbit.cpp + fdk-aac/libAACdec/src/rvlcconceal.cpp + fdk-aac/libAACdec/src/stereo.cpp + fdk-aac/libAACdec/src/usacdec_ace_d4t64.cpp + fdk-aac/libAACdec/src/usacdec_ace_ltp.cpp + fdk-aac/libAACdec/src/usacdec_acelp.cpp + fdk-aac/libAACdec/src/usacdec_fac.cpp + fdk-aac/libAACdec/src/usacdec_lpc.cpp + fdk-aac/libAACdec/src/usacdec_lpd.cpp + fdk-aac/libAACdec/src/usacdec_rom.cpp +) + +set(FDK_SRC + fdk-aac/libFDK/src/FDK_bitbuffer.cpp + fdk-aac/libFDK/src/FDK_core.cpp + fdk-aac/libFDK/src/FDK_crc.cpp + fdk-aac/libFDK/src/FDK_decorrelate.cpp + fdk-aac/libFDK/src/FDK_hybrid.cpp + fdk-aac/libFDK/src/FDK_lpc.cpp + fdk-aac/libFDK/src/FDK_matrixCalloc.cpp + fdk-aac/libFDK/src/FDK_qmf_domain.cpp + fdk-aac/libFDK/src/FDK_tools_rom.cpp + fdk-aac/libFDK/src/FDK_trigFcts.cpp + fdk-aac/libFDK/src/autocorr2nd.cpp + fdk-aac/libFDK/src/dct.cpp + fdk-aac/libFDK/src/fft.cpp + fdk-aac/libFDK/src/fft_rad2.cpp + fdk-aac/libFDK/src/fixpoint_math.cpp + fdk-aac/libFDK/src/huff_nodes.cpp + fdk-aac/libFDK/src/mdct.cpp + fdk-aac/libFDK/src/nlc_dec.cpp + fdk-aac/libFDK/src/qmf.cpp + fdk-aac/libFDK/src/scale.cpp +) + +set(SYS_SRC + fdk-aac/libSYS/src/genericStds.cpp + fdk-aac/libSYS/src/syslib_channelMapDescr.cpp +) + +set(ARITHCODING_SRC + fdk-aac/libArithCoding/src/ac_arith_coder.cpp +) + +set(MPEGTPDEC_SRC + fdk-aac/libMpegTPDec/src/tpdec_adif.cpp + fdk-aac/libMpegTPDec/src/tpdec_adts.cpp + fdk-aac/libMpegTPDec/src/tpdec_asc.cpp + fdk-aac/libMpegTPDec/src/tpdec_drm.cpp + fdk-aac/libMpegTPDec/src/tpdec_latm.cpp + fdk-aac/libMpegTPDec/src/tpdec_lib.cpp +) + +set(SBRDEC_SRC + fdk-aac/libSBRdec/src/HFgen_preFlat.cpp + fdk-aac/libSBRdec/src/env_calc.cpp + fdk-aac/libSBRdec/src/env_dec.cpp + fdk-aac/libSBRdec/src/env_extr.cpp + fdk-aac/libSBRdec/src/hbe.cpp + fdk-aac/libSBRdec/src/huff_dec.cpp + fdk-aac/libSBRdec/src/lpp_tran.cpp + fdk-aac/libSBRdec/src/psbitdec.cpp + fdk-aac/libSBRdec/src/psdec.cpp + fdk-aac/libSBRdec/src/psdec_drm.cpp + fdk-aac/libSBRdec/src/psdecrom_drm.cpp + fdk-aac/libSBRdec/src/pvc_dec.cpp + fdk-aac/libSBRdec/src/sbr_deb.cpp + fdk-aac/libSBRdec/src/sbr_dec.cpp + fdk-aac/libSBRdec/src/sbr_ram.cpp + fdk-aac/libSBRdec/src/sbr_rom.cpp + fdk-aac/libSBRdec/src/sbrdec_drc.cpp + fdk-aac/libSBRdec/src/sbrdec_freq_sca.cpp + fdk-aac/libSBRdec/src/sbrdecoder.cpp +) + +set(PCMUTILS_SRC + fdk-aac/libPCMutils/src/limiter.cpp + fdk-aac/libPCMutils/src/pcm_utils.cpp + fdk-aac/libPCMutils/src/pcmdmx_lib.cpp +) + +set(DRCDEC_SRC + fdk-aac/libDRCdec/src/FDK_drcDecLib.cpp + fdk-aac/libDRCdec/src/drcDec_gainDecoder.cpp + fdk-aac/libDRCdec/src/drcDec_reader.cpp + fdk-aac/libDRCdec/src/drcDec_rom.cpp + fdk-aac/libDRCdec/src/drcDec_selectionProcess.cpp + fdk-aac/libDRCdec/src/drcDec_tools.cpp + fdk-aac/libDRCdec/src/drcGainDec_init.cpp + fdk-aac/libDRCdec/src/drcGainDec_preprocess.cpp + fdk-aac/libDRCdec/src/drcGainDec_process.cpp +) + +set(SACDEC_SRC + fdk-aac/libSACdec/src/sac_bitdec.cpp + fdk-aac/libSACdec/src/sac_calcM1andM2.cpp + fdk-aac/libSACdec/src/sac_dec.cpp + fdk-aac/libSACdec/src/sac_dec_conceal.cpp + fdk-aac/libSACdec/src/sac_dec_lib.cpp + fdk-aac/libSACdec/src/sac_process.cpp + fdk-aac/libSACdec/src/sac_qmf.cpp + fdk-aac/libSACdec/src/sac_reshapeBBEnv.cpp + fdk-aac/libSACdec/src/sac_rom.cpp + fdk-aac/libSACdec/src/sac_smoothing.cpp + fdk-aac/libSACdec/src/sac_stp.cpp + fdk-aac/libSACdec/src/sac_tsd.cpp +) + +add_library(fdk-aac + ${AACDEC_SRC} + ${FDK_SRC} + ${SYS_SRC} + ${ARITHCODING_SRC} + ${MPEGTPDEC_SRC} + ${SBRDEC_SRC} + ${PCMUTILS_SRC} + ${DRCDEC_SRC} + ${SACDEC_SRC} +) + +target_include_directories(fdk-aac + PUBLIC + fdk-aac/libAACdec/include + fdk-aac/libFDK/include + fdk-aac/libSYS/include + fdk-aac/libArithCoding/include + fdk-aac/libMpegTPDec/include + fdk-aac/libSBRdec/include + fdk-aac/libPCMutils/include + fdk-aac/libDRCdec/include + fdk-aac/libSACdec/include +) diff --git a/externals/aacdec/fdk-aac b/externals/aacdec/fdk-aac new file mode 160000 index 000000000..ee76460ef --- /dev/null +++ b/externals/aacdec/fdk-aac @@ -0,0 +1 @@ +Subproject commit ee76460efbdb147e26d804c798949c23f174460b diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core index b0de1dcca..94dde08c8 160000 --- a/externals/ffmpeg-core +++ b/externals/ffmpeg-core @@ -1 +1 @@ -Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff +Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a diff --git a/externals/fmt b/externals/fmt index 64db979e3..ec73fb724 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739 +Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051 diff --git a/externals/sdl3 b/externals/sdl3 index e9c2e9bfc..bdb72bb3f 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7 +Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc diff --git a/src/common/config.cpp b/src/common/config.cpp index 94d8b488c..eac463d0a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -198,7 +198,7 @@ static ConfigEntry pipelineCacheArchive(false); static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); static ConfigEntry isSeparateLogFilesEnabled(false); -static ConfigEntry isFpsColor(true); +static ConfigEntry showFpsCounter(false); static ConfigEntry logEnabled(true); // GUI @@ -222,16 +222,6 @@ static string config_version = Common::g_scm_rev; // These entries aren't stored in the config static bool overrideControllerColor = false; static int controllerCustomColorRGB[3] = {0, 0, 255}; -static bool isGameRunning = false; -static bool load_auto_patches = true; - -bool getGameRunning() { - return isGameRunning; -} - -void setGameRunning(bool running) { - isGameRunning = running; -} std::filesystem::path getSysModulesPath() { if (sys_modules_path.empty()) { @@ -462,8 +452,12 @@ bool isPipelineCacheArchived() { return pipelineCacheArchive.get(); } -bool fpsColor() { - return isFpsColor.get(); +bool getShowFpsCounter() { + return showFpsCounter.get(); +} + +void setShowFpsCounter(bool enable, bool is_game_specific) { + showFpsCounter.set(enable, is_game_specific); } bool isLoggingEnabled() { @@ -846,13 +840,6 @@ void setUsbDeviceBackend(int value, bool is_game_specific) { usbDeviceBackend.set(value, is_game_specific); } -bool getLoadAutoPatches() { - return load_auto_patches; -} -void setLoadAutoPatches(bool enable) { - load_auto_patches = enable; -} - void load(const std::filesystem::path& path, bool is_game_specific) { // If the configuration file does not exist, create it and return, unless it is game specific std::error_code error; @@ -968,7 +955,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific); isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); - isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); + showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific); logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); } @@ -1187,7 +1174,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value; data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value; data["GPU"]["patchShaders"] = shouldPatchShaders.base_value; - data["Debug"]["FPSColor"] = isFpsColor.base_value; + data["Debug"]["showFpsCounter"] = showFpsCounter.base_value; } // Sorting of TOML sections @@ -1295,7 +1282,7 @@ void setDefaultValues(bool is_game_specific) { internalScreenHeight.base_value = 720; // Debug - isFpsColor.base_value = true; + showFpsCounter.base_value = false; } } diff --git a/src/common/config.h b/src/common/config.h index 481ef6444..2a95e6cf0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -125,7 +125,8 @@ int getSpecialPadClass(); bool getPSNSignedIn(); void setPSNSignedIn(bool sign, bool is_game_specific = false); bool patchShaders(); // no set -bool fpsColor(); // no set +bool getShowFpsCounter(); +void setShowFpsCounter(bool enable, bool is_game_specific = false); bool isNeoModeConsole(); void setNeoMode(bool enable, bool is_game_specific = false); bool isDevKitConsole(); @@ -152,8 +153,6 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); std::filesystem::path getSysModulesPath(); void setSysModulesPath(const std::filesystem::path& path); -bool getLoadAutoPatches(); -void setLoadAutoPatches(bool enable); enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad }; int getUsbDeviceBackend(); 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 4a85c4cde..d7c816da3 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -97,6 +98,7 @@ private: std::size_t bytes_written = 0; }; +#ifdef _WIN32 /** * Backend that writes to Visual Studio's output window */ @@ -107,15 +109,14 @@ public: ~DebuggerBackend() = default; void Write(const Entry& entry) { -#ifdef _WIN32 ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); -#endif } void Flush() {} void EnableForStacktrace() {} }; +#endif bool initialization_in_progress_suppress_logging = true; @@ -219,6 +220,7 @@ public: .line_num = line_num, .function = function, .message = std::move(message), + .thread = Common::GetCurrentThreadName(), }; if (Config::getLogType() == "async") { message_queue.EmplaceWait(entry); @@ -266,7 +268,9 @@ private: } void ForEachBackend(auto lambda) { - // lambda(debugger_backend); +#ifdef _WIN32 + lambda(debugger_backend); +#endif lambda(color_console_backend); lambda(file_backend); } @@ -279,7 +283,9 @@ private: static inline bool should_append{false}; Filter filter; +#ifdef _WIN32 DebuggerBackend debugger_backend{}; +#endif ColorConsoleBackend color_console_backend{}; FileBackend file_backend; diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index fd8386aff..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,15 +107,20 @@ 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) \ SUB(Lib, Rtc) \ + SUB(Lib, Rudp) \ SUB(Lib, DiscMap) \ SUB(Lib, Png) \ SUB(Lib, Jpeg) \ @@ -157,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 cd4ae9355..6c529f878 100644 --- a/src/common/logging/log_entry.h +++ b/src/common/logging/log_entry.h @@ -21,6 +21,7 @@ struct Entry { u32 line_num = 0; std::string function; std::string message; + std::string thread; }; } // namespace Common::Log diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index b4fa204bc..e8c5f4979 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -23,8 +24,8 @@ std::string FormatLogMessage(const Entry& entry) { const char* class_name = GetLogClassName(entry.log_class); const char* level_name = GetLevelName(entry.log_level); - return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename, - entry.line_num, entry.function, entry.message); + return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name, entry.thread, + entry.filename, entry.line_num, entry.function, entry.message); } void PrintMessage(const Entry& entry) { diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 82db477ed..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,15 +74,19 @@ 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 Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation Lib_Screenshot, ///< The LibSceScreenshot implementation Lib_LibCInternal, ///< The LibCInternal implementation. Lib_AppContent, ///< The LibSceAppContent implementation. Lib_Rtc, ///< The LibSceRtc implementation. + Lib_Rudp, ///< The LibSceRudp implementation. Lib_DiscMap, ///< The LibSceDiscMap implementation. Lib_Png, ///< The LibScePng implementation. Lib_Jpeg, ///< The LibSceJpeg implementation. @@ -107,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. @@ -125,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/memory_patcher.cpp b/src/common/memory_patcher.cpp index 045a530cb..a7c020246 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.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 @@ -12,6 +12,7 @@ #include "common/elf_info.h" #include "common/logging/log.h" #include "common/path_util.h" +#include "core/emulator_state.h" #include "core/file_format/psf.h" #include "memory_patcher.h" @@ -51,14 +52,14 @@ std::string convertValueToHex(const std::string type, const std::string valueStr uint32_t i; } floatUnion; floatUnion.f = std::stof(valueStr); - result = toHex(floatUnion.i, sizeof(floatUnion.i)); + result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i)); } else if (type == "float64") { union { double d; uint64_t i; } doubleUnion; doubleUnion.d = std::stod(valueStr); - result = toHex(doubleUnion.i, sizeof(doubleUnion.i)); + result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i)); } else if (type == "utf8") { std::vector byteArray = std::vector(valueStr.begin(), valueStr.end()); @@ -192,7 +193,7 @@ void OnGameLoaded() { } else { ApplyPatchesFromXML(file_path); } - } else if (Config::getLoadAutoPatches()) { + } else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) { for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) { if (!repo.is_directory()) { continue; 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 982041ebb..e56953fb6 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -1,11 +1,14 @@ // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include +#include "core/libraries/kernel/threads/pthread.h" + #include "common/error.h" #include "common/logging/log.h" #include "common/thread.h" @@ -171,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()); } @@ -183,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__) @@ -209,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 } @@ -237,4 +249,22 @@ void AccurateTimer::End() { target_interval - std::chrono::duration_cast(now - start_time); } +std::string GetCurrentThreadName() { + using namespace Libraries::Kernel; + if (g_curthread && !g_curthread->name.empty()) { + return g_curthread->name; + } +#ifdef _WIN32 + PWSTR name; + GetThreadDescription(GetCurrentThread(), &name); + return Common::UTF16ToUTF8(name); +#else + char name[256]; + if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) { + return ""; + } + return std::string{name}; +#endif +} + } // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index 5bd83d35c..a300d10c3 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -46,4 +47,6 @@ public: } }; +std::string GetCurrentThreadName(); + } // namespace Common 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/cpu_patches.cpp b/src/core/cpu_patches.cpp index 8c0897a48..e303417c3 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,30 @@ static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* op #endif } +static bool FilterStackCheck(const ZydisDecodedOperand* operands) { + const auto& dst_op = operands[0]; + const auto& src_op = operands[1]; + + // Some compilers emit stack checks by starting a function with + // 'mov (64-bit register), fs:[0x28]', then checking with `xor (64-bit register), fs:[0x28]` + return src_op.type == ZYDIS_OPERAND_TYPE_MEMORY && src_op.mem.segment == ZYDIS_REGISTER_FS && + src_op.mem.base == ZYDIS_REGISTER_NONE && src_op.mem.index == ZYDIS_REGISTER_NONE && + src_op.mem.disp.value == 0x28 && dst_op.reg.value >= ZYDIS_REGISTER_RAX && + dst_op.reg.value <= ZYDIS_REGISTER_R15; +} + +static void GenerateStackCheck(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + c.xor_(dst, 0); +} + +static void GenerateStackCanary(void* /* address */, const ZydisDecodedOperand* operands, + Xbyak::CodeGenerator& c) { + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + c.mov(dst, 0); +} + static bool FilterNoSSE4a(const ZydisDecodedOperand*) { Cpu cpu; return !cpu.has(Cpu::tSSE4a); @@ -440,18 +465,26 @@ struct PatchInfo { bool trampoline; }; -static const std::unordered_map Patches = { +static const std::unordered_map> Patches = { // SSE4a - {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, - {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, - {ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}}, - {ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}}, + {ZYDIS_MNEMONIC_EXTRQ, {{FilterNoSSE4a, GenerateEXTRQ, true}}}, + {ZYDIS_MNEMONIC_INSERTQ, {{FilterNoSSE4a, GenerateINSERTQ, true}}}, + {ZYDIS_MNEMONIC_MOVNTSS, {{FilterNoSSE4a, ReplaceMOVNTSS, false}}}, + {ZYDIS_MNEMONIC_MOVNTSD, {{FilterNoSSE4a, ReplaceMOVNTSD, false}}}, +#if !defined(__APPLE__) + // FS segment patches + // These first two patches are for accesses to the stack canary, fs:[0x28] + {ZYDIS_MNEMONIC_XOR, {{FilterStackCheck, GenerateStackCheck, false}}}, + {ZYDIS_MNEMONIC_MOV, + {{FilterStackCheck, GenerateStackCanary, false}, #if defined(_WIN32) - // Windows needs a trampoline. - {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}}, -#elif !defined(__APPLE__) - {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}}, + // Windows needs a trampoline for Tcb accesses. + {FilterTcbAccess, GenerateTcbAccess, true} +#else + {FilterTcbAccess, GenerateTcbAccess, false} +#endif + }}, #endif }; @@ -503,51 +536,53 @@ static std::pair TryPatch(u8* code, PatchModule* module) { } if (Patches.contains(instruction.mnemonic)) { - const auto& patch_info = Patches.at(instruction.mnemonic); - bool needs_trampoline = patch_info.trampoline; - if (patch_info.filter(operands)) { - auto& patch_gen = module->patch_gen; + const auto& patches = Patches.at(instruction.mnemonic); + for (const auto& patch_info : patches) { + bool needs_trampoline = patch_info.trampoline; + if (patch_info.filter(operands)) { + auto& patch_gen = module->patch_gen; - if (needs_trampoline && instruction.length < 5) { - // Trampoline is needed but instruction is too short to patch. - // Return false and length to signal to AOT compilation that this instruction - // should be skipped and handled at runtime. - return std::make_pair(false, instruction.length); - } + if (needs_trampoline && instruction.length < 5) { + // Trampoline is needed but instruction is too short to patch. + // Return false and length to signal to AOT compilation that this instruction + // should be skipped and handled at runtime. + return std::make_pair(false, instruction.length); + } - // Reset state and move to current code position. - patch_gen.reset(); - patch_gen.setSize(code - patch_gen.getCode()); + // Reset state and move to current code position. + patch_gen.reset(); + patch_gen.setSize(code - patch_gen.getCode()); - if (needs_trampoline) { - auto& trampoline_gen = module->trampoline_gen; - const auto trampoline_ptr = trampoline_gen.getCurr(); + if (needs_trampoline) { + auto& trampoline_gen = module->trampoline_gen; + const auto trampoline_ptr = trampoline_gen.getCurr(); - patch_info.generator(code, operands, trampoline_gen); + patch_info.generator(code, operands, trampoline_gen); - // Return to the following instruction at the end of the trampoline. - trampoline_gen.jmp(code + instruction.length); + // Return to the following instruction at the end of the trampoline. + trampoline_gen.jmp(code + instruction.length); - // Replace instruction with near jump to the trampoline. - patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); - } else { - patch_info.generator(code, operands, patch_gen); - } + // Replace instruction with near jump to the trampoline. + patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); + } else { + patch_info.generator(code, operands, patch_gen); + } - const auto patch_size = patch_gen.getCurr() - code; - if (patch_size > 0) { - ASSERT_MSG(instruction.length >= patch_size, - "Instruction {} with length {} is too short to replace at: {}", - ZydisMnemonicGetString(instruction.mnemonic), instruction.length, - fmt::ptr(code)); + const auto patch_size = patch_gen.getCurr() - code; + if (patch_size > 0) { + ASSERT_MSG(instruction.length >= patch_size, + "Instruction {} with length {} is too short to replace at: {}", + ZydisMnemonicGetString(instruction.mnemonic), instruction.length, + fmt::ptr(code)); - // Fill remaining space with nops. - patch_gen.nop(instruction.length - patch_size); + // Fill remaining space with nops. + patch_gen.nop(instruction.length - patch_size); - module->patched.insert(code); - LOG_DEBUG(Core, "Patched instruction '{}' at: {}", - ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); - return std::make_pair(true, instruction.length); + module->patched.insert(code); + LOG_DEBUG(Core, "Patched instruction '{}' at: {}", + ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); + return std::make_pair(true, instruction.length); + } } } } @@ -755,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) { Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address); if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2) [[unlikely]] { - UNREACHABLE_MSG("ud2 at code address {:#x}", (u64)code_address); + UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast(code_address)); } - LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address, - ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic) - : "Failed to decode"); + UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}", + reinterpret_cast(code_address), + ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic) + : "Failed to decode"); } } diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 1fb810030..928040fec 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.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 "layer.h" @@ -11,6 +11,7 @@ #include "common/singleton.h" #include "common/types.h" #include "core/debug_state.h" +#include "core/emulator_state.h" #include "imgui/imgui_std.h" #include "imgui_internal.h" #include "options.h" @@ -273,14 +274,10 @@ void L::DrawAdvanced() { void L::DrawSimple() { const float frameRate = DebugState.Framerate; - if (Config::fpsColor()) { - if (frameRate < 10) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red - } else if (frameRate >= 10 && frameRate < 20) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange - } else { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White - } + if (frameRate < 10) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red + } else if (frameRate >= 10 && frameRate < 20) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange } else { PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White } @@ -311,6 +308,7 @@ static void LoadSettings(const char* line) { void L::SetupSettings() { frame_graph.is_open = true; + show_simple_fps = Config::getShowFpsCounter(); using SettingLoader = void (*)(const char*); @@ -475,6 +473,11 @@ void ToggleSimpleFps() { visibility_toggled = true; } +void SetSimpleFps(bool enabled) { + show_simple_fps = enabled; + visibility_toggled = true; +} + void ToggleQuitWindow() { show_quit_window = !show_quit_window; } diff --git a/src/core/devtools/layer.h b/src/core/devtools/layer.h index 44afc95bc..96b48a7f0 100644 --- a/src/core/devtools/layer.h +++ b/src/core/devtools/layer.h @@ -30,6 +30,7 @@ private: namespace Overlay { void ToggleSimpleFps(); +void SetSimpleFps(bool enabled); void ToggleQuitWindow(); } // 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 new file mode 100644 index 000000000..1f02043a3 --- /dev/null +++ b/src/core/emulator_state.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "emulator_state.h" + +std::shared_ptr EmulatorState::s_instance = nullptr; +std::mutex EmulatorState::s_mutex; + +EmulatorState::EmulatorState() {} + +EmulatorState::~EmulatorState() {} + +std::shared_ptr EmulatorState::GetInstance() { + std::lock_guard lock(s_mutex); + if (!s_instance) + s_instance = std::make_shared(); + return s_instance; +} + +void EmulatorState::SetInstance(std::shared_ptr instance) { + std::lock_guard lock(s_mutex); + s_instance = instance; +} + +bool EmulatorState::IsGameRunning() const { + return m_running; +} +void EmulatorState::SetGameRunning(bool running) { + m_running = running; +} + +bool EmulatorState::IsAutoPatchesLoadEnabled() const { + return m_load_patches_auto; +} +void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) { + m_load_patches_auto = enable; +} diff --git a/src/core/emulator_state.h b/src/core/emulator_state.h new file mode 100644 index 000000000..c12af5401 --- /dev/null +++ b/src/core/emulator_state.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +class EmulatorState { +public: + EmulatorState(); + ~EmulatorState(); + + static std::shared_ptr GetInstance(); + static void SetInstance(std::shared_ptr instance); + + bool IsGameRunning() const; + void SetGameRunning(bool running); + bool IsAutoPatchesLoadEnabled() const; + void SetAutoPatchesLoadEnabled(bool enable); + +private: + static std::shared_ptr s_instance; + static std::mutex s_mutex; + + // state variables + bool m_running = false; + bool m_load_patches_auto = true; +}; \ No newline at end of file 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 9d37b957e..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 == '>') { @@ -59,95 +46,236 @@ static void hexToBytes(const char* hex, unsigned char* dst) { bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) { std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; if (!std::filesystem::exists(gameSysDir)) { - LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist"); + LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist"); 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/directories/base_directory.cpp b/src/core/file_sys/directories/base_directory.cpp index c709da6a2..75f67577c 100644 --- a/src/core/file_sys/directories/base_directory.cpp +++ b/src/core/file_sys/directories/base_directory.cpp @@ -5,6 +5,7 @@ #include "common/singleton.h" #include "core/file_sys/directories/base_directory.h" #include "core/file_sys/fs.h" +#include "core/libraries/kernel/orbis_error.h" namespace Core::Directories { @@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default; BaseDirectory::~BaseDirectory() = default; +s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { + s64 bytes_read = 0; + for (s32 i = 0; i < iovcnt; i++) { + const s64 result = read(iov[i].iov_base, iov[i].iov_len); + if (result < 0) { + return result; + } + bytes_read += result; + } + return bytes_read; +} + +s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { + const u64 old_file_pointer = file_offset; + file_offset = offset; + const s64 bytes_read = readv(iov, iovcnt); + file_offset = old_file_pointer; + return bytes_read; +} + +s64 BaseDirectory::lseek(s64 offset, s32 whence) { + + s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) + + ((2 == whence) * (directory_size + offset)); + if (file_offset_new < 0) + return ORBIS_KERNEL_ERROR_EINVAL; + + file_offset = file_offset_new; + return file_offset; +} + } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/base_directory.h b/src/core/file_sys/directories/base_directory.h index b412865a2..832b8ac40 100644 --- a/src/core/file_sys/directories/base_directory.h +++ b/src/core/file_sys/directories/base_directory.h @@ -19,6 +19,17 @@ struct OrbisKernelDirent; namespace Core::Directories { class BaseDirectory { +protected: + static inline u32 fileno_pool{10}; + + static u32 next_fileno() { + return ++fileno_pool; + } + + s64 file_offset = 0; + u64 directory_size = 0; + std::vector dirent_cache_bin{}; + public: explicit BaseDirectory(); @@ -28,13 +39,8 @@ public: return ORBIS_KERNEL_ERROR_EBADF; } - virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { - return ORBIS_KERNEL_ERROR_EBADF; - } - - virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { - return ORBIS_KERNEL_ERROR_EBADF; - } + virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt); + virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset); virtual s64 write(const void* buf, u64 nbytes) { return ORBIS_KERNEL_ERROR_EBADF; @@ -48,9 +54,7 @@ public: return ORBIS_KERNEL_ERROR_EBADF; } - virtual s64 lseek(s64 offset, s32 whence) { - return ORBIS_KERNEL_ERROR_EBADF; - } + virtual s64 lseek(s64 offset, s32 whence); virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) { return ORBIS_KERNEL_ERROR_EBADF; diff --git a/src/core/file_sys/directories/normal_directory.cpp b/src/core/file_sys/directories/normal_directory.cpp index a7d76074a..3ed7c9492 100644 --- a/src/core/file_sys/directories/normal_directory.cpp +++ b/src/core/file_sys/directories/normal_directory.cpp @@ -15,111 +15,30 @@ std::shared_ptr NormalDirectory::Create(std::string_view guest_di std::make_shared(guest_directory)); } -NormalDirectory::NormalDirectory(std::string_view guest_directory) { - auto* mnt = Common::Singleton::Instance(); - - static s32 fileno = 0; - mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) { - auto& dirent = dirents.emplace_back(); - dirent.d_fileno = ++fileno; - dirent.d_type = (ent_is_file ? 8 : 4); - strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1); - dirent.d_namlen = ent_path.filename().string().size(); - - // Calculate the appropriate length for this dirent. - // Account for the null terminator in d_name too. - dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) + - sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) + - (dirent.d_namlen + 1), - 4); - - directory_size += dirent.d_reclen; - }); - - // The last entry of a normal directory should have d_reclen covering the remaining data. - // Since the dirents of a folder are constant by this point, we can modify the last dirent - // before creating the emulated file buffer. - const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size; - dirents[dirents.size() - 1].d_reclen += filler_count; - - // Reading from standard directories seems to be based around file pointer logic. - // Keep an internal buffer representing the raw contents of this file descriptor, - // then emulate the various read functions with that. - directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT); - data_buffer.reserve(directory_size); - memset(data_buffer.data(), 0, directory_size); - - u8* current_dirent = data_buffer.data(); - for (const NormalDirectoryDirent& dirent : dirents) { - NormalDirectoryDirent* dirent_to_write = - reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - - // Using size d_namlen + 1 to account for null terminator. - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; - - current_dirent += dirent.d_reclen; - } +NormalDirectory::NormalDirectory(std::string_view guest_directory) + : guest_directory(guest_directory) { + RebuildDirents(); } s64 NormalDirectory::read(void* buf, u64 nbytes) { - // Nothing left to read. - if (file_offset >= directory_size) { - return ORBIS_OK; - } + RebuildDirents(); - const s64 remaining_data = directory_size - file_offset; - const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes; + // data is contiguous. read goes like any regular file would: start at offset, read n bytes + // output is always aligned up to 512 bytes with 0s + // offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer + // space will be left untouched + // reclen always sums up to end of current alignment - std::memcpy(buf, data_buffer.data() + file_offset, bytes); + s64 bytes_available = this->dirent_cache_bin.size() - file_offset; + if (bytes_available <= 0) + return 0; + bytes_available = std::min(bytes_available, static_cast(nbytes)); - file_offset += bytes; - return bytes; -} + // data + memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available); -s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { - s64 bytes_read = 0; - for (s32 i = 0; i < iovcnt; i++) { - const s64 result = read(iov[i].iov_base, iov[i].iov_len); - if (result < 0) { - return result; - } - bytes_read += result; - } - return bytes_read; -} - -s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, - s64 offset) { - const u64 old_file_pointer = file_offset; - file_offset = offset; - const s64 bytes_read = readv(iov, iovcnt); - file_offset = old_file_pointer; - return bytes_read; -} - -s64 NormalDirectory::lseek(s64 offset, s32 whence) { - switch (whence) { - case 0: { - file_offset = offset; - break; - } - case 1: { - file_offset += offset; - break; - } - case 2: { - file_offset = directory_size + offset; - break; - } - default: { - UNREACHABLE_MSG("lseek with unknown whence {}", whence); - } - } - return file_offset; + file_offset += bytes_available; + return bytes_available; } s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { @@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { } s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) { - if (basep != nullptr) { + RebuildDirents(); + + if (basep) *basep = file_offset; + + // same as others, we just don't need a variable + if (file_offset >= directory_size) + return 0; + + s64 bytes_written = 0; + s64 working_offset = file_offset; + s64 dirent_buffer_offset = 0; + s64 aligned_count = Common::AlignDown(nbytes, 512); + + const u8* dirent_buffer = this->dirent_cache_bin.data(); + while (dirent_buffer_offset < this->dirent_cache_bin.size()) { + const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset; + const NormalDirectoryDirent* normal_dirent = + reinterpret_cast(normal_dirent_ptr); + auto d_reclen = normal_dirent->d_reclen; + + // bad, incomplete or OOB entry + if (normal_dirent->d_namlen == 0) + break; + + if (working_offset >= d_reclen) { + dirent_buffer_offset += d_reclen; + working_offset -= d_reclen; + continue; + } + + if ((bytes_written + d_reclen) > aligned_count) + // dirents are aligned to the last full one + break; + + memcpy(static_cast(buf) + bytes_written, normal_dirent_ptr + working_offset, + d_reclen - working_offset); + bytes_written += d_reclen - working_offset; + dirent_buffer_offset += d_reclen; + working_offset = 0; } - // read behaves identically to getdents for normal directories. - return read(buf, nbytes); + + file_offset += bytes_written; + return bytes_written; } + +void NormalDirectory::RebuildDirents() { + // regenerate only when target wants to read contents again + // no reason for testing - read is always raw and dirents get processed on the go + if (previous_file_offset == file_offset) + return; + previous_file_offset = file_offset; + + constexpr u32 dirent_meta_size = + sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) + + sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen); + + u64 next_ceiling = 0; + u64 dirent_offset = 0; + u64 last_reclen_offset = 4; + dirent_cache_bin.clear(); + dirent_cache_bin.reserve(512); + + auto* mnt = Common::Singleton::Instance(); + + mnt->IterateDirectory( + guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset]( + const std::filesystem::path& ent_path, const bool ent_is_file) { + NormalDirectoryDirent tmp{}; + std::string leaf(ent_path.filename().string()); + + // prepare dirent + tmp.d_fileno = BaseDirectory::next_fileno(); + tmp.d_namlen = leaf.size(); + strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1); + tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12; + tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4); + + // next element may break 512 byte alignment + if (tmp.d_reclen + dirent_offset > next_ceiling) { + // align previous dirent's size to the current ceiling + *reinterpret_cast(static_cast(dirent_cache_bin.data()) + + last_reclen_offset) += next_ceiling - dirent_offset; + // set writing pointer to the aligned start position (current ceiling) + dirent_offset = next_ceiling; + // move the ceiling up and zero-out the buffer + next_ceiling += 512; + dirent_cache_bin.resize(next_ceiling); + std::fill(dirent_cache_bin.begin() + dirent_offset, + dirent_cache_bin.begin() + next_ceiling, 0); + } + + // current dirent's reclen position + last_reclen_offset = dirent_offset + 4; + memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen); + dirent_offset += tmp.d_reclen; + }); + + // last reclen, as before + *reinterpret_cast(static_cast(dirent_cache_bin.data()) + last_reclen_offset) += + next_ceiling - dirent_offset; + + // i have no idea if this is the case, but lseek returns size aligned to 512 + directory_size = next_ceiling; +} + } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/normal_directory.h b/src/core/file_sys/directories/normal_directory.h index 70e52f581..4fc84cd2a 100644 --- a/src/core/file_sys/directories/normal_directory.h +++ b/src/core/file_sys/directories/normal_directory.h @@ -19,27 +19,23 @@ public: ~NormalDirectory() override = default; virtual s64 read(void* buf, u64 nbytes) override; - virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override; - virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, - s64 offset) override; - virtual s64 lseek(s64 offset, s32 whence) override; virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override; virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override; private: - static constexpr s32 MAX_LENGTH = 255; - static constexpr s64 DIRECTORY_ALIGNMENT = 0x200; +#pragma pack(push, 1) struct NormalDirectoryDirent { u32 d_fileno; u16 d_reclen; u8 d_type; u8 d_namlen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; +#pragma pack(pop) - u64 directory_size = 0; - s64 file_offset = 0; - std::vector data_buffer; - std::vector dirents; + std::string_view guest_directory{}; + s64 previous_file_offset = -1; + + void RebuildDirents(void); }; } // namespace Core::Directories diff --git a/src/core/file_sys/directories/pfs_directory.cpp b/src/core/file_sys/directories/pfs_directory.cpp index fbd97c019..38ceaf345 100644 --- a/src/core/file_sys/directories/pfs_directory.cpp +++ b/src/core/file_sys/directories/pfs_directory.cpp @@ -15,77 +15,49 @@ std::shared_ptr PfsDirectory::Create(std::string_view guest_direc } PfsDirectory::PfsDirectory(std::string_view guest_directory) { + constexpr u32 dirent_meta_size = + sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) + + sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen); + + dirent_cache_bin.reserve(512); + auto* mnt = Common::Singleton::Instance(); - static s32 fileno = 0; - mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) { - auto& dirent = dirents.emplace_back(); - dirent.d_fileno = ++fileno; - dirent.d_type = (ent_is_file ? 8 : 4); - strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1); - dirent.d_namlen = ent_path.filename().string().size(); + mnt->IterateDirectory( + guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) { + PfsDirectoryDirent tmp{}; + std::string leaf(ent_path.filename().string()); - // Calculate the appropriate length for this dirent. - // Account for the null terminator in d_name too. - dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) + - sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) + - (dirent.d_namlen + 1), - 8); + tmp.d_fileno = BaseDirectory::next_fileno(); + tmp.d_namlen = leaf.size(); + strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1); + tmp.d_type = ent_is_file ? 2 : 4; + tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8); + auto dirent_ptr = reinterpret_cast(&tmp); - // To handle some obscure dirents_index behavior, - // keep track of the "actual" length of this directory. - directory_content_size += dirent.d_reclen; - }); + dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen); + }); - directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT); + directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000); } s64 PfsDirectory::read(void* buf, u64 nbytes) { - if (dirents_index >= dirents.size()) { - if (dirents_index < directory_content_size) { - // We need to find the appropriate dirents_index to start from. - s64 data_to_skip = dirents_index; - u64 corrected_index = 0; - while (data_to_skip > 0) { - const auto dirent = dirents[corrected_index++]; - data_to_skip -= dirent.d_reclen; - } - dirents_index = corrected_index; - } else { - // Nothing left to read. - return ORBIS_OK; - } + s64 bytes_available = this->dirent_cache_bin.size() - file_offset; + if (bytes_available <= 0) + return 0; + + bytes_available = std::min(bytes_available, static_cast(nbytes)); + memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available); + + s64 to_fill = + (std::min(directory_size, static_cast(nbytes))) - bytes_available - file_offset; + if (to_fill < 0) { + LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill); + return -to_fill + bytes_available; } - - s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes; - // read on PfsDirectories will always return the maximum possible value. - const u64 bytes_written = bytes_remaining; - memset(buf, 0, bytes_remaining); - - char* current_dirent = static_cast(buf); - PfsDirectoryDirent dirent = dirents[dirents_index]; - while (bytes_remaining > dirent.d_reclen) { - PfsDirectoryDirent* dirent_to_write = reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - - // Using size d_namlen + 1 to account for null terminator. - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; - - current_dirent += dirent.d_reclen; - bytes_remaining -= dirent.d_reclen; - - if (dirents_index == dirents.size() - 1) { - // Currently at the last dirent, so break out of the loop. - dirents_index++; - break; - } - dirent = dirents[++dirents_index]; - } - - return bytes_written; + memset(static_cast(buf) + bytes_available, 0, to_fill); + file_offset += to_fill + bytes_available; + return to_fill + bytes_available; } s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) { @@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc } s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) { - const u64 old_dirent_index = dirents_index; - dirents_index = 0; - s64 data_to_skip = offset; - // If offset is part-way through one dirent, that dirent is skipped. - while (data_to_skip > 0) { - const auto dirent = dirents[dirents_index++]; - data_to_skip -= dirent.d_reclen; - if (dirents_index == dirents.size()) { - // We've reached the end of the dirents, nothing more can be skipped. - break; - } - } - + const u64 old_file_pointer = file_offset; + file_offset = offset; const s64 bytes_read = readv(iov, iovcnt); - dirents_index = old_dirent_index; + file_offset = old_file_pointer; return bytes_read; } -s64 PfsDirectory::lseek(s64 offset, s32 whence) { - switch (whence) { - // Seek start - case 0: { - dirents_index = 0; - } - case 1: { - // There aren't any dirents left to pass through. - if (dirents_index >= dirents.size()) { - dirents_index = dirents_index + offset; - break; - } - s64 data_to_skip = offset; - while (data_to_skip > 0) { - const auto dirent = dirents[dirents_index++]; - data_to_skip -= dirent.d_reclen; - if (dirents_index == dirents.size()) { - // We've passed through all file dirents. - // Set dirents_index to directory_size + remaining_offset instead. - dirents_index = directory_content_size + data_to_skip; - break; - } - } - break; - } - case 2: { - // Seems like real hardware gives up on tracking dirents_index if you go this route. - dirents_index = directory_size + offset; - break; - } - default: { - UNREACHABLE_MSG("lseek with unknown whence {}", whence); - } - } - - return dirents_index; -} - s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { stat->st_mode = 0000777u | 0040000u; stat->st_size = directory_size; @@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) { } s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) { - // basep is set at the start of the function. - if (basep != nullptr) { - *basep = dirents_index; - } + if (basep) + *basep = file_offset; - if (dirents_index >= dirents.size()) { - if (dirents_index < directory_content_size) { - // We need to find the appropriate dirents_index to start from. - s64 data_to_skip = dirents_index; - u64 corrected_index = 0; - while (data_to_skip > 0) { - const auto dirent = dirents[corrected_index++]; - data_to_skip -= dirent.d_reclen; - } - dirents_index = corrected_index; - } else { - // Nothing left to read. - return ORBIS_OK; - } - } - - s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes; - memset(buf, 0, bytes_remaining); + // same as others, we just don't need a variable + if (file_offset >= directory_size) + return 0; u64 bytes_written = 0; - char* current_dirent = static_cast(buf); - // getdents has to convert pfs dirents to normal dirents - PfsDirectoryDirent dirent = dirents[dirents_index]; - while (bytes_remaining > dirent.d_reclen) { - NormalDirectoryDirent* dirent_to_write = - reinterpret_cast(current_dirent); - dirent_to_write->d_fileno = dirent.d_fileno; - strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1); - dirent_to_write->d_namlen = dirent.d_namlen; - dirent_to_write->d_reclen = dirent.d_reclen; - dirent_to_write->d_type = dirent.d_type; + u64 starting_offset = 0; + u64 buffer_position = 0; + while (buffer_position < this->dirent_cache_bin.size()) { + const PfsDirectoryDirent* pfs_dirent = + reinterpret_cast(this->dirent_cache_bin.data() + buffer_position); - current_dirent += dirent.d_reclen; - bytes_remaining -= dirent.d_reclen; - bytes_written += dirent.d_reclen; - - if (dirents_index == dirents.size() - 1) { - // Currently at the last dirent, so set dirents_index appropriately and break. - dirents_index = directory_size; + // bad, incomplete or OOB entry + if (pfs_dirent->d_namlen == 0) break; + + if (starting_offset < file_offset) { + // reading starts from the nearest full dirent + starting_offset += pfs_dirent->d_reclen; + buffer_position = bytes_written + starting_offset; + continue; } - dirent = dirents[++dirents_index]; + + if ((bytes_written + pfs_dirent->d_reclen) > nbytes) + // dirents are aligned to the last full one + break; + + // if this dirent breaks alignment, skip + // dirents are count-aligned here, excess data is simply not written + // if (Common::AlignUp(buffer_position, count) != + // Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count)) + // break; + + // reclen for both is the same despite difference in var sizes, extra 0s are padded after + // the name + NormalDirectoryDirent normal_dirent{}; + normal_dirent.d_fileno = pfs_dirent->d_fileno; + normal_dirent.d_reclen = pfs_dirent->d_reclen; + normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4; + normal_dirent.d_namlen = pfs_dirent->d_namlen; + memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen); + + memcpy(static_cast(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen); + bytes_written += normal_dirent.d_reclen; + buffer_position = bytes_written + starting_offset; } + file_offset = (buffer_position >= this->dirent_cache_bin.size()) + ? directory_size + : (file_offset + bytes_written); return bytes_written; } } // namespace Core::Directories \ No newline at end of file diff --git a/src/core/file_sys/directories/pfs_directory.h b/src/core/file_sys/directories/pfs_directory.h index 8f3e8d1f5..23b7e1eb0 100644 --- a/src/core/file_sys/directories/pfs_directory.h +++ b/src/core/file_sys/directories/pfs_directory.h @@ -22,32 +22,28 @@ public: virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override; virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) override; - virtual s64 lseek(s64 offset, s32 whence) override; virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override; virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override; private: - static constexpr s32 MAX_LENGTH = 255; - static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000; +#pragma pack(push, 1) struct PfsDirectoryDirent { u32 d_fileno; u32 d_type; u32 d_namlen; u32 d_reclen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; +#pragma pack(pop) +#pragma pack(push, 1) struct NormalDirectoryDirent { u32 d_fileno; u16 d_reclen; u8 d_type; u8 d_namlen; - char d_name[MAX_LENGTH + 1]; + char d_name[256]; }; - - u64 directory_size = 0; - u64 directory_content_size = 0; - s64 dirents_index = 0; - std::vector dirents; +#pragma pack(pop) }; } // namespace Core::Directories diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 2a0fa43dd..cba95fe37 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea pos = corrected_path.find("//", pos + 1); } + if (path.length() > 255) + return ""; + const MntPair* mount = GetMount(corrected_path); if (!mount) { return ""; @@ -229,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/ipc/ipc.cpp b/src/core/ipc/ipc.cpp index 08cf3bbb2..ea7cd38b4 100644 --- a/src/core/ipc/ipc.cpp +++ b/src/core/ipc/ipc.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 "ipc.h" @@ -14,6 +14,7 @@ #include "common/types.h" #include "core/debug_state.h" #include "core/debugger.h" +#include "core/emulator_state.h" #include "core/libraries/audio/audioout.h" #include "input/input_handler.h" #include "sdl_window.h" @@ -71,7 +72,7 @@ void IPC::Init() { return; } - Config::setLoadAutoPatches(false); + EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false); input_thread = std::jthread([this] { Common::SetCurrentThreadName("IPC Read thread"); diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index 83620250b..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); } } @@ -144,9 +144,8 @@ int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInstanceCodecType() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); - return ORBIS_OK; +AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id) { + return static_cast((instance_id >> 14) & 0x1F); } int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type, diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h index 2c529cd4b..1bfd88351 100644 --- a/src/core/libraries/ajm/ajm.h +++ b/src/core/libraries/ajm/ajm.h @@ -82,8 +82,6 @@ enum class AjmStatisticsFlags : u64 { DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags) union AjmStatisticsJobFlags { - AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {} - u64 raw; struct { u64 version : 3; @@ -133,7 +131,7 @@ struct AjmSidebandGaplessDecode { struct AjmSidebandResampleParameters { float ratio; - uint32_t flags; + u32 flags; }; struct AjmDecAt9InitializeParameters { @@ -217,7 +215,7 @@ int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int p AjmDecMp3ParseFrame* frame); int PS4_SYSV_ABI sceAjmFinalize(); int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context); -int PS4_SYSV_ABI sceAjmInstanceCodecType(); +AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id); int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags, u32* instance); int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance); diff --git a/src/core/libraries/ajm/ajm_aac.cpp b/src/core/libraries/ajm/ajm_aac.cpp new file mode 100644 index 000000000..061b77890 --- /dev/null +++ b/src/core/libraries/ajm/ajm_aac.cpp @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ajm.h" +#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 // 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(1024 * 8), + m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) { + m_resample_buffer.reserve(m_pcm_buffer.size()); +} + +AjmAacDecoder::~AjmAacDecoder() { + if (m_decoder) { + aacDecoder_Close(m_decoder); + } +} + +TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) { + switch (config_type) { + case ConfigType::ADTS: + return TT_MP4_ADTS; + case ConfigType::RAW: + return TT_MP4_RAW; + default: + UNREACHABLE(); + } +} + +static UINT g_freq[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, +}; + +void AjmAacDecoder::Reset() { + if (m_decoder) { + aacDecoder_Close(m_decoder); + } + + m_decoder = aacDecoder_Open(TransportTypeFromConfigType(m_init_params.config_type), 1); + if (m_init_params.config_type == ConfigType::RAW) { + // Manually configure the decoder + // Things may be incorrect due to limited documentation + CSAudioSpecificConfig asc{}; + asc.m_aot = AOT_AAC_LC; + asc.m_samplingFrequency = g_freq[m_init_params.sampling_freq_type]; + asc.m_samplingFrequencyIndex = m_init_params.sampling_freq_type; + asc.m_samplesPerFrame = 1024; + asc.m_epConfig = -1; + switch (m_channels) { + case 0: + asc.m_channelConfiguration = 2; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + asc.m_channelConfiguration = m_channels; + break; + case 7: + asc.m_channelConfiguration = 11; + break; + case 8: + asc.m_channelConfiguration = 12; // 7, 12 or 14 ? + break; + default: + UNREACHABLE(); + } + + UCHAR changed = 1; + CAacDecoder_Init(m_decoder, &asc, AC_CM_ALLOC_MEM, &changed); + } + m_skip_frames = True(m_flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2; +} + +void AjmAacDecoder::Initialize(const void* buffer, u32 buffer_size) { + ASSERT(buffer_size == 8); + m_init_params = *reinterpret_cast(buffer); + Reset(); +} + +void AjmAacDecoder::GetInfo(void* out_info) const { + auto* codec_info = reinterpret_cast(out_info); + *codec_info = { + .heaac = True(m_flags & AjmAacCodecFlags::EnableSbrDecode), + }; +} + +AjmSidebandFormat AjmAacDecoder::GetFormat() const { + const auto* const info = aacDecoder_GetStreamInfo(m_decoder); + return { + .num_channels = static_cast(info->numChannels), + .channel_mask = GetChannelMask(info->numChannels), + .sampl_freq = static_cast(info->sampleRate), + .sample_encoding = m_format, + .bitrate = static_cast(info->bitRate), + }; +} + +u32 AjmAacDecoder::GetMinimumInputSize() const { + return 0; +} + +u32 AjmAacDecoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const { + const auto* const info = aacDecoder_GetStreamInfo(m_decoder); + if (info->aacSamplesPerFrame <= 0) { + return 0; + } + const auto skip_samples = std::min(gapless.current.skip_samples, info->frameSize); + const auto samples = + gapless.init.total_samples != 0 + ? std::min(gapless.current.total_samples, info->frameSize - skip_samples) + : info->frameSize - skip_samples; + return samples * info->numChannels * GetPCMSize(m_format); +} + +DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) { + DecoderResult result{}; + + // Discard the previous contents of the internal buffer and replace them with new ones + aacDecoder_SetParam(m_decoder, AAC_TPDEC_CLEAR_BUFFER, 1); + UCHAR* buffers[] = {input.data()}; + const UINT sizes[] = {static_cast(input.size())}; + UINT valid = sizes[0]; + aacDecoder_Fill(m_decoder, buffers, sizes, &valid); + auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0); + + switch (ret) { + case AAC_DEC_OK: + break; + case AAC_DEC_NOT_ENOUGH_BITS: + result.result = ORBIS_AJM_RESULT_PARTIAL_INPUT; + return result; + default: + LOG_ERROR(Lib_Ajm, "aacDecoder_DecodeFrame failed ret = {:#x}", static_cast(ret)); + result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL; + result.internal_result = ret; + return result; + } + + const auto* const info = aacDecoder_GetStreamInfo(m_decoder); + auto bytes_used = info->numTotalBytes; + + result.frames_decoded += 1; + input = input.subspan(bytes_used); + + if (m_skip_frames > 0) { + --m_skip_frames; + return result; + } + + u32 skip_samples = 0; + if (gapless.current.skip_samples > 0) { + skip_samples = std::min(info->frameSize, gapless.current.skip_samples); + gapless.current.skip_samples -= skip_samples; + } + + const auto max_samples = + 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 = output.Write(pcm); + break; + case AjmFormatEncoding::S32: + UNREACHABLE_MSG("NOT IMPLEMENTED"); + break; + case AjmFormatEncoding::Float: + pcm_written = WriteOutputSamples(output, pcm); + break; + default: + UNREACHABLE(); + } + + result.samples_written = pcm_written / info->numChannels; + gapless.current.skipped_samples += info->frameSize - result.samples_written; + if (gapless.init.total_samples != 0) { + gapless.current.total_samples -= result.samples_written; + } + + return result; +} + +} // 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 new file mode 100644 index 000000000..4ff55d843 --- /dev/null +++ b/src/core/libraries/ajm/ajm_aac.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/enum.h" +#include "common/types.h" +#include "core/libraries/ajm/ajm_instance.h" + +#include +#include + +struct AAC_DECODER_INSTANCE; + +namespace Libraries::Ajm { + +enum ConfigType : u32 { + ADTS = 1, + RAW = 2, +}; + +enum AjmAacCodecFlags : u32 { + EnableSbrDecode = 1 << 0, + EnableNondelayOutput = 1 << 1, + SurroundChannelInterleaveOrderExtlExtrLsRs = 1 << 2, + SurroundChannelInterleaveOrderLsRsExtlExtr = 1 << 3, +}; +DECLARE_ENUM_FLAG_OPERATORS(AjmAacCodecFlags) + +struct AjmSidebandDecM4aacCodecInfo { + u32 heaac; + u32 reserved; +}; + +struct AjmAacDecoder final : AjmCodec { + explicit AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels); + ~AjmAacDecoder() override; + + void Reset() override; + void Initialize(const void* buffer, u32 buffer_size) override; + void GetInfo(void* out_info) const override; + AjmSidebandFormat GetFormat() const override; + u32 GetMinimumInputSize() const override; + u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; + DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) override; + +private: + struct InitializeParameters { + ConfigType config_type; + u32 sampling_freq_type; + }; + + template + 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_resample_buffer; + + u32 m_skip_frames = 0; + InitializeParameters m_init_params = {}; + AAC_DECODER_INSTANCE* m_decoder = nullptr; +}; + +} // namespace Libraries::Ajm \ No newline at end of file diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index 014d1a4e5..4452d032d 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "ajm_result.h" #include "common/assert.h" #include "core/libraries/ajm/ajm_at9.h" #include "error_codes.h" @@ -53,7 +54,7 @@ struct RIFFHeader { }; static_assert(sizeof(RIFFHeader) == 12); -AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags) +AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32) : m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {} AjmAt9Decoder::~AjmAt9Decoder() { @@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const { auto* info = reinterpret_cast(out_info); info->super_frame_size = m_codec_info.superframeSize; info->frames_in_super_frame = m_codec_info.framesInSuperframe; + info->next_frame_size = m_superframe_bytes_remain; info->frame_samples = m_codec_info.frameSamples; - info->next_frame_size = static_cast(m_handle)->Config.FrameBytes; } u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D, @@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span& in_buf, AjmInstanceGapless& g } } -std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, - SparseOutputBuffer& output, - AjmInstanceGapless& gapless) { - bool is_reset = false; +u32 AjmAt9Decoder::GetMinimumInputSize() const { + return m_superframe_bytes_remain; +} + +DecoderResult AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) { + DecoderResult result{}; if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) && *reinterpret_cast(in_buf.data()) == 'FFIR') { ParseRIFFHeader(in_buf, gapless); - is_reset = true; + result.is_reset = true; } if (!m_is_initialized) { - return {0, 0, is_reset}; + result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED; + return result; } int ret = 0; @@ -166,7 +171,14 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, default: UNREACHABLE(); } - ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret); + if (ret != At9Status::ERR_SUCCESS) { + LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret); + result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL; + result.internal_result = ret; + return result; + } + + result.frames_decoded += 1; in_buf = in_buf.subspan(bytes_used); m_superframe_bytes_remain -= bytes_used; @@ -196,10 +208,10 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, UNREACHABLE(); } - const auto samples_written = pcm_written / m_codec_info.channels; - gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written; + result.samples_written = pcm_written / m_codec_info.channels; + gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written; if (gapless.init.total_samples != 0) { - gapless.current.total_samples -= samples_written; + gapless.current.total_samples -= result.samples_written; } m_num_frames += 1; @@ -209,9 +221,23 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, } m_superframe_bytes_remain = m_codec_info.superframeSize; m_num_frames = 0; + } else if (gapless.IsEnd()) { + // Drain the remaining superframe + std::vector buf(m_codec_info.frameSamples * m_codec_info.channels, 0); + while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) { + ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used, + True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput)); + in_buf = in_buf.subspan(bytes_used); + m_superframe_bytes_remain -= bytes_used; + result.frames_decoded += 1; + m_num_frames += 1; + } + in_buf = in_buf.subspan(m_superframe_bytes_remain); + m_superframe_bytes_remain = m_codec_info.superframeSize; + m_num_frames = 0; } - return {1, m_codec_info.frameSamples, is_reset}; + return result; } AjmSidebandFormat AjmAt9Decoder::GetFormat() const { @@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const { const auto samples = gapless.init.total_samples != 0 ? std::min(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples) - : m_codec_info.frameSamples; + : m_codec_info.frameSamples - skip_samples; return samples * m_codec_info.channels * GetPCMSize(m_format); } diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index 3262f1aa0..8eb6166e2 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" #include "core/libraries/ajm/ajm_instance.h" @@ -13,8 +14,6 @@ namespace Libraries::Ajm { -constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8; - enum AjmAt9CodecFlags : u32 { ParseRiffHeader = 1 << 0, NonInterleavedOutput = 1 << 8, @@ -29,16 +28,17 @@ struct AjmSidebandDecAt9CodecInfo { }; struct AjmAt9Decoder final : AjmCodec { - explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags); + explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32 channels); ~AjmAt9Decoder() override; void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override; void GetInfo(void* out_info) const override; AjmSidebandFormat GetFormat() const override; + u32 GetMinimumInputSize() const override; u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; - std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmInstanceGapless& gapless) override; + DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) override; private: template diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 30e1deb71..3ab2ed4ab 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -165,7 +165,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf ASSERT(job_flags.has_value()); job.flags = job_flags.value(); - AjmStatisticsJobFlags flags(job.flags); + AjmStatisticsJobFlags flags{.raw = job.flags.raw}; if (input_control_buffer.has_value()) { AjmBatchBuffer input_batch(input_control_buffer.value()); if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) { @@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { job.input.resample_parameters = input_batch.Consume(); } if (True(control_flags & AjmJobControlFlags::Initialize)) { - job.input.init_params = AjmDecAt9InitializeParameters{}; - std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(), - input_batch.BytesRemaining()); + job.input.init_params = input_batch.Consume(); } } diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h index 09daa630d..c18e9efbf 100644 --- a/src/core/libraries/ajm/ajm_batch.h +++ b/src/core/libraries/ajm/ajm_batch.h @@ -21,7 +21,7 @@ namespace Libraries::Ajm { struct AjmJob { struct Input { - std::optional init_params; + std::optional init_params; std::optional resample_parameters; std::optional statistics_engine_parameters; std::optional format; @@ -52,6 +52,7 @@ struct AjmBatch { u32 id{}; std::atomic_bool waiting{}; std::atomic_bool canceled{}; + std::atomic_bool processed{}; std::binary_semaphore finished{0}; boost::container::small_vector jobs; diff --git a/src/core/libraries/ajm/ajm_context.cpp b/src/core/libraries/ajm/ajm_context.cpp index 0e2915f32..8ce8f3434 100644 --- a/src/core/libraries/ajm/ajm_context.cpp +++ b/src/core/libraries/ajm/ajm_context.cpp @@ -18,7 +18,8 @@ namespace Libraries::Ajm { -static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1; +constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1; +constexpr int INSTANCE_ID_MASK = 0x3FFF; AjmContext::AjmContext() { worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); }); @@ -39,7 +40,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) { batch = *p_batch; } - batch->canceled = true; + if (batch->processed) { + return ORBIS_OK; + } + + bool expected = false; + batch->canceled.compare_exchange_strong(expected, true); return ORBIS_OK; } @@ -58,7 +64,9 @@ void AjmContext::WorkerThread(std::stop_token stop) { Common::SetCurrentThreadName("shadPS4:AjmWorker"); while (!stop.stop_requested()) { auto batch = batch_queue.PopWait(stop); - if (batch != nullptr) { + if (batch != nullptr && !batch->canceled) { + bool expected = false; + batch->processed.compare_exchange_strong(expected, true); ProcessBatch(batch->id, batch->jobs); batch->finished.release(); } @@ -77,7 +85,7 @@ void AjmContext::ProcessBatch(u32 id, std::span jobs) { std::shared_ptr instance; { std::shared_lock lock(instances_mutex); - auto* p_instance = instances.Get(job.instance_id); + auto* p_instance = instances.Get(job.instance_id & INSTANCE_ID_MASK); ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance"); instance = *p_instance; } @@ -169,15 +177,15 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, if (!opt_index.has_value()) { return ORBIS_AJM_ERROR_OUT_OF_RESOURCES; } - *out_instance = opt_index.value(); + *out_instance = opt_index.value() | (static_cast(codec_type) << 14); LOG_INFO(Lib_Ajm, "instance = {}", *out_instance); return ORBIS_OK; } -s32 AjmContext::InstanceDestroy(u32 instance) { +s32 AjmContext::InstanceDestroy(u32 instance_id) { std::unique_lock lock(instances_mutex); - if (!instances.Destroy(instance)) { + if (!instances.Destroy(instance_id & INSTANCE_ID_MASK)) { return ORBIS_AJM_ERROR_INVALID_INSTANCE; } return ORBIS_OK; diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp index c4ea395b9..d25517c81 100644 --- a/src/core/libraries/ajm/ajm_instance.cpp +++ b/src/core/libraries/ajm/ajm_instance.cpp @@ -1,27 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/libraries/ajm/ajm_at9.h" -#include "core/libraries/ajm/ajm_instance.h" -#include "core/libraries/ajm/ajm_mp3.h" +#include "ajm_aac.h" +#include "ajm_at9.h" +#include "ajm_instance.h" +#include "ajm_mp3.h" +#include "ajm_result.h" #include namespace Libraries::Ajm { -constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001; -constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002; -constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004; -constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008; -constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010; -constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020; -constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040; -constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080; -constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100; -constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200; -constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000; -constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000; - u8 GetPCMSize(AjmFormatEncoding format) { switch (format) { case AjmFormatEncoding::S16: @@ -38,13 +27,18 @@ u8 GetPCMSize(AjmFormatEncoding format) { AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) { switch (codec_type) { case AjmCodecType::At9Dec: { - m_codec = std::make_unique(AjmFormatEncoding(flags.format), - AjmAt9CodecFlags(flags.codec)); + m_codec = std::make_unique( + AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels)); break; } case AjmCodecType::Mp3Dec: { - m_codec = std::make_unique(AjmFormatEncoding(flags.format), - AjmMp3CodecFlags(flags.codec)); + m_codec = std::make_unique( + AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels)); + break; + } + case AjmCodecType::M4aacDec: { + m_codec = std::make_unique( + AjmFormatEncoding(flags.format), AjmAacCodecFlags(flags.codec), u32(flags.channels)); break; } default: @@ -60,6 +54,7 @@ void AjmInstance::Reset() { void AjmInstance::ExecuteJob(AjmJob& job) { const auto control_flags = job.flags.control_flags; + job.output.p_result->result = 0; if (True(control_flags & AjmJobControlFlags::Reset)) { LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id); Reset(); @@ -91,8 +86,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) { m_gapless.current.total_samples -= sample_difference; } else { LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER"); - job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER; - return; + job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER; } } @@ -106,61 +100,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) { m_gapless.current.skip_samples -= sample_difference; } else { LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER"); - job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER; - return; + job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER; } } } - if (!job.input.buffer.empty() && !job.output.buffers.empty()) { - std::span in_buf(job.input.buffer); - SparseOutputBuffer out_buf(job.output.buffers); + std::span in_buf(job.input.buffer); + SparseOutputBuffer out_buf(job.output.buffers); + auto in_size = in_buf.size(); + auto out_size = out_buf.Size(); + u32 frames_decoded = 0; - u32 frames_decoded = 0; - auto in_size = in_buf.size(); - auto out_size = out_buf.Size(); - while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) { - if (!HasEnoughSpace(out_buf)) { - if (job.output.p_mframe == nullptr || frames_decoded == 0) { - LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", - out_buf.Size(), m_codec->GetNextFrameSize(m_gapless)); - job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM; - break; - } - } - - const auto [nframes, nsamples, reset] = - m_codec->ProcessData(in_buf, out_buf, m_gapless); - if (reset) { + if (!job.input.buffer.empty()) { + for (;;) { + if (m_flags.gapless_loop && m_gapless.IsEnd()) { + m_gapless.Reset(); m_total_samples = 0; } - if (!nframes) { - LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED"); - job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED; + if (!HasEnoughSpace(out_buf)) { + LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(), + m_codec->GetNextFrameSize(m_gapless)); + job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM; + } + if (in_buf.size() < m_codec->GetMinimumInputSize()) { + job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT; + } + if (job.output.p_result->result != 0) { + break; + } + const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless); + if (result.is_reset) { + m_total_samples = 0; + } else { + m_total_samples += result.samples_written; + } + frames_decoded += result.frames_decoded; + if (result.result != 0) { + job.output.p_result->result |= result.result; + job.output.p_result->internal_result = result.internal_result; break; } - frames_decoded += nframes; - m_total_samples += nsamples; - if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) { break; } } + } - const auto total_decoded_samples = m_total_samples; - if (m_flags.gapless_loop && m_gapless.IsEnd()) { - in_buf = in_buf.subspan(in_buf.size()); - m_gapless.Reset(); - m_codec->Reset(); - } - if (job.output.p_mframe) { - job.output.p_mframe->num_frames = frames_decoded; - } - if (job.output.p_stream) { - job.output.p_stream->input_consumed = in_size - in_buf.size(); - job.output.p_stream->output_written = out_size - out_buf.Size(); - job.output.p_stream->total_decoded_samples = total_decoded_samples; - } + if (job.output.p_mframe) { + job.output.p_mframe->num_frames = frames_decoded; + } + if (job.output.p_stream) { + job.output.p_stream->input_consumed = in_size - in_buf.size(); + job.output.p_stream->output_written = out_size - out_buf.Size(); + job.output.p_stream->total_decoded_samples = m_total_samples; } if (job.output.p_format != nullptr) { @@ -175,6 +167,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) { } bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const { + if (m_gapless.IsEnd()) { + return true; + } return output.Size() >= m_codec->GetNextFrameSize(m_gapless); } diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index ad0a82f29..db53add4d 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -73,6 +73,14 @@ struct AjmInstanceGapless { } }; +struct DecoderResult { + s32 result = 0; + s32 internal_result = 0; + u32 frames_decoded = 0; + u32 samples_written = 0; + bool is_reset = false; +}; + class AjmCodec { public: virtual ~AjmCodec() = default; @@ -81,9 +89,10 @@ public: virtual void Reset() = 0; virtual void GetInfo(void* out_info) const = 0; virtual AjmSidebandFormat GetFormat() const = 0; + virtual u32 GetMinimumInputSize() const = 0; virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0; - virtual std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmInstanceGapless& gapless) = 0; + virtual DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) = 0; }; class AjmInstance { @@ -94,7 +103,6 @@ public: private: bool HasEnoughSpace(const SparseOutputBuffer& output) const; - std::optional GetNumRemainingSamples() const; void Reset(); AjmInstanceFlags m_flags{}; diff --git a/src/core/libraries/ajm/ajm_instance_statistics.cpp b/src/core/libraries/ajm/ajm_instance_statistics.cpp index c0c1af8bb..2e4a65914 100644 --- a/src/core/libraries/ajm/ajm_instance_statistics.cpp +++ b/src/core/libraries/ajm/ajm_instance_statistics.cpp @@ -8,7 +8,7 @@ namespace Libraries::Ajm { void AjmInstanceStatistics::ExecuteJob(AjmJob& job) { if (job.output.p_engine) { - job.output.p_engine->usage_batch = 0.01; + job.output.p_engine->usage_batch = 0.05; const auto ic = job.input.statistics_engine_parameters->interval_count; for (u32 idx = 0; idx < ic; ++idx) { job.output.p_engine->usage_interval[idx] = 0.01; @@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) { job.output.p_memory->batch_size = 0x4200; job.output.p_memory->input_size = 0x2000; job.output.p_memory->output_size = 0x2000; - job.output.p_memory->small_size = 0x200; + job.output.p_memory->small_size = 0x400; } } +void AjmInstanceStatistics::Reset() {} + AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() { static AjmInstanceStatistics instance; return instance; diff --git a/src/core/libraries/ajm/ajm_instance_statistics.h b/src/core/libraries/ajm/ajm_instance_statistics.h index ea70c9d56..0ec79aeac 100644 --- a/src/core/libraries/ajm/ajm_instance_statistics.h +++ b/src/core/libraries/ajm/ajm_instance_statistics.h @@ -10,6 +10,7 @@ namespace Libraries::Ajm { class AjmInstanceStatistics { public: void ExecuteJob(AjmJob& job); + void Reset(); static AjmInstanceStatistics& Getinstance(); }; diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index f17f53d51..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" { @@ -105,7 +107,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) { return new_frame; } -AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags) +AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32) : m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)), m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) { int ret = avcodec_open2(m_codec_context, m_codec, nullptr); @@ -138,16 +140,31 @@ void AjmMp3Decoder::GetInfo(void* out_info) const { } } -std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, - SparseOutputBuffer& output, - AjmInstanceGapless& gapless) { +u32 AjmMp3Decoder::GetMinimumInputSize() const { + // 4 bytes is for mp3 header that contains frame_size + return 4; +} + +DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) { + 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(), false, &info); - m_frame_samples = info.samples_per_channel; + 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), + .skipped_samples = 0, + }; + 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(), @@ -155,9 +172,6 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); in_buf = in_buf.subspan(ret); - u32 frames_decoded = 0; - u32 samples_decoded = 0; - if (pkt->size) { // Send the packet with the compressed data to the decoder pkt->pts = m_parser->pts; @@ -177,9 +191,8 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, UNREACHABLE_MSG("Error during decoding"); } frame = ConvertAudioFrame(frame); - samples_decoded += u32(frame->nb_samples); - frames_decoded += 1; + result.frames_decoded += 1; u32 skip_samples = 0; if (gapless.current.skip_samples > 0) { skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples); @@ -211,6 +224,7 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, if (gapless.init.total_samples != 0) { gapless.current.total_samples -= samples; } + result.samples_written += samples; av_frame_free(&frame); } @@ -218,16 +232,16 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, av_packet_free(&pkt); - return {frames_decoded, samples_decoded, false}; + return result; } u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const { - const auto max_samples = gapless.init.total_samples != 0 - ? std::min(gapless.current.total_samples, m_frame_samples) - : m_frame_samples; - const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples); - return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels * - GetPCMSize(m_format); + const auto skip_samples = std::min(gapless.current.skip_samples, m_frame_samples); + const auto samples = + gapless.init.total_samples != 0 + ? std::min(gapless.current.total_samples, m_frame_samples - skip_samples) + : m_frame_samples - skip_samples; + return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format); } class BitReader { @@ -264,7 +278,7 @@ private: int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame) { - LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); + LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); if (p_begin == nullptr || stream_size < 4 || frame == nullptr) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; @@ -301,7 +315,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ BitReader reader(p_current); if (header->protection_type == 0) { - reader.Skip(16); // crc = reader.Read(16); + // crc = reader.Read(16); + reader.Skip(16); } if (header->version == Mp3AudioVersion::V1) { @@ -413,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 7ac65fdba..1113e222a 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" #include "core/libraries/ajm/ajm_instance.h" @@ -63,16 +64,17 @@ struct AjmSidebandDecMp3CodecInfo { class AjmMp3Decoder : public AjmCodec { public: - explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags); + explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32 channels); ~AjmMp3Decoder() override; void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override {} void GetInfo(void* out_info) const override; AjmSidebandFormat GetFormat() const override; + u32 GetMinimumInputSize() const override; u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; - std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmInstanceGapless& gapless) override; + DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output, + AjmInstanceGapless& gapless) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame); diff --git a/src/core/libraries/ajm/ajm_result.h b/src/core/libraries/ajm/ajm_result.h new file mode 100644 index 000000000..d4e6d1147 --- /dev/null +++ b/src/core/libraries/ajm/ajm_result.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001; +constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002; +constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004; +constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008; +constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010; +constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020; +constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040; +constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080; +constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100; +constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200; +constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000; +constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000; 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/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index bac497840..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); @@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() { s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) { LOG_DEBUG(Lib_Audio3d, "called"); if (params) { - *params = OrbisAudio3dOpenParameters{ + auto default_params = OrbisAudio3dOpenParameters{ .size_this = 0x20, .granularity = 0x100, .rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000, @@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* .queue_depth = 2, .buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH, }; + memcpy(params, &default_params, 0x20); } return ORBIS_OK; } @@ -421,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, @@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id, } *port_id = id; - std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters)); + std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this); return ORBIS_OK; } 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/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 1d1638bd0..4a95c60c9 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -48,7 +48,7 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, title.resize(title_len * 4 + 1); title[title_len * 4] = '\0'; - if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) { + if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4 + 1)) { LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding"); } } @@ -59,14 +59,14 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, placeholder[placeholder_len * 4] = '\0'; if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0], - placeholder_len * 4)) { + placeholder_len * 4 + 1)) { LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding"); } } std::size_t text_len = std::char_traits::length(text_buffer); if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), - ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4 + 1)) { LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); } } @@ -110,7 +110,7 @@ bool ImeDialogState::CopyTextToOrbisBuffer() { } return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer, - max_text_length); + static_cast(max_text_length) + 1); } bool ImeDialogState::CallTextFilter() { @@ -380,10 +380,12 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { .timestamp = {0}, }; - if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { + char16_t tmp_char[2] = {0}; + if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, tmp_char, 2)) { LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed"); return 0; } + src_keycode.character = tmp_char[0]; LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}", static_cast(src_keycode.character)); src_keycode.keycode = src_keycode.character; // TODO set this to the correct value diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index b4c342f18..bc4e2def6 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -34,9 +34,11 @@ #include #else #include +#include #endif namespace D = Core::Devices; +namespace fs = std::filesystem; using FactoryDevice = std::function(u32, const char*, int, u16)>; #define GET_DEVICE_FD(fd) \ @@ -74,6 +76,7 @@ namespace Libraries::Kernel { s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) { LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode); + auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); @@ -87,6 +90,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) { return -1; } + if (strlen(raw_path) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } + bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0; bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0; // Flags fsync and sync behave the same @@ -121,7 +129,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) { bool read_only = false; file->m_guest_name = path; file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only); - bool exists = std::filesystem::exists(file->m_host_name); + bool exists = fs::exists(file->m_host_name); s32 e = 0; if (create) { @@ -149,14 +157,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) { return -1; } - if (std::filesystem::is_directory(file->m_host_name) || directory) { + if (fs::is_directory(file->m_host_name) || directory) { // Directories can be opened even if the directory flag isn't set. // In these cases, error behavior is identical to the directory code path. directory = true; } if (directory) { - if (!std::filesystem::is_directory(file->m_host_name)) { + if (!fs::is_directory(file->m_host_name)) { // If the opened file is not a directory, return ENOTDIR. // This will trigger when create & directory is specified, this is expected. h->DeleteHandle(handle); @@ -554,6 +562,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) { s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode); + if (strlen(path) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } if (path == nullptr) { *__Error() = POSIX_ENOTDIR; return -1; @@ -563,7 +575,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { bool ro = false; const auto dir_name = mnt->GetHostPath(path, &ro); - if (std::filesystem::exists(dir_name)) { + if (fs::exists(dir_name)) { *__Error() = POSIX_EEXIST; return -1; } @@ -575,12 +587,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { // CUSA02456: path = /aotl after sceSaveDataMount(mode = 1) std::error_code ec; - if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) { + if (dir_name.empty() || !fs::create_directory(dir_name, ec)) { *__Error() = POSIX_EIO; return -1; } - if (!std::filesystem::exists(dir_name)) { + if (!fs::exists(dir_name)) { *__Error() = POSIX_ENOENT; return -1; } @@ -597,28 +609,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { } s32 PS4_SYSV_ABI posix_rmdir(const char* path) { + if (strlen(path) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } auto* mnt = Common::Singleton::Instance(); bool ro = false; - const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro); + const fs::path dir_name = mnt->GetHostPath(path, &ro); if (ro) { *__Error() = POSIX_EROFS; return -1; } - if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) { + if (dir_name.empty() || !fs::is_directory(dir_name)) { *__Error() = POSIX_ENOTDIR; return -1; } - if (!std::filesystem::exists(dir_name)) { + if (!fs::exists(dir_name)) { *__Error() = POSIX_ENOENT; return -1; } std::error_code ec; - s32 result = std::filesystem::remove_all(dir_name, ec); + s32 result = fs::remove_all(dir_name, ec); if (ec) { *__Error() = POSIX_EIO; @@ -638,11 +654,15 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) { s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path); + if (strlen(path) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } auto* mnt = Common::Singleton::Instance(); const auto path_name = mnt->GetHostPath(path); std::memset(sb, 0, sizeof(OrbisKernelStat)); - const bool is_dir = std::filesystem::is_directory(path_name); - const bool is_file = std::filesystem::is_regular_file(path_name); + const bool is_dir = fs::is_directory(path_name); + const bool is_file = fs::is_regular_file(path_name); if (!is_dir && !is_file) { *__Error() = POSIX_ENOENT; return -1; @@ -650,12 +670,12 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { // get the difference between file clock and system clock const auto now_sys = std::chrono::system_clock::now(); - const auto now_file = std::filesystem::file_time_type::clock::now(); + const auto now_file = fs::file_time_type::clock::now(); // calculate the file modified time - const auto mtime = std::filesystem::last_write_time(path_name); + const auto mtime = fs::last_write_time(path_name); const auto mtimestamp = now_sys + (mtime - now_file); - if (std::filesystem::is_directory(path_name)) { + if (fs::is_directory(path_name)) { sb->st_mode = 0000777u | 0040000u; sb->st_size = 65536; sb->st_blksize = 65536; @@ -665,7 +685,7 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { // TODO incomplete } else { sb->st_mode = 0000777u | 0100000u; - sb->st_size = static_cast(std::filesystem::file_size(path_name)); + sb->st_size = static_cast(fs::file_size(path_name)); sb->st_blksize = 512; sb->st_blocks = (sb->st_size + 511) / 512; sb->st_mtim.tv_sec = @@ -686,6 +706,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { } s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { + if (strlen(path) > 255) { + return ORBIS_KERNEL_ERROR_ENAMETOOLONG; + } + auto* mnt = Common::Singleton::Instance(); std::string_view guest_path{path}; for (const auto& prefix : available_device | std::views::keys) { @@ -694,7 +718,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { } } const auto path_name = mnt->GetHostPath(guest_path); - if (!std::filesystem::exists(path_name)) { + if (!fs::exists(path_name)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; @@ -728,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; } @@ -807,7 +855,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) { auto* mnt = Common::Singleton::Instance(); bool ro = false; const auto src_path = mnt->GetHostPath(from, &ro); - if (!std::filesystem::exists(src_path)) { + if (strlen(from) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } + if (strlen(to) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } + if (!fs::exists(src_path)) { *__Error() = POSIX_ENOENT; return -1; } @@ -820,32 +876,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) { *__Error() = POSIX_EROFS; return -1; } - const bool src_is_dir = std::filesystem::is_directory(src_path); - const bool dst_is_dir = std::filesystem::is_directory(dst_path); - if (src_is_dir && !dst_is_dir) { - *__Error() = POSIX_ENOTDIR; - return -1; - } - if (!src_is_dir && dst_is_dir) { - *__Error() = POSIX_EISDIR; - return -1; - } - if (dst_is_dir && !std::filesystem::is_empty(dst_path)) { - *__Error() = POSIX_ENOTEMPTY; - return -1; + const bool src_is_dir = fs::is_directory(src_path); + const bool dst_is_dir = fs::is_directory(dst_path); + + if (fs::exists(dst_path)) { + if (src_is_dir && !dst_is_dir) { + *__Error() = POSIX_ENOTDIR; + return -1; + } + if (!src_is_dir && dst_is_dir) { + *__Error() = POSIX_EISDIR; + return -1; + } + if (dst_is_dir && !fs::is_empty(dst_path)) { + *__Error() = POSIX_ENOTEMPTY; + return -1; + } } - // On Windows, std::filesystem::rename will error if the file has been opened before. - std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + // On Windows, fs::rename will error if the file has been opened before. + fs::copy(src_path, dst_path, + fs::copy_options::overwrite_existing | fs::copy_options::recursive); auto* h = Common::Singleton::Instance(); auto file = h->GetFile(src_path); if (file) { auto access_mode = file->f.GetAccessMode(); file->f.Close(); - std::filesystem::remove(src_path); + fs::remove(src_path); file->f.Open(dst_path, access_mode); } else { - std::filesystem::remove(src_path); + fs::remove_all(src_path); } return ORBIS_OK; @@ -1098,6 +1158,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn } s32 PS4_SYSV_ABI posix_unlink(const char* path) { + if (strlen(path) > 255) { + *__Error() = POSIX_ENAMETOOLONG; + return -1; + } if (path == nullptr) { *__Error() = POSIX_EINVAL; return -1; @@ -1118,7 +1182,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) { return -1; } - if (std::filesystem::is_directory(host_path)) { + if (fs::is_directory(host_path)) { *__Error() = POSIX_EPERM; return -1; } @@ -1223,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) { @@ -1491,6 +1556,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev); LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite); LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev); + LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink); LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink); LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select); LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select); diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 92280308d..378064e44 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -5,24 +5,35 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/elf_info.h" #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/singleton.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/process.h" #include "core/libraries/libs.h" #include "core/linker.h" #include "core/memory.h" namespace Libraries::Kernel { +static s32 g_sdk_version = -1; +static bool g_alias_dmem = false; + u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { LOG_TRACE(Kernel_Vmm, "called"); const auto* memory = Core::Memory::Instance(); return memory->GetTotalDirectSize(); } +s32 PS4_SYSV_ABI sceKernelEnableDmemAliasing() { + LOG_DEBUG(Kernel_Vmm, "called"); + g_alias_dmem = true; + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, s32 memoryType, s64* physAddrOut) { if (searchStart < 0 || searchEnd < 0) { @@ -78,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; } @@ -197,8 +218,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s const VAddr in_addr = reinterpret_cast(*addr); auto* memory = Core::Memory::Instance(); - const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, - Core::VMAType::Direct, name, false, phys_addr, alignment); + bool should_check = false; + if (g_sdk_version >= Common::ElfInfo::FW_25 && False(map_flags & Core::MemoryMapFlags::Stack)) { + // Under these conditions, this would normally redirect to sceKernelMapDirectMemory2. + should_check = !g_alias_dmem; + } + const auto ret = + memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, name, + should_check, phys_addr, alignment); LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr)); return ret; @@ -244,8 +271,9 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p const VAddr in_addr = reinterpret_cast(*addr); auto* memory = Core::Memory::Instance(); - const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, - Core::VMAType::Direct, "anon", true, phys_addr, alignment); + const auto ret = + memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "anon", + !g_alias_dmem, phys_addr, alignment); if (ret == 0) { // If the map call succeeds, set the direct memory type using the output address. @@ -668,10 +696,21 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, } s32 result = ORBIS_OK; - if (fd == -1) { + if (True(mem_flags & Core::MemoryMapFlags::Anon)) { + // Maps flexible memory result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, Core::VMAType::Flexible, "anon", false); + } else if (True(mem_flags & Core::MemoryMapFlags::Stack)) { + // Maps stack memory + result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, + Core::VMAType::Stack, "anon", false); + } else if (True(mem_flags & Core::MemoryMapFlags::Void)) { + // Reserves memory + result = + memory->MapMemory(&addr_out, aligned_addr, aligned_size, Core::MemoryProt::NoAccess, + mem_flags, Core::VMAType::Reserved, "anon", false); } else { + // Default to file mapping result = memory->MapFile(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, fd, phys_addr); } @@ -769,6 +808,12 @@ s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) { } void RegisterMemory(Core::Loader::SymbolsResolver* sym) { + ASSERT_MSG(sceKernelGetCompiledSdkVersion(&g_sdk_version) == ORBIS_OK, + "Failed to get compiled SDK verision."); + + LIB_FUNCTION("usHTMoFoBTM", "libkernel_dmem_aliasing2", 1, "libkernel", + sceKernelEnableDmemAliasing); + LIB_FUNCTION("usHTMoFoBTM", "libkernel", 1, "libkernel", sceKernelEnableDmemAliasing); LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", sceKernelAllocateDirectMemory); LIB_FUNCTION("B+vc2AO2Zrc", "libkernel", 1, "libkernel", sceKernelAllocateMainDirectMemory); LIB_FUNCTION("C0f7TJcbfac", "libkernel", 1, "libkernel", sceKernelAvailableDirectMemorySize); diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index e88446e02..4ea7fa062 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -36,9 +36,11 @@ s32 PS4_SYSV_ABI sceKernelGetMainSocId() { } s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) { - s32 version = Common::ElfInfo::Instance().CompiledSdkVer(); - *ver = version; - return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; + if (!ver) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + *ver = Common::ElfInfo::Instance().CompiledSdkVer(); + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelGetCpumode() { diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 5455d425e..95ced79c0 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -173,6 +173,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", sceKernelDebugRaiseException); LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel", sceKernelDebugRaiseExceptionOnReleaseMode); + LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler); + LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 5d97c5dc1..7a046e973 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -378,7 +378,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/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 1f7ecb75e..67c3d4b7d 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -35,19 +35,23 @@ #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" #include "core/libraries/playgo/playgo.h" #include "core/libraries/playgo/playgo_dialog.h" #include "core/libraries/random/random.h" #include "core/libraries/razor_cpu/razor_cpu.h" #include "core/libraries/remote_play/remoteplay.h" -#include "core/libraries/rtc/rtc.h" +#include "core/libraries/rudp/rudp.h" #include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" @@ -70,7 +74,6 @@ #include "core/libraries/web_browser_dialog/webbrowserdialog.h" #include "core/libraries/zlib/zlib_sce.h" #include "fiber/fiber.h" -#include "jpeg/jpegenc.h" namespace Libraries { @@ -97,13 +100,17 @@ 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); + Libraries::Np::NpWebApi2::RegisterLib(sym); Libraries::Np::NpProfileDialog::RegisterLib(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); @@ -117,17 +124,16 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ErrorDialog::RegisterLib(sym); Libraries::ImeDialog::RegisterLib(sym); Libraries::AvPlayer::RegisterLib(sym); - Libraries::Vdec2::RegisterLib(sym); + Libraries::Videodec::RegisterLib(sym); + Libraries::Videodec2::RegisterLib(sym); Libraries::Audio3d::RegisterLib(sym); Libraries::Ime::RegisterLib(sym); Libraries::GameLiveStreaming::RegisterLib(sym); Libraries::SharePlay::RegisterLib(sym); Libraries::Remoteplay::RegisterLib(sym); - Libraries::Videodec::RegisterLib(sym); Libraries::RazorCpu::RegisterLib(sym); Libraries::Move::RegisterLib(sym); Libraries::Fiber::RegisterLib(sym); - Libraries::JpegEnc::RegisterLib(sym); Libraries::Mouse::RegisterLib(sym); Libraries::WebBrowserDialog::RegisterLib(sym); Libraries::Zlib::RegisterLib(sym); @@ -140,7 +146,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::CompanionHttpd::RegisterLib(sym); Libraries::CompanionUtil::RegisterLib(sym); Libraries::Voice::RegisterLib(sym); - Libraries::Rtc::RegisterLib(sym); + Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); // Loading libSceSsl is locked behind a title workaround that currently applies to nothing. diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 97005813b..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; } @@ -1357,7 +1379,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() { } int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) { - LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags); + const char* safe_name = name ? name : ""; + LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags); if (flags != 0) { *sceNetErrnoLoc() = ORBIS_NET_EINVAL; @@ -1368,8 +1391,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) { auto* resolver = FDTable::Instance()->GetFile(fd); resolver->is_opened = true; resolver->type = Core::FileSys::FileType::Resolver; - resolver->resolver = std::make_shared(name, poolid, flags); - resolver->m_guest_name = name; + resolver->resolver = std::make_shared(safe_name, poolid, flags); + resolver->m_guest_name = safe_name; return fd; } diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h index 2f1339d0a..4cb7afdda 100644 --- a/src/core/libraries/network/net.h +++ b/src/core/libraries/network/net.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 @@ -110,6 +110,15 @@ enum OrbisNetSocketSoOption : u32 { ORBIS_NET_SO_PRIORITY = 0x1203 }; +enum OrbisNetFlags : u32 { + ORBIS_NET_MSG_PEEK = 0x00000002, + ORBIS_NET_MSG_WAITALL = 0x00000040, + ORBIS_NET_MSG_DONTWAIT = 0x00000080, + ORBIS_NET_MSG_USECRYPTO = 0x00100000, + ORBIS_NET_MSG_USESIGNATURE = 0x00200000, + ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK) +}; + constexpr std::string_view NameOf(OrbisNetSocketSoOption o) { switch (o) { case ORBIS_NET_SO_REUSEADDR: diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp index 3ffb42528..f2adccf50 100644 --- a/src/core/libraries/network/posix_sockets.cpp +++ b/src/core/libraries/network/posix_sockets.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 @@ -184,28 +184,104 @@ int PosixSocket::Listen(int backlog) { return ConvertReturnErrorCode(::listen(sock, backlog)); } +static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) { + int posix_flags = 0; + + if (sce_flags & ORBIS_NET_MSG_PEEK) + posix_flags |= MSG_PEEK; +#ifndef _WIN32 + if (sce_flags & ORBIS_NET_MSG_DONTWAIT) + posix_flags |= MSG_DONTWAIT; +#endif + // MSG_WAITALL is only valid for stream sockets + if ((sce_flags & ORBIS_NET_MSG_WAITALL) && + ((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P))) + posix_flags |= MSG_WAITALL; + + return posix_flags; +} + +// On Windows, MSG_DONTWAIT is not handled natively by recv/send. +// This function uses select() with zero timeout to simulate non-blocking behavior. +static int socket_is_ready(int sock, bool is_read = true) { + fd_set fds{}; + FD_ZERO(&fds); + FD_SET(sock, &fds); + timeval timeout{0, 0}; + int res = + select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout); + if (res == 0) { + *Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK; + return -1; + } else if (res < 0) { + return ConvertReturnErrorCode(res); + } + return res; +} + int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) { std::scoped_lock lock{m_mutex}; + #ifdef _WIN32 - DWORD bytesSent = 0; - LPFN_WSASENDMSG wsasendmsg = nullptr; - GUID guid = WSAID_WSASENDMSG; - DWORD bytes = 0; + int totalSent = 0; + bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0; + bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0; - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg, - sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) { - return ConvertReturnErrorCode(-1); + // stream socket with multiple buffers + bool use_wsamsg = + (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) && + msg->msg_iovlen > 1; + + for (int i = 0; i < msg->msg_iovlen; ++i) { + char* buf = (char*)msg->msg_iov[i].iov_base; + size_t remaining = msg->msg_iov[i].iov_len; + + while (remaining > 0) { + if (dontWait) { + int ready = socket_is_ready(sock, false); + if (ready <= 0) + return ready; + } + + int sent = 0; + if (use_wsamsg) { + // only call WSASendMsg if we have multiple buffers + LPFN_WSASENDMSG wsasendmsg = nullptr; + GUID guid = WSAID_WSASENDMSG; + DWORD bytes = 0; + if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), + &wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) { + // fallback to send() + sent = ::send(sock, buf, remaining, 0); + } else { + DWORD bytesSent = 0; + int res = wsasendmsg( + sock, reinterpret_cast(const_cast(msg)), 0, + &bytesSent, nullptr, nullptr); + if (res == SOCKET_ERROR) + return ConvertReturnErrorCode(WSAGetLastError()); + sent = bytesSent; + } + } else { + sent = ::send(sock, buf, remaining, 0); + if (sent == SOCKET_ERROR) + return ConvertReturnErrorCode(WSAGetLastError()); + } + + totalSent += sent; + remaining -= sent; + buf += sent; + + if (!waitAll) + break; + } } - int res = wsasendmsg(sock, reinterpret_cast(const_cast(msg)), flags, - &bytesSent, nullptr, nullptr); + return totalSent; - if (res == SOCKET_ERROR) { - return ConvertReturnErrorCode(-1); - } - return static_cast(bytesSent); #else - int res = sendmsg(sock, reinterpret_cast(msg), flags); + int native_flags = convertOrbisFlagsToPosix(socket_type, flags); + int res = sendmsg(sock, reinterpret_cast(msg), native_flags); return ConvertReturnErrorCode(res); #endif } @@ -213,37 +289,92 @@ int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) { int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to, u32 tolen) { std::scoped_lock lock{m_mutex}; - if (to != nullptr) { - sockaddr addr; - convertOrbisNetSockaddrToPosix(to, &addr); - return ConvertReturnErrorCode( - sendto(sock, (const char*)msg, len, flags, &addr, sizeof(sockaddr_in))); - } else { - return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags)); + int res = 0; +#ifdef _WIN32 + if (flags & ORBIS_NET_MSG_DONTWAIT) { + res = socket_is_ready(sock, false); + if (res <= 0) + return res; } +#endif + const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags); + if (to == nullptr) { + res = send(sock, (const char*)msg, len, posix_flags); + } else { + sockaddr addr{}; + convertOrbisNetSockaddrToPosix(to, &addr); + res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen); + } + return ConvertReturnErrorCode(res); } int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) { std::scoped_lock lock{receive_mutex}; + #ifdef _WIN32 - LPFN_WSARECVMSG wsarecvmsg = nullptr; - GUID guid = WSAID_WSARECVMSG; - DWORD bytes = 0; + int totalReceived = 0; + bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0; + bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0; - if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg, - sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) { - return ConvertReturnErrorCode(-1); + // stream socket with multiple buffers + bool use_wsarecvmsg = + (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) && + msg->msg_iovlen > 1; + + for (int i = 0; i < msg->msg_iovlen; ++i) { + char* buf = (char*)msg->msg_iov[i].iov_base; + size_t remaining = msg->msg_iov[i].iov_len; + + while (remaining > 0) { + // emulate DONTWAIT + if (dontWait) { + int ready = socket_is_ready(sock, true); + if (ready <= 0) + return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error + } + + int received = 0; + if (use_wsarecvmsg) { + // only call WSARecvMsg if multiple buffers + stream + LPFN_WSARECVMSG wsarecvmsg = nullptr; + GUID guid = WSAID_WSARECVMSG; + DWORD bytes = 0; + if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), + &wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) { + // fallback to recv() + received = ::recv(sock, buf, remaining, 0); + if (received == SOCKET_ERROR) + return ConvertReturnErrorCode(WSAGetLastError()); + } else { + DWORD bytesReceived = 0; + int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived, + nullptr, nullptr); + if (res == SOCKET_ERROR) + return ConvertReturnErrorCode(WSAGetLastError()); + received = bytesReceived; + } + } else { + // fallback to recv() for UDP or single-buffer + received = ::recv(sock, buf, remaining, 0); + if (received == SOCKET_ERROR) + return ConvertReturnErrorCode(WSAGetLastError()); + } + + totalReceived += received; + remaining -= received; + buf += received; + + // stop after first receive if WAITALL is not set + if (!waitAll) + break; + } } - DWORD bytesReceived = 0; - int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived, nullptr, nullptr); + return totalReceived; - if (res == SOCKET_ERROR) { - return ConvertReturnErrorCode(-1); - } - return static_cast(bytesReceived); #else - int res = recvmsg(sock, reinterpret_cast(msg), flags); + int native_flags = convertOrbisFlagsToPosix(socket_type, flags); + int res = recvmsg(sock, reinterpret_cast(msg), native_flags); return ConvertReturnErrorCode(res); #endif } @@ -251,15 +382,27 @@ int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) { int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) { std::scoped_lock lock{receive_mutex}; - if (from != nullptr) { - sockaddr addr; - int res = recvfrom(sock, (char*)buf, len, flags, &addr, (socklen_t*)fromlen); - convertPosixSockaddrToOrbis(&addr, from); - *fromlen = sizeof(OrbisNetSockaddrIn); - return ConvertReturnErrorCode(res); - } else { - return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags)); + int res = 0; +#ifdef _WIN32 + if (flags & ORBIS_NET_MSG_DONTWAIT) { + res = socket_is_ready(sock); + if (res <= 0) + return res; } +#endif + const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags); + if (from == nullptr) { + res = recv(sock, (char*)buf, len, posix_flags); + } else { + sockaddr addr{}; + socklen_t addrlen = sizeof(addr); + res = recvfrom(sock, (char*)buf, len, posix_flags, &addr, + (fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen)); + if (res > 0) + convertPosixSockaddrToOrbis(&addr, from); + } + + return ConvertReturnErrorCode(res); } SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) { diff --git a/src/core/libraries/network/sockets.h b/src/core/libraries/network/sockets.h index 281c83ab4..86661c71c 100644 --- a/src/core/libraries/network/sockets.h +++ b/src/core/libraries/network/sockets.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 @@ -62,7 +62,7 @@ struct OrbisNetLinger { s32 l_linger; }; struct Socket { - explicit Socket(int domain, int type, int protocol) {} + explicit Socket(int domain, int type, int protocol) : socket_type(type) {} virtual ~Socket() = default; virtual bool IsValid() const = 0; virtual int Close() = 0; @@ -84,6 +84,7 @@ struct Socket { virtual std::optional Native() = 0; std::mutex m_mutex; std::mutex receive_mutex; + int socket_type; }; struct PosixSocket : public Socket { 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_manager.cpp b/src/core/libraries/np/np_manager.cpp index ebc940bf3..229ae33af 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,8 +700,22 @@ struct NpStateCallbackForNpToolkit { NpStateCallbackForNpToolkit NpStateCbForNp; +struct NpStateCallback { + std::variant func; + void* userdata; +}; + +NpStateCallback NpStateCb; + s32 PS4_SYSV_ABI sceNpCheckCallback() { LOG_DEBUG(Lib_NpManager, "(STUBBED) called"); + + std::scoped_lock lk{g_np_callbacks_mutex}; + + for (auto i : g_np_callbacks) { + (i.second)(); + } + return ORBIS_OK; } @@ -692,6 +724,40 @@ s32 PS4_SYSV_ABI sceNpCheckCallbackForLib() { return ORBIS_OK; } +s32 PS4_SYSV_ABI sceNpRegisterStateCallback(OrbisNpStateCallback callback, void* userdata) { + static s32 id = 0; + LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata); + NpStateCb.func = callback; + NpStateCb.userdata = userdata; + + return id; +} + +s32 PS4_SYSV_ABI sceNpRegisterStateCallbackA(OrbisNpStateCallbackA callback, void* userdata) { + static s32 id = 0; + LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata); + NpStateCb.func = callback; + NpStateCb.userdata = userdata; + + return id; +} + +struct NpReachabilityStateCallback { + OrbisNpReachabilityStateCallback func; + void* userdata; +}; + +NpReachabilityStateCallback NpReachabilityCb; + +s32 PS4_SYSV_ABI sceNpRegisterNpReachabilityStateCallback(OrbisNpReachabilityStateCallback callback, + void* userdata) { + static s32 id = 0; + LOG_ERROR(Lib_NpManager, "(STUBBED) called"); + NpReachabilityCb.func = callback; + NpReachabilityCb.userdata = userdata; + return id; +} + s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback, void* userdata) { static s32 id = 0; @@ -701,6 +767,22 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT return id; } +void RegisterNpCallback(std::string key, std::function cb) { + std::scoped_lock lk{g_np_callbacks_mutex}; + + LOG_DEBUG(Lib_NpManager, "registering callback processing for {}", key); + + g_np_callbacks.emplace(key, cb); +} + +void DeregisterNpCallback(std::string key) { + std::scoped_lock lk{g_np_callbacks_mutex}; + + LOG_DEBUG(Lib_NpManager, "deregistering callback processing for {}", key); + + g_np_callbacks.erase(key); +} + void RegisterLib(Core::Loader::SymbolsResolver* sym) { g_signed_in = Config::getPSNSignedIn(); @@ -739,9 +821,14 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("p-o74CnoNzY", "libSceNpManager", 1, "libSceNpManager", sceNpGetNpId); LIB_FUNCTION("XDncXQIJUSk", "libSceNpManager", 1, "libSceNpManager", sceNpGetOnlineId); LIB_FUNCTION("eQH7nWPcAgc", "libSceNpManager", 1, "libSceNpManager", sceNpGetState); + LIB_FUNCTION("VgYczPGB5ss", "libSceNpManager", 1, "libSceNpManager", sceNpGetUserIdByAccountId); LIB_FUNCTION("Oad3rvY-NJQ", "libSceNpManager", 1, "libSceNpManager", sceNpHasSignedUp); LIB_FUNCTION("3Zl8BePTh9Y", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallback); LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib); + LIB_FUNCTION("VfRSmPmj8Q8", "libSceNpManager", 1, "libSceNpManager", + sceNpRegisterStateCallback); + LIB_FUNCTION("hw5KNqAAels", "libSceNpManager", 1, "libSceNpManager", + sceNpRegisterNpReachabilityStateCallback); LIB_FUNCTION("JELHf4xPufo", "libSceNpManagerForToolkit", 1, "libSceNpManager", sceNpCheckCallbackForLib); LIB_FUNCTION("0c7HbXRKUt4", "libSceNpManagerForToolkit", 1, "libSceNpManager", diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 61a283ba7..078fa804a 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,11 @@ struct OrbisNpCreateAsyncRequestParameter { u8 padding[4]; }; +void RegisterNpCallback(std::string key, std::function cb); +void DeregisterNpCallback(std::string key); + +s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpOnlineId* online_id); + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpManager diff --git a/src/core/libraries/np/np_matching2.cpp b/src/core/libraries/np/np_matching2.cpp new file mode 100644 index 000000000..cf4faea39 --- /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::ORBIS_NP_PLATFORM_TYPE_PS4}, + onlineId, + {0, 0, 0, 0}, + 1, + requestCopy.teamId, + 1, + 0, + nullptr, + nullptr, + 0}; + OrbisNpMatching2RoomDataInternal room{requestCopy.maxSlot, + 0, + static_cast(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..e0d0aaad1 --- /dev/null +++ b/src/core/libraries/np/np_tus.cpp @@ -0,0 +1,1002 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_tus.h" + +namespace Libraries::Np::NpTus { + +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +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 sceNpTusGetData() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +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 sceNpTusGetMultiSlotVariable() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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 sceNpTusSetData() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetData() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTssGetDataAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +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 sceNpTusCreateNpTitleCtxA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusCreateRequest() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusDeleteRequest() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetDataAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +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 sceNpTusGetMultiSlotDataStatusA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataA() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTusSetDataAAsync() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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() { + LOG_ERROR(Lib_NpTus, "(STUBBED) called"); + 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..3c18099b2 --- /dev/null +++ b/src/core/libraries/np/np_tus.h @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Np::NpTus { + +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariable(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAsync(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariable(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusGetData(); +s32 PS4_SYSV_ABI sceNpTusGetDataAsync(); +s32 PS4_SYSV_ABI sceNpTusGetDataVUser(); +s32 PS4_SYSV_ABI sceNpTusGetDataVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatus(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariable(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatus(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariable(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusSetData(); +s32 PS4_SYSV_ABI sceNpTusSetDataAsync(); +s32 PS4_SYSV_ABI sceNpTusSetDataVUser(); +s32 PS4_SYSV_ABI sceNpTusSetDataVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariable(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAsync(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA(); +s32 PS4_SYSV_ABI sceNpTssGetData(); +s32 PS4_SYSV_ABI sceNpTssGetDataAsync(); +s32 PS4_SYSV_ABI sceNpTssGetSmallStorage(); +s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(); +s32 PS4_SYSV_ABI sceNpTssGetStorage(); +s32 PS4_SYSV_ABI sceNpTssGetStorageAsync(); +s32 PS4_SYSV_ABI sceNpTusAbortRequest(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableA(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUser(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners(); +s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA(); +s32 PS4_SYSV_ABI sceNpTusCreateRequest(); +s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataA(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAAsync(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUser(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableA(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx(); +s32 PS4_SYSV_ABI sceNpTusDeleteRequest(); +s32 PS4_SYSV_ABI sceNpTusGetDataA(); +s32 PS4_SYSV_ABI sceNpTusGetDataAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetDataAVUser(); +s32 PS4_SYSV_ABI sceNpTusGetDataAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusA(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableA(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableA(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusA(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableA(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusPollAsync(); +s32 PS4_SYSV_ABI sceNpTusSetDataA(); +s32 PS4_SYSV_ABI sceNpTusSetDataAAsync(); +s32 PS4_SYSV_ABI sceNpTusSetDataAVUser(); +s32 PS4_SYSV_ABI sceNpTusSetDataAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableA(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUser(); +s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusSetThreadParam(); +s32 PS4_SYSV_ABI sceNpTusSetTimeout(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableA(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAAsync(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUser(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSave(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveAsync(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUser(); +s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(); +s32 PS4_SYSV_ABI sceNpTusWaitAsync(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Np::NpTus \ 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..cc37b5a3d 100644 --- a/src/core/libraries/np/np_types.h +++ b/src/core/libraries/np/np_types.h @@ -9,6 +9,8 @@ // For structs and constants shared between multiple Np libraries. namespace Libraries::Np { +using OrbisNpAccountId = u64; + constexpr s32 ORBIS_NP_ONLINEID_MAX_LENGTH = 16; struct OrbisNpOnlineId { @@ -43,4 +45,19 @@ struct OrbisNpIdToken { u8 padding[7]; }; +using OrbisNpServiceLabel = u32; + +enum class OrbisNpPlatformType : s32 { + ORBIS_NP_PLATFORM_TYPE_NONE = 0, + ORBIS_NP_PLATFORM_TYPE_PS3 = 1, + ORBIS_NP_PLATFORM_TYPE_VITA = 2, + ORBIS_NP_PLATFORM_TYPE_PS4 = 3, +}; + +struct OrbisNpPeerAddressA { + OrbisNpAccountId accountId; + OrbisNpPlatformType platformType; + u8 padding[4]; +}; + }; // namespace Libraries::Np \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2.cpp b/src/core/libraries/np/np_web_api2.cpp new file mode 100644 index 000000000..c03636e73 --- /dev/null +++ b/src/core/libraries/np/np_web_api2.cpp @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#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_web_api2.h" +#include "core/libraries/np/np_web_api2_error.h" +#include "core/libraries/system/userservice.h" + +namespace Libraries::Np::NpWebApi2 { + +s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id, + UserService::OrbisUserServiceUserId user_id) { + if (lib_ctx_id >= 0x8000) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID; + } + if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}, user_id = {:#x}", lib_ctx_id, + user_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, user_ctx_id = {:#x}", user_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id, + OrbisNpWebApi2MemoryPoolStats* stats) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}'", + args->lib_http_ctx_id, args->pool_size, args->name); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR( + Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', group = {:#x}", + args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) { + if (data == nullptr || size == 0) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, size = {:#x}", request_id, + size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendRequest() { + if (!Config::getPSNSignedIn()) { + LOG_INFO(Lib_NpWebApi2, "called, returning PSN signed out."); + return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, timeout = {}", request_id, + timeout); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_03D22863300D2B73() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_97296F7578AAD541() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest); + LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddHttpRequestHeader); + LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddMultipartPart); + LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddWebTraceTag); + LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout); + LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateMultipartRequest); + LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest); + LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateUserContext); + LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest); + LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2DeleteUserContext); + LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValue); + LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValueLength); + LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetMemoryPoolStats); + LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize); + LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2InitializeForPresence); + LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntCreateRequest); + LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize); + LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntInitialize2); + LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntPushEventCreateCtxIndFilter); + LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventAbortHandle); + LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateFilter); + LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateHandle); + LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreatePushContext); + LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteFilter); + LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteHandle); + LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeletePushContext); + LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterCallback); + LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterPushContextCallback); + LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventSetHandleTimeout); + LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventStartPushContextCallback); + LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterCallback); + LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterPushContextCallback); + LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData); + LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SendMultipartRequest); + LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest); + LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetMultipartContentType); + LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetRequestTimeout); + LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate); + LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620); + LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_03D22863300D2B73); + LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_97296F7578AAD541); + LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_E0DF39A36F087DB9); +}; + +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2.h b/src/core/libraries/np/np_web_api2.h new file mode 100644 index 000000000..1e970d08e --- /dev/null +++ b/src/core/libraries/np/np_web_api2.h @@ -0,0 +1,40 @@ +// 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::NpWebApi2 { + +struct OrbisNpWebApi2IntInitializeArgs { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u64 struct_size; +}; + +struct OrbisNpWebApi2IntInitialize2Args { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u32 push_config_group; + s32 reserved2; + u64 struct_size; +}; + +struct OrbisNpWebApi2MemoryPoolStats { + u64 pool_size; + u64 max_inuse_size; + u64 current_inuse_size; + s32 reserved; +}; + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2_error.h b/src/core/libraries/np/np_web_api2_error.h new file mode 100644 index 000000000..6ab4fdb31 --- /dev/null +++ b/src/core/libraries/np/np_web_api2_error.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_NP_WEBAPI2_ERROR_OUT_OF_MEMORY = 0x80553401; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = 0x80553402; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID = 0x80553403; +constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80553404; +constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND = 0x80553405; +constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406; +constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408; +constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d; +constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410; +constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411; +constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414; +constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415; +constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419; +constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a; +constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c; \ No newline at end of file diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 09f404969..f433a87cc 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -159,7 +159,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 +257,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 +279,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) 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/rudp/rudp.cpp b/src/core/libraries/rudp/rudp.cpp new file mode 100644 index 000000000..2dfb66f64 --- /dev/null +++ b/src/core/libraries/rudp/rudp.cpp @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/rudp/rudp.h" + +namespace Libraries::Rudp { + +s32 PS4_SYSV_ABI sceRudpAccept() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpActivate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpBind() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpCreateContext() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread2() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpEnd() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpFlush() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetContextStatus() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetLocalInfo() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetMaxSegmentSize() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetNumberOfPacketsToRead() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetOption() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetRemoteInfo() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetSizeReadable() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetSizeWritable() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpGetStatus() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpInit() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpInitiate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpListen() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpNetFlush() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpNetReceived() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollCancel() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollControl() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollCreate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollDestroy() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpPollWait() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpProcessEvents() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpRead() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetEventHandler() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetMaxSegmentSize() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpSetOption() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpTerminate() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceRudpWrite() { + LOG_ERROR(Lib_Rudp, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("uQiK7fjU6y8", "libSceRudp", 1, "libSceRudp", sceRudpAccept); + LIB_FUNCTION("J-6d0WTjzMc", "libSceRudp", 1, "libSceRudp", sceRudpActivate); + LIB_FUNCTION("l4SLBpKUDK4", "libSceRudp", 1, "libSceRudp", sceRudpBind); + LIB_FUNCTION("CAbbX6BuQZ0", "libSceRudp", 1, "libSceRudp", sceRudpCreateContext); + LIB_FUNCTION("6PBNpsgyaxw", "libSceRudp", 1, "libSceRudp", sceRudpEnableInternalIOThread); + LIB_FUNCTION("fJ51weR1WAI", "libSceRudp", 1, "libSceRudp", sceRudpEnableInternalIOThread2); + LIB_FUNCTION("3hBvwqEwqj8", "libSceRudp", 1, "libSceRudp", sceRudpEnd); + LIB_FUNCTION("Ms0cLK8sTtE", "libSceRudp", 1, "libSceRudp", sceRudpFlush); + LIB_FUNCTION("wIJsiqY+BMk", "libSceRudp", 1, "libSceRudp", sceRudpGetContextStatus); + LIB_FUNCTION("2G7-vVz9SIg", "libSceRudp", 1, "libSceRudp", sceRudpGetLocalInfo); + LIB_FUNCTION("vfrL8gPlm2Y", "libSceRudp", 1, "libSceRudp", sceRudpGetMaxSegmentSize); + LIB_FUNCTION("Px0miD2LuW0", "libSceRudp", 1, "libSceRudp", sceRudpGetNumberOfPacketsToRead); + LIB_FUNCTION("mCQIhSmCP6o", "libSceRudp", 1, "libSceRudp", sceRudpGetOption); + LIB_FUNCTION("Qignjmfgha0", "libSceRudp", 1, "libSceRudp", sceRudpGetRemoteInfo); + LIB_FUNCTION("sAZqO2+5Qqo", "libSceRudp", 1, "libSceRudp", sceRudpGetSizeReadable); + LIB_FUNCTION("fRc1ahQppR4", "libSceRudp", 1, "libSceRudp", sceRudpGetSizeWritable); + LIB_FUNCTION("i3STzxuwPx0", "libSceRudp", 1, "libSceRudp", sceRudpGetStatus); + LIB_FUNCTION("amuBfI-AQc4", "libSceRudp", 1, "libSceRudp", sceRudpInit); + LIB_FUNCTION("szEVu+edXV4", "libSceRudp", 1, "libSceRudp", sceRudpInitiate); + LIB_FUNCTION("tYVWcWDnctE", "libSceRudp", 1, "libSceRudp", sceRudpListen); + LIB_FUNCTION("+BJ9svDmjYs", "libSceRudp", 1, "libSceRudp", sceRudpNetFlush); + LIB_FUNCTION("vPzJldDSxXc", "libSceRudp", 1, "libSceRudp", sceRudpNetReceived); + LIB_FUNCTION("yzeXuww-UWg", "libSceRudp", 1, "libSceRudp", sceRudpPollCancel); + LIB_FUNCTION("haMpc7TFx0A", "libSceRudp", 1, "libSceRudp", sceRudpPollControl); + LIB_FUNCTION("MVbmLASjn5M", "libSceRudp", 1, "libSceRudp", sceRudpPollCreate); + LIB_FUNCTION("LjwbHpEeW0A", "libSceRudp", 1, "libSceRudp", sceRudpPollDestroy); + LIB_FUNCTION("M6ggviwXpLs", "libSceRudp", 1, "libSceRudp", sceRudpPollWait); + LIB_FUNCTION("9U9m1YH0ScQ", "libSceRudp", 1, "libSceRudp", sceRudpProcessEvents); + LIB_FUNCTION("rZqWV3eXgOA", "libSceRudp", 1, "libSceRudp", sceRudpRead); + LIB_FUNCTION("SUEVes8gvmw", "libSceRudp", 1, "libSceRudp", sceRudpSetEventHandler); + LIB_FUNCTION("beAsSTVWVPQ", "libSceRudp", 1, "libSceRudp", sceRudpSetMaxSegmentSize); + LIB_FUNCTION("0yzYdZf0IwE", "libSceRudp", 1, "libSceRudp", sceRudpSetOption); + LIB_FUNCTION("OMYRTU0uc4w", "libSceRudp", 1, "libSceRudp", sceRudpTerminate); + LIB_FUNCTION("KaPL3fbTLCA", "libSceRudp", 1, "libSceRudp", sceRudpWrite); +}; + +} // namespace Libraries::Rudp \ No newline at end of file diff --git a/src/core/libraries/rudp/rudp.h b/src/core/libraries/rudp/rudp.h new file mode 100644 index 000000000..22d2576a2 --- /dev/null +++ b/src/core/libraries/rudp/rudp.h @@ -0,0 +1,50 @@ +// 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::Rudp { + +s32 PS4_SYSV_ABI sceRudpAccept(); +s32 PS4_SYSV_ABI sceRudpActivate(); +s32 PS4_SYSV_ABI sceRudpBind(); +s32 PS4_SYSV_ABI sceRudpCreateContext(); +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread(); +s32 PS4_SYSV_ABI sceRudpEnableInternalIOThread2(); +s32 PS4_SYSV_ABI sceRudpEnd(); +s32 PS4_SYSV_ABI sceRudpFlush(); +s32 PS4_SYSV_ABI sceRudpGetContextStatus(); +s32 PS4_SYSV_ABI sceRudpGetLocalInfo(); +s32 PS4_SYSV_ABI sceRudpGetMaxSegmentSize(); +s32 PS4_SYSV_ABI sceRudpGetNumberOfPacketsToRead(); +s32 PS4_SYSV_ABI sceRudpGetOption(); +s32 PS4_SYSV_ABI sceRudpGetRemoteInfo(); +s32 PS4_SYSV_ABI sceRudpGetSizeReadable(); +s32 PS4_SYSV_ABI sceRudpGetSizeWritable(); +s32 PS4_SYSV_ABI sceRudpGetStatus(); +s32 PS4_SYSV_ABI sceRudpInit(); +s32 PS4_SYSV_ABI sceRudpInitiate(); +s32 PS4_SYSV_ABI sceRudpListen(); +s32 PS4_SYSV_ABI sceRudpNetFlush(); +s32 PS4_SYSV_ABI sceRudpNetReceived(); +s32 PS4_SYSV_ABI sceRudpPollCancel(); +s32 PS4_SYSV_ABI sceRudpPollControl(); +s32 PS4_SYSV_ABI sceRudpPollCreate(); +s32 PS4_SYSV_ABI sceRudpPollDestroy(); +s32 PS4_SYSV_ABI sceRudpPollWait(); +s32 PS4_SYSV_ABI sceRudpProcessEvents(); +s32 PS4_SYSV_ABI sceRudpRead(); +s32 PS4_SYSV_ABI sceRudpSetEventHandler(); +s32 PS4_SYSV_ABI sceRudpSetMaxSegmentSize(); +s32 PS4_SYSV_ABI sceRudpSetOption(); +s32 PS4_SYSV_ABI sceRudpTerminate(); +s32 PS4_SYSV_ABI sceRudpWrite(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Rudp \ No newline at end of file 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/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index a1edb3b68..121818f39 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -8,7 +8,7 @@ #include "core/libraries/videodec/videodec2_impl.h" #include "core/libraries/videodec/videodec_error.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { static constexpr u64 kMinimumMemorySize = 16_MB; ///> Fake minimum memory size for querying @@ -34,7 +34,35 @@ s32 PS4_SYSV_ABI sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, OrbisVideodec2ComputeQueue* computeQueue) { - LOG_INFO(Lib_Vdec2, "called"); + LOG_WARNING(Lib_Vdec2, "called"); + if (!computeCfgInfo || !computeMemInfo || !computeQueue) { + LOG_ERROR(Lib_Vdec2, "Invalid arguments"); + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (computeCfgInfo->thisSize != sizeof(OrbisVideodec2ComputeConfigInfo) || + computeMemInfo->thisSize != sizeof(OrbisVideodec2ComputeMemoryInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + if (computeCfgInfo->reserved0 != 0 || computeCfgInfo->reserved1 != 0) { + LOG_ERROR(Lib_Vdec2, "Invalid compute config"); + return ORBIS_VIDEODEC2_ERROR_CONFIG_INFO; + } + if (computeCfgInfo->computePipeId > 4) { + LOG_ERROR(Lib_Vdec2, "Invalid compute pipe id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_PIPE_ID; + } + if (computeCfgInfo->computeQueueId > 7) { + LOG_ERROR(Lib_Vdec2, "Invalid compute queue id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_QUEUE_ID; + } + if (!computeMemInfo->cpuGpuMemory) { + LOG_ERROR(Lib_Vdec2, "Invalid memory pointer"); + return ORBIS_VIDEODEC2_ERROR_MEMORY_POINTER; + } + + // The real library returns a pointer to memory inside cpuGpuMemory + *computeQueue = computeMemInfo->cpuGpuMemory; return ORBIS_OK; } @@ -233,4 +261,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideodec2GetPictureInfo); } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2.h b/src/core/libraries/videodec/videodec2.h index 0f080129f..0311e4d27 100644 --- a/src/core/libraries/videodec/videodec2.h +++ b/src/core/libraries/videodec/videodec2.h @@ -10,7 +10,7 @@ namespace Core::Loader { class SymbolsResolver; } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { class VdecDecoder; @@ -138,4 +138,4 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp void* p1stPictureInfo, void* p2ndPictureInfo); void RegisterLib(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 725d2335f..dcf6ae007 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -5,7 +5,7 @@ #include "common/types.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { struct OrbisVideodec2AvcPictureInfo { u64 thisSize; @@ -127,4 +127,4 @@ struct OrbisVideodec2LegacyAvcPictureInfo { }; static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 2ad3d7e19..08a7f8f00 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -9,7 +9,7 @@ #include "common/support/avdec.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { std::vector gPictureInfos; std::vector gLegacyPictureInfos; @@ -278,4 +278,4 @@ AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { return nv12_frame; } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index 7ee3339db..da16b3baa 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -13,7 +13,7 @@ extern "C" { #include } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { extern std::vector gPictureInfos; extern std::vector gLegacyPictureInfos; @@ -37,4 +37,4 @@ private: SwsContext* mSwsContext = nullptr; }; -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file 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 ac6b37769..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" @@ -104,19 +104,8 @@ void Linker::Execute(const std::vector& args) { memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2); - // Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init. - // Due to the large size of this mapping, failing to emulate it causes issues in some titles. - // This mapping belongs in the system reserved area, which starts at address 0x880000000. - static constexpr VAddr KernelAllocBase = 0x880000000ULL; - static constexpr s64 InternalMemorySize = 0x1000000; - void* addr_out{reinterpret_cast(KernelAllocBase)}; - - s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3, - 0, "SceKernelInternalMemory"); - ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping"); - main_thread.Run([this, module, &args](std::stop_token) { - Common::SetCurrentThreadName("GAME_MainThread"); + Common::SetCurrentThreadName("Game:Main"); if (auto& ipc = IPC::Instance()) { ipc.WaitForStart(); } @@ -191,8 +180,8 @@ s32 Linker::LoadAndStartModule(const std::filesystem::path& path, u64 args, cons } // Retrieve and verify proc param according to libkernel. - u64* param = module->GetProcParam(); - ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]); + auto* param = module->GetProcParam(); + ASSERT_MSG(!param || param->size >= 0x18, "Invalid module param size: {}", param->size); s32 ret = module->Start(args, argp, param); if (pRes) { *pRes = ret; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b9fd7fd7d..561e72617 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -5,6 +5,7 @@ #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,14 +59,14 @@ 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); @@ -75,6 +79,7 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { return size; } + std::shared_lock lk{mutex}; ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}", virtual_addr); @@ -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,54 @@ 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{mutex, unmap_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,35 +386,53 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 prot |= MemoryProt::CpuRead; } - // Carve out the new VMA representing this mapping - const auto new_vma_handle = CarveVMA(mapped_addr, size); + // 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.is_exec = false; + new_vma.phys_areas.clear(); - // Find a suitable physical address + // Find suitable physical addresses auto handle = dmem_map.begin(); - while (handle != dmem_map.end() && - (handle->second.dma_type != Core::DMAType::Pooled || handle->second.size < size)) { + u64 remaining_size = size; + VAddr current_addr = mapped_addr; + 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. + u64 size_to_map = std::min(remaining_size, handle->second.size); + + // Use the start of this area as the physical backing for this mapping. + 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 = PhysicalMemoryType::Committed; + new_dmem_area.memory_type = mtype; + + // 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); + current_addr += size_to_map; + remaining_size -= size_to_map; handle++; } - ASSERT_MSG(handle != dmem_map.end() && handle->second.dma_type == Core::DMAType::Pooled, - "No suitable physical memory areas to map"); + ASSERT_MSG(remaining_size == 0, "Failed to commit pooled memory"); - // Use the start of this area as the physical backing for this mapping. - const auto new_dmem_handle = CarveDmemArea(handle->second.base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Committed; - new_dmem_area.memory_type = mtype; - new_vma.phys_base = new_dmem_area.base; - MergeAdjacent(dmem_map, new_dmem_handle); - - // Perform the mapping - void* out_addr = impl.Map(mapped_addr, size, alignment, new_vma.phys_base, false); - TRACK_ALLOC(out_addr, size, "VMEM"); + // Merge this VMA with similar nearby areas + MergeAdjacent(vma_map, new_vma_handle); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); @@ -351,6 +441,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) { @@ -363,9 +494,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) { @@ -378,15 +509,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; @@ -394,136 +525,135 @@ 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::scoped_lock lk2{mutex}; + + // Create VMA representing this mapping. + auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment); auto& new_vma = new_vma_handle->second; + auto mapped_addr = new_vma.base; + bool is_exec = True(prot & MemoryProt::CpuExec); // 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"); + } + // 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; @@ -531,29 +661,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); @@ -572,189 +681,239 @@ 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}; - const auto it = FindVMA(virtual_addr); - const auto& vma_base = it->second; - ASSERT_MSG(vma_base.Contains(virtual_addr, size), - "Existing mapping does not contain requested unmap range"); - - const auto vma_base_addr = vma_base.base; - const auto vma_base_size = vma_base.size; - 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 type = vma_base.type; - - if (type != VMAType::PoolReserved && type != VMAType::Pooled) { - LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); - return ORBIS_KERNEL_ERROR_EINVAL; + // Do an initial search to ensure this decommit is valid. + auto it = FindVMA(virtual_addr); + while (it != vma_map.end() && it->second.base + it->second.size <= virtual_addr + size) { + if (it->second.type != VMAType::PoolReserved && it->second.type != VMAType::Pooled) { + LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + it++; } - if (type == VMAType::Pooled) { - // We always map PoolCommitted memory to GPU, so unmap when decomitting. - if (IsValidGpuMapping(virtual_addr, size)) { - rasterizer->UnmapMemory(virtual_addr, size); + // 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 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) { + // Track how much pooled memory is decommitted + pool_budget += size_in_vma; + + // Re-pool the direct memory used by this mapping + 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); + + // 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; + + // 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"); } - // Track how much pooled memory is decommitted - pool_budget += size; + // Mark region as pool reserved and attempt to coalesce it with neighbours. + const auto new_it = CarveVMA(current_addr, size_in_vma); + auto& vma = new_it->second; + vma.type = VMAType::PoolReserved; + vma.prot = MemoryProt::NoAccess; + vma.disallow_merge = false; + vma.name = "anon"; + vma.phys_areas.clear(); + MergeAdjacent(vma_map, new_it); - // Re-pool the direct memory used by this mapping - const auto unmap_phys_base = phys_base + start_in_vma; - const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Pooled; - - // Coalesce with nearby direct memory areas. - MergeAdjacent(dmem_map, new_dmem_handle); + current_addr += size_in_vma; + remaining_size -= size_in_vma; } - // Mark region as pool reserved and attempt to coalesce it with neighbours. - const auto new_it = CarveVMA(virtual_addr, 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"; - MergeAdjacent(vma_map, new_it); - - if (type != VMAType::PoolReserved) { - // Unmap the memory region. - impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, - is_exec, true, false); - TRACK_FREE(virtual_addr, "VMEM"); - } + // 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) { @@ -772,8 +931,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; @@ -791,6 +950,7 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr if (prot != nullptr) { *prot = static_cast(vma.prot); } + return ORBIS_OK; } @@ -798,6 +958,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. @@ -838,8 +1000,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. @@ -847,20 +1012,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. @@ -890,8 +1057,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; @@ -899,6 +1064,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) { @@ -919,9 +1086,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. @@ -930,33 +1100,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; } @@ -967,8 +1128,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++; } @@ -978,14 +1139,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; } @@ -1024,45 +1185,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++; } @@ -1070,32 +1232,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++; } } @@ -1106,8 +1272,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; } @@ -1119,6 +1286,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()) { @@ -1139,12 +1307,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; @@ -1152,7 +1319,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++; @@ -1163,6 +1330,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; } @@ -1232,6 +1400,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); @@ -1260,11 +1474,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; @@ -1274,38 +1488,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) { @@ -1318,13 +1508,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; @@ -1333,19 +1553,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 db988c305..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, @@ -45,18 +48,46 @@ enum class MemoryMapFlags : u32 { Private = 2, Fixed = 0x10, NoOverwrite = 0x80, + Void = 0x100, + Stack = 0x400, NoSync = 0x800, + Anon = 0x1000, NoCore = 0x20000, NoCoalesce = 0x400000, }; 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 { @@ -71,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; } @@ -138,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; @@ -217,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++; @@ -242,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); @@ -250,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); @@ -297,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); @@ -350,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/signals.cpp b/src/core/signals.cpp index 4099ac237..8df4edea8 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.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/arch.h" @@ -33,6 +33,10 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept { case EXCEPTION_ILLEGAL_INSTRUCTION: handled = signals->DispatchIllegalInstruction(pExp); break; + case DBG_PRINTEXCEPTION_C: + case DBG_PRINTEXCEPTION_WIDE_C: + // Used by OutputDebugString functions. + return EXCEPTION_CONTINUE_EXECUTION; default: break; } @@ -42,14 +46,6 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept { #else -static std::string GetThreadName() { - char name[256]; - if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) { - return ""; - } - return std::string{name}; -} - static std::string DisassembleInstruction(void* code_address) { char buffer[256] = ""; @@ -80,18 +76,16 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { case SIGBUS: { const bool is_write = Common::IsWriteError(raw_context); if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { - UNREACHABLE_MSG( - "Unhandled access violation in thread '{}' at code address {}: {} address {}", - GetThreadName(), fmt::ptr(code_address), is_write ? "Write to" : "Read from", - fmt::ptr(info->si_addr)); + UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}", + fmt::ptr(code_address), is_write ? "Write to" : "Read from", + fmt::ptr(info->si_addr)); } break; } case SIGILL: if (!signals->DispatchIllegalInstruction(raw_context)) { - UNREACHABLE_MSG("Unhandled illegal instruction in thread '{}' at code address {}: {}", - GetThreadName(), fmt::ptr(code_address), - DisassembleInstruction(code_address)); + UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}", + fmt::ptr(code_address), DisassembleInstruction(code_address)); } break; case SIGUSR1: { // Sleep thread until signal is received diff --git a/src/emulator.cpp b/src/emulator.cpp index f0026068c..6ba80b096 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 @@ -14,6 +14,7 @@ #include "common/debug.h" #include "common/logging/backend.h" #include "common/logging/log.h" +#include "common/thread.h" #include "core/ipc/ipc.h" #ifdef ENABLE_DISCORD_RPC #include "common/discord_rpc_handler.h" @@ -33,7 +34,9 @@ #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/font/font.h" #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" @@ -73,8 +76,28 @@ Emulator::Emulator() { Emulator::~Emulator() {} +s32 ReadCompiledSdkVersion(const std::filesystem::path& file) { + Core::Loader::Elf elf; + elf.Open(file); + if (!elf.IsElfFile()) { + return 0; + } + const auto elf_pheader = elf.GetProgramHeader(); + auto i_procparam = std::find_if(elf_pheader.begin(), elf_pheader.end(), [](const auto& entry) { + return entry.p_type == PT_SCE_PROCPARAM; + }); + + if (i_procparam != elf_pheader.end()) { + Core::OrbisProcParam param{}; + elf.LoadSegment(u64(¶m), i_procparam->p_offset, i_procparam->p_filesz); + return param.sdk_version; + } + return 0; +} + void Emulator::Run(std::filesystem::path file, std::vector args, std::optional p_game_folder) { + Common::SetCurrentThreadName("shadPS4:Main"); if (waitForDebuggerBeforeRun) { Debugger::WaitForDebuggerAttach(); } @@ -160,6 +183,9 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } } + auto guest_eboot_path = "/app0/" + eboot_name.generic_string(); + const auto eboot_path = mnt->GetHostPath(guest_eboot_path); + auto& game_info = Common::ElfInfo::Instance(); game_info.initialized = true; game_info.game_serial = id; @@ -167,7 +193,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, game_info.app_ver = app_version; game_info.firmware_ver = fw_version & 0xFFF00000; game_info.raw_firmware_ver = fw_version; - game_info.sdk_ver = sdk_version; + game_info.sdk_ver = ReadCompiledSdkVersion(eboot_path); game_info.psf_attributes = psf_attributes; const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png"); @@ -239,7 +265,8 @@ void Emulator::Run(std::filesystem::path file, std::vector args, if (param_sfo_exists) { LOG_INFO(Loader, "Game id: {} Title: {}", id, title); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); - LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version); + LOG_INFO(Loader, "param.sfo SDK version: {:#x}", sdk_version); + LOG_INFO(Loader, "eboot SDK version: {:#x}", game_info.sdk_ver); LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value()); LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value()); } @@ -337,8 +364,6 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Libraries::InitHLELibs(&linker->GetHLESymbols()); // Load the module with the linker - auto guest_eboot_path = "/app0/" + eboot_name.generic_string(); - const auto eboot_path = mnt->GetHostPath(guest_eboot_path); if (linker->LoadModule(eboot_path) == -1) { LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}", Common::FS::PathToUTF8String(std::filesystem::absolute(eboot_path))); @@ -500,10 +525,15 @@ void Emulator::LoadSystemModules(const std::string& game_serial) { constexpr auto ModulesToLoad = std::to_array( {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib}, {"libSceUlt.sprx", nullptr}, + {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib}, + {"libSceJpegDec.sprx", nullptr}, + {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib}, + {"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/input_mouse.cpp b/src/input/input_mouse.cpp index cbb07721b..996c35ef9 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -70,7 +70,7 @@ void EmulateJoystick(GameController* controller, u32 interval) { SDL_GetRelativeMouseState(&d_x, &d_y); float output_speed = - SDL_clamp((sqrt(d_x * d_x + d_y * d_y) + mouse_speed_offset * 128) * mouse_speed, + SDL_clamp(sqrt(d_x * d_x + d_y * d_y) * mouse_speed + mouse_speed_offset * 128, mouse_deadzone_offset * 128, 128.0); float angle = atan2(d_y, d_x); diff --git a/src/main.cpp b/src/main.cpp index f1e5ce932..d3799e2ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,17 @@ -// 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 +#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" @@ -26,242 +28,176 @@ int main(int argc, char* argv[]) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); #endif + IPC::Instance().Init(); - // 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" - " -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]); - }}}; + 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.", 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/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/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 94cefb958..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])}; @@ -1050,7 +1136,14 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { - ASSERT(inst.src[1].field == OperandField::ConstZero); + const bool is_zero = inst.src[1].field == OperandField::ConstZero; + const bool is_neg_one = inst.src[1].field == OperandField::SignedConstIntNeg; + ASSERT(is_zero || is_neg_one); + if (is_neg_one) { + ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1, + "SignedConstIntNeg must be -1"); + } + const IR::U1 src0 = [&] { switch (inst.src[0].field) { case OperandField::ScalarGPR: @@ -1064,10 +1157,11 @@ void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const const IR::U1 result = [&] { switch (op) { case ConditionOp::EQ: - return ir.LogicalNot(src0); + return is_zero ? ir.LogicalNot(src0) : src0; case ConditionOp::LG: // NE - return src0; + return is_zero ? src0 : ir.LogicalNot(src0); case ConditionOp::GT: + ASSERT(is_zero); return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))); default: UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); @@ -1349,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_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index ca7d09c52..44aa79d98 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -299,6 +299,7 @@ bool Instance::CreateDevice() { amd_shader_trinary_minmax = add_extension(VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME); nv_framebuffer_mixed_samples = add_extension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME); amd_mixed_attachment_samples = add_extension(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME); + shader_atomic_float = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME); shader_atomic_float2 = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_2_EXTENSION_NAME); if (shader_atomic_float2) { shader_atomic_float2_features = diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 901874354..603f0fba1 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -494,6 +494,7 @@ private: bool amd_shader_trinary_minmax{}; bool nv_framebuffer_mixed_samples{}; bool amd_mixed_attachment_samples{}; + bool shader_atomic_float{}; bool shader_atomic_float2{}; bool workgroup_memory_explicit_layout{}; bool portability_subset{}; 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 d76fbb99d..2c3c70ca9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1022,6 +1022,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};