mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-25 12:15:32 -06:00
Merge remote-tracking branch 'upstream/main' into feature/sceRudpGetStatus
This commit is contained in:
commit
14174cdc26
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -3,7 +3,16 @@
|
||||
|
||||
name: Build and Release
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "documents/**"
|
||||
- "**/*.md"
|
||||
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "documents/**"
|
||||
- "**/*.md"
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name }}-${{ github.ref }}
|
||||
|
||||
11
.gitmodules
vendored
11
.gitmodules
vendored
@ -2,10 +2,6 @@
|
||||
path = externals/zlib-ng
|
||||
url = https://github.com/shadps4-emu/ext-zlib-ng.git
|
||||
shallow = true
|
||||
[submodule "externals/sdl3"]
|
||||
path = externals/sdl3
|
||||
url = https://github.com/shadps4-emu/ext-SDL.git
|
||||
shallow = true
|
||||
[submodule "externals/fmt"]
|
||||
path = externals/fmt
|
||||
url = https://github.com/shadps4-emu/ext-fmt.git
|
||||
@ -123,3 +119,10 @@
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
[submodule "externals/CLI11"]
|
||||
path = externals/CLI11
|
||||
url = https://github.com/shadexternals/CLI11.git
|
||||
[submodule "externals/sdl3"]
|
||||
path = externals/sdl3
|
||||
url = https://github.com/shadexternals/sdl3.git
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE
|
||||
@ -202,7 +202,7 @@ execute_process(
|
||||
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "13")
|
||||
set(EMULATOR_VERSION_MINOR "14")
|
||||
set(EMULATOR_VERSION_PATCH "1")
|
||||
|
||||
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
||||
@ -221,6 +221,7 @@ endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
find_package(Boost 1.84.0 CONFIG)
|
||||
find_package(CLI11 2.6.1 CONFIG)
|
||||
find_package(FFmpeg 5.1.2 MODULE)
|
||||
find_package(fmt 10.2.0 CONFIG)
|
||||
find_package(glslang 15 CONFIG)
|
||||
@ -281,15 +282,16 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||
|
||||
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||
src/core/libraries/audio/audioin.h
|
||||
src/core/libraries/audio/sdl_in.h
|
||||
src/core/libraries/audio/sdl_in.cpp
|
||||
src/core/libraries/audio/audioin_backend.h
|
||||
src/core/libraries/audio/audioin_error.h
|
||||
src/core/libraries/audio/sdl_audio_in.cpp
|
||||
src/core/libraries/voice/voice.cpp
|
||||
src/core/libraries/voice/voice.h
|
||||
src/core/libraries/audio/audioout.cpp
|
||||
src/core/libraries/audio/audioout.h
|
||||
src/core/libraries/audio/audioout_backend.h
|
||||
src/core/libraries/audio/audioout_error.h
|
||||
src/core/libraries/audio/sdl_audio.cpp
|
||||
src/core/libraries/audio/sdl_audio_out.cpp
|
||||
src/core/libraries/ngs2/ngs2.cpp
|
||||
src/core/libraries/ngs2/ngs2.h
|
||||
)
|
||||
@ -494,6 +496,8 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp
|
||||
src/core/libraries/libc_internal/libc_internal_str.h
|
||||
src/core/libraries/libc_internal/libc_internal_math.cpp
|
||||
src/core/libraries/libc_internal/libc_internal_math.h
|
||||
src/core/libraries/libc_internal/libc_internal_threads.cpp
|
||||
src/core/libraries/libc_internal/libc_internal_threads.h
|
||||
src/core/libraries/libc_internal/printf.h
|
||||
)
|
||||
|
||||
@ -524,6 +528,9 @@ set(SYSTEM_GESTURE_LIB
|
||||
set(PNG_LIB src/core/libraries/libpng/pngdec.cpp
|
||||
src/core/libraries/libpng/pngdec.h
|
||||
src/core/libraries/libpng/pngdec_error.h
|
||||
src/core/libraries/libpng/pngenc.cpp
|
||||
src/core/libraries/libpng/pngenc.h
|
||||
src/core/libraries/libpng/pngenc_error.h
|
||||
)
|
||||
|
||||
set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h
|
||||
@ -581,14 +588,21 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_commerce.h
|
||||
src/core/libraries/np/np_manager.cpp
|
||||
src/core/libraries/np/np_manager.h
|
||||
src/core/libraries/np/np_matching2.cpp
|
||||
src/core/libraries/np/np_matching2.h
|
||||
src/core/libraries/np/np_score.cpp
|
||||
src/core/libraries/np/np_score.h
|
||||
src/core/libraries/np/np_trophy.cpp
|
||||
src/core/libraries/np/np_trophy.h
|
||||
src/core/libraries/np/np_tus.cpp
|
||||
src/core/libraries/np/np_tus.h
|
||||
src/core/libraries/np/trophy_ui.cpp
|
||||
src/core/libraries/np/trophy_ui.h
|
||||
src/core/libraries/np/np_web_api.cpp
|
||||
src/core/libraries/np/np_web_api.h
|
||||
src/core/libraries/np/np_web_api_error.h
|
||||
src/core/libraries/np/np_web_api_internal.cpp
|
||||
src/core/libraries/np/np_web_api_internal.h
|
||||
src/core/libraries/np/np_web_api2.cpp
|
||||
src/core/libraries/np/np_web_api2.h
|
||||
src/core/libraries/np/np_party.cpp
|
||||
@ -599,6 +613,9 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_profile_dialog.h
|
||||
src/core/libraries/np/np_sns_facebook_dialog.cpp
|
||||
src/core/libraries/np/np_sns_facebook_dialog.h
|
||||
src/core/libraries/np/np_partner.cpp
|
||||
src/core/libraries/np/np_partner.h
|
||||
src/core/libraries/np/object_manager.h
|
||||
)
|
||||
|
||||
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp
|
||||
@ -744,6 +761,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/memory_patcher.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
|
||||
src/common/scm_rev.h
|
||||
src/common/key_manager.cpp
|
||||
src/common/key_manager.h
|
||||
)
|
||||
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
@ -787,6 +806,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/file_format/playgo_chunk.h
|
||||
src/core/file_format/trp.cpp
|
||||
src/core/file_format/trp.h
|
||||
src/core/file_format/npbind.cpp
|
||||
src/core/file_format/npbind.h
|
||||
src/core/file_sys/fs.cpp
|
||||
src/core/file_sys/fs.h
|
||||
src/core/ipc/ipc.cpp
|
||||
@ -912,6 +933,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
|
||||
src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
|
||||
src/shader_recompiler/ir/passes/hull_shader_transform.cpp
|
||||
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
|
||||
src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp
|
||||
src/shader_recompiler/ir/passes/ir_passes.h
|
||||
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
|
||||
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp
|
||||
@ -1093,7 +1115,7 @@ create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
|
||||
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
@ -58,6 +58,11 @@ This project began for fun. Given our limited free time, it may take some time b
|
||||
|
||||
# Building
|
||||
|
||||
## Docker
|
||||
|
||||
For building shadPS4 in a containerized environment using Docker and VSCode, check the instructions here:
|
||||
[**Docker Build Instructions**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-docker.md)
|
||||
|
||||
## Windows
|
||||
|
||||
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).
|
||||
@ -150,7 +155,7 @@ The following firmware modules are supported and must be placed in shadPS4's `sy
|
||||
| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx |
|
||||
| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx |
|
||||
| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx |
|
||||
| libSceUlt.sprx | | | |
|
||||
| libSceUlt.sprx | libSceAudiodec.sprx | | |
|
||||
</div>
|
||||
|
||||
> [!Caution]
|
||||
|
||||
@ -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",
|
||||
|
||||
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -38,6 +38,9 @@
|
||||
<category translate="no">Game</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="0.14.0" date="2026-02-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0</url>
|
||||
</release>
|
||||
<release version="0.13.0" date="2025-12-24">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
|
||||
</release>
|
||||
|
||||
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
{
|
||||
"name": "shadPS4-dev",
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml"
|
||||
],
|
||||
"containerEnv": {
|
||||
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
|
||||
"GITHUB_USER": "${localEnv:GITHUB_USER}"
|
||||
},
|
||||
"service": "shadps4",
|
||||
"workspaceFolder": "/workspaces/shadPS4",
|
||||
"remoteUser": "root",
|
||||
"shutdownAction": "none",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"xaver.clang-format"
|
||||
],
|
||||
"settings": {
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--clang-tidy",
|
||||
"--completion-style=detailed",
|
||||
"--header-insertion=never",
|
||||
"--compile-commands-dir=/workspaces/shadPS4/Build/x64-Clang-Release"
|
||||
],
|
||||
"C_Cpp.intelliSenseEngine": "Disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false,
|
||||
"cmake.generator": "Unix Makefiles",
|
||||
"cmake.environment": {
|
||||
"CC": "clang",
|
||||
"CXX": "clang++"
|
||||
},
|
||||
"cmake.configureEnvironment": {
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"clang-format.executable": "clang-format-19"
|
||||
}
|
||||
}
|
||||
45
documents/Docker Builder/.docker/Dockerfile
Normal file
45
documents/Docker Builder/.docker/Dockerfile
Normal file
@ -0,0 +1,45 @@
|
||||
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
FROM archlinux:latest
|
||||
|
||||
RUN pacman-key --init && \
|
||||
pacman-key --populate archlinux && \
|
||||
pacman -Syu --noconfirm
|
||||
|
||||
RUN pacman -S --noconfirm \
|
||||
base-devel \
|
||||
clang \
|
||||
clang19 \
|
||||
ninja \
|
||||
git \
|
||||
ca-certificates \
|
||||
wget \
|
||||
alsa-lib \
|
||||
libpulse \
|
||||
openal \
|
||||
openssl \
|
||||
zlib \
|
||||
libedit \
|
||||
systemd-libs \
|
||||
libevdev \
|
||||
sdl2 \
|
||||
jack \
|
||||
sndio \
|
||||
libxtst \
|
||||
vulkan-headers \
|
||||
vulkan-validation-layers \
|
||||
libpng \
|
||||
clang-tools-extra \
|
||||
cmake \
|
||||
libx11 \
|
||||
libxrandr \
|
||||
libxcursor \
|
||||
libxi \
|
||||
libxinerama \
|
||||
libxss \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
RUN ln -sf /usr/lib/llvm19/bin/clang-format /usr/bin/clang-format-19
|
||||
|
||||
WORKDIR /workspaces/shadPS4
|
||||
10
documents/Docker Builder/docker-compose.yml
Normal file
10
documents/Docker Builder/docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
services:
|
||||
shadps4:
|
||||
build:
|
||||
context: ./.docker
|
||||
volumes:
|
||||
- ./emu:/workspaces/shadPS4:cached
|
||||
tty: true
|
||||
BIN
documents/Screenshots/Windows/vscode-ext-1.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
documents/Screenshots/Windows/vscode-ext-2.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/Screenshots/Windows/vscode-ext-3.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
100
documents/building-docker.md
Normal file
100
documents/building-docker.md
Normal file
@ -0,0 +1,100 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
# Building shadPS4 with Docker and VSCode Support
|
||||
|
||||
This guide explains how to build **shadPS4** using Docker while keeping full compatibility with **VSCode** development.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
- **Docker Engine** or **Docker Desktop** installed
|
||||
[Installation Guide](https://docs.docker.com/engine/install/)
|
||||
|
||||
- **Git** installed on your system.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Prepare the Docker Environment
|
||||
|
||||
Inside the container (or on your host if mounting volumes):
|
||||
|
||||
1. Navigate to the repository folder containing the Docker Builder folder:
|
||||
|
||||
```bash
|
||||
cd <path-to-repo>
|
||||
```
|
||||
|
||||
2. Start the Docker container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will spin up a container with all the necessary build dependencies, including Clang, CMake, SDL2, Vulkan, and more.
|
||||
|
||||
## Step 2: Clone shadPS4 Source
|
||||
|
||||
```bash
|
||||
mkdir emu
|
||||
cd emu
|
||||
git clone --recursive https://github.com/shadps4-emu/shadPS4.git .
|
||||
|
||||
or your fork link.
|
||||
```
|
||||
|
||||
3. Initialize submodules:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Step 3: Build with CMake Tools (GUI)
|
||||
|
||||
Generate build with CMake Tools.
|
||||
|
||||
1. Go `CMake Tools > Configure > '>'`
|
||||
2. And `Build > '>'`
|
||||
|
||||
Compiled executable in `Build` folder.
|
||||
|
||||
## Alternative Step 3: Build with CMake
|
||||
|
||||
Generate the build directory and configure the project using Clang:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
```
|
||||
|
||||
Then build the project:
|
||||
|
||||
```bash
|
||||
cmake --build ./build --parallel $(nproc)
|
||||
```
|
||||
|
||||
* Tip: To enable debug builds, add -DCMAKE_BUILD_TYPE=Debug to the CMake command.
|
||||
|
||||
---
|
||||
|
||||
After a successful build, the executable is located at:
|
||||
|
||||
```bash
|
||||
./build/shadps4
|
||||
```
|
||||
|
||||
## Step 4: VSCode Integration
|
||||
|
||||
1. Open the repository in VSCode.
|
||||
2. The CMake Tools extension should automatically detect the build directory inside the container or on your host.
|
||||
3. You can configure build options, build, and debug directly from the VSCode interface without extra manual setup.
|
||||
|
||||
# Notes
|
||||
|
||||
* The Docker environment contains all dependencies, so you don’t need to install anything manually.
|
||||
* Using Clang inside Docker ensures consistent builds across Linux and macOS runners.
|
||||
* GitHub Actions are recommended for cross-platform builds, including Windows .exe output, which is not trivial to produce locally without Visual Studio or clang-cl.
|
||||
@ -41,10 +41,171 @@ Go through the Git for Windows installation as normal
|
||||
|
||||
Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\`
|
||||
|
||||
## Option 2: MSYS2/MinGW
|
||||
## Option 2: VSCode with Visual Studio Build Tools
|
||||
|
||||
If your default IDE is VSCode, we have a fully functional example for that as well.
|
||||
|
||||
### Requirements
|
||||
|
||||
* [**Git for Windows**](https://git-scm.com/download/win)
|
||||
* [**LLVM 19.1.1**](https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.1/LLVM-19.1.1-win64.exe)
|
||||
* [**CMake 4.2.3 or newer**](https://github.com/Kitware/CMake/releases/download/v4.2.3/cmake-4.2.3-windows-x86_64.msi)
|
||||
* [**Ninja 1.13.2 or newer**](https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-win.zip)
|
||||
|
||||
**The main reason we use clang19 is because that version is used in CI for formatting.**
|
||||
|
||||
### Installs
|
||||
|
||||
1. Go through the Git for Windows installation as normal
|
||||
2. Download and Run LLVM Installer and `Add LLVM to the system PATH for all users`
|
||||
3. Download and Run CMake Installer and `Add CMake to the system PATH for all users`
|
||||
4. Download Ninja and extract it to `C:\ninja` and add it to the system PATH for all users
|
||||
* You can do this by going to `Search with Start Menu -> Environment Variables -> System Variables -> Path -> Edit -> New -> C:\ninja`
|
||||
|
||||
### Validate the installs
|
||||
|
||||
```bash
|
||||
git --version
|
||||
# git version 2.49.0.windows.1
|
||||
|
||||
cmake --version
|
||||
# cmake version 4.2.3
|
||||
|
||||
ninja --version
|
||||
# 1.13.2
|
||||
|
||||
clang --version
|
||||
# clang version 19.1.1
|
||||
```
|
||||
|
||||
### Install Visual Studio Build Tools
|
||||
|
||||
1. Download [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||
2. Select `MSVC - Windows SDK` and install (you don't need to install an IDE)
|
||||
|
||||
* Or you can install via `.vsconfig` file:
|
||||
|
||||
```
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
|
||||
"Microsoft.Component.MSBuild",
|
||||
"Microsoft.VisualStudio.Component.CoreBuildTools",
|
||||
"Microsoft.VisualStudio.Workload.MSBuildTools",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK",
|
||||
"Microsoft.VisualStudio.Component.VC.CoreBuildTools",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
|
||||
"Microsoft.VisualStudio.Component.Windows11SDK.26100",
|
||||
"Microsoft.VisualStudio.Component.TestTools.BuildTools",
|
||||
"Microsoft.VisualStudio.Component.VC.ASAN",
|
||||
"Microsoft.VisualStudio.Component.TextTemplating",
|
||||
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
"Microsoft.VisualStudio.Workload.VCTools"
|
||||
],
|
||||
"extensions": []
|
||||
}
|
||||
|
||||
Save the file as `.vsconfig` and run the following command:
|
||||
|
||||
%userprofile%\Downloads\vs_BuildTools.exe --passive --config ".vsconfig"
|
||||
|
||||
Be carefull path to vs_BuildTools.exe and .vsconfig file.
|
||||
```
|
||||
|
||||
__This will install the necessary components to build shadPS4.__
|
||||
|
||||
### Project structure
|
||||
|
||||
```
|
||||
shadps4/
|
||||
├── shared (shadps4 main files)
|
||||
└── shadps4.code-workspace
|
||||
```
|
||||
|
||||
### Content of `shadps4.code-workspace`
|
||||
|
||||
```json
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "shared"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"cmake.generator": "Ninja",
|
||||
|
||||
"cmake.configureEnvironment": {
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
},
|
||||
|
||||
"cmake.configureOnOpen": false,
|
||||
|
||||
"C_Cpp.intelliSenseEngine": "Disabled",
|
||||
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--clang-tidy",
|
||||
"--completion-style=detailed",
|
||||
"--header-insertion=never",
|
||||
"--compile-commands-dir=Build/x64-Clang-Release"
|
||||
],
|
||||
|
||||
"editor.formatOnSave": true,
|
||||
"clang-format.executable": "clang-format"
|
||||
},
|
||||
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"xaver.clang-format"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cloning the source code
|
||||
|
||||
1. Open your terminal and where to shadPS4 folder: `cd shadps4\shared`
|
||||
3. Clone the repository by running
|
||||
`git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4 .`
|
||||
|
||||
_or fork link_
|
||||
|
||||
* If you have already cloned repo:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Requirements VSCode extensions
|
||||
1. CMake Tools
|
||||
2. Clangd
|
||||
3. Clang-Format
|
||||
|
||||
_These plugins are suggested in the workspace file above and are already configured._
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Building
|
||||
1. Open VS Code, `File > Open workspace from file > shadps4.code-workspace`
|
||||
2. Go to the CMake Tools extension on left side bar
|
||||
3. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build.
|
||||
4. Click build.
|
||||
|
||||
Your shadps4.exe will be in `shadps4\shared\Build\x64-Clang-Release\`
|
||||
|
||||
## Option 3: MSYS2/MinGW
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022).
|
||||
> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022) or [Option 2: VSCode with Visual Studio Build Tools](#option-2-vscode-with-visual-studio-build-tools).
|
||||
|
||||
### (Prerequisite) Download [**MSYS2**](https://www.msys2.org/)
|
||||
|
||||
|
||||
1
externals/CLI11
vendored
Submodule
1
externals/CLI11
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit bf5a16a26a34a9a7ad75f4a7705585e44675fef0
|
||||
10
externals/CMakeLists.txt
vendored
10
externals/CMakeLists.txt
vendored
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
@ -204,6 +204,7 @@ add_subdirectory(tracy)
|
||||
|
||||
# pugixml
|
||||
if (NOT TARGET pugixml::pugixml)
|
||||
option(PUGIXML_NO_EXCEPTIONS "" ON)
|
||||
add_subdirectory(pugixml)
|
||||
endif()
|
||||
|
||||
@ -268,3 +269,10 @@ add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
# cli11
|
||||
if (NOT TARGET CLI11::CLI11)
|
||||
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(CLI11)
|
||||
endif()
|
||||
|
||||
2
externals/MoltenVK
vendored
2
externals/MoltenVK
vendored
@ -1 +1 @@
|
||||
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
|
||||
Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030
|
||||
@ -1,7 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <fmt/core.h>
|
||||
@ -14,6 +15,8 @@
|
||||
#include "common/path_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
|
||||
#include "input/input_handler.h"
|
||||
|
||||
using std::nullopt;
|
||||
using std::optional;
|
||||
using std::string;
|
||||
@ -361,7 +364,7 @@ u32 getWindowHeight() {
|
||||
}
|
||||
|
||||
u32 getInternalScreenWidth() {
|
||||
return internalScreenHeight.get();
|
||||
return internalScreenWidth.get();
|
||||
}
|
||||
|
||||
u32 getInternalScreenHeight() {
|
||||
@ -1289,16 +1292,6 @@ void setDefaultValues(bool is_game_specific) {
|
||||
constexpr std::string_view GetDefaultGlobalConfig() {
|
||||
return R"(# Anything put here will be loaded for all games,
|
||||
# alongside the game's config or default.ini depending on your preference.
|
||||
|
||||
hotkey_renderdoc_capture = f12
|
||||
hotkey_fullscreen = f11
|
||||
hotkey_show_fps = f10
|
||||
hotkey_pause = f9
|
||||
hotkey_reload_inputs = f8
|
||||
hotkey_toggle_mouse_to_joystick = f7
|
||||
hotkey_toggle_mouse_to_gyro = f6
|
||||
hotkey_toggle_mouse_to_touchpad = delete
|
||||
hotkey_quit = lctrl, lshift, end
|
||||
)";
|
||||
}
|
||||
|
||||
@ -1376,7 +1369,7 @@ analog_deadzone = rightjoystick, 2, 127
|
||||
override_controller_color = false, 0, 0, 255
|
||||
)";
|
||||
}
|
||||
std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) {
|
||||
std::filesystem::path GetInputConfigFile(const string& game_id) {
|
||||
// Read configuration file of the game, and if it doesn't exist, generate it from default
|
||||
// If that doesn't exist either, generate that from getDefaultConfig() and try again
|
||||
// If even the folder is missing, we start with that.
|
||||
@ -1415,6 +1408,39 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (game_id == "global") {
|
||||
std::map<string, string> default_bindings_to_add = {
|
||||
{"hotkey_renderdoc_capture", "f12"},
|
||||
{"hotkey_fullscreen", "f11"},
|
||||
{"hotkey_show_fps", "f10"},
|
||||
{"hotkey_pause", "f9"},
|
||||
{"hotkey_reload_inputs", "f8"},
|
||||
{"hotkey_toggle_mouse_to_joystick", "f7"},
|
||||
{"hotkey_toggle_mouse_to_gyro", "f6"},
|
||||
{"hotkey_toggle_mouse_to_touchpad", "delete"},
|
||||
{"hotkey_quit", "lctrl, lshift, end"},
|
||||
{"hotkey_volume_up", "kpplus"},
|
||||
{"hotkey_volume_down", "kpminus"},
|
||||
};
|
||||
std::ifstream global_in(config_file);
|
||||
string line;
|
||||
while (std::getline(global_in, line)) {
|
||||
line.erase(std::remove_if(line.begin(), line.end(),
|
||||
[](unsigned char c) { return std::isspace(c); }),
|
||||
line.end());
|
||||
std::size_t equal_pos = line.find('=');
|
||||
if (equal_pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
std::string output_string = line.substr(0, equal_pos);
|
||||
default_bindings_to_add.erase(output_string);
|
||||
}
|
||||
global_in.close();
|
||||
std::ofstream global_out(config_file, std::ios::app);
|
||||
for (auto const& b : default_bindings_to_add) {
|
||||
global_out << b.first << " = " << b.second << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// If game-specific config doesn't exist, create it from the default config
|
||||
if (!std::filesystem::exists(config_file)) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -183,6 +183,6 @@ std::filesystem::path getAddonInstallDir();
|
||||
void setDefaultValues(bool is_game_specific = false);
|
||||
|
||||
constexpr std::string_view GetDefaultGlobalConfig();
|
||||
std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = "");
|
||||
std::filesystem::path GetInputConfigFile(const std::string& game_id = "");
|
||||
|
||||
}; // namespace Config
|
||||
|
||||
@ -75,6 +75,7 @@ class ElfInfo {
|
||||
std::filesystem::path game_folder{};
|
||||
|
||||
public:
|
||||
static constexpr u32 FW_10 = 0x1000000;
|
||||
static constexpr u32 FW_15 = 0x1500000;
|
||||
static constexpr u32 FW_16 = 0x1600000;
|
||||
static constexpr u32 FW_17 = 0x1700000;
|
||||
|
||||
161
src/common/key_manager.cpp
Normal file
161
src/common/key_manager.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include "common/logging/log.h"
|
||||
#include "key_manager.h"
|
||||
#include "path_util.h"
|
||||
|
||||
std::shared_ptr<KeyManager> KeyManager::s_instance = nullptr;
|
||||
std::mutex KeyManager::s_mutex;
|
||||
|
||||
// ------------------- Constructor & Singleton -------------------
|
||||
KeyManager::KeyManager() {
|
||||
SetDefaultKeys();
|
||||
}
|
||||
KeyManager::~KeyManager() {
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
std::shared_ptr<KeyManager> KeyManager::GetInstance() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<KeyManager>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void KeyManager::SetInstance(std::shared_ptr<KeyManager> instance) {
|
||||
std::lock_guard<std::mutex> 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<AllKeys>(); // deserialize back
|
||||
}
|
||||
|
||||
// ------------------- Defaults / Checks -------------------
|
||||
void KeyManager::SetDefaultKeys() {
|
||||
m_keys = AllKeys{};
|
||||
}
|
||||
|
||||
bool KeyManager::HasKeys() const {
|
||||
return !m_keys.TrophyKeySet.ReleaseTrophyKey.empty();
|
||||
}
|
||||
|
||||
// ------------------- Hex conversion -------------------
|
||||
std::vector<u8> KeyManager::HexStringToBytes(const std::string& hexStr) {
|
||||
std::vector<u8> 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<u8>& 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;
|
||||
}
|
||||
79
src/common/key_manager.h
Normal file
79
src/common/key_manager.h
Normal file
@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
// ------------------- Nested keysets -------------------
|
||||
struct TrophyKeySet {
|
||||
std::vector<u8> ReleaseTrophyKey;
|
||||
};
|
||||
|
||||
struct AllKeys {
|
||||
KeyManager::TrophyKeySet TrophyKeySet;
|
||||
};
|
||||
|
||||
// ------------------- Construction -------------------
|
||||
KeyManager();
|
||||
~KeyManager();
|
||||
|
||||
// ------------------- Singleton -------------------
|
||||
static std::shared_ptr<KeyManager> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<KeyManager> 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<u8> HexStringToBytes(const std::string& hexStr);
|
||||
static std::string BytesToHexString(const std::vector<u8>& bytes);
|
||||
|
||||
private:
|
||||
void KeysToJson(json& j) const;
|
||||
void JsonToKeys(const json& j);
|
||||
|
||||
AllKeys m_keys{};
|
||||
|
||||
static std::shared_ptr<KeyManager> 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<std::vector<u8>> {
|
||||
static void to_json(json& j, const std::vector<u8>& vec) {
|
||||
j = KeyManager::BytesToHexString(vec);
|
||||
}
|
||||
static void from_json(const json& j, std::vector<u8>& vec) {
|
||||
vec = KeyManager::HexStringToBytes(j.get<std::string>());
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@ -208,26 +209,41 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock entry_loc(_mutex);
|
||||
|
||||
if (_last_entry.message == message) {
|
||||
++_last_entry.counter;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_last_entry.counter >= 2) {
|
||||
_last_entry.message += " x" + std::to_string(_last_entry.counter);
|
||||
}
|
||||
|
||||
if (_last_entry.counter >= 1) {
|
||||
if (Config::getLogType() == "async") {
|
||||
message_queue.EmplaceWait(_last_entry);
|
||||
} else {
|
||||
ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); });
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::steady_clock;
|
||||
|
||||
const Entry entry = {
|
||||
this->_last_entry = {
|
||||
.timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
|
||||
.log_class = log_class,
|
||||
.log_level = log_level,
|
||||
.filename = filename,
|
||||
.line_num = line_num,
|
||||
.function = function,
|
||||
.message = std::move(message),
|
||||
.message = message,
|
||||
.thread = Common::GetCurrentThreadName(),
|
||||
.counter = 1,
|
||||
};
|
||||
if (Config::getLogType() == "async") {
|
||||
message_queue.EmplaceWait(entry);
|
||||
} else {
|
||||
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@ -259,6 +275,22 @@ private:
|
||||
}
|
||||
|
||||
void StopBackendThread() {
|
||||
// log last message
|
||||
if (_last_entry.counter >= 2) {
|
||||
_last_entry.message += " x" + std::to_string(_last_entry.counter);
|
||||
}
|
||||
|
||||
if (_last_entry.counter >= 1) {
|
||||
if (Config::getLogType() == "async") {
|
||||
message_queue.EmplaceWait(_last_entry);
|
||||
} else {
|
||||
ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); });
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
this->_last_entry = {};
|
||||
|
||||
backend_thread.request_stop();
|
||||
if (backend_thread.joinable()) {
|
||||
backend_thread.join();
|
||||
@ -292,6 +324,8 @@ private:
|
||||
MPSCQueue<Entry> message_queue{};
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
std::jthread backend_thread;
|
||||
Entry _last_entry;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@ -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 <algorithm>
|
||||
@ -106,12 +107,15 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, NpCommon) \
|
||||
SUB(Lib, NpCommerce) \
|
||||
SUB(Lib, NpManager) \
|
||||
SUB(Lib, NpMatching2) \
|
||||
SUB(Lib, NpScore) \
|
||||
SUB(Lib, NpTrophy) \
|
||||
SUB(Lib, NpTus) \
|
||||
SUB(Lib, NpWebApi) \
|
||||
SUB(Lib, NpWebApi2) \
|
||||
SUB(Lib, NpProfileDialog) \
|
||||
SUB(Lib, NpSnsFacebookDialog) \
|
||||
SUB(Lib, NpPartner) \
|
||||
SUB(Lib, Screenshot) \
|
||||
SUB(Lib, LibCInternal) \
|
||||
SUB(Lib, AppContent) \
|
||||
@ -159,6 +163,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
CLS(ImGui) \
|
||||
CLS(Input) \
|
||||
CLS(Tty) \
|
||||
CLS(KeyManager) \
|
||||
CLS(Loader)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
||||
@ -22,6 +22,7 @@ struct Entry {
|
||||
std::string function;
|
||||
std::string message;
|
||||
std::string thread;
|
||||
u32 counter = 0;
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -73,8 +74,10 @@ enum class Class : u8 {
|
||||
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
|
||||
Lib_NpAuth, ///< The LibSceNpAuth implementation
|
||||
Lib_NpManager, ///< The LibSceNpManager implementation
|
||||
Lib_NpMatching2, ///< The LibSceNpMatching2 implementation
|
||||
Lib_NpScore, ///< The LibSceNpScore implementation
|
||||
Lib_NpTrophy, ///< The LibSceNpTrophy implementation
|
||||
Lib_NpTus, ///< The LibSceNpTus implementation
|
||||
Lib_NpWebApi, ///< The LibSceWebApi implementation
|
||||
Lib_NpWebApi2, ///< The LibSceWebApi2 implementation
|
||||
Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation
|
||||
@ -109,6 +112,7 @@ enum class Class : u8 {
|
||||
Lib_Mouse, ///< The LibSceMouse implementation
|
||||
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
|
||||
Lib_NpParty, ///< The LibSceNpParty implementation
|
||||
Lib_NpPartner, ///< The LibSceNpPartner implementation
|
||||
Lib_Zlib, ///< The LibSceZlib implementation.
|
||||
Lib_Hmd, ///< The LibSceHmd implementation.
|
||||
Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation.
|
||||
@ -127,6 +131,7 @@ enum class Class : u8 {
|
||||
Loader, ///< ROM loader
|
||||
Input, ///< Input emulation
|
||||
Tty, ///< Debug output from emu
|
||||
KeyManager, ///< Key management system
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
||||
@ -17,6 +17,15 @@ public:
|
||||
writer_active = true;
|
||||
}
|
||||
|
||||
bool try_lock() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (writer_active || readers > 0) {
|
||||
return false;
|
||||
}
|
||||
writer_active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
writer_active = false;
|
||||
|
||||
@ -174,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
}
|
||||
|
||||
@ -186,6 +189,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
#if !defined(_WIN32) || defined(_MSC_VER)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
pthread_setname_np(name);
|
||||
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
@ -212,6 +218,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
|
||||
#if defined(_WIN32)
|
||||
void SetCurrentThreadName(const char*) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
// Do Nothing on MinGW
|
||||
}
|
||||
|
||||
|
||||
@ -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<VAddr>(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<HANDLE>(fd) : backing_handle;
|
||||
if (fd && prot == PAGE_READONLY) {
|
||||
HANDLE backing = fd != -1 ? reinterpret_cast<HANDLE>(fd) : backing_handle;
|
||||
if (fd != -1 && prot == PAGE_READONLY) {
|
||||
DWORD resultvar;
|
||||
ptr = VirtualAlloc2(process, reinterpret_cast<PVOID>(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<PVOID>(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<PVOID>(virtual_addr),
|
||||
MEM_PRESERVE_PLACEHOLDER);
|
||||
} else {
|
||||
ret = VirtualFreeEx(process, reinterpret_cast<PVOID>(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<u64>(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<VAddr, MemoryRegion> 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<VAddr> m_free_regions;
|
||||
};
|
||||
#endif
|
||||
@ -675,8 +771,7 @@ AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
|
||||
|
||||
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<Core::MemoryProt>(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);
|
||||
|
||||
@ -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<VAddr> GetUsableRegions();
|
||||
@ -93,11 +92,11 @@ private:
|
||||
std::unique_ptr<Impl> 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
|
||||
|
||||
@ -32,6 +32,9 @@ static bool show_simple_fps = false;
|
||||
static bool visibility_toggled = false;
|
||||
static bool show_quit_window = false;
|
||||
|
||||
static bool show_volume = false;
|
||||
static float volume_start_time;
|
||||
|
||||
static float fps_scale = 1.0f;
|
||||
static int dump_frame_count = 1;
|
||||
|
||||
@ -454,6 +457,27 @@ void L::Draw() {
|
||||
End();
|
||||
}
|
||||
|
||||
if (show_volume) {
|
||||
float current_time = ImGui::GetTime();
|
||||
|
||||
// Show volume for 3 seconds
|
||||
if (current_time - volume_start_time >= 3.0) {
|
||||
show_volume = false;
|
||||
} else {
|
||||
SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->WorkPos.x +
|
||||
ImGui::GetMainViewport()->WorkSize.x - 10,
|
||||
ImGui::GetMainViewport()->WorkPos.y + 10),
|
||||
ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
||||
|
||||
if (ImGui::Begin("Volume Window", &show_volume,
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
|
||||
Text("Volume: %d", Config::getVolumeSlider());
|
||||
}
|
||||
End();
|
||||
}
|
||||
}
|
||||
|
||||
PopID();
|
||||
}
|
||||
|
||||
@ -482,4 +506,9 @@ void ToggleQuitWindow() {
|
||||
show_quit_window = !show_quit_window;
|
||||
}
|
||||
|
||||
void ShowVolume() {
|
||||
volume_start_time = ImGui::GetTime();
|
||||
show_volume = true;
|
||||
}
|
||||
|
||||
} // namespace Overlay
|
||||
|
||||
@ -32,5 +32,6 @@ namespace Overlay {
|
||||
void ToggleSimpleFps();
|
||||
void SetSimpleFps(bool enabled);
|
||||
void ToggleQuitWindow();
|
||||
void ShowVolume();
|
||||
|
||||
} // namespace Overlay
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -35,3 +35,11 @@ bool EmulatorState::IsAutoPatchesLoadEnabled() const {
|
||||
void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) {
|
||||
m_load_patches_auto = enable;
|
||||
}
|
||||
|
||||
bool EmulatorState::IsGameSpecifigConfigUsed() const {
|
||||
return m_game_specific_config_used;
|
||||
}
|
||||
|
||||
void EmulatorState::SetGameSpecifigConfigUsed(bool used) {
|
||||
m_game_specific_config_used = used;
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ public:
|
||||
void SetGameRunning(bool running);
|
||||
bool IsAutoPatchesLoadEnabled() const;
|
||||
void SetAutoPatchesLoadEnabled(bool enable);
|
||||
bool IsGameSpecifigConfigUsed() const;
|
||||
void SetGameSpecifigConfigUsed(bool used);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<EmulatorState> s_instance;
|
||||
@ -26,4 +28,5 @@ private:
|
||||
// state variables
|
||||
bool m_running = false;
|
||||
bool m_load_patches_auto = true;
|
||||
};
|
||||
bool m_game_specific_config_used = false;
|
||||
};
|
||||
|
||||
114
src/core/file_format/npbind.cpp
Normal file
114
src/core/file_format/npbind.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#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<u8> buf(static_cast<size_t>(sz));
|
||||
if (!f.read(reinterpret_cast<char*>(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<size_t>(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<std::string> NPBindFile::GetNpCommIds() const {
|
||||
std::vector<std::string> 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<const char*>(body.npcommid.data.data()),
|
||||
body.npcommid.data.size());
|
||||
npcommids.push_back(raw_string);
|
||||
}
|
||||
}
|
||||
|
||||
return npcommids;
|
||||
}
|
||||
87
src/core/file_format/npbind.h
Normal file
87
src/core/file_format/npbind.h
Normal file
@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<u8> 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<NPBindBody> 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<NPBindBody>& Bodies() const {
|
||||
return m_bodies;
|
||||
}
|
||||
const u8* Digest() const {
|
||||
return m_digest;
|
||||
}
|
||||
|
||||
// Get npcommid data
|
||||
std::vector<std::string> 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));
|
||||
}
|
||||
};
|
||||
@ -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<u8, 16> trophyKey, std::span<u8, 16> NPcommID,
|
||||
std::span<u8, 16> efsmIv, std::span<u8> ciphertext,
|
||||
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
|
||||
std::span<const u8, 16> efsmIv, std::span<const u8> ciphertext,
|
||||
std::span<u8> decrypted) {
|
||||
// Step 1: Encrypt NPcommID
|
||||
std::array<u8, 16> trophyIv{};
|
||||
std::array<u8, 16> 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<u8*>(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<u8>(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<u8>& vec) {
|
||||
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
|
||||
if (*it == '>') {
|
||||
@ -63,91 +50,232 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto user_key_str = Config::getTrophyKey();
|
||||
if (user_key_str.size() != 32) {
|
||||
const auto& user_key_vec =
|
||||
KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey;
|
||||
|
||||
if (user_key_vec.size() != 16) {
|
||||
LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<u8, 16> 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<int>(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<s64>(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<u8> 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<u8> ESFM(entry.entry_len - iv_len);
|
||||
std::vector<u8> 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<unsigned int>(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<u8> 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<u8, 16>& 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<u8, IV_LEN> 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<u8> ESFM(entry.entry_len - IV_LEN);
|
||||
std::vector<u8> 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<const u8, 16> key_span(user_key);
|
||||
|
||||
// Convert npCommId string to span (pad or truncate to 16 bytes)
|
||||
std::array<u8, 16> npcommid_array{};
|
||||
size_t copy_len = std::min(npCommId.size(), npcommid_array.size());
|
||||
std::memcpy(npcommid_array.data(), npCommId.data(), copy_len);
|
||||
std::span<const u8, 16> 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;
|
||||
}
|
||||
@ -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<u8, 16>& user_key, const std::string& npCommId);
|
||||
|
||||
std::vector<u8> NPcommID = std::vector<u8>(12);
|
||||
std::array<u8, 16> np_comm_id{};
|
||||
std::array<u8, 16> esfmIv{};
|
||||
|
||||
@ -232,6 +232,9 @@ File* HandleTable::GetSocket(int d) {
|
||||
return nullptr;
|
||||
}
|
||||
auto file = m_files.at(d);
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
if (file->type != Core::FileSys::FileType::Socket) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,20 +5,45 @@
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <aacdecoder_lib.h>
|
||||
// using this internal header to manually configure the decoder in RAW mode
|
||||
#include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h"
|
||||
|
||||
#include <aacdecoder_lib.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <algorithm> // std::transform
|
||||
#include <iterator> // std::back_inserter
|
||||
#include <limits>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
std::span<const s16> 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<u32>(pcm_data.size(), max_pcm));
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t AjmAacDecoder::WriteOutputSamples<float>(SparseOutputBuffer& out, std::span<const s16> pcm) {
|
||||
if (pcm.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_resample_buffer.clear();
|
||||
constexpr float inv_scale = 1.0f / std::numeric_limits<s16>::max();
|
||||
std::transform(pcm.begin(), pcm.end(), std::back_inserter(m_resample_buffer),
|
||||
[](auto sample) { return float(sample) * inv_scale; });
|
||||
|
||||
return out.Write(std::span(m_resample_buffer));
|
||||
}
|
||||
|
||||
AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels)
|
||||
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(2048 * 8),
|
||||
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {}
|
||||
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(1024 * 8),
|
||||
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {
|
||||
m_resample_buffer.reserve(m_pcm_buffer.size());
|
||||
}
|
||||
|
||||
AjmAacDecoder::~AjmAacDecoder() {
|
||||
aacDecoder_Close(m_decoder);
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
}
|
||||
|
||||
TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
|
||||
@ -98,7 +123,7 @@ AjmSidebandFormat AjmAacDecoder::GetFormat() const {
|
||||
.num_channels = static_cast<u32>(info->numChannels),
|
||||
.channel_mask = GetChannelMask(info->numChannels),
|
||||
.sampl_freq = static_cast<u32>(info->sampleRate),
|
||||
.sample_encoding = m_format, // AjmFormatEncoding
|
||||
.sample_encoding = m_format,
|
||||
.bitrate = static_cast<u32>(info->bitRate),
|
||||
};
|
||||
}
|
||||
@ -130,8 +155,7 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
|
||||
const UINT sizes[] = {static_cast<UINT>(input.size())};
|
||||
UINT valid = sizes[0];
|
||||
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
|
||||
auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast<s16*>(m_pcm_buffer.data()),
|
||||
m_pcm_buffer.size() / 2, 0);
|
||||
auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0);
|
||||
|
||||
switch (ret) {
|
||||
case AAC_DEC_OK:
|
||||
@ -167,16 +191,16 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
|
||||
gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
|
||||
|
||||
size_t pcm_written = 0;
|
||||
auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels);
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
pcm_written = WriteOutputSamples<s16>(output, skip_samples * info->numChannels,
|
||||
max_samples * info->numChannels);
|
||||
pcm_written = output.Write(pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
pcm_written = WriteOutputSamples<float>(output, pcm);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
@ -191,4 +215,4 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
} // namespace Libraries::Ajm
|
||||
@ -52,22 +52,18 @@ private:
|
||||
};
|
||||
|
||||
template <class T>
|
||||
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) {
|
||||
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
|
||||
m_pcm_buffer.size() / sizeof(T)};
|
||||
pcm_data = pcm_data.subspan(skipped_pcm);
|
||||
const auto pcm_size = std::min(u32(pcm_data.size()), max_pcm);
|
||||
return output.Write(pcm_data.subspan(0, pcm_size));
|
||||
}
|
||||
size_t WriteOutputSamples(SparseOutputBuffer& output, std::span<const s16> pcm);
|
||||
std::span<const s16> GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const;
|
||||
|
||||
const AjmFormatEncoding m_format;
|
||||
const AjmAacCodecFlags m_flags;
|
||||
const u32 m_channels;
|
||||
std::vector<u8> m_pcm_buffer;
|
||||
std::vector<s16> m_pcm_buffer;
|
||||
std::vector<float> m_resample_buffer;
|
||||
|
||||
u32 m_skip_frames = 0;
|
||||
InitializeParameters m_init_params = {};
|
||||
AAC_DECODER_INSTANCE* m_decoder = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
} // namespace Libraries::Ajm
|
||||
@ -1,9 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm_error.h"
|
||||
#include "ajm_mp3.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_error.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
extern "C" {
|
||||
@ -122,7 +124,6 @@ void AjmMp3Decoder::Reset() {
|
||||
avcodec_flush_buffers(m_codec_context);
|
||||
m_header.reset();
|
||||
m_frame_samples = 0;
|
||||
m_frame_size = 0;
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
@ -141,7 +142,7 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
|
||||
u32 AjmMp3Decoder::GetMinimumInputSize() const {
|
||||
// 4 bytes is for mp3 header that contains frame_size
|
||||
return std::max<u32>(m_frame_size, 4);
|
||||
return 4;
|
||||
}
|
||||
|
||||
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
@ -149,12 +150,11 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuff
|
||||
DecoderResult result{};
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
|
||||
AjmDecMp3ParseFrame info{};
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
|
||||
m_frame_samples = info.samples_per_channel;
|
||||
m_frame_size = info.frame_size;
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(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<u16>(info.encoder_delay),
|
||||
@ -163,6 +163,10 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuff
|
||||
gapless.current = gapless.init;
|
||||
}
|
||||
|
||||
if (in_buf.size() < info.frame_size) {
|
||||
result.result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
|
||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||
in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
@ -424,11 +428,7 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
|
||||
frame->encoder_delay = std::byteswap(*reinterpret_cast<const u16*>(p_fgh + 1));
|
||||
frame->total_samples = std::byteswap(*reinterpret_cast<const u32*>(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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +99,6 @@ private:
|
||||
SwrContext* m_swr_context = nullptr;
|
||||
std::optional<u32> m_header;
|
||||
u32 m_frame_samples = 0;
|
||||
u32 m_frame_size = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
||||
@ -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 <cmath>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,23 +1,264 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <shared_mutex>
|
||||
#include "audioin_backend.h"
|
||||
#include "audioin_error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioin.h"
|
||||
#include "core/libraries/audio/sdl_in.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
static std::unique_ptr<SDLAudioIn> audio = std::make_unique<SDLAudioIn>();
|
||||
std::array<std::shared_ptr<PortIn>, ORBIS_AUDIO_IN_NUM_PORTS> port_table{};
|
||||
std::shared_mutex port_table_mutex;
|
||||
std::mutex port_allocation_mutex;
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
static std::unique_ptr<AudioInBackend> audio;
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
**/
|
||||
static int GetPortId(s32 handle) {
|
||||
int port_id = handle & 0xFF;
|
||||
|
||||
if (port_id >= ORBIS_AUDIO_IN_NUM_PORTS) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port");
|
||||
return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
if ((handle & 0x7f000000) != 0x30000000) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid handle format");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return port_id;
|
||||
}
|
||||
|
||||
static s32 GetPortType(s32 handle) {
|
||||
return (handle >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
static int AllocatePort(OrbisAudioInType type) {
|
||||
// TODO implement port type ranges if needed
|
||||
for (int i = 0; i <= ORBIS_AUDIO_IN_NUM_PORTS; i++) {
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (!port_table[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* sceAudioIn implementation
|
||||
**/
|
||||
static bool initOnce = false;
|
||||
int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
|
||||
type, index, len, freq, param);
|
||||
if (!initOnce) {
|
||||
// sceAudioInInit doesn't seem to be called by most apps before sceAudioInOpen so we init
|
||||
// here
|
||||
audio = std::make_unique<SDLAudioIn>();
|
||||
initOnce = true;
|
||||
}
|
||||
|
||||
if (len == 0 || len > 2048) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid size");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
OrbisAudioInType in_type = static_cast<OrbisAudioInType>(type);
|
||||
OrbisAudioInParamFormat format = static_cast<OrbisAudioInParamFormat>(param);
|
||||
|
||||
if (format != OrbisAudioInParamFormat::S16Mono &&
|
||||
format != OrbisAudioInParamFormat::S16Stereo) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid format");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (freq != 16000 && freq != 48000) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid sample rate");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port_allocation_mutex};
|
||||
|
||||
// Allocate port
|
||||
int port_id = AllocatePort(in_type);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "No free audio input ports available");
|
||||
return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
// Create port object
|
||||
std::shared_ptr<PortIn> port;
|
||||
try {
|
||||
port = std::make_shared<PortIn>();
|
||||
|
||||
port->type = in_type;
|
||||
port->format = format;
|
||||
port->samples_num = len;
|
||||
port->freq = freq;
|
||||
|
||||
// Determine channel count and sample size based on format
|
||||
switch (format) {
|
||||
case OrbisAudioInParamFormat::S16Mono:
|
||||
port->channels_num = 1;
|
||||
port->sample_size = 2;
|
||||
break;
|
||||
case OrbisAudioInParamFormat::S16Stereo:
|
||||
port->channels_num = 2;
|
||||
port->sample_size = 2;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioIn, "Unsupported audio format: {}", static_cast<u32>(format));
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// Open backend
|
||||
port->impl = audio->Open(*port);
|
||||
if (!port->impl) {
|
||||
throw std::runtime_error("Failed to create audio backend");
|
||||
}
|
||||
|
||||
} catch (const std::bad_alloc&) {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to allocate memory for audio port");
|
||||
return ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to open audio input port: {}", e.what());
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
// Store the port pointer with write lock
|
||||
{
|
||||
std::unique_lock write_lock{port_table_mutex};
|
||||
port_table[port_id] = port;
|
||||
}
|
||||
|
||||
// Create handle
|
||||
s32 handle = (type << 16) | port_id | 0x30000000;
|
||||
|
||||
LOG_INFO(Lib_AudioIn, "Opened audio input port {}: type={}, samples={}, freq={}, format={}",
|
||||
handle, static_cast<u32>(in_type), len, freq, static_cast<u32>(format));
|
||||
return handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
|
||||
type, index, len, freq, param);
|
||||
int result = sceAudioInOpen(userId, type, index, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInClose(s32 handle) {
|
||||
audio->AudioInClose(handle);
|
||||
LOG_INFO(Lib_AudioIn, "called, handle={:#x}", handle);
|
||||
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port_allocation_mutex};
|
||||
std::shared_ptr<PortIn> port;
|
||||
|
||||
// Get and clear the port pointer with write lock
|
||||
{
|
||||
std::unique_lock write_lock{port_table_mutex};
|
||||
port = std::move(port_table[port_id]);
|
||||
if (!port) {
|
||||
LOG_ERROR(Lib_AudioIn, "Port wasn't open {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
port_table[port_id].reset();
|
||||
}
|
||||
|
||||
// Free resources
|
||||
std::scoped_lock port_lock{port->mutex};
|
||||
port->impl.reset();
|
||||
|
||||
LOG_INFO(Lib_AudioIn, "Closed audio input port {}", handle);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
|
||||
LOG_TRACE(Lib_AudioIn, "called, handle={:#x}, dest={}", handle, fmt::ptr(dest));
|
||||
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (!dest) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid output buffer pointer");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
// Get port with read lock
|
||||
std::shared_ptr<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(port_table.size())) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
port = port_table[port_id];
|
||||
}
|
||||
|
||||
if (!port || !port->impl) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{port->mutex};
|
||||
return port->impl->Read(dest);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle) {
|
||||
LOG_TRACE(Lib_AudioIn, "called, handle={:#x}", handle);
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
// Get port with read lock
|
||||
std::shared_ptr<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(port_table.size())) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
port = port_table[port_id];
|
||||
}
|
||||
|
||||
if (!port || !port->impl) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
u32 silent_state = 0;
|
||||
std::scoped_lock lock{port->mutex};
|
||||
if (!port->impl->IsAvailable()) { // if no mic exist or is not available
|
||||
silent_state |= ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE;
|
||||
}
|
||||
return silent_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stubbed functions
|
||||
**/
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -91,20 +332,6 @@ int PS4_SYSV_ABI sceAudioInGetRerouteCount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
int result = audio->AudioInOpen(type, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpenEx() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -115,10 +342,6 @@ int PS4_SYSV_ABI sceAudioInInit() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
|
||||
return audio->AudioInInput(handle, dest);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInputs() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -129,15 +352,6 @@ int PS4_SYSV_ABI sceAudioInIsSharedDevice() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
int result = audio->AudioInOpen(type, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInOpenEx() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
|
||||
@ -12,10 +13,31 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
class PortInBackend;
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_IN_NUM_PORTS = 7;
|
||||
|
||||
enum class OrbisAudioInParamFormat : u32 { S16Mono = 0, S16Stereo = 2 };
|
||||
|
||||
enum class OrbisAudioInType : u32 { VoiceChat = 0, General = 1, VoiceRecognition = 5 };
|
||||
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x00000001;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_PRIORITY_LOW = 0x00000002;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_USER_SETTING = 0x0000000;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_UNABLE_FORMAT = 0x00000008;
|
||||
|
||||
struct PortIn {
|
||||
std::mutex mutex;
|
||||
std::unique_ptr<PortInBackend> impl{};
|
||||
OrbisAudioInType type;
|
||||
OrbisAudioInParamFormat format;
|
||||
|
||||
u32 samples_num = 0;
|
||||
u32 freq = 0;
|
||||
u32 channels_num = 0;
|
||||
u32 sample_size = 0;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState();
|
||||
int PS4_SYSV_ABI sceAudioInClose(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioInCountPorts();
|
||||
@ -32,7 +54,7 @@ int PS4_SYSV_ABI sceAudioInExtSetAecMode();
|
||||
int PS4_SYSV_ABI sceAudioInGetGain();
|
||||
int PS4_SYSV_ABI sceAudioInGetHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioInGetRerouteCount();
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState();
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param);
|
||||
int PS4_SYSV_ABI sceAudioInHqOpenEx();
|
||||
|
||||
31
src/core/libraries/audio/audioin_backend.h
Normal file
31
src/core/libraries/audio/audioin_backend.h
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
struct PortIn;
|
||||
|
||||
class PortInBackend {
|
||||
public:
|
||||
virtual ~PortInBackend() = default;
|
||||
virtual int Read(void* out_buffer) = 0;
|
||||
virtual void Clear() = 0;
|
||||
virtual bool IsAvailable() = 0;
|
||||
};
|
||||
|
||||
class AudioInBackend {
|
||||
public:
|
||||
AudioInBackend() = default;
|
||||
virtual ~AudioInBackend() = default;
|
||||
virtual std::unique_ptr<PortInBackend> Open(PortIn& port) = 0;
|
||||
};
|
||||
|
||||
class SDLAudioIn final : public AudioInBackend {
|
||||
public:
|
||||
std::unique_ptr<PortInBackend> Open(PortIn& port) override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
20
src/core/libraries/audio/audioin_error.h
Normal file
20
src/core/libraries/audio/audioin_error.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
// AudioIn library
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE = 0x80260101;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_SIZE = 0x80260102;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_FREQ = 0x80260103;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_TYPE = 0x80260104;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_POINTER = 0x80260105;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_PARAM = 0x80260106;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_PORT_FULL = 0x80260107;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY = 0x80260108;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_NOT_OPENED = 0x80260109;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_BUSY = 0x8026010A;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_MEMORY = 0x8026010B;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_IPC = 0x8026010C;
|
||||
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,32 @@ class PortBackend;
|
||||
|
||||
// Main up to 8 ports, BGM 1 port, voice up to 4 ports,
|
||||
// personal up to 4 ports, padspk up to 5 ports, aux 1 port
|
||||
constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22;
|
||||
constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
|
||||
constexpr s32 ORBIS_AUDIO_OUT_NUM_PORTS = 25;
|
||||
constexpr s32 ORBIS_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT = 11626; // default -9db
|
||||
constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_0DB = 32768; // max volume
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED = 0x00010000;
|
||||
constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN = 0x00020000;
|
||||
|
||||
// Volume flags
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_L_CH = (1u << 0);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_R_CH = (1u << 1);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_C_CH = (1u << 2);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LFE_CH = (1u << 3);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LS_CH = (1u << 4);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RS_CH = (1u << 5);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LE_CH = (1u << 6);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RE_CH = (1u << 7);
|
||||
|
||||
// Port state constants
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN = 0x00;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY = 0x01;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_SECONDARY = 0x02;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY = 0x04;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE = 0x40;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL = 0x80;
|
||||
|
||||
enum class OrbisAudioOutPort {
|
||||
Main = 0,
|
||||
@ -53,6 +77,9 @@ union OrbisAudioOutParamExtendedInformation {
|
||||
BitField<16, 4, OrbisAudioOutParamAttr> attributes;
|
||||
BitField<20, 10, u32> reserve1;
|
||||
BitField<31, 1, u32> unused;
|
||||
u32 Unpack() const {
|
||||
return *reinterpret_cast<const u32*>(this);
|
||||
}
|
||||
};
|
||||
|
||||
struct OrbisAudioOutOutputParam {
|
||||
@ -70,6 +97,12 @@ struct OrbisAudioOutPortState {
|
||||
u64 reserved64[2];
|
||||
};
|
||||
|
||||
struct OrbisAudioOutSystemState {
|
||||
float loudness;
|
||||
u8 reserved8[4];
|
||||
u64 reserved64[3];
|
||||
};
|
||||
|
||||
struct AudioFormatInfo {
|
||||
bool is_float;
|
||||
u8 sample_size;
|
||||
@ -77,6 +110,7 @@ struct AudioFormatInfo {
|
||||
/// Layout array remapping channel indices, specified in this order:
|
||||
/// FL, FR, FC, LFE, BL, BR, SL, SR
|
||||
std::array<int, 8> channel_layout;
|
||||
bool is_std;
|
||||
|
||||
[[nodiscard]] u16 FrameSize() const {
|
||||
return sample_size * num_channels;
|
||||
@ -87,100 +121,100 @@ struct PortOut {
|
||||
std::mutex mutex;
|
||||
std::unique_ptr<PortBackend> impl{};
|
||||
|
||||
void* output_buffer;
|
||||
void* output_buffer = nullptr;
|
||||
std::condition_variable_any output_cv;
|
||||
bool output_ready;
|
||||
bool output_ready = false;
|
||||
Kernel::Thread output_thread{};
|
||||
|
||||
OrbisAudioOutPort type;
|
||||
AudioFormatInfo format_info;
|
||||
u32 sample_rate;
|
||||
u32 buffer_frames;
|
||||
u64 last_output_time;
|
||||
u32 sample_rate = 48000;
|
||||
u32 buffer_frames = 1024;
|
||||
u64 last_output_time = 0;
|
||||
std::array<s32, 8> volume;
|
||||
|
||||
[[nodiscard]] bool IsOpen() const {
|
||||
return impl != nullptr;
|
||||
}
|
||||
s32 userId = 0;
|
||||
s32 mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT;
|
||||
bool is_restricted = false;
|
||||
bool is_mix_to_main = false;
|
||||
|
||||
[[nodiscard]] u32 BufferSize() const {
|
||||
return buffer_frames * format_info.FrameSize();
|
||||
}
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
|
||||
int PS4_SYSV_ABI sceAudioDeviceControlGet();
|
||||
int PS4_SYSV_ABI sceAudioDeviceControlSet();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dControl();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dExit();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dInit();
|
||||
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
||||
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
||||
int PS4_SYSV_ABI sceAudioOutClose(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
||||
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
||||
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtClose();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtOpen();
|
||||
int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
|
||||
int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
|
||||
int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
|
||||
int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
|
||||
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSparkVss();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSystemState();
|
||||
int PS4_SYSV_ABI sceAudioOutInit();
|
||||
int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringGetState();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringInit();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringSetParam();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringTerm();
|
||||
int PS4_SYSV_ABI sceAudioOutMbusInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioDeviceControlGet();
|
||||
s32 PS4_SYSV_ABI sceAudioDeviceControlSet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dControl();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dExit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtClose();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSparkVss();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* state);
|
||||
s32 PS4_SYSV_ABI sceAudioOutInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringGetState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags);
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringTerm();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMbusInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
|
||||
OrbisAudioOutPort port_type, s32 index, u32 length,
|
||||
u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type);
|
||||
int PS4_SYSV_ABI sceAudioOutOpenEx();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOpenEx();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr);
|
||||
s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num);
|
||||
int PS4_SYSV_ABI sceAudioOutPtClose();
|
||||
int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
|
||||
int PS4_SYSV_ABI sceAudioOutPtOpen();
|
||||
int PS4_SYSV_ABI sceAudioOutSetConnections();
|
||||
int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
|
||||
int PS4_SYSV_ABI sceAudioOutSetDevConnection();
|
||||
int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
|
||||
int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMainOutput();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetPortConnections();
|
||||
int PS4_SYSV_ABI sceAudioOutSetPortStatuses();
|
||||
int PS4_SYSV_ABI sceAudioOutSetRecMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetSparkParam();
|
||||
int PS4_SYSV_ABI sceAudioOutSetUsbVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtClose();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetConnections();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetDevConnection();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMainOutput();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel);
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetPortConnections();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetRecMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetSparkParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol);
|
||||
int PS4_SYSV_ABI sceAudioOutSetVolumeDown();
|
||||
int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
|
||||
int PS4_SYSV_ABI sceAudioOutStartSharePlay();
|
||||
int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
|
||||
int PS4_SYSV_ABI sceAudioOutStopSharePlay();
|
||||
int PS4_SYSV_ABI sceAudioOutSuspendResume();
|
||||
int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSystemControlGet();
|
||||
int PS4_SYSV_ABI sceAudioOutSystemControlSet();
|
||||
int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
|
||||
int PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStartSharePlay();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStopSharePlay();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSuspendResume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSystemControlGet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSystemControlSet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
|
||||
|
||||
void AdjustVol();
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
|
||||
#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
class SDLPortBackend : public PortBackend {
|
||||
public:
|
||||
explicit SDLPortBackend(const PortOut& port)
|
||||
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
|
||||
const SDL_AudioSpec fmt = {
|
||||
.format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
|
||||
.channels = port.format_info.num_channels,
|
||||
.freq = static_cast<int>(port.sample_rate),
|
||||
};
|
||||
|
||||
// Determine port type
|
||||
std::string port_name = port.type == OrbisAudioOutPort::PadSpk
|
||||
? Config::getPadSpkOutputDevice()
|
||||
: Config::getMainOutputDevice();
|
||||
SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
|
||||
if (port_name == "None") {
|
||||
stream = nullptr;
|
||||
return;
|
||||
} else if (port_name == "Default Device") {
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
} else {
|
||||
try {
|
||||
SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr);
|
||||
for (; dev_array != 0;) {
|
||||
std::string dev_name(SDL_GetAudioDeviceName(*dev_array));
|
||||
if (dev_name == port_name) {
|
||||
dev_id = *dev_array;
|
||||
break;
|
||||
} else {
|
||||
dev_array++;
|
||||
}
|
||||
}
|
||||
if (dev_id == SDL_INVALID_AUDIODEVICEID) {
|
||||
LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name);
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the audio stream
|
||||
stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
|
||||
if (stream == nullptr) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
CalculateQueueThreshold();
|
||||
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
|
||||
port.format_info.num_channels)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
|
||||
SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
if (!SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f);
|
||||
}
|
||||
|
||||
~SDLPortBackend() override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
void Output(void* ptr) override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
// AudioOut library manages timing, but we still need to guard against the SDL
|
||||
// audio queue stalling, which may happen during device changes, for example.
|
||||
// Otherwise, latency may grow over time unbounded.
|
||||
if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
|
||||
LOG_INFO(Lib_AudioOut, "SDL audio queue backed up ({} queued, {} threshold), clearing.",
|
||||
queued, queue_threshold);
|
||||
SDL_ClearAudioStream(stream);
|
||||
// Recalculate the threshold in case this happened because of a device change.
|
||||
CalculateQueueThreshold();
|
||||
}
|
||||
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(guest_buffer_size))) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void SetVolume(const std::array<int, 8>& ch_volumes) override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
// SDL does not have per-channel volumes, for now just take the maximum of the channels.
|
||||
const auto vol = *std::ranges::max_element(ch_volumes);
|
||||
if (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB *
|
||||
Config::getVolumeSlider() / 100.0f)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void CalculateQueueThreshold() {
|
||||
SDL_AudioSpec discard;
|
||||
int sdl_buffer_frames;
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
|
||||
&sdl_buffer_frames)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}",
|
||||
SDL_GetError());
|
||||
sdl_buffer_frames = 0;
|
||||
}
|
||||
const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
|
||||
const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
|
||||
if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
|
||||
host_buffer_size = sdl_buffer_size;
|
||||
queue_threshold = new_threshold;
|
||||
LOG_INFO(Lib_AudioOut,
|
||||
"SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
|
||||
guest_buffer_size, host_buffer_size, queue_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
u32 frame_size;
|
||||
u32 guest_buffer_size;
|
||||
u32 host_buffer_size{};
|
||||
u32 queue_threshold{};
|
||||
SDL_AudioStream* stream{};
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#include "audioin.h"
|
||||
#include "audioin_backend.h"
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
class SDLInPortBackend : public PortInBackend {
|
||||
public:
|
||||
explicit SDLInPortBackend(const PortIn& port) : port(port) {
|
||||
SDL_AudioFormat sampleFormat = SDL_AUDIO_S16; // PS4 uses S16 format
|
||||
|
||||
SDL_AudioSpec fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.format = sampleFormat;
|
||||
fmt.channels = static_cast<Uint8>(port.channels_num);
|
||||
fmt.freq = static_cast<int>(port.freq);
|
||||
|
||||
std::string micDevStr = Config::getMicDevice();
|
||||
uint32_t devId = 0;
|
||||
if (micDevStr == "None") {
|
||||
nullDevice = true;
|
||||
LOG_INFO(Lib_AudioIn, "Audio input disabled by configuration");
|
||||
} else if (micDevStr == "Default Device") {
|
||||
devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
|
||||
LOG_INFO(Lib_AudioIn, "Using default audio input device");
|
||||
} else {
|
||||
try {
|
||||
devId = static_cast<uint32_t>(std::stoul(micDevStr));
|
||||
LOG_INFO(Lib_AudioIn, "Using audio input device ID: {}", devId);
|
||||
} catch (const std::exception& e) {
|
||||
nullDevice = true;
|
||||
LOG_WARNING(Lib_AudioIn, "Invalid device ID '{}', disabling input", micDevStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nullDevice) {
|
||||
stream = SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
|
||||
if (stream) {
|
||||
if (SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_INFO(Lib_AudioIn, "Audio input opened: {} Hz, {} channels, format {}",
|
||||
port.freq, port.channels_num, static_cast<u32>(port.format));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to resume audio input stream");
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to open audio input device: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate internal buffer for null device simulation
|
||||
if (!stream) {
|
||||
const size_t bufferSize = port.samples_num * port.sample_size * port.channels_num;
|
||||
internal_buffer = std::malloc(bufferSize);
|
||||
if (internal_buffer) {
|
||||
// Fill with silence
|
||||
std::memset(internal_buffer, 0, bufferSize);
|
||||
LOG_INFO(Lib_AudioIn, "Created null audio input buffer of {} bytes", bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~SDLInPortBackend() override {
|
||||
if (stream) {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
if (internal_buffer) {
|
||||
std::free(internal_buffer);
|
||||
internal_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int Read(void* out_buffer) override {
|
||||
const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
|
||||
|
||||
if (stream) {
|
||||
// Read from actual audio device
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(stream) < bytesToRead) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const int bytesRead = SDL_GetAudioStreamData(stream, out_buffer, bytesToRead);
|
||||
if (bytesRead < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input read error: {}", SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int framesRead = bytesRead / (port.sample_size * port.channels_num);
|
||||
return framesRead;
|
||||
} else if (internal_buffer) {
|
||||
// Return silence from null device buffer
|
||||
std::memcpy(out_buffer, internal_buffer, bytesToRead);
|
||||
return port.samples_num;
|
||||
} else {
|
||||
// No device available
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() override {
|
||||
if (stream) {
|
||||
SDL_ClearAudioStream(stream);
|
||||
}
|
||||
}
|
||||
bool IsAvailable() override {
|
||||
if (nullDevice) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PortIn& port;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
void* internal_buffer = nullptr;
|
||||
bool nullDevice = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<PortInBackend> SDLAudioIn::Open(PortIn& port) {
|
||||
return std::make_unique<SDLInPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
600
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
600
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
@ -0,0 +1,600 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
|
||||
// SIMD support detection
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#include <immintrin.h>
|
||||
#define HAS_SSE2
|
||||
#endif
|
||||
|
||||
#define SDL_INVALID_AUDIODEVICEID 0
|
||||
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
// Volume constants
|
||||
constexpr float VOLUME_0DB = 32768.0f; // 1 << 15
|
||||
constexpr float INV_VOLUME_0DB = 1.0f / VOLUME_0DB;
|
||||
constexpr float VOLUME_EPSILON = 0.001f;
|
||||
// Timing constants
|
||||
constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms
|
||||
constexpr u64 MIN_SLEEP_THRESHOLD_US = 10;
|
||||
constexpr u64 TIMING_RESYNC_THRESHOLD_US = 100000; // Resync if >100ms behind
|
||||
|
||||
// Queue management
|
||||
constexpr u32 QUEUE_MULTIPLIER = 4;
|
||||
// Memory alignment for SIMD
|
||||
constexpr size_t AUDIO_BUFFER_ALIGNMENT = 32;
|
||||
|
||||
// Channel positions
|
||||
enum ChannelPos : u8 {
|
||||
FL = 0,
|
||||
FR = 1,
|
||||
FC = 2,
|
||||
LF = 3,
|
||||
SL = 4,
|
||||
SR = 5,
|
||||
BL = 6,
|
||||
BR = 7,
|
||||
STD_SL = 6,
|
||||
STD_SR = 7,
|
||||
STD_BL = 4,
|
||||
STD_BR = 5
|
||||
};
|
||||
|
||||
class SDLPortBackend : public PortBackend {
|
||||
public:
|
||||
explicit SDLPortBackend(const PortOut& port)
|
||||
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()),
|
||||
buffer_frames(port.buffer_frames), sample_rate(port.sample_rate),
|
||||
num_channels(port.format_info.num_channels), is_float(port.format_info.is_float),
|
||||
is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout) {
|
||||
|
||||
if (!Initialize(port.type)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to initialize SDL audio backend");
|
||||
}
|
||||
}
|
||||
|
||||
~SDLPortBackend() override {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void Output(void* ptr) override {
|
||||
if (!stream || !internal_buffer || !convert) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateVolumeIfChanged();
|
||||
const u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
convert(ptr, internal_buffer, buffer_frames, nullptr);
|
||||
HandleTiming(current_time);
|
||||
|
||||
if ((output_count++ & 0xF) == 0) { // Check every 16 outputs
|
||||
ManageAudioQueue();
|
||||
}
|
||||
|
||||
if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) [[unlikely]] {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
last_output_time.store(current_time, std::memory_order_release);
|
||||
}
|
||||
|
||||
void SetVolume(const std::array<int, 8>& ch_volumes) override {
|
||||
if (!stream) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
float max_channel_gain = 0.0f;
|
||||
const u32 channels_to_check = std::min(num_channels, 8u);
|
||||
|
||||
for (u32 i = 0; i < channels_to_check; i++) {
|
||||
const float channel_gain = static_cast<float>(ch_volumes[i]) * INV_VOLUME_0DB;
|
||||
max_channel_gain = std::max(max_channel_gain, channel_gain);
|
||||
}
|
||||
|
||||
const float slider_gain = Config::getVolumeSlider() * 0.01f; // Faster than /100.0f
|
||||
const float total_gain = max_channel_gain * slider_gain;
|
||||
|
||||
const float current = current_gain.load(std::memory_order_acquire);
|
||||
if (std::abs(total_gain - current) < VOLUME_EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply volume change
|
||||
if (SDL_SetAudioStreamGain(stream, total_gain)) {
|
||||
current_gain.store(total_gain, std::memory_order_release);
|
||||
LOG_DEBUG(Lib_AudioOut,
|
||||
"Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})",
|
||||
total_gain, max_channel_gain, slider_gain);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
u64 GetLastOutputTime() const {
|
||||
return last_output_time.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
private:
|
||||
bool Initialize(OrbisAudioOutPort type) {
|
||||
// Calculate timing parameters
|
||||
period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate;
|
||||
|
||||
// Allocate aligned internal buffer for SIMD operations
|
||||
internal_buffer_size = buffer_frames * sizeof(float) * num_channels;
|
||||
|
||||
#ifdef _WIN32
|
||||
internal_buffer = _aligned_malloc(internal_buffer_size, AUDIO_BUFFER_ALIGNMENT);
|
||||
#else
|
||||
if (posix_memalign(&internal_buffer, AUDIO_BUFFER_ALIGNMENT, internal_buffer_size) != 0) {
|
||||
internal_buffer = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!internal_buffer) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to allocate aligned audio buffer of size {}",
|
||||
internal_buffer_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize current gain
|
||||
current_gain.store(Config::getVolumeSlider() * 0.01f, std::memory_order_relaxed);
|
||||
|
||||
if (!SelectConverter()) {
|
||||
FreeAlignedBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open SDL device
|
||||
if (!OpenDevice(type)) {
|
||||
FreeAlignedBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
CalculateQueueThreshold();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
if (stream) {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
FreeAlignedBuffer();
|
||||
}
|
||||
|
||||
void FreeAlignedBuffer() {
|
||||
if (internal_buffer) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(internal_buffer);
|
||||
#else
|
||||
free(internal_buffer);
|
||||
#endif
|
||||
internal_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVolumeIfChanged() {
|
||||
const u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
|
||||
if (current_time - last_volume_check_time < VOLUME_CHECK_INTERVAL_US) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_volume_check_time = current_time;
|
||||
|
||||
const float config_volume = Config::getVolumeSlider() * 0.01f;
|
||||
const float stored_gain = current_gain.load(std::memory_order_acquire);
|
||||
|
||||
// Only update if the difference is significant
|
||||
if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) {
|
||||
if (SDL_SetAudioStreamGain(stream, config_volume)) {
|
||||
current_gain.store(config_volume, std::memory_order_release);
|
||||
LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTiming(u64 current_time) {
|
||||
if (next_output_time == 0) [[unlikely]] {
|
||||
// First output - set initial timing
|
||||
next_output_time = current_time + period_us;
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 time_diff = static_cast<s64>(current_time - next_output_time);
|
||||
|
||||
if (time_diff > static_cast<s64>(TIMING_RESYNC_THRESHOLD_US)) [[unlikely]] {
|
||||
// We're far behind - resync
|
||||
next_output_time = current_time + period_us;
|
||||
} else if (time_diff < 0) {
|
||||
// We're ahead of schedule - wait
|
||||
const u64 time_to_wait = static_cast<u64>(-time_diff);
|
||||
next_output_time += period_us;
|
||||
|
||||
if (time_to_wait > MIN_SLEEP_THRESHOLD_US) {
|
||||
// Sleep for most of the wait period
|
||||
const u64 sleep_duration = time_to_wait - MIN_SLEEP_THRESHOLD_US;
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration));
|
||||
}
|
||||
} else {
|
||||
// Slightly behind or on time - just advance
|
||||
next_output_time += period_us;
|
||||
}
|
||||
}
|
||||
|
||||
void ManageAudioQueue() {
|
||||
const auto queued = SDL_GetAudioStreamQueued(stream);
|
||||
|
||||
if (queued >= queue_threshold) [[unlikely]] {
|
||||
LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued,
|
||||
queue_threshold);
|
||||
SDL_ClearAudioStream(stream);
|
||||
CalculateQueueThreshold();
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenDevice(OrbisAudioOutPort type) {
|
||||
const SDL_AudioSpec fmt = {
|
||||
.format = SDL_AUDIO_F32LE,
|
||||
.channels = static_cast<u8>(num_channels),
|
||||
.freq = static_cast<int>(sample_rate),
|
||||
};
|
||||
|
||||
// Determine device
|
||||
const std::string device_name = GetDeviceName(type);
|
||||
const SDL_AudioDeviceID dev_id = SelectAudioDevice(device_name, type);
|
||||
|
||||
if (dev_id == SDL_INVALID_AUDIODEVICEID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create audio stream
|
||||
stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
|
||||
if (!stream) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure channel mapping
|
||||
if (!ConfigureChannelMap()) {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set initial volume
|
||||
const float initial_gain = current_gain.load(std::memory_order_relaxed);
|
||||
if (!SDL_SetAudioStreamGain(stream, initial_gain)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to set initial audio gain: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
// Start playback
|
||||
if (!SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to resume audio stream: {}", SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AudioOut, "Opened audio device: {} ({} Hz, {} ch, gain: {:.3f})", device_name,
|
||||
sample_rate, num_channels, initial_gain);
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID SelectAudioDevice(const std::string& device_name, OrbisAudioOutPort type) {
|
||||
if (device_name == "None") {
|
||||
LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}",
|
||||
static_cast<int>(type));
|
||||
return SDL_INVALID_AUDIODEVICEID;
|
||||
}
|
||||
|
||||
if (device_name.empty() || device_name == "Default Device") {
|
||||
return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
|
||||
// Search for specific device
|
||||
int num_devices = 0;
|
||||
SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices);
|
||||
|
||||
if (!dev_array) {
|
||||
LOG_WARNING(Lib_AudioOut, "No audio devices found, using default");
|
||||
return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID selected_device = SDL_INVALID_AUDIODEVICEID;
|
||||
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]);
|
||||
if (dev_name && device_name == dev_name) {
|
||||
selected_device = dev_array[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(dev_array);
|
||||
|
||||
if (selected_device == SDL_INVALID_AUDIODEVICEID) {
|
||||
LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default", device_name);
|
||||
return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
|
||||
return selected_device;
|
||||
}
|
||||
|
||||
bool ConfigureChannelMap() {
|
||||
if (num_channels == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<int> channel_map(num_channels);
|
||||
|
||||
if (is_std && num_channels == 8) {
|
||||
// Standard 8CH layout requires remapping
|
||||
channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR};
|
||||
} else {
|
||||
std::copy_n(channel_layout.begin(), num_channels, channel_map.begin());
|
||||
}
|
||||
|
||||
if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetDeviceName(OrbisAudioOutPort type) const {
|
||||
switch (type) {
|
||||
case OrbisAudioOutPort::Main:
|
||||
case OrbisAudioOutPort::Bgm:
|
||||
return Config::getMainOutputDevice();
|
||||
case OrbisAudioOutPort::PadSpk:
|
||||
return Config::getPadSpkOutputDevice();
|
||||
default:
|
||||
return Config::getMainOutputDevice();
|
||||
}
|
||||
}
|
||||
|
||||
bool SelectConverter() {
|
||||
if (is_float) {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertF32Mono;
|
||||
break;
|
||||
case 2:
|
||||
convert = &ConvertF32Stereo;
|
||||
break;
|
||||
case 8:
|
||||
convert = is_std ? &ConvertF32Std8CH : &ConvertF32_8CH;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertS16Mono;
|
||||
break;
|
||||
case 2:
|
||||
#if defined(HAS_SSE2)
|
||||
convert = &ConvertS16StereoSIMD;
|
||||
#else
|
||||
convert = &ConvertS16Stereo;
|
||||
#endif
|
||||
break;
|
||||
case 8:
|
||||
#if defined(HAS_SSE2)
|
||||
convert = &ConvertS16_8CH_SIMD;
|
||||
#else
|
||||
convert = &ConvertS16_8CH;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalculateQueueThreshold() {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AudioSpec discard;
|
||||
int sdl_buffer_frames = 0;
|
||||
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
|
||||
&sdl_buffer_frames)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
const u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels;
|
||||
queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * QUEUE_MULTIPLIER;
|
||||
|
||||
LOG_DEBUG(Lib_AudioOut, "Audio queue threshold: {} bytes (SDL buffer: {} frames)",
|
||||
queue_threshold, sdl_buffer_frames);
|
||||
}
|
||||
|
||||
using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes);
|
||||
|
||||
static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
d[i] = s[i] * INV_VOLUME_0DB;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 1; // * 2
|
||||
for (u32 i = 0; i < num_samples; i++) {
|
||||
d[i] = s[i] * INV_VOLUME_0DB;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_SSE2
|
||||
static void ConvertS16StereoSIMD(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB);
|
||||
const u32 num_samples = frames << 1;
|
||||
u32 i = 0;
|
||||
|
||||
// Process 8 samples at a time (4 stereo frames)
|
||||
for (; i + 8 <= num_samples; i += 8) {
|
||||
// Load 8 s16 values
|
||||
__m128i s16_vals = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&s[i]));
|
||||
|
||||
// Convert to 32-bit integers
|
||||
__m128i s32_lo = _mm_cvtepi16_epi32(s16_vals);
|
||||
__m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8));
|
||||
|
||||
// Convert to float and scale
|
||||
__m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale);
|
||||
__m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale);
|
||||
|
||||
// Store results
|
||||
_mm_storeu_ps(&d[i], f_lo);
|
||||
_mm_storeu_ps(&d[i + 4], f_hi);
|
||||
}
|
||||
|
||||
// Handle remaining samples
|
||||
for (; i < num_samples; i++) {
|
||||
d[i] = s[i] * INV_VOLUME_0DB;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
const u32 num_samples = frames << 3; // * 8
|
||||
for (u32 i = 0; i < num_samples; i++) {
|
||||
d[i] = s[i] * INV_VOLUME_0DB;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_SSE2
|
||||
static void ConvertS16_8CH_SIMD(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB);
|
||||
const u32 num_samples = frames << 3;
|
||||
u32 i = 0;
|
||||
|
||||
// Process 8 samples at a time
|
||||
for (; i + 8 <= num_samples; i += 8) {
|
||||
__m128i s16_vals = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&s[i]));
|
||||
__m128i s32_lo = _mm_cvtepi16_epi32(s16_vals);
|
||||
__m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8));
|
||||
__m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale);
|
||||
__m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale);
|
||||
_mm_storeu_ps(&d[i], f_lo);
|
||||
_mm_storeu_ps(&d[i + 4], f_hi);
|
||||
}
|
||||
|
||||
for (; i < num_samples; i++) {
|
||||
d[i] = s[i] * INV_VOLUME_0DB;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * 2 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * 8 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Std8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
// Channel remapping for standard 8CH layout
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
const u32 offset = i << 3; // * 8
|
||||
|
||||
d[offset + FL] = s[offset + FL];
|
||||
d[offset + FR] = s[offset + FR];
|
||||
d[offset + FC] = s[offset + FC];
|
||||
d[offset + LF] = s[offset + LF];
|
||||
d[offset + SL] = s[offset + STD_SL];
|
||||
d[offset + SR] = s[offset + STD_SR];
|
||||
d[offset + BL] = s[offset + STD_BL];
|
||||
d[offset + BR] = s[offset + STD_BR];
|
||||
}
|
||||
}
|
||||
|
||||
// Audio format parameters
|
||||
const u32 frame_size;
|
||||
const u32 guest_buffer_size;
|
||||
const u32 buffer_frames;
|
||||
const u32 sample_rate;
|
||||
const u32 num_channels;
|
||||
const bool is_float;
|
||||
const bool is_std;
|
||||
const std::array<int, 8> channel_layout;
|
||||
|
||||
alignas(64) u64 period_us{0};
|
||||
alignas(64) std::atomic<u64> last_output_time{0};
|
||||
u64 next_output_time{0};
|
||||
u64 last_volume_check_time{0};
|
||||
u32 output_count{0};
|
||||
|
||||
// Buffers
|
||||
u32 internal_buffer_size{0};
|
||||
void* internal_buffer{nullptr};
|
||||
|
||||
// Converter function pointer
|
||||
ConverterFunc convert{nullptr};
|
||||
|
||||
// Volume management
|
||||
alignas(64) std::atomic<float> current_gain{1.0f};
|
||||
|
||||
// SDL audio stream
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
u32 queue_threshold{0};
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
@ -1,139 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#include "sdl_in.h"
|
||||
|
||||
int SDLAudioIn::AudioInit() {
|
||||
return SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
int SDLAudioIn::AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
for (int id = 0; id < static_cast<int>(portsIn.size()); ++id) {
|
||||
auto& port = portsIn[id];
|
||||
if (!port.isOpen) {
|
||||
port.isOpen = true;
|
||||
port.type = type;
|
||||
port.samples_num = samples_num;
|
||||
port.freq = freq;
|
||||
port.format = format;
|
||||
|
||||
SDL_AudioFormat sampleFormat;
|
||||
switch (format) {
|
||||
case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO:
|
||||
sampleFormat = SDL_AUDIO_S16;
|
||||
port.channels_num = 1;
|
||||
port.sample_size = 2;
|
||||
break;
|
||||
case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO:
|
||||
sampleFormat = SDL_AUDIO_S16;
|
||||
port.channels_num = 2;
|
||||
port.sample_size = 2;
|
||||
break;
|
||||
default:
|
||||
port.isOpen = false;
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
SDL_AudioSpec fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.format = sampleFormat;
|
||||
fmt.channels = port.channels_num;
|
||||
fmt.freq = port.freq;
|
||||
|
||||
std::string micDevStr = Config::getMicDevice();
|
||||
uint32_t devId;
|
||||
|
||||
bool nullDevice = false;
|
||||
if (micDevStr == "None") {
|
||||
nullDevice = true;
|
||||
} else if (micDevStr == "Default Device") {
|
||||
devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
|
||||
} else {
|
||||
try {
|
||||
devId = static_cast<uint32_t>(std::stoul(micDevStr));
|
||||
} catch (const std::exception& e) {
|
||||
nullDevice = true;
|
||||
}
|
||||
}
|
||||
|
||||
port.stream =
|
||||
nullDevice ? nullptr : SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
|
||||
|
||||
if (!port.stream) {
|
||||
// if stream is null, either due to configuration disabling the input,
|
||||
// or no input devices present in the system, still return a valid id
|
||||
// as some games require that (e.g. L.A. Noire)
|
||||
return id + 1;
|
||||
}
|
||||
|
||||
if (SDL_ResumeAudioStreamDevice(port.stream) == false) {
|
||||
SDL_DestroyAudioStream(port.stream);
|
||||
port = {};
|
||||
return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
|
||||
}
|
||||
|
||||
return id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
int SDLAudioIn::AudioInInput(int handle, void* out_buffer) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (handle < 1 || handle > static_cast<int>(portsIn.size()) || !out_buffer)
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
|
||||
auto& port = portsIn[handle - 1];
|
||||
if (!port.isOpen)
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
|
||||
const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
|
||||
|
||||
if (out_buffer == nullptr) {
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 0) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
return 0; // done
|
||||
}
|
||||
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) < bytesToRead) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
const int bytesRead = SDL_GetAudioStreamData(port.stream, out_buffer, bytesToRead);
|
||||
if (bytesRead < 0) {
|
||||
// SDL_GetAudioStreamData failed
|
||||
LOG_ERROR(Lib_AudioIn, "AudioInInput error: {}", SDL_GetError());
|
||||
return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
|
||||
}
|
||||
const int framesRead = bytesRead / (port.sample_size * port.channels_num);
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
void SDLAudioIn::AudioInClose(int handle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (handle < 1 || handle > (int)portsIn.size())
|
||||
return;
|
||||
|
||||
auto& port = portsIn[handle - 1];
|
||||
if (!port.isOpen)
|
||||
return;
|
||||
|
||||
SDL_DestroyAudioStream(port.stream);
|
||||
port = {};
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
enum OrbisAudioInParam {
|
||||
ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO = 0,
|
||||
ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO = 2
|
||||
};
|
||||
}
|
||||
|
||||
#define ORBIS_AUDIO_IN_ERROR_INVALID_PORT -1
|
||||
#define ORBIS_AUDIO_IN_ERROR_TIMEOUT -2
|
||||
#define ORBIS_AUDIO_IN_ERROR_STREAM_FAIL -3
|
||||
|
||||
class SDLAudioIn {
|
||||
public:
|
||||
int AudioInit();
|
||||
int AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format);
|
||||
int AudioInInput(int handle, void* out_buffer);
|
||||
void AudioInClose(int handle);
|
||||
|
||||
private:
|
||||
struct AudioInPort {
|
||||
bool isOpen = false;
|
||||
int type = 0;
|
||||
uint32_t samples_num = 0;
|
||||
uint32_t freq = 0;
|
||||
int channels_num = 0;
|
||||
int sample_size = 0;
|
||||
uint32_t format = 0;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
|
||||
std::array<AudioInPort, 8> portsIn;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
|
||||
return AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
|
||||
const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
LOG_INFO(Lib_Audio3d,
|
||||
"called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}",
|
||||
port_id, user_id, type, index, len, freq);
|
||||
@ -422,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<uint32_t>(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<uint32_t>(
|
||||
OrbisImeInitExtKeyboardMode::INPUT_METHOD_STATE_FULL_WIDTH)) != 0) {
|
||||
*width = *width * 5 / 4;
|
||||
}
|
||||
|
||||
// Check for format characters enabled
|
||||
if ((extended->ext_keyboard_mode &
|
||||
static_cast<uint32_t>(
|
||||
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<OrbisImeOption>(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<OrbisImeLanguage>(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<OrbisImeLanguage>(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<OrbisImeLanguage>(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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace D = Core::Devices;
|
||||
@ -751,6 +752,30 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
|
||||
sb->st_size = file->f.GetSize();
|
||||
sb->st_blksize = 512;
|
||||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
struct stat filestat = {};
|
||||
stat(file->f.GetPath().c_str(), &filestat);
|
||||
sb->st_atim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_atim);
|
||||
sb->st_mtim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_mtim);
|
||||
sb->st_ctim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_ctim);
|
||||
#elif defined(__APPLE__)
|
||||
struct stat filestat = {};
|
||||
stat(file->f.GetPath().c_str(), &filestat);
|
||||
sb->st_atim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_atimespec);
|
||||
sb->st_mtim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_mtimespec);
|
||||
sb->st_ctim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_ctimespec);
|
||||
#else
|
||||
const auto ft = std::filesystem::last_write_time(file->f.GetPath());
|
||||
const auto sctp = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||
ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
const auto secs = std::chrono::time_point_cast<std::chrono::seconds>(sctp);
|
||||
const auto nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(sctp - secs);
|
||||
|
||||
sb->st_mtim.tv_sec = static_cast<int64_t>(secs.time_since_epoch().count());
|
||||
sb->st_mtim.tv_nsec = static_cast<int64_t>(nsecs.count());
|
||||
sb->st_atim = sb->st_mtim;
|
||||
sb->st_ctim = sb->st_mtim;
|
||||
#endif
|
||||
// TODO incomplete
|
||||
break;
|
||||
}
|
||||
@ -1262,7 +1287,8 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri
|
||||
if (file->type == Core::FileSys::FileType::Regular ||
|
||||
file->type == Core::FileSys::FileType::Device) {
|
||||
// Disk files always ready
|
||||
if (want_read) {
|
||||
// For devices, stdin (fd 0) is never read-ready.
|
||||
if (want_read && i != 0) {
|
||||
FD_SET_POSIX(i, &read_ready);
|
||||
}
|
||||
if (want_write) {
|
||||
|
||||
@ -65,6 +65,9 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000;
|
||||
constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000;
|
||||
constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000;
|
||||
|
||||
s32 PS4_SYSV_ABI posix_open(const char* path, s32 flags, u16 mode);
|
||||
s32 PS4_SYSV_ABI posix_close(s32 fd);
|
||||
s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence);
|
||||
s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, u64 nbytes);
|
||||
s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes);
|
||||
s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, u64 nbytes, s64 offset);
|
||||
|
||||
@ -89,21 +89,31 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 m
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
return ORBIS_OK;
|
||||
return memory->Free(start, len, true);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
memory->Free(start, len, false);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -191,6 +191,26 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfo(s32 handle, Core::OrbisKernelModuleInfo*
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetModuleInfo2(s32 handle, Core::OrbisKernelModuleInfo* info) {
|
||||
if (info == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
if (info->st_size != sizeof(Core::OrbisKernelModuleInfo)) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
auto* module = linker->GetModule(handle);
|
||||
if (module == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
if (module->IsSystemLib()) {
|
||||
return ORBIS_KERNEL_ERROR_EPERM;
|
||||
}
|
||||
*info = module->GetModuleInfo();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetModuleInfoInternal(s32 handle, Core::OrbisKernelModuleInfoEx* info) {
|
||||
if (info == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EFAULT;
|
||||
@ -230,6 +250,31 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList(s32* handles, u64 num_array, u64* out_co
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_count) {
|
||||
if (handles == nullptr || out_count == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
u64 id = 0;
|
||||
u64 index = 0;
|
||||
auto* module = linker->GetModule(id);
|
||||
while (module != nullptr && index < num_array) {
|
||||
if (!module->IsSystemLib()) {
|
||||
handles[index++] = id;
|
||||
}
|
||||
id++;
|
||||
module = linker->GetModule(id);
|
||||
}
|
||||
|
||||
if (index == num_array && module != nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
|
||||
*out_count = index;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI exit(s32 status) {
|
||||
UNREACHABLE_MSG("Exiting with status code {}", status);
|
||||
return 0;
|
||||
@ -249,8 +294,11 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", sceKernelGetModuleInfoForUnwind);
|
||||
LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", sceKernelGetModuleInfoFromAddr);
|
||||
LIB_FUNCTION("kUpgrXIrz7Q", "libkernel", 1, "libkernel", sceKernelGetModuleInfo);
|
||||
LIB_FUNCTION("QgsKEUfkqMA", "libkernel", 1, "libkernel", sceKernelGetModuleInfo2);
|
||||
LIB_FUNCTION("QgsKEUfkqMA", "libkernel_module_info", 1, "libkernel", sceKernelGetModuleInfo2);
|
||||
LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal);
|
||||
LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList);
|
||||
LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2);
|
||||
LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit);
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,16 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return);
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr);
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type);
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr);
|
||||
|
||||
int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr,
|
||||
const char* name);
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex);
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex);
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex);
|
||||
|
||||
void RegisterThreads(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
class Thread {
|
||||
|
||||
@ -261,7 +261,6 @@ int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) {
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex,
|
||||
const OrbisKernelTimespec* abstime) {
|
||||
CHECK_AND_INIT_MUTEX
|
||||
UNREACHABLE();
|
||||
return (*mutex)->Lock(abstime);
|
||||
}
|
||||
|
||||
@ -378,7 +377,8 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
|
||||
if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) {
|
||||
if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck ||
|
||||
type >= PthreadMutexType::Max) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
(*attr)->m_type = type;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -24,9 +24,9 @@ static constexpr std::array<PthreadPrio, 3> 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,
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "libc_internal_math.h"
|
||||
#include "libc_internal_memory.h"
|
||||
#include "libc_internal_str.h"
|
||||
#include "libc_internal_threads.h"
|
||||
#include "printf.h"
|
||||
|
||||
namespace Libraries::LibcInternal {
|
||||
@ -20,5 +21,11 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
RegisterlibSceLibcInternalStr(sym);
|
||||
RegisterlibSceLibcInternalMemory(sym);
|
||||
RegisterlibSceLibcInternalIo(sym);
|
||||
RegisterlibSceLibcInternalThreads(sym);
|
||||
}
|
||||
|
||||
void ForceRegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
// Used to forcibly enable HLEs for broken LLE functions.
|
||||
ForceRegisterlibSceLibcInternalIo(sym);
|
||||
}
|
||||
} // namespace Libraries::LibcInternal
|
||||
@ -15,4 +15,5 @@ namespace Libraries::LibcInternal {
|
||||
// so everything is just in the .cpp file
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
void ForceRegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::LibcInternal
|
||||
@ -3,21 +3,484 @@
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
|
||||
#include <common/va_ctx.h>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/file_system.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/posix_error.h"
|
||||
#include "core/libraries/libc_internal/libc_internal_io.h"
|
||||
#include "core/libraries/libc_internal/libc_internal_threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "libc_internal_io.h"
|
||||
#include "printf.h"
|
||||
|
||||
namespace Libraries::LibcInternal {
|
||||
int PS4_SYSV_ABI internal_snprintf(char* s, size_t n, VA_ARGS) {
|
||||
|
||||
s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS) {
|
||||
VA_CTX(ctx);
|
||||
return snprintf_ctx(s, n, &ctx);
|
||||
}
|
||||
|
||||
std::map<s32, OrbisFILE*> g_files{};
|
||||
// Constants for tracking accurate file indexes.
|
||||
// Since the file struct is exposed to the application, accuracy is important.
|
||||
static constexpr s32 g_initial_files = 5;
|
||||
static constexpr s32 g_max_files = 0x100;
|
||||
|
||||
OrbisFILE* PS4_SYSV_ABI internal__Fofind() {
|
||||
u64 index = g_initial_files;
|
||||
while (index != g_max_files) {
|
||||
OrbisFILE* file = g_files[index];
|
||||
// If file doesn't exist, create it.
|
||||
if (file == nullptr) {
|
||||
file = new OrbisFILE();
|
||||
if (file == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
// Store new file in the array, initialize default values, and return it.
|
||||
g_files[index] = file;
|
||||
file->_Mode = 0x80;
|
||||
file->_Idx = index;
|
||||
return file;
|
||||
}
|
||||
// Special case, files with mode 0 are returned?
|
||||
if (file->_Mode == 0) {
|
||||
file->_Mode = 0xff7f;
|
||||
return file;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file) {
|
||||
if (file != nullptr && file->_Mutex != nullptr) {
|
||||
internal__Mtxlock(&file->_Mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file) {
|
||||
if (file != nullptr && file->_Mutex != nullptr) {
|
||||
internal__Mtxunlock(&file->_Mutex);
|
||||
}
|
||||
}
|
||||
|
||||
OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file,
|
||||
s32 fd, s32 s_mode, s32 flag) {
|
||||
if (file == nullptr) {
|
||||
*Kernel::__Error() = POSIX_ENOMEM;
|
||||
}
|
||||
|
||||
// Preserve mode and index
|
||||
Libraries::Kernel::PthreadMutexT mtx = file->_Mutex;
|
||||
Libraries::Kernel::PthreadMutexT* mtx_ptr = &file->_Mutex;
|
||||
u8 file_index = file->_Idx;
|
||||
u16 file_mode = file->_Mode & 0x80;
|
||||
|
||||
// Real library does a memcpy using a static global FILE object.
|
||||
// This stored file is just zeros, with the only exception being a handle of -1.
|
||||
memset(file, 0, sizeof(OrbisFILE));
|
||||
file->_Handle = -1;
|
||||
|
||||
// Not sure what this magic is for, but I'll replicate it.
|
||||
u8* ptr = &file->_Cbuf;
|
||||
// Note: this field is supposed to be a pthread mutex.
|
||||
// Since we don't export pthread HLEs for other functions, I'll avoid handling this for now.
|
||||
file->_Mutex = nullptr;
|
||||
file->_Idx = file_index;
|
||||
file->_Buf = ptr;
|
||||
file->_Bend = &file->unk2;
|
||||
file->_Next = ptr;
|
||||
file->_Rend = ptr;
|
||||
file->_WRend = ptr;
|
||||
file->_Wend = ptr;
|
||||
file->_WWend = ptr;
|
||||
file->_Rback = ptr;
|
||||
file->_WRback = &file->unk1;
|
||||
|
||||
// Parse inputted mode string
|
||||
const char* mode_str = mode;
|
||||
u16 calc_mode = 0;
|
||||
u16 access_mode = 0;
|
||||
if (mode_str[0] == 'r') {
|
||||
calc_mode = 1 | file_mode;
|
||||
} else if (mode_str[0] == 'w') {
|
||||
calc_mode = 0x1a | file_mode;
|
||||
} else if (mode_str[0] == 'a') {
|
||||
calc_mode = 0x16 | file_mode;
|
||||
} else {
|
||||
// Closes the file and returns EINVAL.
|
||||
file->_Mode = file_mode;
|
||||
if (flag == 0) {
|
||||
internal__Mtxinit(mtx_ptr, nullptr);
|
||||
} else {
|
||||
file->_Mutex = mtx;
|
||||
internal__Unlockfilelock(file);
|
||||
}
|
||||
internal_fclose(file);
|
||||
*Kernel::__Error() = POSIX_EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
file->_Mode = calc_mode;
|
||||
|
||||
do {
|
||||
// This is all basically straight from decomp, need to cleanup at some point.
|
||||
if (mode_str[1] == '+') {
|
||||
file_mode = 3;
|
||||
if ((~calc_mode & 3) == 0) {
|
||||
break;
|
||||
}
|
||||
} else if (mode_str[1] != 'b') {
|
||||
file_mode = 0x20;
|
||||
if ((calc_mode & 0x20) != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mode_str++;
|
||||
calc_mode = file_mode | calc_mode;
|
||||
file->_Mode = calc_mode;
|
||||
} while (true);
|
||||
|
||||
if (path == nullptr && fd >= 0) {
|
||||
// I guess this is for some internal behavior?
|
||||
file->_Handle = fd;
|
||||
} else {
|
||||
fd = internal__Fopen(path, calc_mode, s_mode == 0x55);
|
||||
file->_Handle = fd;
|
||||
}
|
||||
|
||||
// Error case
|
||||
if (fd < 0) {
|
||||
// Closes the file, but ensures errno is unchanged.
|
||||
if (flag == 0) {
|
||||
internal__Mtxinit(mtx_ptr, nullptr);
|
||||
} else {
|
||||
file->_Mutex = mtx;
|
||||
internal__Unlockfilelock(file);
|
||||
}
|
||||
s32 old_errno = *Kernel::__Error();
|
||||
internal_fclose(file);
|
||||
*Kernel::__Error() = old_errno;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (flag == 0) {
|
||||
char mtx_name[0x20];
|
||||
std::snprintf(mtx_name, 0x20, "FileFD:0x%08X", fd);
|
||||
internal__Mtxinit(mtx_ptr, mtx_name);
|
||||
} else {
|
||||
file->_Mutex = mtx;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag) {
|
||||
u32 large_mode = mode;
|
||||
u16 open_mode = 0600;
|
||||
if (!flag) {
|
||||
open_mode = 0666;
|
||||
}
|
||||
// Straight from decomp, should probably get cleaned up at some point.
|
||||
s32 creat_flag = large_mode << 5 & 0x200;
|
||||
s32 excl_flag = large_mode << 5 & 0x800;
|
||||
s32 misc_flags = (large_mode & 8) * 0x80 + (large_mode & 4) * 2;
|
||||
// Real library has an array for this, where large_mode & 3 is used as an index.
|
||||
// That array has values [0, 0, 1, 2], so this call should match the result.
|
||||
s32 access_flag = std::max<s32>((large_mode & 3) - 1, 0);
|
||||
s32 open_flags = creat_flag | misc_flags | excl_flag | access_flag;
|
||||
return Libraries::Kernel::posix_open(path, open_flags, open_mode);
|
||||
}
|
||||
|
||||
OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode) {
|
||||
std::scoped_lock lk{g_file_mtx};
|
||||
LOG_INFO(Lib_LibcInternal, "called, path {}, mode {}", path, mode);
|
||||
OrbisFILE* file = internal__Fofind();
|
||||
return internal__Foprep(path, mode, file, -1, 0, 0);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file) {
|
||||
if (file == nullptr) {
|
||||
std::scoped_lock lk{g_file_mtx};
|
||||
s32 fflush_result = 0;
|
||||
for (auto& file : g_files) {
|
||||
s32 res = internal_fflush(file.second);
|
||||
if (res < 0) {
|
||||
fflush_result = -1;
|
||||
}
|
||||
}
|
||||
return fflush_result;
|
||||
}
|
||||
if ((file->_Mode & 0x2000) != 0) {
|
||||
internal__Lockfilelock(file);
|
||||
u16 file_mode = file->_Mode;
|
||||
u8* file_buf_start = file->_Buf;
|
||||
u8* file_buf_end = file->_Next;
|
||||
while (file_buf_start < file_buf_end) {
|
||||
u64 size_to_write = static_cast<u64>(file_buf_end - file_buf_start);
|
||||
s32 write_bytes =
|
||||
Libraries::Kernel::sceKernelWrite(file->_Handle, file_buf_start, size_to_write);
|
||||
if (write_bytes < 1) {
|
||||
file_buf_start = file->_Buf;
|
||||
file->_Next = file_buf_start;
|
||||
file->_Wend = file_buf_start;
|
||||
file->_WWend = file_buf_start;
|
||||
u8* off_mode = reinterpret_cast<u8*>(&file->_Mode) + 1;
|
||||
*off_mode = *off_mode | 2;
|
||||
internal__Unlockfilelock(file);
|
||||
return -1;
|
||||
}
|
||||
file_buf_end = file->_Next;
|
||||
file_buf_start += write_bytes;
|
||||
}
|
||||
file->_Next = file_buf_start;
|
||||
file->_Wend = file_buf_start;
|
||||
file->_WWend = file_buf_start;
|
||||
file->_Mode = file_mode & 0xdfff;
|
||||
internal__Unlockfilelock(file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2) {
|
||||
if (val1 < val2) {
|
||||
return val2 - val1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence) {
|
||||
if ((file->_Mode & 3) == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (internal_fflush(file) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (whence >= 3) {
|
||||
*Libraries::Kernel::__Error() = POSIX_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (file_pos != nullptr) {
|
||||
offset = offset + file_pos->_Off;
|
||||
}
|
||||
if (whence == 1 && (file->_Mode & 0x1000) != 0) {
|
||||
s64 val1 = internal__Nnl(file, file->_Rback, &file->_Cbuf);
|
||||
u8* rsave_ptr = file->_Rsave;
|
||||
if (rsave_ptr == nullptr) {
|
||||
rsave_ptr = file->_Rend;
|
||||
}
|
||||
s64 val2 = internal__Nnl(file, file->_Next, rsave_ptr);
|
||||
s64 val3 = internal__Nnl(file, file->_Next, file->_WRend);
|
||||
offset = offset - (val1 + val2 + val3);
|
||||
}
|
||||
s64 result = 0;
|
||||
if (whence == 2 || (whence == 1 && offset != 0) || (whence == 0 && offset != -1)) {
|
||||
result = Libraries::Kernel::posix_lseek(file->_Handle, offset, whence);
|
||||
}
|
||||
if (result == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
u16 file_mode = file->_Mode;
|
||||
if ((file_mode & 0x3000) != 0) {
|
||||
u8* file_buf = file->_Buf;
|
||||
file->_Next = file_buf;
|
||||
file->_Rend = file_buf;
|
||||
file->_WRend = file_buf;
|
||||
file->_Wend = file_buf;
|
||||
file->_WWend = file_buf;
|
||||
file->_Rback = &file->_Cbuf;
|
||||
file->_WRback = &file->unk1;
|
||||
file->_Rsave = nullptr;
|
||||
}
|
||||
if (file_pos != nullptr) {
|
||||
std::memcpy(&file->_Wstate, &file_pos->_Wstate, sizeof(Orbis_Mbstatet));
|
||||
}
|
||||
file->_Mode = file_mode & 0xceff;
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence) {
|
||||
internal__Lockfilelock(file);
|
||||
LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, offset {:#x}, whence {:#x}",
|
||||
file->_Handle, offset, whence);
|
||||
s32 result = internal__Fspos(file, nullptr, offset, whence);
|
||||
internal__Unlockfilelock(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file) {
|
||||
if (file->_Rend > file->_Next) {
|
||||
return 1;
|
||||
}
|
||||
if ((file->_Mode & 0x100) != 0) {
|
||||
return 0;
|
||||
}
|
||||
u16 mode = file->_Mode;
|
||||
if ((mode & 0xa001) != 1) {
|
||||
// Lot of magic here, might be valuable to figure out what this does.
|
||||
file->_Mode = (((mode ^ 0x8000) >> 0xf) << 0xe) | mode | 0x200;
|
||||
return -1;
|
||||
}
|
||||
|
||||
u8* file_buf = file->_Buf;
|
||||
if ((mode & 0x800) == 0 && file_buf == &file->_Cbuf) {
|
||||
// Allocate a new file buffer, for now, we'll use host malloc to create it.
|
||||
// When we have an HLE for malloc, that should be used instead.
|
||||
u8* new_buffer = std::bit_cast<u8*>(std::malloc(0x10000));
|
||||
if (new_buffer == nullptr) {
|
||||
file->_Buf = file_buf;
|
||||
file->_Bend = file_buf + 1;
|
||||
} else {
|
||||
file->_Mode = file->_Mode | 0x40;
|
||||
file->_Buf = new_buffer;
|
||||
file->_Bend = new_buffer + 0x10000;
|
||||
file->_WRend = new_buffer;
|
||||
file->_WWend = new_buffer;
|
||||
file_buf = new_buffer;
|
||||
}
|
||||
}
|
||||
file->_Next = file_buf;
|
||||
file->_Rend = file_buf;
|
||||
file->_Wend = file_buf;
|
||||
// Intentional shrinking here, library treats value as 32-bit.
|
||||
s32 read_result =
|
||||
Libraries::Kernel::sceKernelRead(file->_Handle, file_buf, file->_Bend - file_buf);
|
||||
if (read_result < 0) {
|
||||
u8* off_mode = reinterpret_cast<u8*>(&file->_Mode) + 1;
|
||||
*off_mode = *off_mode | 0x42;
|
||||
return -1;
|
||||
} else if (read_result != 0) {
|
||||
file->_Mode = file->_Mode | 0x5000;
|
||||
file->_Rend = file->_Rend + read_result;
|
||||
return 1;
|
||||
}
|
||||
file->_Mode = (file->_Mode & 0xaeff) | 0x4100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file) {
|
||||
if (size == 0 || nmemb == 0) {
|
||||
return 0;
|
||||
}
|
||||
internal__Lockfilelock(file);
|
||||
LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, size {:#x}, nmemb {:#x}", file->_Handle,
|
||||
size, nmemb);
|
||||
s64 total_size = size * nmemb;
|
||||
s64 remaining_size = total_size;
|
||||
if ((file->_Mode & 0x4000) != 0) {
|
||||
while (remaining_size != 0) {
|
||||
u8* rback_ptr = file->_Rback;
|
||||
if (&file->_Cbuf <= rback_ptr) {
|
||||
break;
|
||||
}
|
||||
file->_Rback = rback_ptr + 1;
|
||||
*ptr = *rback_ptr;
|
||||
ptr++;
|
||||
remaining_size--;
|
||||
}
|
||||
}
|
||||
|
||||
while (remaining_size != 0) {
|
||||
u8* file_ptr = file->_Rsave;
|
||||
if (file_ptr == nullptr) {
|
||||
file_ptr = file->_Rend;
|
||||
} else {
|
||||
file->_Rend = file_ptr;
|
||||
file->_Rsave = nullptr;
|
||||
}
|
||||
u8* src = file->_Next;
|
||||
if (file_ptr <= src) {
|
||||
s32 res = internal__Frprep(file);
|
||||
if (res < 1) {
|
||||
internal__Unlockfilelock(file);
|
||||
return (total_size - remaining_size) / size;
|
||||
}
|
||||
src = file->_Next;
|
||||
file_ptr = file->_Rend;
|
||||
}
|
||||
u64 copy_bytes = std::min<u64>(file_ptr - src, remaining_size);
|
||||
std::memcpy(ptr, src, copy_bytes);
|
||||
file->_Next += copy_bytes;
|
||||
ptr += copy_bytes;
|
||||
remaining_size -= copy_bytes;
|
||||
}
|
||||
internal__Unlockfilelock(file);
|
||||
return (total_size - remaining_size) / size;
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI internal__Fofree(OrbisFILE* file) {
|
||||
u8* cbuf_ptr = &file->_Cbuf;
|
||||
s8 trunc_mode = static_cast<s8>(file->_Mode);
|
||||
|
||||
file->_Mode = 0;
|
||||
file->_Handle = -1;
|
||||
file->_Buf = cbuf_ptr;
|
||||
file->_Next = cbuf_ptr;
|
||||
file->_Rend = cbuf_ptr;
|
||||
file->_WRend = cbuf_ptr;
|
||||
file->_Wend = cbuf_ptr;
|
||||
file->_WWend = cbuf_ptr;
|
||||
file->_Rback = cbuf_ptr;
|
||||
file->_WRback = &file->unk1;
|
||||
if (trunc_mode < 0) {
|
||||
// Remove file from vector
|
||||
g_files.erase(file->_Idx);
|
||||
internal__Mtxdst(&file->_Mutex);
|
||||
free(file);
|
||||
}
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file) {
|
||||
if (file == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_LibcInternal, "called, file handle {:#x}", file->_Handle);
|
||||
if ((file->_Mode & 3) == 0 || file->_Handle < 0) {
|
||||
std::scoped_lock lk{g_file_mtx};
|
||||
internal__Fofree(file);
|
||||
*Libraries::Kernel::__Error() = POSIX_EBADF;
|
||||
} else {
|
||||
s32 fflush_result = internal_fflush(file);
|
||||
std::scoped_lock lk{g_file_mtx};
|
||||
if ((file->_Mode & 0x40) != 0) {
|
||||
std::free(file->_Buf);
|
||||
}
|
||||
file->_Buf = nullptr;
|
||||
s32 close_result = Libraries::Kernel::posix_close(file->_Handle);
|
||||
internal__Fofree(file);
|
||||
// Need to figure out what exactly this means.
|
||||
return ~-(close_result == 0) | fflush_result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("eLdDw6l0-bU", "libSceLibcInternal", 1, "libSceLibcInternal", internal_snprintf);
|
||||
LIB_FUNCTION("MUjC4lbHrK4", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fflush);
|
||||
LIB_FUNCTION("xGT4Mc55ViQ", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofind);
|
||||
LIB_FUNCTION("dREVnZkAKRE", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Foprep);
|
||||
LIB_FUNCTION("sQL8D-jio7U", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fopen);
|
||||
LIB_FUNCTION("A+Y3xfrWLLo", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fspos);
|
||||
LIB_FUNCTION("Ss3108pBuZY", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Nnl);
|
||||
LIB_FUNCTION("9s3P+LCvWP8", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Frprep);
|
||||
LIB_FUNCTION("jVDuvE3s5Bs", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofree);
|
||||
LIB_FUNCTION("vZkmJmvqueY", "libSceLibcInternal", 1, "libSceLibcInternal",
|
||||
internal__Lockfilelock);
|
||||
LIB_FUNCTION("0x7rx8TKy2Y", "libSceLibcInternal", 1, "libSceLibcInternal",
|
||||
internal__Unlockfilelock);
|
||||
}
|
||||
|
||||
void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) {
|
||||
// Goal is to be minimally intrusive here to allow LLE for printf/stdout writes.
|
||||
LIB_FUNCTION("xeYO4u7uyJ0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fopen);
|
||||
LIB_FUNCTION("rQFVBXp-Cxg", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fseek);
|
||||
LIB_FUNCTION("lbB+UlZqVG0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fread);
|
||||
LIB_FUNCTION("uodLYyUip20", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fclose);
|
||||
}
|
||||
|
||||
} // namespace Libraries::LibcInternal
|
||||
@ -3,12 +3,96 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::LibcInternal {
|
||||
|
||||
static std::recursive_mutex g_file_mtx{};
|
||||
|
||||
union Orbis__mbstate_t {
|
||||
u8 __mbstate8[128];
|
||||
s64 _mbstateL;
|
||||
};
|
||||
|
||||
struct Orbis_Mbstatet {
|
||||
u64 _Wchar;
|
||||
u16 _Byte, _State;
|
||||
s32 : 32;
|
||||
};
|
||||
|
||||
struct Orbisfpos_t {
|
||||
s64 _Off;
|
||||
Orbis_Mbstatet _Wstate;
|
||||
};
|
||||
|
||||
struct Orbis__sbuf {
|
||||
u8* _base;
|
||||
s32 _size;
|
||||
};
|
||||
|
||||
struct OrbisFILE {
|
||||
u16 _Mode;
|
||||
u8 _Idx;
|
||||
s32 _Handle;
|
||||
u8 *_Buf, *_Bend, *_Next;
|
||||
u8 *_Rend, *_Wend, *_Rback;
|
||||
u16 *_WRback, _WBack[2];
|
||||
u16 unk1;
|
||||
u8 *_Rsave, *_WRend, *_WWend;
|
||||
Orbis_Mbstatet _Wstate;
|
||||
u8* _Tmpnam;
|
||||
u8 _Back[6], _Cbuf;
|
||||
u8 unk2;
|
||||
Libraries::Kernel::PthreadMutexT _Mutex;
|
||||
u8* _p;
|
||||
s32 _r;
|
||||
s32 _w;
|
||||
s16 _flags;
|
||||
s16 _file;
|
||||
Orbis__sbuf _bf;
|
||||
s32 _lbfsize;
|
||||
void* _cookie;
|
||||
s32 PS4_SYSV_ABI (*_close)(void*);
|
||||
s32 PS4_SYSV_ABI (*_read)(void*, char*, s32);
|
||||
Orbisfpos_t PS4_SYSV_ABI (*_seek)(void*, Orbisfpos_t, s32);
|
||||
s32 (*_write)(void*, const char*, s32);
|
||||
Orbis__sbuf _ub;
|
||||
u8* _up;
|
||||
s32 _ur;
|
||||
u8 _ubuf[3];
|
||||
u8 _nbuf[1];
|
||||
Orbis__sbuf _lb;
|
||||
s32 _blksize;
|
||||
Orbisfpos_t _offset;
|
||||
void* _fl_mutex;
|
||||
void* _fl_owner;
|
||||
s32 _fl_count;
|
||||
s32 _orientation;
|
||||
Orbis__mbstate_t _mbstate;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS);
|
||||
void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file);
|
||||
void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file);
|
||||
OrbisFILE* PS4_SYSV_ABI internal__Fofind();
|
||||
OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file,
|
||||
s32 fd, s32 flag1, s32 flag2);
|
||||
s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag);
|
||||
OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode);
|
||||
s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2);
|
||||
s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence);
|
||||
s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file);
|
||||
s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence);
|
||||
s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file);
|
||||
u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file);
|
||||
s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file);
|
||||
|
||||
void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym);
|
||||
void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::LibcInternal
|
||||
65
src/core/libraries/libc_internal/libc_internal_threads.cpp
Normal file
65
src/core/libraries/libc_internal/libc_internal_threads.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
#include "core/libraries/libc_internal/libc_internal_threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::LibcInternal {
|
||||
|
||||
void getMutexName(char* buf, u64 size, const char* name) {
|
||||
if (name != nullptr) {
|
||||
std::snprintf(buf, size, "SceLibcI_%s", name);
|
||||
} else {
|
||||
std::snprintf(buf, size, "SceLibcI");
|
||||
}
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name) {
|
||||
char mtx_name[0x20];
|
||||
getMutexName(mtx_name, sizeof(mtx_name), name);
|
||||
|
||||
Libraries::Kernel::PthreadMutexAttrT attr{};
|
||||
s32 result = Libraries::Kernel::posix_pthread_mutexattr_init(&attr);
|
||||
if (result != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
result = Libraries::Kernel::posix_pthread_mutexattr_settype(
|
||||
&attr, Libraries::Kernel::PthreadMutexType::Recursive);
|
||||
if (result == 0) {
|
||||
s32 mtx_init_result = Libraries::Kernel::scePthreadMutexInit(mtx, &attr, mtx_name);
|
||||
result = Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
|
||||
if (mtx_init_result == 0 && result == 0) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx) {
|
||||
s32 result = Libraries::Kernel::posix_pthread_mutex_lock(mtx);
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx) {
|
||||
s32 result = Libraries::Kernel::posix_pthread_mutex_unlock(mtx);
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx) {
|
||||
s32 result = Libraries::Kernel::posix_pthread_mutex_destroy(mtx);
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("z7STeF6abuU", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxinit);
|
||||
LIB_FUNCTION("pE4Ot3CffW0", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxlock);
|
||||
LIB_FUNCTION("cMwgSSmpE5o", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxunlock);
|
||||
LIB_FUNCTION("LaPaA6mYA38", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxdst);
|
||||
}
|
||||
|
||||
} // namespace Libraries::LibcInternal
|
||||
22
src/core/libraries/libc_internal/libc_internal_threads.h
Normal file
22
src/core/libraries/libc_internal/libc_internal_threads.h
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::LibcInternal {
|
||||
|
||||
s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name);
|
||||
s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx);
|
||||
s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx);
|
||||
s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx);
|
||||
|
||||
void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::LibcInternal
|
||||
266
src/core/libraries/libpng/pngenc.cpp
Normal file
266
src/core/libraries/libpng/pngenc.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <png.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libpng/pngenc.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
#include "pngenc_error.h"
|
||||
|
||||
namespace Libraries::PngEnc {
|
||||
|
||||
struct PngHandler {
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
};
|
||||
|
||||
struct PngWriter {
|
||||
u8* cursor;
|
||||
u8* start;
|
||||
size_t capacity;
|
||||
bool cancel_write;
|
||||
};
|
||||
|
||||
static inline int MapPngFilter(u16 filter) {
|
||||
if (filter == (u16)OrbisPngEncFilterType::All) {
|
||||
return PNG_ALL_FILTERS;
|
||||
}
|
||||
|
||||
int f = 0;
|
||||
|
||||
if (filter & (u16)OrbisPngEncFilterType::None)
|
||||
f |= PNG_FILTER_NONE;
|
||||
if (filter & (u16)OrbisPngEncFilterType::Sub)
|
||||
f |= PNG_FILTER_SUB;
|
||||
if (filter & (u16)OrbisPngEncFilterType::Up)
|
||||
f |= PNG_FILTER_UP;
|
||||
if (filter & (u16)OrbisPngEncFilterType::Average)
|
||||
f |= PNG_FILTER_AVG;
|
||||
if (filter & (u16)OrbisPngEncFilterType::Paeth)
|
||||
f |= PNG_FILTER_PAETH;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void PngWriteFn(png_structp png_ptr, png_bytep data, size_t length) {
|
||||
PngWriter* ctx = (PngWriter*)png_get_io_ptr(png_ptr);
|
||||
|
||||
if ((size_t)(ctx->cursor - ctx->start) + length > ctx->capacity) {
|
||||
LOG_ERROR(Lib_Png, "PNG output buffer too small");
|
||||
ctx->cancel_write = true;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(ctx->cursor, data, length);
|
||||
ctx->cursor += length;
|
||||
}
|
||||
|
||||
void PngFlushFn(png_structp png_ptr) {}
|
||||
|
||||
void PngEncError(png_structp png_ptr, png_const_charp error_message) {
|
||||
LOG_ERROR(Lib_Png, "PNG error {}", error_message);
|
||||
}
|
||||
|
||||
void PngEncWarning(png_structp png_ptr, png_const_charp error_message) {
|
||||
LOG_ERROR(Lib_Png, "PNG warning {}", error_message);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePngEncCreate(const OrbisPngEncCreateParam* param, void* memoryAddress,
|
||||
u32 memorySize, OrbisPngEncHandle* handle) {
|
||||
if (param == nullptr || param->attribute != 0) {
|
||||
LOG_ERROR(Lib_Png, "Invalid param");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (memoryAddress == nullptr) {
|
||||
LOG_ERROR(Lib_Png, "Invalid memory address");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (param->max_image_width - 1 > 1000000) {
|
||||
LOG_ERROR(Lib_Png, "Invalid Size, width = {}", param->max_image_width);
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto pngh = (PngHandler*)memoryAddress;
|
||||
|
||||
pngh->png_ptr =
|
||||
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngEncError, PngEncWarning);
|
||||
|
||||
if (pngh->png_ptr == nullptr)
|
||||
return ORBIS_PNG_ENC_ERROR_FATAL;
|
||||
|
||||
pngh->info_ptr = png_create_info_struct(pngh->png_ptr);
|
||||
if (pngh->info_ptr == nullptr) {
|
||||
png_destroy_write_struct(&pngh->png_ptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
*handle = pngh;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePngEncDelete(OrbisPngEncHandle handle) {
|
||||
auto pngh = (PngHandler*)handle;
|
||||
png_destroy_write_struct(&pngh->png_ptr, &pngh->info_ptr);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePngEncEncode(OrbisPngEncHandle handle, const OrbisPngEncEncodeParam* param,
|
||||
OrbisPngEncOutputInfo* outputInfo) {
|
||||
LOG_TRACE(Lib_Png, "called png addr = {}, image addr = {}, image size = {}",
|
||||
(void*)param->png_mem_addr, (void*)param->image_mem_addr, param->image_mem_size);
|
||||
|
||||
if (handle == nullptr) {
|
||||
LOG_ERROR(Lib_Png, "Invalid handle");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (param == nullptr) {
|
||||
LOG_ERROR(Lib_Png, "Invalid param");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->image_mem_addr == nullptr || param->png_mem_addr == nullptr) {
|
||||
LOG_ERROR(Lib_Png, "Invalid input or output address");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (param->png_mem_size == 0 || param->image_mem_size == 0 || param->image_height == 0 ||
|
||||
param->image_width == 0) {
|
||||
LOG_ERROR(Lib_Png, "Invalid Size");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto pngh = (PngHandler*)handle;
|
||||
|
||||
if (setjmp(png_jmpbuf(pngh->png_ptr))) {
|
||||
LOG_ERROR(Lib_Png, "LibPNG aborted encode");
|
||||
return ORBIS_PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
int png_color_type = PNG_COLOR_TYPE_RGB;
|
||||
|
||||
if (param->color_space == OrbisPngEncColorSpace::RGBA) {
|
||||
png_color_type |= PNG_COLOR_MASK_ALPHA;
|
||||
}
|
||||
|
||||
int png_interlace_type = PNG_INTERLACE_NONE;
|
||||
int png_compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
|
||||
int png_filter_method = PNG_FILTER_TYPE_DEFAULT;
|
||||
|
||||
PngWriter writer{};
|
||||
writer.cursor = param->png_mem_addr;
|
||||
writer.start = param->png_mem_addr;
|
||||
writer.capacity = param->png_mem_size;
|
||||
|
||||
png_set_write_fn(pngh->png_ptr, &writer, PngWriteFn, PngFlushFn);
|
||||
|
||||
png_set_IHDR(pngh->png_ptr, pngh->info_ptr, param->image_width, param->image_height,
|
||||
param->bit_depth, png_color_type, png_interlace_type, png_compression_type,
|
||||
png_filter_method);
|
||||
|
||||
if (param->pixel_format == OrbisPngEncPixelFormat::B8G8R8A8) {
|
||||
png_set_bgr(pngh->png_ptr);
|
||||
}
|
||||
|
||||
png_set_compression_level(pngh->png_ptr, std::clamp<u16>(param->compression_level, 0, 9));
|
||||
png_set_filter(pngh->png_ptr, 0, MapPngFilter(param->filter_type));
|
||||
|
||||
png_write_info(pngh->png_ptr, pngh->info_ptr);
|
||||
|
||||
int channels = 4;
|
||||
size_t row_stride = param->image_width * channels;
|
||||
|
||||
uint32_t processed_height = 0;
|
||||
|
||||
if (param->color_space == OrbisPngEncColorSpace::RGBA) {
|
||||
for (; processed_height < param->image_height; ++processed_height) {
|
||||
png_bytep row = (png_bytep)param->image_mem_addr + processed_height * row_stride;
|
||||
png_write_row(pngh->png_ptr, row);
|
||||
|
||||
if (outputInfo != nullptr) {
|
||||
outputInfo->processed_height = processed_height;
|
||||
}
|
||||
|
||||
if (writer.cancel_write) {
|
||||
LOG_ERROR(Lib_Png, "Ran out of room to write PNG");
|
||||
return ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// our input data is always rgba but when outputting without an alpha channel, libpng
|
||||
// expects the input to not have alpha either, i couldn't find a way around this easily?
|
||||
// png_strip_alpha is for reading and set_background wasn't working, this seems fine...?
|
||||
std::vector<uint8_t> rgb_row(param->image_width * 3);
|
||||
|
||||
for (; processed_height < param->image_height; ++processed_height) {
|
||||
const unsigned char* src =
|
||||
param->image_mem_addr + processed_height * param->image_pitch;
|
||||
|
||||
uint8_t* dst = rgb_row.data();
|
||||
|
||||
for (uint32_t x = 0; x < param->image_width; ++x) {
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
src += 4; // skip reading alpha channel
|
||||
dst += 3;
|
||||
}
|
||||
|
||||
png_write_row(pngh->png_ptr, rgb_row.data());
|
||||
|
||||
if (outputInfo != nullptr) {
|
||||
outputInfo->processed_height = processed_height;
|
||||
}
|
||||
|
||||
if (writer.cancel_write) {
|
||||
LOG_ERROR(Lib_Png, "Ran out of room to write PNG");
|
||||
return ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
png_write_flush(pngh->png_ptr);
|
||||
|
||||
png_write_end(pngh->png_ptr, pngh->info_ptr);
|
||||
|
||||
if (outputInfo != nullptr) {
|
||||
outputInfo->data_size = writer.cursor - writer.start;
|
||||
outputInfo->processed_height = processed_height;
|
||||
}
|
||||
|
||||
return writer.cursor - writer.start;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePngEncQueryMemorySize(const OrbisPngEncCreateParam* param) {
|
||||
if (param == nullptr) {
|
||||
LOG_ERROR(Lib_Png, "Invalid Address");
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (param->attribute != 0 || param->max_filter_number > 5) {
|
||||
LOG_ERROR(Lib_Png, "Invalid Param, attribute = {}, max_filter_number = {}",
|
||||
param->attribute, param->max_filter_number);
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->max_image_width - 1 > 1000000) {
|
||||
LOG_ERROR(Lib_Png, "Invalid Size, width = {}", param->max_image_width);
|
||||
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return sizeof(PngHandler);
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("7aGTPfrqT9s", "libScePngEnc", 1, "libScePngEnc", scePngEncCreate);
|
||||
LIB_FUNCTION("RUrWdwTWZy8", "libScePngEnc", 1, "libScePngEnc", scePngEncDelete);
|
||||
LIB_FUNCTION("xgDjJKpcyHo", "libScePngEnc", 1, "libScePngEnc", scePngEncEncode);
|
||||
LIB_FUNCTION("9030RnBDoh4", "libScePngEnc", 1, "libScePngEnc", scePngEncQueryMemorySize);
|
||||
};
|
||||
|
||||
} // namespace Libraries::PngEnc
|
||||
67
src/core/libraries/libpng/pngenc.h
Normal file
67
src/core/libraries/libpng/pngenc.h
Normal file
@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::PngEnc {
|
||||
|
||||
enum class OrbisPngEncAttribute { None = 0 };
|
||||
|
||||
enum class OrbisPngEncColorSpace : u16 { RGB = 3, RGBA = 19 };
|
||||
|
||||
enum class OrbisPngEncPixelFormat : u16 { R8G8B8A8 = 0, B8G8R8A8 };
|
||||
|
||||
enum class OrbisPngEncFilterType : u16 {
|
||||
None = 0,
|
||||
Sub = 1,
|
||||
Up = 2,
|
||||
Average = 4,
|
||||
Paeth = 8,
|
||||
All = 15
|
||||
};
|
||||
|
||||
struct OrbisPngEncCreateParam {
|
||||
u32 this_size;
|
||||
u32 attribute;
|
||||
u32 max_image_width;
|
||||
u32 max_filter_number;
|
||||
};
|
||||
|
||||
struct OrbisPngEncEncodeParam {
|
||||
const u8* image_mem_addr;
|
||||
u8* png_mem_addr;
|
||||
u32 image_mem_size;
|
||||
u32 png_mem_size;
|
||||
u32 image_width;
|
||||
u32 image_height;
|
||||
u32 image_pitch;
|
||||
OrbisPngEncPixelFormat pixel_format;
|
||||
OrbisPngEncColorSpace color_space;
|
||||
u16 bit_depth;
|
||||
u16 clut_number;
|
||||
u16 filter_type;
|
||||
u16 compression_level;
|
||||
};
|
||||
|
||||
struct OrbisPngEncOutputInfo {
|
||||
u32 data_size;
|
||||
u32 processed_height;
|
||||
};
|
||||
|
||||
using OrbisPngEncHandle = void*;
|
||||
|
||||
s32 PS4_SYSV_ABI scePngEncCreate(const OrbisPngEncCreateParam* param, void* memoryAddress,
|
||||
u32 memorySize, OrbisPngEncHandle* handle);
|
||||
s32 PS4_SYSV_ABI scePngEncDelete(OrbisPngEncHandle handle);
|
||||
s32 PS4_SYSV_ABI scePngEncEncode(OrbisPngEncHandle, const OrbisPngEncEncodeParam* param,
|
||||
OrbisPngEncOutputInfo* outputInfo);
|
||||
s32 PS4_SYSV_ABI scePngEncQueryMemorySize(const OrbisPngEncCreateParam* param);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::PngEnc
|
||||
14
src/core/libraries/libpng/pngenc_error.h
Normal file
14
src/core/libraries/libpng/pngenc_error.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
// PngEnc library
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_ADDR = 0x80690101;
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_SIZE = 0x80690102;
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_PARAM = 0x80690103;
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_HANDLE = 0x80690104;
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW = 0x80690110;
|
||||
constexpr int ORBIS_PNG_ENC_ERROR_FATAL = 0x80690120;
|
||||
@ -35,11 +35,14 @@
|
||||
#include "core/libraries/np/np_commerce.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_matching2.h"
|
||||
#include "core/libraries/np/np_partner.h"
|
||||
#include "core/libraries/np/np_party.h"
|
||||
#include "core/libraries/np/np_profile_dialog.h"
|
||||
#include "core/libraries/np/np_score.h"
|
||||
#include "core/libraries/np/np_sns_facebook_dialog.h"
|
||||
#include "core/libraries/np/np_trophy.h"
|
||||
#include "core/libraries/np/np_tus.h"
|
||||
#include "core/libraries/np/np_web_api.h"
|
||||
#include "core/libraries/np/np_web_api2.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
@ -77,6 +80,7 @@ namespace Libraries {
|
||||
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
LOG_INFO(Lib_Kernel, "Initializing HLE libraries");
|
||||
Libraries::Kernel::RegisterLib(sym);
|
||||
Libraries::LibcInternal::ForceRegisterLib(sym);
|
||||
Libraries::GnmDriver::RegisterLib(sym);
|
||||
Libraries::VideoOut::RegisterLib(sym);
|
||||
Libraries::UserService::RegisterLib(sym);
|
||||
@ -97,6 +101,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::Np::NpCommerce::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpMatching2::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
Libraries::Np::NpTrophy::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi::RegisterLib(sym);
|
||||
@ -105,6 +110,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym);
|
||||
Libraries::Np::NpAuth::RegisterLib(sym);
|
||||
Libraries::Np::NpParty::RegisterLib(sym);
|
||||
Libraries::Np::NpPartner::RegisterLib(sym);
|
||||
Libraries::Np::NpTus::RegisterLib(sym);
|
||||
Libraries::ScreenShot::RegisterLib(sym);
|
||||
Libraries::AppContent::RegisterLib(sym);
|
||||
Libraries::PngDec::RegisterLib(sym);
|
||||
|
||||
@ -430,8 +430,8 @@ int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpReadData() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, c
|
||||
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
|
||||
int32_t* httpMinorVer, int32_t* responseCode,
|
||||
const char** reasonPhrase, u64* phraseLen);
|
||||
int PS4_SYSV_ABI sceHttpReadData();
|
||||
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
|
||||
int PS4_SYSV_ABI sceHttpRedirectCacheFlush();
|
||||
int PS4_SYSV_ABI sceHttpRemoveRequestHeader();
|
||||
int PS4_SYSV_ABI sceHttpRequestGetAllHeaders();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -14,4 +14,12 @@ constexpr int ORBIS_NP_ERROR_INVALID_SIZE = 0x80550011;
|
||||
constexpr int ORBIS_NP_ERROR_ABORTED = 0x80550012;
|
||||
constexpr int ORBIS_NP_ERROR_REQUEST_MAX = 0x80550013;
|
||||
constexpr int ORBIS_NP_ERROR_REQUEST_NOT_FOUND = 0x80550014;
|
||||
constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015;
|
||||
constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015;
|
||||
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = 0x80550704;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = 0x80550706;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070c;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070e;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = 0x80550714;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = 0x80550718;
|
||||
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719;
|
||||
@ -1,7 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <variant>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
@ -17,6 +19,9 @@ static bool g_signed_in = false;
|
||||
static s32 g_active_requests = 0;
|
||||
static std::mutex g_request_mutex;
|
||||
|
||||
static std::map<std::string, std::function<void()>> g_np_callbacks;
|
||||
static std::mutex g_np_callbacks_mutex;
|
||||
|
||||
// Internal types for storing request-related information
|
||||
enum class NpRequestState {
|
||||
None = 0,
|
||||
@ -665,6 +670,19 @@ s32 PS4_SYSV_ABI sceNpGetState(Libraries::UserService::OrbisUserServiceUserId us
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServiceUserId* user_id) {
|
||||
if (user_id == nullptr) {
|
||||
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!g_signed_in) {
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
*user_id = 1;
|
||||
LOG_DEBUG(Lib_NpManager, "userid({}) = {}", account_id, *user_id);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
bool* has_signed_up) {
|
||||
LOG_DEBUG(Lib_NpManager, "called");
|
||||
@ -682,11 +700,25 @@ struct NpStateCallbackForNpToolkit {
|
||||
|
||||
NpStateCallbackForNpToolkit NpStateCbForNp;
|
||||
|
||||
struct NpStateCallback {
|
||||
std::variant<OrbisNpStateCallback, OrbisNpStateCallbackA> func;
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
NpStateCallback NpStateCb;
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCheckCallback() {
|
||||
if (!g_signed_in) {
|
||||
return ORBIS_NP_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
LOG_DEBUG(Lib_NpManager, "(STUBBED) called");
|
||||
|
||||
std::scoped_lock lk{g_np_callbacks_mutex};
|
||||
|
||||
for (auto i : g_np_callbacks) {
|
||||
(i.second)();
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -695,6 +727,40 @@ s32 PS4_SYSV_ABI sceNpCheckCallbackForLib() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpRegisterStateCallback(OrbisNpStateCallback callback, void* userdata) {
|
||||
static s32 id = 0;
|
||||
LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata);
|
||||
NpStateCb.func = callback;
|
||||
NpStateCb.userdata = userdata;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpRegisterStateCallbackA(OrbisNpStateCallbackA callback, void* userdata) {
|
||||
static s32 id = 0;
|
||||
LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata);
|
||||
NpStateCb.func = callback;
|
||||
NpStateCb.userdata = userdata;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
struct NpReachabilityStateCallback {
|
||||
OrbisNpReachabilityStateCallback func;
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
NpReachabilityStateCallback NpReachabilityCb;
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpRegisterNpReachabilityStateCallback(OrbisNpReachabilityStateCallback callback,
|
||||
void* userdata) {
|
||||
static s32 id = 0;
|
||||
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
|
||||
NpReachabilityCb.func = callback;
|
||||
NpReachabilityCb.userdata = userdata;
|
||||
return id;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback,
|
||||
void* userdata) {
|
||||
static s32 id = 0;
|
||||
@ -704,6 +770,22 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT
|
||||
return id;
|
||||
}
|
||||
|
||||
void RegisterNpCallback(std::string key, std::function<void()> cb) {
|
||||
std::scoped_lock lk{g_np_callbacks_mutex};
|
||||
|
||||
LOG_DEBUG(Lib_NpManager, "registering callback processing for {}", key);
|
||||
|
||||
g_np_callbacks.emplace(key, cb);
|
||||
}
|
||||
|
||||
void DeregisterNpCallback(std::string key) {
|
||||
std::scoped_lock lk{g_np_callbacks_mutex};
|
||||
|
||||
LOG_DEBUG(Lib_NpManager, "deregistering callback processing for {}", key);
|
||||
|
||||
g_np_callbacks.erase(key);
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
g_signed_in = Config::getPSNSignedIn();
|
||||
|
||||
@ -742,9 +824,14 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("p-o74CnoNzY", "libSceNpManager", 1, "libSceNpManager", sceNpGetNpId);
|
||||
LIB_FUNCTION("XDncXQIJUSk", "libSceNpManager", 1, "libSceNpManager", sceNpGetOnlineId);
|
||||
LIB_FUNCTION("eQH7nWPcAgc", "libSceNpManager", 1, "libSceNpManager", sceNpGetState);
|
||||
LIB_FUNCTION("VgYczPGB5ss", "libSceNpManager", 1, "libSceNpManager", sceNpGetUserIdByAccountId);
|
||||
LIB_FUNCTION("Oad3rvY-NJQ", "libSceNpManager", 1, "libSceNpManager", sceNpHasSignedUp);
|
||||
LIB_FUNCTION("3Zl8BePTh9Y", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallback);
|
||||
LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib);
|
||||
LIB_FUNCTION("VfRSmPmj8Q8", "libSceNpManager", 1, "libSceNpManager",
|
||||
sceNpRegisterStateCallback);
|
||||
LIB_FUNCTION("hw5KNqAAels", "libSceNpManager", 1, "libSceNpManager",
|
||||
sceNpRegisterNpReachabilityStateCallback);
|
||||
LIB_FUNCTION("JELHf4xPufo", "libSceNpManagerForToolkit", 1, "libSceNpManager",
|
||||
sceNpCheckCallbackForLib);
|
||||
LIB_FUNCTION("0c7HbXRKUt4", "libSceNpManagerForToolkit", 1, "libSceNpManager",
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
@ -23,20 +25,28 @@ enum class OrbisNpState : u32 {
|
||||
SignedIn = 2,
|
||||
};
|
||||
|
||||
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(s32 userId, OrbisNpState state,
|
||||
void* userdata);
|
||||
|
||||
enum class OrbisNpGamePresenseStatus {
|
||||
Offline = 0,
|
||||
Online = 1,
|
||||
};
|
||||
|
||||
enum class OrbisNpReachabilityState {
|
||||
Unavailable = 0,
|
||||
Available = 1,
|
||||
Reachable = 2,
|
||||
};
|
||||
|
||||
using OrbisNpStateCallback =
|
||||
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state,
|
||||
OrbisNpId* npId, void* userdata);
|
||||
using OrbisNpStateCallbackA = PS4_SYSV_ABI void (*)(
|
||||
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
|
||||
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
|
||||
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
|
||||
using OrbisNpReachabilityStateCallback =
|
||||
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
OrbisNpReachabilityState state, void* userdata);
|
||||
|
||||
enum class OrbisNpGamePresenseStatus {
|
||||
Offline = 0,
|
||||
Online = 1,
|
||||
};
|
||||
|
||||
struct OrbisNpCountryCode {
|
||||
char country_code[2];
|
||||
char end;
|
||||
@ -80,5 +90,13 @@ struct OrbisNpCreateAsyncRequestParameter {
|
||||
u8 padding[4];
|
||||
};
|
||||
|
||||
void RegisterNpCallback(std::string key, std::function<void()> cb);
|
||||
void DeregisterNpCallback(std::string key);
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
OrbisNpId* np_id);
|
||||
s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
OrbisNpOnlineId* online_id);
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpManager
|
||||
|
||||
812
src/core/libraries/np/np_matching2.cpp
Normal file
812
src/core/libraries/np/np_matching2.cpp
Normal file
@ -0,0 +1,812 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_matching2.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Libraries::Np::NpMatching2 {
|
||||
|
||||
static bool g_initialized = false;
|
||||
static OrbisNpMatching2ContextId contextId = 1;
|
||||
|
||||
struct NpMatching2ContextEvent {
|
||||
OrbisNpMatching2ContextId contextId;
|
||||
OrbisNpMatching2Event event;
|
||||
OrbisNpMatching2EventCause cause;
|
||||
int errorCode;
|
||||
};
|
||||
|
||||
struct NpMatching2LobbyEvent {
|
||||
OrbisNpMatching2ContextId contextId;
|
||||
OrbisNpMatching2LobbyId lobbyId;
|
||||
OrbisNpMatching2Event event;
|
||||
void* data;
|
||||
};
|
||||
|
||||
struct NpMatching2RoomEvent {
|
||||
OrbisNpMatching2ContextId contextId;
|
||||
OrbisNpMatching2RoomId roomId;
|
||||
OrbisNpMatching2Event event;
|
||||
void* data;
|
||||
};
|
||||
|
||||
static std::mutex g_events_mutex;
|
||||
static std::deque<NpMatching2ContextEvent> g_ctx_events;
|
||||
static std::deque<NpMatching2LobbyEvent> g_lobby_events;
|
||||
static std::deque<NpMatching2RoomEvent> g_room_events;
|
||||
static std::mutex g_responses_mutex;
|
||||
static std::deque<std::function<void()>> g_responses;
|
||||
|
||||
struct OrbisNpMatching2CreateContextParameter {
|
||||
Libraries::Np::OrbisNpId* npId;
|
||||
void* npCommunicationId;
|
||||
void* npPassphrase;
|
||||
Libraries::Np::OrbisNpServiceLabel serviceLabel;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
static_assert(sizeof(OrbisNpMatching2CreateContextParameter) == 0x28);
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
|
||||
OrbisNpMatching2ContextId* ctxId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}",
|
||||
param->npId->handle.data, param->serviceLabel, param->size);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!param || param->size != 0x28 || !ctxId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*ctxId = contextId++;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2CreateContextParameterA {
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
Libraries::Np::OrbisNpServiceLabel serviceLabel;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
static_assert(sizeof(OrbisNpMatching2CreateContextParameterA) == 16);
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
|
||||
OrbisNpMatching2ContextId* ctxId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, userId = {}, serviceLabel = {}, size = {}", param->userId,
|
||||
param->serviceLabel, param->size);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!param || param->size != 0x10 || !ctxId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*ctxId = contextId++;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
using OrbisNpMatching2RequestCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId,
|
||||
OrbisNpMatching2RequestId,
|
||||
OrbisNpMatching2Event, int, void*,
|
||||
void*);
|
||||
|
||||
struct OrbisNpMatching2RequestOptParam {
|
||||
OrbisNpMatching2RequestCallback callback;
|
||||
void* arg;
|
||||
u32 timeout;
|
||||
u16 appId;
|
||||
u8 dummy[2];
|
||||
};
|
||||
|
||||
static std::optional<OrbisNpMatching2RequestOptParam> defaultRequestOptParam = std::nullopt;
|
||||
|
||||
auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) {
|
||||
return requestOpt ? *requestOpt
|
||||
: (defaultRequestOptParam ? defaultRequestOptParam
|
||||
: std::optional<OrbisNpMatching2RequestOptParam>{});
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2CreateJoinRoomRequestA {
|
||||
u16 maxSlot;
|
||||
OrbisNpMatching2TeamId teamId;
|
||||
u8 pad[5];
|
||||
OrbisNpMatching2Flags flags;
|
||||
OrbisNpMatching2WorldId worldId;
|
||||
OrbisNpMatching2LobbyId lobbyId;
|
||||
void* roomPasswd;
|
||||
void* passwdSlotMask;
|
||||
void* groupConfig;
|
||||
u64 groupConfigs;
|
||||
void* joinGroupLabel;
|
||||
Libraries::Np::OrbisNpAccountId* allowedUser;
|
||||
u64 allowedUsers;
|
||||
Libraries::Np::OrbisNpAccountId* blockedUser;
|
||||
u64 blockedUsers;
|
||||
void* internalBinAttr;
|
||||
u64 internalBinAttrs;
|
||||
void* externalSearchIntAttr;
|
||||
u64 externalSearchIntAttrs;
|
||||
void* externalSearchBinAttr;
|
||||
u64 externalSearchBinAttrs;
|
||||
void* externalBinAttr;
|
||||
u64 externalBinAttrs;
|
||||
void* memberInternalBinAttr;
|
||||
u64 memberInternalBinAttrs;
|
||||
void* signalingParam;
|
||||
};
|
||||
|
||||
static_assert(sizeof(OrbisNpMatching2CreateJoinRoomRequestA) == 184);
|
||||
|
||||
struct OrbisNpMatching2RoomDataInternal {
|
||||
u16 publicSlots;
|
||||
u16 privateSlots;
|
||||
u16 openPublicSlots;
|
||||
u16 openPrivateSlots;
|
||||
u16 maxSlot;
|
||||
OrbisNpMatching2ServerId serverId;
|
||||
OrbisNpMatching2WorldId worldId;
|
||||
OrbisNpMatching2LobbyId lobbyId;
|
||||
OrbisNpMatching2RoomId roomId;
|
||||
u64 passwdSlotMask;
|
||||
u64 joinedSlotMask;
|
||||
void* roomGroup;
|
||||
u64 roomGroups;
|
||||
OrbisNpMatching2Flags flags;
|
||||
u8 pad[4];
|
||||
void* internalBinAttr;
|
||||
u64 internalBinAttrs;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2RoomMemberDataInternalA {
|
||||
OrbisNpMatching2RoomMemberDataInternalA* next;
|
||||
u64 joinDateTicks;
|
||||
Libraries::Np::OrbisNpPeerAddressA user;
|
||||
Libraries::Np::OrbisNpOnlineId onlineId;
|
||||
u8 pad[4];
|
||||
OrbisNpMatching2RoomMemberId memberId;
|
||||
OrbisNpMatching2TeamId teamId;
|
||||
OrbisNpMatching2NatType natType;
|
||||
OrbisNpMatching2Flags flags;
|
||||
void* roomGroup;
|
||||
void* roomMemberInternalBinAttr;
|
||||
u64 roomMemberInternalBinAttrs;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2RoomMemberDataInternalListA {
|
||||
OrbisNpMatching2RoomMemberDataInternalA* members;
|
||||
u64 membersNum;
|
||||
OrbisNpMatching2RoomMemberDataInternalA* me;
|
||||
OrbisNpMatching2RoomMemberDataInternalA* owner;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2CreateJoinRoomResponseA {
|
||||
OrbisNpMatching2RoomDataInternal* roomData;
|
||||
OrbisNpMatching2RoomMemberDataInternalListA members;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2CreateJoinRoomRequestA* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Lib_NpMatching2,
|
||||
"maxSlot = {}, teamId = {}, worldId = {}, lobbyId = {}, groupConfig = {}, "
|
||||
"joinGroupLabel = {}",
|
||||
request->maxSlot, request->teamId, request->worldId, request->lobbyId,
|
||||
request->groupConfig, request->joinGroupLabel);
|
||||
|
||||
static OrbisNpMatching2RequestId id = 10;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
auto requestCopy = *request;
|
||||
g_responses.emplace_back([=]() {
|
||||
Libraries::Np::OrbisNpOnlineId onlineId{};
|
||||
if (NpManager::sceNpGetOnlineId(1, &onlineId) != ORBIS_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
OrbisNpMatching2RoomMemberDataInternalA me{
|
||||
nullptr,
|
||||
0,
|
||||
{0xace104e, Libraries::Np::OrbisNpPlatformType::PS4},
|
||||
onlineId,
|
||||
{0, 0, 0, 0},
|
||||
1,
|
||||
requestCopy.teamId,
|
||||
1,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0};
|
||||
OrbisNpMatching2RoomDataInternal room{requestCopy.maxSlot,
|
||||
0,
|
||||
static_cast<u16>(requestCopy.maxSlot - 1u),
|
||||
0,
|
||||
15,
|
||||
0xac,
|
||||
requestCopy.worldId,
|
||||
requestCopy.lobbyId,
|
||||
0x10,
|
||||
0,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
{0, 0, 0, 0},
|
||||
nullptr,
|
||||
0};
|
||||
OrbisNpMatching2CreateJoinRoomResponseA resp{&room, {&me, 1, &me, &me}};
|
||||
optParam->callback(ctxId, reqIdCopy,
|
||||
ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A, 0, &resp,
|
||||
optParam->arg);
|
||||
});
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
using OrbisNpMatching2ContextCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId,
|
||||
OrbisNpMatching2Event event,
|
||||
OrbisNpMatching2EventCause cause,
|
||||
int errorCode, void* userdata);
|
||||
|
||||
std::function<void(const NpMatching2ContextEvent*)> npMatching2ContextCallback = nullptr;
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback,
|
||||
void* userdata) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, userdata = {}", userdata);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
npMatching2ContextCallback = [callback, userdata](auto arg) {
|
||||
callback(arg->contextId, arg->event, arg->cause, arg->errorCode, userdata);
|
||||
};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
using OrbisNpMatching2LobbyEventCallback =
|
||||
PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2LobbyId lobbyId,
|
||||
OrbisNpMatching2Event event, void* data, void* userdata);
|
||||
|
||||
std::function<void(const NpMatching2LobbyEvent*)> npMatching2LobbyCallback = nullptr;
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback(
|
||||
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
npMatching2LobbyCallback = [callback, userdata](auto arg) {
|
||||
callback(arg->contextId, arg->lobbyId, arg->event, arg->data, userdata);
|
||||
};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
using OrbisNpMatching2RoomEventCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId,
|
||||
OrbisNpMatching2RoomId roomId,
|
||||
OrbisNpMatching2Event event,
|
||||
void* data, void* userdata);
|
||||
|
||||
std::function<void(const NpMatching2RoomEvent*)> npMatching2RoomCallback = nullptr;
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2RoomEventCallback callback,
|
||||
void* userdata) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
npMatching2RoomCallback = [callback, userdata](auto arg) {
|
||||
callback(arg->contextId, arg->roomId, arg->event, arg->data, userdata);
|
||||
};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2SignalingEvent {
|
||||
OrbisNpMatching2ContextId contextId;
|
||||
OrbisNpMatching2RoomId roomId;
|
||||
OrbisNpMatching2RoomMemberId roomMemberId;
|
||||
OrbisNpMatching2Event event;
|
||||
int errorCode;
|
||||
};
|
||||
|
||||
using OrbisNpMatching2SignalingCallback =
|
||||
PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2RoomId roomId,
|
||||
OrbisNpMatching2RoomMemberId roomMemberId, OrbisNpMatching2Event event,
|
||||
int errorCode, void* userdata);
|
||||
|
||||
std::function<void(const OrbisNpMatching2SignalingEvent*)> npMatching2SignalingCallback = nullptr;
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2SignalingCallback callback,
|
||||
void* userdata) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
npMatching2SignalingCallback = [callback, userdata](auto arg) {
|
||||
callback(arg->contextId, arg->roomId, arg->roomMemberId, arg->event, arg->errorCode,
|
||||
userdata);
|
||||
};
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, timeout = {}", ctxId, timeout);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_events_mutex};
|
||||
if (Config::getIsConnectedToNetwork() && Config::getPSNSignedIn()) {
|
||||
g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED,
|
||||
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, 0);
|
||||
} else {
|
||||
// error confirmed with a real console disconnected from the internet
|
||||
constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2;
|
||||
g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER,
|
||||
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR,
|
||||
ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT);
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void ProcessEvents() {
|
||||
{
|
||||
std::scoped_lock lk{g_events_mutex};
|
||||
|
||||
if (npMatching2ContextCallback) {
|
||||
while (!g_ctx_events.empty()) {
|
||||
npMatching2ContextCallback(&g_ctx_events.front());
|
||||
g_ctx_events.pop_front();
|
||||
}
|
||||
}
|
||||
if (npMatching2LobbyCallback) {
|
||||
while (!g_lobby_events.empty()) {
|
||||
npMatching2LobbyCallback(&g_lobby_events.front());
|
||||
g_lobby_events.pop_front();
|
||||
}
|
||||
}
|
||||
if (npMatching2RoomCallback) {
|
||||
while (!g_room_events.empty()) {
|
||||
npMatching2RoomCallback(&g_room_events.front());
|
||||
g_room_events.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
while (!g_responses.empty()) {
|
||||
(g_responses.front())();
|
||||
g_responses.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2InitializeParameter {
|
||||
u64 poolSize;
|
||||
//
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called");
|
||||
|
||||
if (g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
Libraries::Np::NpManager::RegisterNpCallback("NpMatching2", ProcessEvents);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2Terminate() {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called");
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
g_initialized = false;
|
||||
Libraries::Np::NpManager::DeregisterNpCallback("NpMatching2");
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam(
|
||||
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!requestOpt) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
defaultRequestOptParam = *requestOpt;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2ServerId* serverId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId);
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!serverId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*serverId = 0xac;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2GetWorldInfoListRequest {
|
||||
OrbisNpMatching2ServerId serverId;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2World {
|
||||
OrbisNpMatching2World* next;
|
||||
OrbisNpMatching2WorldId worldId;
|
||||
u32 lobbiesNum;
|
||||
u32 maxLobbyMembersNum;
|
||||
u32 lobbyMembersNum;
|
||||
u32 roomsNum;
|
||||
u32 roomMembersNum;
|
||||
u8 pad[3];
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2GetWorldInfoListResponse {
|
||||
OrbisNpMatching2World* world;
|
||||
u64 worldNum;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2GetWorldInfoListRequest* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, request.serverId = {}, requestOpt = {}", ctxId,
|
||||
request ? request->serverId : 0xFFFF, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 1;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
auto reqIdCopy = *requestId;
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
g_responses.emplace_back([=]() {
|
||||
OrbisNpMatching2World w{nullptr, 1, 10, 0, 10, 0, {}};
|
||||
OrbisNpMatching2GetWorldInfoListResponse resp{&w, 1};
|
||||
optParam->callback(ctxId, reqIdCopy,
|
||||
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, 0, &resp,
|
||||
optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2PresenceOptionData {
|
||||
u8 data[16];
|
||||
u64 len;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2LeaveRoomRequest {
|
||||
OrbisNpMatching2RoomId roomId;
|
||||
OrbisNpMatching2PresenceOptionData optData;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2LeaveRoomRequest* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 500;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
g_responses.emplace_back([=]() {
|
||||
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM, 0,
|
||||
nullptr, optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2RangeFilter {
|
||||
u32 start;
|
||||
u32 max;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2SearchRoomRequest {
|
||||
int option;
|
||||
OrbisNpMatching2WorldId worldId;
|
||||
OrbisNpMatching2LobbyId lobbyId;
|
||||
OrbisNpMatching2RangeFilter rangeFilter;
|
||||
OrbisNpMatching2Flags flags1;
|
||||
OrbisNpMatching2Flags flags2;
|
||||
void* intFilter;
|
||||
u64 intFilters;
|
||||
void* binFilter;
|
||||
u64 binFilters;
|
||||
OrbisNpMatching2AttributeId* attr;
|
||||
u64 attrs;
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2Range {
|
||||
u32 start;
|
||||
u32 total;
|
||||
u32 results;
|
||||
u8 pad[4];
|
||||
};
|
||||
|
||||
struct OrbisNpMatching2SearchRoomResponseA {
|
||||
OrbisNpMatching2Range range;
|
||||
void* roomDataExt;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2SearchRoomRequest* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 1;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
auto requestCopy = *request;
|
||||
g_responses.emplace_back([=]() {
|
||||
OrbisNpMatching2SearchRoomResponseA resp{{0, 0, 0, {}}, nullptr};
|
||||
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A, 0,
|
||||
&resp, optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
struct OrbisNpMatching2SetUserInfoRequest {
|
||||
OrbisNpMatching2ServerId serverId;
|
||||
u8 padding[6];
|
||||
void* userBinAttr;
|
||||
u64 userBinAttrs;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
|
||||
OrbisNpMatching2SetUserInfoRequest* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 100;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
g_responses.emplace_back([=]() {
|
||||
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO, 0,
|
||||
nullptr, optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 1000;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
g_responses.emplace_back([=]() {
|
||||
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE,
|
||||
0, nullptr, optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(OrbisNpMatching2ContextId ctxId, void* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 800;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
g_responses.emplace_back([=]() {
|
||||
optParam->callback(ctxId, reqIdCopy,
|
||||
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL, 0, nullptr,
|
||||
optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(OrbisNpMatching2ContextId ctxId, void* request,
|
||||
OrbisNpMatching2RequestOptParam* requestOpt,
|
||||
OrbisNpMatching2RequestId* requestId) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
|
||||
|
||||
if (!g_initialized) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (!request || !requestId) {
|
||||
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
static OrbisNpMatching2RequestId id = 200;
|
||||
*requestId = id++;
|
||||
|
||||
if (auto optParam = GetOptParam(requestOpt); optParam) {
|
||||
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
|
||||
optParam->appId);
|
||||
std::scoped_lock lk{g_responses_mutex};
|
||||
auto reqIdCopy = *requestId;
|
||||
g_responses.emplace_back([=]() {
|
||||
optParam->callback(ctxId, reqIdCopy,
|
||||
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL, 0, nullptr,
|
||||
optParam->arg);
|
||||
});
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("10t3e5+JPnU", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2Initialize);
|
||||
LIB_FUNCTION("Mqp3lJ+sjy4", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2Terminate);
|
||||
LIB_FUNCTION("YfmpW719rMo", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2CreateContext);
|
||||
LIB_FUNCTION("ajvzc8e2upo", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2CreateContextA);
|
||||
LIB_FUNCTION("V6KSpKv9XJE", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2CreateJoinRoomA);
|
||||
LIB_FUNCTION("fQQfP87I7hs", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2RegisterContextCallback);
|
||||
LIB_FUNCTION("4Nj7u5B5yCA", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2RegisterLobbyEventCallback);
|
||||
LIB_FUNCTION("p+2EnxmaAMM", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2RegisterRoomEventCallback);
|
||||
LIB_FUNCTION("0UMeWRGnZKA", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2RegisterSignalingCallback);
|
||||
LIB_FUNCTION("7vjNQ6Z1op0", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2ContextStart);
|
||||
LIB_FUNCTION("LhCPctIICxQ", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2GetServerId);
|
||||
LIB_FUNCTION("rJNPJqDCpiI", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2GetWorldInfoList);
|
||||
LIB_FUNCTION("BD6kfx442Do", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2LeaveRoom);
|
||||
LIB_FUNCTION("+8e7wXLmjds", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SetDefaultRequestOptParam);
|
||||
LIB_FUNCTION("VqZX7POg2Mk", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SearchRoom);
|
||||
LIB_FUNCTION("Iw2h0Jrrb5U", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SendRoomMessage);
|
||||
LIB_FUNCTION("meEjIdbjAA0", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SetUserInfo);
|
||||
LIB_FUNCTION("q7GK98-nYSE", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SetRoomDataExternal);
|
||||
LIB_FUNCTION("S9D8JSYIrjE", "libSceNpMatching2", 1, "libSceNpMatching2",
|
||||
sceNpMatching2SetRoomDataInternal);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpMatching2
|
||||
48
src/core/libraries/np/np_matching2.h
Normal file
48
src/core/libraries/np/np_matching2.h
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpMatching2 {
|
||||
|
||||
using OrbisNpMatching2AttributeId = u16;
|
||||
using OrbisNpMatching2ContextId = u16;
|
||||
using OrbisNpMatching2Event = u16;
|
||||
using OrbisNpMatching2EventCause = u8;
|
||||
using OrbisNpMatching2Flags = u32;
|
||||
using OrbisNpMatching2LobbyId = u64;
|
||||
using OrbisNpMatching2NatType = u8;
|
||||
using OrbisNpMatching2RequestId = u16;
|
||||
using OrbisNpMatching2RoomId = u64;
|
||||
using OrbisNpMatching2RoomMemberId = u16;
|
||||
using OrbisNpMatching2ServerId = u16;
|
||||
using OrbisNpMatching2TeamId = u8;
|
||||
using OrbisNpMatching2WorldId = u32;
|
||||
|
||||
constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED = 0x80550c01;
|
||||
constexpr int ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = 0x80550c02;
|
||||
constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT = 0x80550c15;
|
||||
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM = 0x0101;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A = 0x7101;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST = 0x0002;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM = 0x0103;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL = 0x0004;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x1106;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO = 0x0007;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER = 0x6F01;
|
||||
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED = 0x6F02;
|
||||
|
||||
constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR = 10;
|
||||
constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION = 11;
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpMatching2
|
||||
83
src/core/libraries/np/np_partner.cpp
Normal file
83
src/core/libraries/np/np_partner.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_partner.h"
|
||||
#include "core/libraries/np/np_partner_error.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Libraries::Np::NpPartner {
|
||||
|
||||
static bool g_library_init = false;
|
||||
std::mutex g_library_mutex{};
|
||||
|
||||
/**
|
||||
* Terminates the library
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_A4CC5784DA33517F() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
std::scoped_lock lk{g_library_mutex};
|
||||
g_library_init = false;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts requests started by Func_F8E9DB52CD425743
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_A507D84D91F39CC7() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
// Request logic is unimplemented, so this does nothing.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the library
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_EC2C48E74FF19429() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
g_library_init = true;
|
||||
// Also retrieves and sends compiled SDK version to server.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NP request to determine if the user has a subscription to EA's services.
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_F8E9DB52CD425743(UserService::OrbisUserServiceUserId user_id, bool* result) {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (result == nullptr) {
|
||||
return ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
std::scoped_lock lk{g_library_mutex};
|
||||
// In the real library, this creates and sends a request that checks for EA subscription,
|
||||
// then waits for the request to return a response, and returns that response.
|
||||
// NP signed out likely returns an error, but I haven't figured out the error code yet.
|
||||
// For now, stub having no subscription.
|
||||
*result = false;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("pMxXhNozUX8", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_A4CC5784DA33517F);
|
||||
LIB_FUNCTION("pQfYTZHznMc", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_A507D84D91F39CC7);
|
||||
LIB_FUNCTION("7CxI50-xlCk", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_EC2C48E74FF19429);
|
||||
LIB_FUNCTION("+OnbUs1CV0M", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_F8E9DB52CD425743);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpPartner
|
||||
15
src/core/libraries/np/np_partner.h
Normal file
15
src/core/libraries/np/np_partner.h
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpPartner {
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Np::NpPartner
|
||||
9
src/core/libraries/np/np_partner_error.h
Normal file
9
src/core/libraries/np/np_partner_error.h
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
constexpr int ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED = 0x819d0001;
|
||||
constexpr int ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT = 0x819d0002;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user