mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-08 10:01:28 -06:00
Merge branch 'shadps4-emu:main' into gc2
This commit is contained in:
commit
bffaae4ffa
15
.github/workflows/build.yml
vendored
15
.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 }}
|
||||
@ -160,7 +169,7 @@ jobs:
|
||||
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
@ -216,7 +225,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
|
||||
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
|
||||
|
||||
- name: Cache CMake Configuration
|
||||
uses: actions/cache@v4
|
||||
|
||||
14
.gitmodules
vendored
14
.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
|
||||
@ -120,3 +116,13 @@
|
||||
[submodule "externals/miniz"]
|
||||
path = externals/miniz
|
||||
url = https://github.com/richgel999/miniz
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
[submodule "externals/CLI11"]
|
||||
path = externals/CLI11
|
||||
url = https://github.com/shadexternals/CLI11.git
|
||||
[submodule "externals/sdl3"]
|
||||
path = externals/sdl3
|
||||
url = https://github.com/shadexternals/sdl3.git
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE
|
||||
@ -202,8 +202,8 @@ execute_process(
|
||||
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "12")
|
||||
set(EMULATOR_VERSION_PATCH "6")
|
||||
set(EMULATOR_VERSION_MINOR "14")
|
||||
set(EMULATOR_VERSION_PATCH "1")
|
||||
|
||||
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
||||
|
||||
@ -262,6 +262,8 @@ include_directories(src)
|
||||
|
||||
set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||
src/core/libraries/ajm/ajm.h
|
||||
src/core/libraries/ajm/ajm_aac.cpp
|
||||
src/core/libraries/ajm/ajm_aac.h
|
||||
src/core/libraries/ajm/ajm_at9.cpp
|
||||
src/core/libraries/ajm/ajm_at9.h
|
||||
src/core/libraries/ajm/ajm_batch.cpp
|
||||
@ -422,6 +424,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
||||
src/core/libraries/rtc/rtc.cpp
|
||||
src/core/libraries/rtc/rtc.h
|
||||
src/core/libraries/rtc/rtc_error.h
|
||||
src/core/libraries/rudp/rudp.cpp
|
||||
src/core/libraries/rudp/rudp.h
|
||||
src/core/libraries/disc_map/disc_map.cpp
|
||||
src/core/libraries/disc_map/disc_map.h
|
||||
src/core/libraries/disc_map/disc_map_codes.h
|
||||
@ -520,6 +524,9 @@ set(SYSTEM_GESTURE_LIB
|
||||
set(PNG_LIB src/core/libraries/libpng/pngdec.cpp
|
||||
src/core/libraries/libpng/pngdec.h
|
||||
src/core/libraries/libpng/pngdec_error.h
|
||||
src/core/libraries/libpng/pngenc.cpp
|
||||
src/core/libraries/libpng/pngenc.h
|
||||
src/core/libraries/libpng/pngenc_error.h
|
||||
)
|
||||
|
||||
set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h
|
||||
@ -577,14 +584,20 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_commerce.h
|
||||
src/core/libraries/np/np_manager.cpp
|
||||
src/core/libraries/np/np_manager.h
|
||||
src/core/libraries/np/np_matching2.cpp
|
||||
src/core/libraries/np/np_matching2.h
|
||||
src/core/libraries/np/np_score.cpp
|
||||
src/core/libraries/np/np_score.h
|
||||
src/core/libraries/np/np_trophy.cpp
|
||||
src/core/libraries/np/np_trophy.h
|
||||
src/core/libraries/np/np_tus.cpp
|
||||
src/core/libraries/np/np_tus.h
|
||||
src/core/libraries/np/trophy_ui.cpp
|
||||
src/core/libraries/np/trophy_ui.h
|
||||
src/core/libraries/np/np_web_api.cpp
|
||||
src/core/libraries/np/np_web_api.h
|
||||
src/core/libraries/np/np_web_api2.cpp
|
||||
src/core/libraries/np/np_web_api2.h
|
||||
src/core/libraries/np/np_party.cpp
|
||||
src/core/libraries/np/np_party.h
|
||||
src/core/libraries/np/np_auth.cpp
|
||||
@ -593,6 +606,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_profile_dialog.h
|
||||
src/core/libraries/np/np_sns_facebook_dialog.cpp
|
||||
src/core/libraries/np/np_sns_facebook_dialog.h
|
||||
src/core/libraries/np/np_partner.cpp
|
||||
src/core/libraries/np/np_partner.h
|
||||
)
|
||||
|
||||
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp
|
||||
@ -738,6 +753,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/memory_patcher.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
|
||||
src/common/scm_rev.h
|
||||
src/common/key_manager.cpp
|
||||
src/common/key_manager.h
|
||||
)
|
||||
|
||||
if (ENABLE_DISCORD_RPC)
|
||||
@ -781,6 +798,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/file_format/playgo_chunk.h
|
||||
src/core/file_format/trp.cpp
|
||||
src/core/file_format/trp.h
|
||||
src/core/file_format/npbind.cpp
|
||||
src/core/file_format/npbind.h
|
||||
src/core/file_sys/fs.cpp
|
||||
src/core/file_sys/fs.h
|
||||
src/core/ipc/ipc.cpp
|
||||
@ -836,6 +855,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||
src/core/thread.h
|
||||
src/core/tls.cpp
|
||||
src/core/tls.h
|
||||
src/core/emulator_state.cpp
|
||||
src/core/emulator_state.h
|
||||
)
|
||||
|
||||
if (ARCHITECTURE STREQUAL "x86_64")
|
||||
@ -904,6 +925,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
|
||||
src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
|
||||
src/shader_recompiler/ir/passes/hull_shader_transform.cpp
|
||||
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
|
||||
src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp
|
||||
src/shader_recompiler/ir/passes/ir_passes.h
|
||||
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
|
||||
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp
|
||||
@ -1085,7 +1107,7 @@ create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
||||
13
README.md
13
README.md
@ -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).
|
||||
@ -148,9 +153,9 @@ The following firmware modules are supported and must be placed in shadPS4's `sy
|
||||
| Modules | Modules | Modules | Modules |
|
||||
|-------------------------|-------------------------|-------------------------|-------------------------|
|
||||
| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx |
|
||||
| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx |
|
||||
| libSceUlt.sprx | | | |
|
||||
|
||||
| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx |
|
||||
| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx |
|
||||
| libSceUlt.sprx | libSceAudiodec.sprx | | |
|
||||
</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",
|
||||
|
||||
7
dist/net.shadps4.shadPS4.metainfo.xml
vendored
7
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -11,6 +11,7 @@
|
||||
<project_license translate="no">GPL-2.0</project_license>
|
||||
<launchable type="desktop-id" translate="no">net.shadps4.shadPS4.desktop</launchable>
|
||||
<url type="homepage" translate="no">https://shadps4.net/</url>
|
||||
<url type="vcs-browser" translate="no">https://github.com/shadps4-emu/shadPS4</url>
|
||||
<description>
|
||||
<p>shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.</p>
|
||||
<p>The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.</p>
|
||||
@ -37,6 +38,12 @@
|
||||
<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>
|
||||
<release version="0.12.5" date="2025-11-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</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
|
||||
13
externals/CMakeLists.txt
vendored
13
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()
|
||||
|
||||
@ -258,9 +259,19 @@ if (WIN32)
|
||||
add_subdirectory(ext-wepoll)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET fdk-aac)
|
||||
add_subdirectory(aacdec)
|
||||
endif()
|
||||
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
# cli11
|
||||
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(CLI11)
|
||||
2
externals/MoltenVK
vendored
2
externals/MoltenVK
vendored
@ -1 +1 @@
|
||||
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
|
||||
Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030
|
||||
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(AACDEC_SRC
|
||||
fdk-aac/libAACdec/src/FDK_delay.cpp
|
||||
fdk-aac/libAACdec/src/aac_ram.cpp
|
||||
fdk-aac/libAACdec/src/aac_rom.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_drc.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr_bit.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcrs.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_pns.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_tns.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder_lib.cpp
|
||||
fdk-aac/libAACdec/src/block.cpp
|
||||
fdk-aac/libAACdec/src/channel.cpp
|
||||
fdk-aac/libAACdec/src/channelinfo.cpp
|
||||
fdk-aac/libAACdec/src/conceal.cpp
|
||||
fdk-aac/libAACdec/src/ldfiltbank.cpp
|
||||
fdk-aac/libAACdec/src/pulsedata.cpp
|
||||
fdk-aac/libAACdec/src/rvlc.cpp
|
||||
fdk-aac/libAACdec/src/rvlcbit.cpp
|
||||
fdk-aac/libAACdec/src/rvlcconceal.cpp
|
||||
fdk-aac/libAACdec/src/stereo.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_d4t64.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_ltp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_acelp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_fac.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpc.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpd.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_rom.cpp
|
||||
)
|
||||
|
||||
set(FDK_SRC
|
||||
fdk-aac/libFDK/src/FDK_bitbuffer.cpp
|
||||
fdk-aac/libFDK/src/FDK_core.cpp
|
||||
fdk-aac/libFDK/src/FDK_crc.cpp
|
||||
fdk-aac/libFDK/src/FDK_decorrelate.cpp
|
||||
fdk-aac/libFDK/src/FDK_hybrid.cpp
|
||||
fdk-aac/libFDK/src/FDK_lpc.cpp
|
||||
fdk-aac/libFDK/src/FDK_matrixCalloc.cpp
|
||||
fdk-aac/libFDK/src/FDK_qmf_domain.cpp
|
||||
fdk-aac/libFDK/src/FDK_tools_rom.cpp
|
||||
fdk-aac/libFDK/src/FDK_trigFcts.cpp
|
||||
fdk-aac/libFDK/src/autocorr2nd.cpp
|
||||
fdk-aac/libFDK/src/dct.cpp
|
||||
fdk-aac/libFDK/src/fft.cpp
|
||||
fdk-aac/libFDK/src/fft_rad2.cpp
|
||||
fdk-aac/libFDK/src/fixpoint_math.cpp
|
||||
fdk-aac/libFDK/src/huff_nodes.cpp
|
||||
fdk-aac/libFDK/src/mdct.cpp
|
||||
fdk-aac/libFDK/src/nlc_dec.cpp
|
||||
fdk-aac/libFDK/src/qmf.cpp
|
||||
fdk-aac/libFDK/src/scale.cpp
|
||||
)
|
||||
|
||||
set(SYS_SRC
|
||||
fdk-aac/libSYS/src/genericStds.cpp
|
||||
fdk-aac/libSYS/src/syslib_channelMapDescr.cpp
|
||||
)
|
||||
|
||||
set(ARITHCODING_SRC
|
||||
fdk-aac/libArithCoding/src/ac_arith_coder.cpp
|
||||
)
|
||||
|
||||
set(MPEGTPDEC_SRC
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adif.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adts.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_asc.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_drm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_latm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_lib.cpp
|
||||
)
|
||||
|
||||
set(SBRDEC_SRC
|
||||
fdk-aac/libSBRdec/src/HFgen_preFlat.cpp
|
||||
fdk-aac/libSBRdec/src/env_calc.cpp
|
||||
fdk-aac/libSBRdec/src/env_dec.cpp
|
||||
fdk-aac/libSBRdec/src/env_extr.cpp
|
||||
fdk-aac/libSBRdec/src/hbe.cpp
|
||||
fdk-aac/libSBRdec/src/huff_dec.cpp
|
||||
fdk-aac/libSBRdec/src/lpp_tran.cpp
|
||||
fdk-aac/libSBRdec/src/psbitdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec_drm.cpp
|
||||
fdk-aac/libSBRdec/src/psdecrom_drm.cpp
|
||||
fdk-aac/libSBRdec/src/pvc_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_deb.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_ram.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_rom.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_drc.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_freq_sca.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdecoder.cpp
|
||||
)
|
||||
|
||||
set(PCMUTILS_SRC
|
||||
fdk-aac/libPCMutils/src/limiter.cpp
|
||||
fdk-aac/libPCMutils/src/pcm_utils.cpp
|
||||
fdk-aac/libPCMutils/src/pcmdmx_lib.cpp
|
||||
)
|
||||
|
||||
set(DRCDEC_SRC
|
||||
fdk-aac/libDRCdec/src/FDK_drcDecLib.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_gainDecoder.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_reader.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_rom.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_selectionProcess.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_tools.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_init.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_preprocess.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_process.cpp
|
||||
)
|
||||
|
||||
set(SACDEC_SRC
|
||||
fdk-aac/libSACdec/src/sac_bitdec.cpp
|
||||
fdk-aac/libSACdec/src/sac_calcM1andM2.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_conceal.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_lib.cpp
|
||||
fdk-aac/libSACdec/src/sac_process.cpp
|
||||
fdk-aac/libSACdec/src/sac_qmf.cpp
|
||||
fdk-aac/libSACdec/src/sac_reshapeBBEnv.cpp
|
||||
fdk-aac/libSACdec/src/sac_rom.cpp
|
||||
fdk-aac/libSACdec/src/sac_smoothing.cpp
|
||||
fdk-aac/libSACdec/src/sac_stp.cpp
|
||||
fdk-aac/libSACdec/src/sac_tsd.cpp
|
||||
)
|
||||
|
||||
add_library(fdk-aac
|
||||
${AACDEC_SRC}
|
||||
${FDK_SRC}
|
||||
${SYS_SRC}
|
||||
${ARITHCODING_SRC}
|
||||
${MPEGTPDEC_SRC}
|
||||
${SBRDEC_SRC}
|
||||
${PCMUTILS_SRC}
|
||||
${DRCDEC_SRC}
|
||||
${SACDEC_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(fdk-aac
|
||||
PUBLIC
|
||||
fdk-aac/libAACdec/include
|
||||
fdk-aac/libFDK/include
|
||||
fdk-aac/libSYS/include
|
||||
fdk-aac/libArithCoding/include
|
||||
fdk-aac/libMpegTPDec/include
|
||||
fdk-aac/libSBRdec/include
|
||||
fdk-aac/libPCMutils/include
|
||||
fdk-aac/libDRCdec/include
|
||||
fdk-aac/libSACdec/include
|
||||
)
|
||||
1
externals/aacdec/fdk-aac
vendored
Submodule
1
externals/aacdec/fdk-aac
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ee76460efbdb147e26d804c798949c23f174460b
|
||||
2
externals/ffmpeg-core
vendored
2
externals/ffmpeg-core
vendored
@ -1 +1 @@
|
||||
Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff
|
||||
Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a
|
||||
2
externals/fmt
vendored
2
externals/fmt
vendored
@ -1 +1 @@
|
||||
Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739
|
||||
Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051
|
||||
2
externals/sdl3
vendored
2
externals/sdl3
vendored
@ -1 +1 @@
|
||||
Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7
|
||||
Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc
|
||||
@ -198,7 +198,7 @@ static ConfigEntry<bool> pipelineCacheArchive(false);
|
||||
static ConfigEntry<bool> isDebugDump(false);
|
||||
static ConfigEntry<bool> isShaderDebug(false);
|
||||
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
|
||||
static ConfigEntry<bool> isFpsColor(true);
|
||||
static ConfigEntry<bool> showFpsCounter(false);
|
||||
static ConfigEntry<bool> logEnabled(true);
|
||||
|
||||
// GUI
|
||||
@ -222,16 +222,6 @@ static string config_version = Common::g_scm_rev;
|
||||
// These entries aren't stored in the config
|
||||
static bool overrideControllerColor = false;
|
||||
static int controllerCustomColorRGB[3] = {0, 0, 255};
|
||||
static bool isGameRunning = false;
|
||||
static bool load_auto_patches = true;
|
||||
|
||||
bool getGameRunning() {
|
||||
return isGameRunning;
|
||||
}
|
||||
|
||||
void setGameRunning(bool running) {
|
||||
isGameRunning = running;
|
||||
}
|
||||
|
||||
std::filesystem::path getSysModulesPath() {
|
||||
if (sys_modules_path.empty()) {
|
||||
@ -462,8 +452,12 @@ bool isPipelineCacheArchived() {
|
||||
return pipelineCacheArchive.get();
|
||||
}
|
||||
|
||||
bool fpsColor() {
|
||||
return isFpsColor.get();
|
||||
bool getShowFpsCounter() {
|
||||
return showFpsCounter.get();
|
||||
}
|
||||
|
||||
void setShowFpsCounter(bool enable, bool is_game_specific) {
|
||||
showFpsCounter.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool isLoggingEnabled() {
|
||||
@ -846,13 +840,6 @@ void setUsbDeviceBackend(int value, bool is_game_specific) {
|
||||
usbDeviceBackend.set(value, is_game_specific);
|
||||
}
|
||||
|
||||
bool getLoadAutoPatches() {
|
||||
return load_auto_patches;
|
||||
}
|
||||
void setLoadAutoPatches(bool enable) {
|
||||
load_auto_patches = enable;
|
||||
}
|
||||
|
||||
void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
// If the configuration file does not exist, create it and return, unless it is game specific
|
||||
std::error_code error;
|
||||
@ -968,7 +955,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
|
||||
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
|
||||
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
|
||||
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
|
||||
showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific);
|
||||
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
|
||||
current_version = toml::find_or<std::string>(debug, "ConfigVersion", current_version);
|
||||
}
|
||||
@ -1187,7 +1174,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value;
|
||||
data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value;
|
||||
data["GPU"]["patchShaders"] = shouldPatchShaders.base_value;
|
||||
data["Debug"]["FPSColor"] = isFpsColor.base_value;
|
||||
data["Debug"]["showFpsCounter"] = showFpsCounter.base_value;
|
||||
}
|
||||
|
||||
// Sorting of TOML sections
|
||||
@ -1295,7 +1282,7 @@ void setDefaultValues(bool is_game_specific) {
|
||||
internalScreenHeight.base_value = 720;
|
||||
|
||||
// Debug
|
||||
isFpsColor.base_value = true;
|
||||
showFpsCounter.base_value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -125,7 +125,8 @@ int getSpecialPadClass();
|
||||
bool getPSNSignedIn();
|
||||
void setPSNSignedIn(bool sign, bool is_game_specific = false);
|
||||
bool patchShaders(); // no set
|
||||
bool fpsColor(); // no set
|
||||
bool getShowFpsCounter();
|
||||
void setShowFpsCounter(bool enable, bool is_game_specific = false);
|
||||
bool isNeoModeConsole();
|
||||
void setNeoMode(bool enable, bool is_game_specific = false);
|
||||
bool isDevKitConsole();
|
||||
@ -152,8 +153,6 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false);
|
||||
void setUserName(const std::string& name, bool is_game_specific = false);
|
||||
std::filesystem::path getSysModulesPath();
|
||||
void setSysModulesPath(const std::filesystem::path& path);
|
||||
bool getLoadAutoPatches();
|
||||
void setLoadAutoPatches(bool enable);
|
||||
|
||||
enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad };
|
||||
int getUsbDeviceBackend();
|
||||
|
||||
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
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
@ -97,6 +98,7 @@ private:
|
||||
std::size_t bytes_written = 0;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Backend that writes to Visual Studio's output window
|
||||
*/
|
||||
@ -107,15 +109,14 @@ public:
|
||||
~DebuggerBackend() = default;
|
||||
|
||||
void Write(const Entry& entry) {
|
||||
#ifdef _WIN32
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Flush() {}
|
||||
|
||||
void EnableForStacktrace() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
bool initialization_in_progress_suppress_logging = true;
|
||||
|
||||
@ -219,6 +220,7 @@ public:
|
||||
.line_num = line_num,
|
||||
.function = function,
|
||||
.message = std::move(message),
|
||||
.thread = Common::GetCurrentThreadName(),
|
||||
};
|
||||
if (Config::getLogType() == "async") {
|
||||
message_queue.EmplaceWait(entry);
|
||||
@ -266,7 +268,9 @@ private:
|
||||
}
|
||||
|
||||
void ForEachBackend(auto lambda) {
|
||||
// lambda(debugger_backend);
|
||||
#ifdef _WIN32
|
||||
lambda(debugger_backend);
|
||||
#endif
|
||||
lambda(color_console_backend);
|
||||
lambda(file_backend);
|
||||
}
|
||||
@ -279,7 +283,9 @@ private:
|
||||
static inline bool should_append{false};
|
||||
|
||||
Filter filter;
|
||||
#ifdef _WIN32
|
||||
DebuggerBackend debugger_backend{};
|
||||
#endif
|
||||
ColorConsoleBackend color_console_backend{};
|
||||
FileBackend file_backend;
|
||||
|
||||
|
||||
@ -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,15 +107,20 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, NpCommon) \
|
||||
SUB(Lib, NpCommerce) \
|
||||
SUB(Lib, NpManager) \
|
||||
SUB(Lib, NpMatching2) \
|
||||
SUB(Lib, NpScore) \
|
||||
SUB(Lib, NpTrophy) \
|
||||
SUB(Lib, NpTus) \
|
||||
SUB(Lib, NpWebApi) \
|
||||
SUB(Lib, NpWebApi2) \
|
||||
SUB(Lib, NpProfileDialog) \
|
||||
SUB(Lib, NpSnsFacebookDialog) \
|
||||
SUB(Lib, NpPartner) \
|
||||
SUB(Lib, Screenshot) \
|
||||
SUB(Lib, LibCInternal) \
|
||||
SUB(Lib, AppContent) \
|
||||
SUB(Lib, Rtc) \
|
||||
SUB(Lib, Rudp) \
|
||||
SUB(Lib, DiscMap) \
|
||||
SUB(Lib, Png) \
|
||||
SUB(Lib, Jpeg) \
|
||||
@ -157,6 +163,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
CLS(ImGui) \
|
||||
CLS(Input) \
|
||||
CLS(Tty) \
|
||||
CLS(KeyManager) \
|
||||
CLS(Loader)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
||||
@ -21,6 +21,7 @@ struct Entry {
|
||||
u32 line_num = 0;
|
||||
std::string function;
|
||||
std::string message;
|
||||
std::string thread;
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
@ -23,8 +24,8 @@ std::string FormatLogMessage(const Entry& entry) {
|
||||
const char* class_name = GetLogClassName(entry.log_class);
|
||||
const char* level_name = GetLevelName(entry.log_level);
|
||||
|
||||
return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename,
|
||||
entry.line_num, entry.function, entry.message);
|
||||
return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name, entry.thread,
|
||||
entry.filename, entry.line_num, entry.function, entry.message);
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -73,15 +74,19 @@ enum class Class : u8 {
|
||||
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
|
||||
Lib_NpAuth, ///< The LibSceNpAuth implementation
|
||||
Lib_NpManager, ///< The LibSceNpManager implementation
|
||||
Lib_NpMatching2, ///< The LibSceNpMatching2 implementation
|
||||
Lib_NpScore, ///< The LibSceNpScore implementation
|
||||
Lib_NpTrophy, ///< The LibSceNpTrophy implementation
|
||||
Lib_NpTus, ///< The LibSceNpTus implementation
|
||||
Lib_NpWebApi, ///< The LibSceWebApi implementation
|
||||
Lib_NpWebApi2, ///< The LibSceWebApi2 implementation
|
||||
Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation
|
||||
Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation
|
||||
Lib_Screenshot, ///< The LibSceScreenshot implementation
|
||||
Lib_LibCInternal, ///< The LibCInternal implementation.
|
||||
Lib_AppContent, ///< The LibSceAppContent implementation.
|
||||
Lib_Rtc, ///< The LibSceRtc implementation.
|
||||
Lib_Rudp, ///< The LibSceRudp implementation.
|
||||
Lib_DiscMap, ///< The LibSceDiscMap implementation.
|
||||
Lib_Png, ///< The LibScePng implementation.
|
||||
Lib_Jpeg, ///< The LibSceJpeg implementation.
|
||||
@ -107,6 +112,7 @@ enum class Class : u8 {
|
||||
Lib_Mouse, ///< The LibSceMouse implementation
|
||||
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
|
||||
Lib_NpParty, ///< The LibSceNpParty implementation
|
||||
Lib_NpPartner, ///< The LibSceNpPartner implementation
|
||||
Lib_Zlib, ///< The LibSceZlib implementation.
|
||||
Lib_Hmd, ///< The LibSceHmd implementation.
|
||||
Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation.
|
||||
@ -125,6 +131,7 @@ enum class Class : u8 {
|
||||
Loader, ///< ROM loader
|
||||
Input, ///< Input emulation
|
||||
Tty, ///< Debug output from emu
|
||||
KeyManager, ///< Key management system
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
||||
@ -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 <algorithm>
|
||||
@ -12,6 +12,7 @@
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "memory_patcher.h"
|
||||
|
||||
@ -51,14 +52,14 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
|
||||
uint32_t i;
|
||||
} floatUnion;
|
||||
floatUnion.f = std::stof(valueStr);
|
||||
result = toHex(floatUnion.i, sizeof(floatUnion.i));
|
||||
result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i));
|
||||
} else if (type == "float64") {
|
||||
union {
|
||||
double d;
|
||||
uint64_t i;
|
||||
} doubleUnion;
|
||||
doubleUnion.d = std::stod(valueStr);
|
||||
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
|
||||
result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i));
|
||||
} else if (type == "utf8") {
|
||||
std::vector<unsigned char> byteArray =
|
||||
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
|
||||
@ -192,7 +193,7 @@ void OnGameLoaded() {
|
||||
} else {
|
||||
ApplyPatchesFromXML(file_path);
|
||||
}
|
||||
} else if (Config::getLoadAutoPatches()) {
|
||||
} else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) {
|
||||
for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) {
|
||||
if (!repo.is_directory()) {
|
||||
continue;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
@ -171,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
}
|
||||
|
||||
@ -183,6 +189,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
#if !defined(_WIN32) || defined(_MSC_VER)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
pthread_setname_np(name);
|
||||
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
@ -209,6 +218,9 @@ void SetThreadName(void* thread, const char* name) {
|
||||
|
||||
#if defined(_WIN32)
|
||||
void SetCurrentThreadName(const char*) {
|
||||
if (Libraries::Kernel::g_curthread) {
|
||||
Libraries::Kernel::g_curthread->name = name;
|
||||
}
|
||||
// Do Nothing on MinGW
|
||||
}
|
||||
|
||||
@ -237,4 +249,22 @@ void AccurateTimer::End() {
|
||||
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||
}
|
||||
|
||||
std::string GetCurrentThreadName() {
|
||||
using namespace Libraries::Kernel;
|
||||
if (g_curthread && !g_curthread->name.empty()) {
|
||||
return g_curthread->name;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
PWSTR name;
|
||||
GetThreadDescription(GetCurrentThread(), &name);
|
||||
return Common::UTF16ToUTF8(name);
|
||||
#else
|
||||
char name[256];
|
||||
if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
|
||||
return "<unknown name>";
|
||||
}
|
||||
return std::string{name};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -46,4 +47,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::string GetCurrentThreadName();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -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
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <Zydis/Zydis.h>
|
||||
#include <xbyak/xbyak.h>
|
||||
#include <xbyak/xbyak_util.h>
|
||||
@ -122,6 +123,30 @@ static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* op
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool FilterStackCheck(const ZydisDecodedOperand* operands) {
|
||||
const auto& dst_op = operands[0];
|
||||
const auto& src_op = operands[1];
|
||||
|
||||
// Some compilers emit stack checks by starting a function with
|
||||
// 'mov (64-bit register), fs:[0x28]', then checking with `xor (64-bit register), fs:[0x28]`
|
||||
return src_op.type == ZYDIS_OPERAND_TYPE_MEMORY && src_op.mem.segment == ZYDIS_REGISTER_FS &&
|
||||
src_op.mem.base == ZYDIS_REGISTER_NONE && src_op.mem.index == ZYDIS_REGISTER_NONE &&
|
||||
src_op.mem.disp.value == 0x28 && dst_op.reg.value >= ZYDIS_REGISTER_RAX &&
|
||||
dst_op.reg.value <= ZYDIS_REGISTER_R15;
|
||||
}
|
||||
|
||||
static void GenerateStackCheck(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||
c.xor_(dst, 0);
|
||||
}
|
||||
|
||||
static void GenerateStackCanary(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||
c.mov(dst, 0);
|
||||
}
|
||||
|
||||
static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
|
||||
Cpu cpu;
|
||||
return !cpu.has(Cpu::tSSE4a);
|
||||
@ -440,18 +465,26 @@ struct PatchInfo {
|
||||
bool trampoline;
|
||||
};
|
||||
|
||||
static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
||||
static const std::unordered_map<ZydisMnemonic, std::vector<PatchInfo>> Patches = {
|
||||
// SSE4a
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {{FilterNoSSE4a, GenerateEXTRQ, true}}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {{FilterNoSSE4a, GenerateINSERTQ, true}}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSS, {{FilterNoSSE4a, ReplaceMOVNTSS, false}}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSD, {{FilterNoSSE4a, ReplaceMOVNTSD, false}}},
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
// FS segment patches
|
||||
// These first two patches are for accesses to the stack canary, fs:[0x28]
|
||||
{ZYDIS_MNEMONIC_XOR, {{FilterStackCheck, GenerateStackCheck, false}}},
|
||||
{ZYDIS_MNEMONIC_MOV,
|
||||
{{FilterStackCheck, GenerateStackCanary, false},
|
||||
#if defined(_WIN32)
|
||||
// Windows needs a trampoline.
|
||||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}},
|
||||
#elif !defined(__APPLE__)
|
||||
{ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}},
|
||||
// Windows needs a trampoline for Tcb accesses.
|
||||
{FilterTcbAccess, GenerateTcbAccess, true}
|
||||
#else
|
||||
{FilterTcbAccess, GenerateTcbAccess, false}
|
||||
#endif
|
||||
}},
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -503,51 +536,53 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
||||
}
|
||||
|
||||
if (Patches.contains(instruction.mnemonic)) {
|
||||
const auto& patch_info = Patches.at(instruction.mnemonic);
|
||||
bool needs_trampoline = patch_info.trampoline;
|
||||
if (patch_info.filter(operands)) {
|
||||
auto& patch_gen = module->patch_gen;
|
||||
const auto& patches = Patches.at(instruction.mnemonic);
|
||||
for (const auto& patch_info : patches) {
|
||||
bool needs_trampoline = patch_info.trampoline;
|
||||
if (patch_info.filter(operands)) {
|
||||
auto& patch_gen = module->patch_gen;
|
||||
|
||||
if (needs_trampoline && instruction.length < 5) {
|
||||
// Trampoline is needed but instruction is too short to patch.
|
||||
// Return false and length to signal to AOT compilation that this instruction
|
||||
// should be skipped and handled at runtime.
|
||||
return std::make_pair(false, instruction.length);
|
||||
}
|
||||
if (needs_trampoline && instruction.length < 5) {
|
||||
// Trampoline is needed but instruction is too short to patch.
|
||||
// Return false and length to signal to AOT compilation that this instruction
|
||||
// should be skipped and handled at runtime.
|
||||
return std::make_pair(false, instruction.length);
|
||||
}
|
||||
|
||||
// Reset state and move to current code position.
|
||||
patch_gen.reset();
|
||||
patch_gen.setSize(code - patch_gen.getCode());
|
||||
// Reset state and move to current code position.
|
||||
patch_gen.reset();
|
||||
patch_gen.setSize(code - patch_gen.getCode());
|
||||
|
||||
if (needs_trampoline) {
|
||||
auto& trampoline_gen = module->trampoline_gen;
|
||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||
if (needs_trampoline) {
|
||||
auto& trampoline_gen = module->trampoline_gen;
|
||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||
|
||||
patch_info.generator(code, operands, trampoline_gen);
|
||||
patch_info.generator(code, operands, trampoline_gen);
|
||||
|
||||
// Return to the following instruction at the end of the trampoline.
|
||||
trampoline_gen.jmp(code + instruction.length);
|
||||
// Return to the following instruction at the end of the trampoline.
|
||||
trampoline_gen.jmp(code + instruction.length);
|
||||
|
||||
// Replace instruction with near jump to the trampoline.
|
||||
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
|
||||
} else {
|
||||
patch_info.generator(code, operands, patch_gen);
|
||||
}
|
||||
// Replace instruction with near jump to the trampoline.
|
||||
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
|
||||
} else {
|
||||
patch_info.generator(code, operands, patch_gen);
|
||||
}
|
||||
|
||||
const auto patch_size = patch_gen.getCurr() - code;
|
||||
if (patch_size > 0) {
|
||||
ASSERT_MSG(instruction.length >= patch_size,
|
||||
"Instruction {} with length {} is too short to replace at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
|
||||
fmt::ptr(code));
|
||||
const auto patch_size = patch_gen.getCurr() - code;
|
||||
if (patch_size > 0) {
|
||||
ASSERT_MSG(instruction.length >= patch_size,
|
||||
"Instruction {} with length {} is too short to replace at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), instruction.length,
|
||||
fmt::ptr(code));
|
||||
|
||||
// Fill remaining space with nops.
|
||||
patch_gen.nop(instruction.length - patch_size);
|
||||
// Fill remaining space with nops.
|
||||
patch_gen.nop(instruction.length - patch_size);
|
||||
|
||||
module->patched.insert(code);
|
||||
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
|
||||
return std::make_pair(true, instruction.length);
|
||||
module->patched.insert(code);
|
||||
LOG_DEBUG(Core, "Patched instruction '{}' at: {}",
|
||||
ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code));
|
||||
return std::make_pair(true, instruction.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -755,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) {
|
||||
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
|
||||
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
|
||||
[[unlikely]] {
|
||||
UNREACHABLE_MSG("ud2 at code address {:#x}", (u64)code_address);
|
||||
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
|
||||
}
|
||||
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address,
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
|
||||
reinterpret_cast<u64>(code_address),
|
||||
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
|
||||
: "Failed to decode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "layer.h"
|
||||
@ -11,6 +11,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "options.h"
|
||||
@ -273,14 +274,10 @@ void L::DrawAdvanced() {
|
||||
|
||||
void L::DrawSimple() {
|
||||
const float frameRate = DebugState.Framerate;
|
||||
if (Config::fpsColor()) {
|
||||
if (frameRate < 10) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
|
||||
} else if (frameRate >= 10 && frameRate < 20) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
|
||||
} else {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
|
||||
}
|
||||
if (frameRate < 10) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
|
||||
} else if (frameRate >= 10 && frameRate < 20) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
|
||||
} else {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
|
||||
}
|
||||
@ -311,6 +308,7 @@ static void LoadSettings(const char* line) {
|
||||
|
||||
void L::SetupSettings() {
|
||||
frame_graph.is_open = true;
|
||||
show_simple_fps = Config::getShowFpsCounter();
|
||||
|
||||
using SettingLoader = void (*)(const char*);
|
||||
|
||||
@ -475,6 +473,11 @@ void ToggleSimpleFps() {
|
||||
visibility_toggled = true;
|
||||
}
|
||||
|
||||
void SetSimpleFps(bool enabled) {
|
||||
show_simple_fps = enabled;
|
||||
visibility_toggled = true;
|
||||
}
|
||||
|
||||
void ToggleQuitWindow() {
|
||||
show_quit_window = !show_quit_window;
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ private:
|
||||
namespace Overlay {
|
||||
|
||||
void ToggleSimpleFps();
|
||||
void SetSimpleFps(bool enabled);
|
||||
void ToggleQuitWindow();
|
||||
|
||||
} // 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;
|
||||
|
||||
37
src/core/emulator_state.cpp
Normal file
37
src/core/emulator_state.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "emulator_state.h"
|
||||
|
||||
std::shared_ptr<EmulatorState> EmulatorState::s_instance = nullptr;
|
||||
std::mutex EmulatorState::s_mutex;
|
||||
|
||||
EmulatorState::EmulatorState() {}
|
||||
|
||||
EmulatorState::~EmulatorState() {}
|
||||
|
||||
std::shared_ptr<EmulatorState> EmulatorState::GetInstance() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
if (!s_instance)
|
||||
s_instance = std::make_shared<EmulatorState>();
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void EmulatorState::SetInstance(std::shared_ptr<EmulatorState> instance) {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
s_instance = instance;
|
||||
}
|
||||
|
||||
bool EmulatorState::IsGameRunning() const {
|
||||
return m_running;
|
||||
}
|
||||
void EmulatorState::SetGameRunning(bool running) {
|
||||
m_running = running;
|
||||
}
|
||||
|
||||
bool EmulatorState::IsAutoPatchesLoadEnabled() const {
|
||||
return m_load_patches_auto;
|
||||
}
|
||||
void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) {
|
||||
m_load_patches_auto = enable;
|
||||
}
|
||||
29
src/core/emulator_state.h
Normal file
29
src/core/emulator_state.h
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class EmulatorState {
|
||||
public:
|
||||
EmulatorState();
|
||||
~EmulatorState();
|
||||
|
||||
static std::shared_ptr<EmulatorState> GetInstance();
|
||||
static void SetInstance(std::shared_ptr<EmulatorState> instance);
|
||||
|
||||
bool IsGameRunning() const;
|
||||
void SetGameRunning(bool running);
|
||||
bool IsAutoPatchesLoadEnabled() const;
|
||||
void SetAutoPatchesLoadEnabled(bool enable);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<EmulatorState> s_instance;
|
||||
static std::mutex s_mutex;
|
||||
|
||||
// state variables
|
||||
bool m_running = false;
|
||||
bool m_load_patches_auto = true;
|
||||
};
|
||||
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 == '>') {
|
||||
@ -59,95 +46,236 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
|
||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
|
||||
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto user_key_str = Config::getTrophyKey();
|
||||
if (user_key_str.size() != 32) {
|
||||
const auto& user_key_vec =
|
||||
KeyManager::GetInstance()->GetAllKeys().TrophyKeySet.ReleaseTrophyKey;
|
||||
|
||||
if (user_key_vec.size() != 16) {
|
||||
LOG_INFO(Common_Filesystem, "Trophy decryption key is not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<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{};
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/directories/base_directory.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
|
||||
namespace Core::Directories {
|
||||
|
||||
@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default;
|
||||
|
||||
BaseDirectory::~BaseDirectory() = default;
|
||||
|
||||
s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::lseek(s64 offset, s32 whence) {
|
||||
|
||||
s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) +
|
||||
((2 == whence) * (directory_size + offset));
|
||||
if (file_offset_new < 0)
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
||||
file_offset = file_offset_new;
|
||||
return file_offset;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@ -19,6 +19,17 @@ struct OrbisKernelDirent;
|
||||
namespace Core::Directories {
|
||||
|
||||
class BaseDirectory {
|
||||
protected:
|
||||
static inline u32 fileno_pool{10};
|
||||
|
||||
static u32 next_fileno() {
|
||||
return ++fileno_pool;
|
||||
}
|
||||
|
||||
s64 file_offset = 0;
|
||||
u64 directory_size = 0;
|
||||
std::vector<u8> dirent_cache_bin{};
|
||||
|
||||
public:
|
||||
explicit BaseDirectory();
|
||||
|
||||
@ -28,13 +39,8 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt);
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset);
|
||||
|
||||
virtual s64 write(const void* buf, u64 nbytes) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
@ -48,9 +54,7 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
virtual s64 lseek(s64 offset, s32 whence);
|
||||
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
|
||||
@ -15,111 +15,30 @@ std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_di
|
||||
std::make_shared<NormalDirectory>(guest_directory));
|
||||
}
|
||||
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
4);
|
||||
|
||||
directory_size += dirent.d_reclen;
|
||||
});
|
||||
|
||||
// The last entry of a normal directory should have d_reclen covering the remaining data.
|
||||
// Since the dirents of a folder are constant by this point, we can modify the last dirent
|
||||
// before creating the emulated file buffer.
|
||||
const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size;
|
||||
dirents[dirents.size() - 1].d_reclen += filler_count;
|
||||
|
||||
// Reading from standard directories seems to be based around file pointer logic.
|
||||
// Keep an internal buffer representing the raw contents of this file descriptor,
|
||||
// then emulate the various read functions with that.
|
||||
directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT);
|
||||
data_buffer.reserve(directory_size);
|
||||
memset(data_buffer.data(), 0, directory_size);
|
||||
|
||||
u8* current_dirent = data_buffer.data();
|
||||
for (const NormalDirectoryDirent& dirent : dirents) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
}
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory)
|
||||
: guest_directory(guest_directory) {
|
||||
RebuildDirents();
|
||||
}
|
||||
|
||||
s64 NormalDirectory::read(void* buf, u64 nbytes) {
|
||||
// Nothing left to read.
|
||||
if (file_offset >= directory_size) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
RebuildDirents();
|
||||
|
||||
const s64 remaining_data = directory_size - file_offset;
|
||||
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
|
||||
// data is contiguous. read goes like any regular file would: start at offset, read n bytes
|
||||
// output is always aligned up to 512 bytes with 0s
|
||||
// offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer
|
||||
// space will be left untouched
|
||||
// reclen always sums up to end of current alignment
|
||||
|
||||
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
|
||||
file_offset += bytes;
|
||||
return bytes;
|
||||
}
|
||||
// data
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
case 0: {
|
||||
file_offset = offset;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
file_offset += offset;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
file_offset = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
return file_offset;
|
||||
file_offset += bytes_available;
|
||||
return bytes_available;
|
||||
}
|
||||
|
||||
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
if (basep != nullptr) {
|
||||
RebuildDirents();
|
||||
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
s64 bytes_written = 0;
|
||||
s64 working_offset = file_offset;
|
||||
s64 dirent_buffer_offset = 0;
|
||||
s64 aligned_count = Common::AlignDown(nbytes, 512);
|
||||
|
||||
const u8* dirent_buffer = this->dirent_cache_bin.data();
|
||||
while (dirent_buffer_offset < this->dirent_cache_bin.size()) {
|
||||
const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset;
|
||||
const NormalDirectoryDirent* normal_dirent =
|
||||
reinterpret_cast<const NormalDirectoryDirent*>(normal_dirent_ptr);
|
||||
auto d_reclen = normal_dirent->d_reclen;
|
||||
|
||||
// bad, incomplete or OOB entry
|
||||
if (normal_dirent->d_namlen == 0)
|
||||
break;
|
||||
|
||||
if (working_offset >= d_reclen) {
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset -= d_reclen;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((bytes_written + d_reclen) > aligned_count)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, normal_dirent_ptr + working_offset,
|
||||
d_reclen - working_offset);
|
||||
bytes_written += d_reclen - working_offset;
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset = 0;
|
||||
}
|
||||
// read behaves identically to getdents for normal directories.
|
||||
return read(buf, nbytes);
|
||||
|
||||
file_offset += bytes_written;
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
void NormalDirectory::RebuildDirents() {
|
||||
// regenerate only when target wants to read contents again
|
||||
// no reason for testing - read is always raw and dirents get processed on the go
|
||||
if (previous_file_offset == file_offset)
|
||||
return;
|
||||
previous_file_offset = file_offset;
|
||||
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) +
|
||||
sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen);
|
||||
|
||||
u64 next_ceiling = 0;
|
||||
u64 dirent_offset = 0;
|
||||
u64 last_reclen_offset = 4;
|
||||
dirent_cache_bin.clear();
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset](
|
||||
const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
NormalDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// prepare dirent
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4);
|
||||
|
||||
// next element may break 512 byte alignment
|
||||
if (tmp.d_reclen + dirent_offset > next_ceiling) {
|
||||
// align previous dirent's size to the current ceiling
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) +
|
||||
last_reclen_offset) += next_ceiling - dirent_offset;
|
||||
// set writing pointer to the aligned start position (current ceiling)
|
||||
dirent_offset = next_ceiling;
|
||||
// move the ceiling up and zero-out the buffer
|
||||
next_ceiling += 512;
|
||||
dirent_cache_bin.resize(next_ceiling);
|
||||
std::fill(dirent_cache_bin.begin() + dirent_offset,
|
||||
dirent_cache_bin.begin() + next_ceiling, 0);
|
||||
}
|
||||
|
||||
// current dirent's reclen position
|
||||
last_reclen_offset = dirent_offset + 4;
|
||||
memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen);
|
||||
dirent_offset += tmp.d_reclen;
|
||||
});
|
||||
|
||||
// last reclen, as before
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) + last_reclen_offset) +=
|
||||
next_ceiling - dirent_offset;
|
||||
|
||||
// i have no idea if this is the case, but lseek returns size aligned to 512
|
||||
directory_size = next_ceiling;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@ -19,27 +19,23 @@ public:
|
||||
~NormalDirectory() override = default;
|
||||
|
||||
virtual s64 read(void* buf, u64 nbytes) override;
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
u64 directory_size = 0;
|
||||
s64 file_offset = 0;
|
||||
std::vector<u8> data_buffer;
|
||||
std::vector<NormalDirectoryDirent> dirents;
|
||||
std::string_view guest_directory{};
|
||||
s64 previous_file_offset = -1;
|
||||
|
||||
void RebuildDirents(void);
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
@ -15,77 +15,49 @@ std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_direc
|
||||
}
|
||||
|
||||
PfsDirectory::PfsDirectory(std::string_view guest_directory) {
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) +
|
||||
sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen);
|
||||
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
PfsDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
8);
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = ent_is_file ? 2 : 4;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8);
|
||||
auto dirent_ptr = reinterpret_cast<const u8*>(&tmp);
|
||||
|
||||
// To handle some obscure dirents_index behavior,
|
||||
// keep track of the "actual" length of this directory.
|
||||
directory_content_size += dirent.d_reclen;
|
||||
});
|
||||
dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen);
|
||||
});
|
||||
|
||||
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
|
||||
directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000);
|
||||
}
|
||||
|
||||
s64 PfsDirectory::read(void* buf, u64 nbytes) {
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 to_fill =
|
||||
(std::min<s64>(directory_size, static_cast<s64>(nbytes))) - bytes_available - file_offset;
|
||||
if (to_fill < 0) {
|
||||
LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill);
|
||||
return -to_fill + bytes_available;
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
// read on PfsDirectories will always return the maximum possible value.
|
||||
const u64 bytes_written = bytes_remaining;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
PfsDirectoryDirent* dirent_to_write = reinterpret_cast<PfsDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so break out of the loop.
|
||||
dirents_index++;
|
||||
break;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
memset(static_cast<u8*>(buf) + bytes_available, 0, to_fill);
|
||||
file_offset += to_fill + bytes_available;
|
||||
return to_fill + bytes_available;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc
|
||||
}
|
||||
|
||||
s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_dirent_index = dirents_index;
|
||||
dirents_index = 0;
|
||||
s64 data_to_skip = offset;
|
||||
// If offset is part-way through one dirent, that dirent is skipped.
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've reached the end of the dirents, nothing more can be skipped.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
dirents_index = old_dirent_index;
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
// Seek start
|
||||
case 0: {
|
||||
dirents_index = 0;
|
||||
}
|
||||
case 1: {
|
||||
// There aren't any dirents left to pass through.
|
||||
if (dirents_index >= dirents.size()) {
|
||||
dirents_index = dirents_index + offset;
|
||||
break;
|
||||
}
|
||||
s64 data_to_skip = offset;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've passed through all file dirents.
|
||||
// Set dirents_index to directory_size + remaining_offset instead.
|
||||
dirents_index = directory_content_size + data_to_skip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Seems like real hardware gives up on tracking dirents_index if you go this route.
|
||||
dirents_index = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
|
||||
return dirents_index;
|
||||
}
|
||||
|
||||
s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
stat->st_mode = 0000777u | 0040000u;
|
||||
stat->st_size = directory_size;
|
||||
@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
// basep is set at the start of the function.
|
||||
if (basep != nullptr) {
|
||||
*basep = dirents_index;
|
||||
}
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
u64 bytes_written = 0;
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
// getdents has to convert pfs dirents to normal dirents
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
u64 starting_offset = 0;
|
||||
u64 buffer_position = 0;
|
||||
while (buffer_position < this->dirent_cache_bin.size()) {
|
||||
const PfsDirectoryDirent* pfs_dirent =
|
||||
reinterpret_cast<PfsDirectoryDirent*>(this->dirent_cache_bin.data() + buffer_position);
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
bytes_written += dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so set dirents_index appropriately and break.
|
||||
dirents_index = directory_size;
|
||||
// bad, incomplete or OOB entry
|
||||
if (pfs_dirent->d_namlen == 0)
|
||||
break;
|
||||
|
||||
if (starting_offset < file_offset) {
|
||||
// reading starts from the nearest full dirent
|
||||
starting_offset += pfs_dirent->d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
continue;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
|
||||
if ((bytes_written + pfs_dirent->d_reclen) > nbytes)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
// if this dirent breaks alignment, skip
|
||||
// dirents are count-aligned here, excess data is simply not written
|
||||
// if (Common::AlignUp(buffer_position, count) !=
|
||||
// Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count))
|
||||
// break;
|
||||
|
||||
// reclen for both is the same despite difference in var sizes, extra 0s are padded after
|
||||
// the name
|
||||
NormalDirectoryDirent normal_dirent{};
|
||||
normal_dirent.d_fileno = pfs_dirent->d_fileno;
|
||||
normal_dirent.d_reclen = pfs_dirent->d_reclen;
|
||||
normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4;
|
||||
normal_dirent.d_namlen = pfs_dirent->d_namlen;
|
||||
memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen);
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen);
|
||||
bytes_written += normal_dirent.d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
}
|
||||
|
||||
file_offset = (buffer_position >= this->dirent_cache_bin.size())
|
||||
? directory_size
|
||||
: (file_offset + bytes_written);
|
||||
return bytes_written;
|
||||
}
|
||||
} // namespace Core::Directories
|
||||
@ -22,32 +22,28 @@ public:
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000;
|
||||
#pragma pack(push, 1)
|
||||
struct PfsDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u32 d_type;
|
||||
u32 d_namlen;
|
||||
u32 d_reclen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
|
||||
u64 directory_size = 0;
|
||||
u64 directory_content_size = 0;
|
||||
s64 dirents_index = 0;
|
||||
std::vector<PfsDirectoryDirent> dirents;
|
||||
#pragma pack(pop)
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
||||
pos = corrected_path.find("//", pos + 1);
|
||||
}
|
||||
|
||||
if (path.length() > 255)
|
||||
return "";
|
||||
|
||||
const MntPair* mount = GetMount(corrected_path);
|
||||
if (!mount) {
|
||||
return "";
|
||||
@ -229,6 +232,9 @@ File* HandleTable::GetSocket(int d) {
|
||||
return nullptr;
|
||||
}
|
||||
auto file = m_files.at(d);
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
if (file->type != Core::FileSys::FileType::Socket) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ipc.h"
|
||||
@ -14,6 +14,7 @@
|
||||
#include "common/types.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "input/input_handler.h"
|
||||
#include "sdl_window.h"
|
||||
@ -71,7 +72,7 @@ void IPC::Init() {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::setLoadAutoPatches(false);
|
||||
EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false);
|
||||
|
||||
input_thread = std::jthread([this] {
|
||||
Common::SetCurrentThreadName("IPC Read thread");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
@ -34,7 +34,7 @@ u32 GetChannelMask(u32 num_channels) {
|
||||
case 8:
|
||||
return ORBIS_AJM_CHANNELMASK_7POINT1;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
UNREACHABLE_MSG("Unexpected number of channels: {}", num_channels);
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,9 +144,8 @@ int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id) {
|
||||
return static_cast<AjmCodecType>((instance_id >> 14) & 0x1F);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
|
||||
|
||||
@ -82,8 +82,6 @@ enum class AjmStatisticsFlags : u64 {
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags)
|
||||
|
||||
union AjmStatisticsJobFlags {
|
||||
AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {}
|
||||
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 version : 3;
|
||||
@ -133,7 +131,7 @@ struct AjmSidebandGaplessDecode {
|
||||
|
||||
struct AjmSidebandResampleParameters {
|
||||
float ratio;
|
||||
uint32_t flags;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct AjmDecAt9InitializeParameters {
|
||||
@ -217,7 +215,7 @@ int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int p
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
int PS4_SYSV_ABI sceAjmFinalize();
|
||||
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType();
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
u32* instance);
|
||||
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
|
||||
|
||||
218
src/core/libraries/ajm/ajm_aac.cpp
Normal file
218
src/core/libraries/ajm/ajm_aac.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <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 <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(1024 * 8),
|
||||
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {
|
||||
m_resample_buffer.reserve(m_pcm_buffer.size());
|
||||
}
|
||||
|
||||
AjmAacDecoder::~AjmAacDecoder() {
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
}
|
||||
|
||||
TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
|
||||
switch (config_type) {
|
||||
case ConfigType::ADTS:
|
||||
return TT_MP4_ADTS;
|
||||
case ConfigType::RAW:
|
||||
return TT_MP4_RAW;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static UINT g_freq[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000,
|
||||
};
|
||||
|
||||
void AjmAacDecoder::Reset() {
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
|
||||
m_decoder = aacDecoder_Open(TransportTypeFromConfigType(m_init_params.config_type), 1);
|
||||
if (m_init_params.config_type == ConfigType::RAW) {
|
||||
// Manually configure the decoder
|
||||
// Things may be incorrect due to limited documentation
|
||||
CSAudioSpecificConfig asc{};
|
||||
asc.m_aot = AOT_AAC_LC;
|
||||
asc.m_samplingFrequency = g_freq[m_init_params.sampling_freq_type];
|
||||
asc.m_samplingFrequencyIndex = m_init_params.sampling_freq_type;
|
||||
asc.m_samplesPerFrame = 1024;
|
||||
asc.m_epConfig = -1;
|
||||
switch (m_channels) {
|
||||
case 0:
|
||||
asc.m_channelConfiguration = 2;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
asc.m_channelConfiguration = m_channels;
|
||||
break;
|
||||
case 7:
|
||||
asc.m_channelConfiguration = 11;
|
||||
break;
|
||||
case 8:
|
||||
asc.m_channelConfiguration = 12; // 7, 12 or 14 ?
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
UCHAR changed = 1;
|
||||
CAacDecoder_Init(m_decoder, &asc, AC_CM_ALLOC_MEM, &changed);
|
||||
}
|
||||
m_skip_frames = True(m_flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2;
|
||||
}
|
||||
|
||||
void AjmAacDecoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||
ASSERT(buffer_size == 8);
|
||||
m_init_params = *reinterpret_cast<const InitializeParameters*>(buffer);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AjmAacDecoder::GetInfo(void* out_info) const {
|
||||
auto* codec_info = reinterpret_cast<AjmSidebandDecM4aacCodecInfo*>(out_info);
|
||||
*codec_info = {
|
||||
.heaac = True(m_flags & AjmAacCodecFlags::EnableSbrDecode),
|
||||
};
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAacDecoder::GetFormat() const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
return {
|
||||
.num_channels = static_cast<u32>(info->numChannels),
|
||||
.channel_mask = GetChannelMask(info->numChannels),
|
||||
.sampl_freq = static_cast<u32>(info->sampleRate),
|
||||
.sample_encoding = m_format,
|
||||
.bitrate = static_cast<u32>(info->bitRate),
|
||||
};
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetMinimumInputSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
if (info->aacSamplesPerFrame <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, info->frameSize);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, info->frameSize - skip_samples)
|
||||
: info->frameSize - skip_samples;
|
||||
return samples * info->numChannels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
|
||||
// Discard the previous contents of the internal buffer and replace them with new ones
|
||||
aacDecoder_SetParam(m_decoder, AAC_TPDEC_CLEAR_BUFFER, 1);
|
||||
UCHAR* buffers[] = {input.data()};
|
||||
const UINT sizes[] = {static_cast<UINT>(input.size())};
|
||||
UINT valid = sizes[0];
|
||||
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
|
||||
auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0);
|
||||
|
||||
switch (ret) {
|
||||
case AAC_DEC_OK:
|
||||
break;
|
||||
case AAC_DEC_NOT_ENOUGH_BITS:
|
||||
result.result = ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
default:
|
||||
LOG_ERROR(Lib_Ajm, "aacDecoder_DecodeFrame failed ret = {:#x}", static_cast<u32>(ret));
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
auto bytes_used = info->numTotalBytes;
|
||||
|
||||
result.frames_decoded += 1;
|
||||
input = input.subspan(bytes_used);
|
||||
|
||||
if (m_skip_frames > 0) {
|
||||
--m_skip_frames;
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min<u16>(info->frameSize, gapless.current.skip_samples);
|
||||
gapless.current.skip_samples -= skip_samples;
|
||||
}
|
||||
|
||||
const auto max_samples =
|
||||
gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
|
||||
|
||||
size_t pcm_written = 0;
|
||||
auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels);
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
pcm_written = output.Write(pcm);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
pcm_written = WriteOutputSamples<float>(output, pcm);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
result.samples_written = pcm_written / info->numChannels;
|
||||
gapless.current.skipped_samples += info->frameSize - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
69
src/core/libraries/ajm/ajm_aac.h
Normal file
69
src/core/libraries/ajm/ajm_aac.h
Normal file
@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
struct AAC_DECODER_INSTANCE;
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
enum ConfigType : u32 {
|
||||
ADTS = 1,
|
||||
RAW = 2,
|
||||
};
|
||||
|
||||
enum AjmAacCodecFlags : u32 {
|
||||
EnableSbrDecode = 1 << 0,
|
||||
EnableNondelayOutput = 1 << 1,
|
||||
SurroundChannelInterleaveOrderExtlExtrLsRs = 1 << 2,
|
||||
SurroundChannelInterleaveOrderLsRsExtlExtr = 1 << 3,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmAacCodecFlags)
|
||||
|
||||
struct AjmSidebandDecM4aacCodecInfo {
|
||||
u32 heaac;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct AjmAacDecoder final : AjmCodec {
|
||||
explicit AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels);
|
||||
~AjmAacDecoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
struct InitializeParameters {
|
||||
ConfigType config_type;
|
||||
u32 sampling_freq_type;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
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<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
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm_result.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "error_codes.h"
|
||||
@ -53,7 +54,7 @@ struct RIFFHeader {
|
||||
};
|
||||
static_assert(sizeof(RIFFHeader) == 12);
|
||||
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {}
|
||||
|
||||
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||
@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
|
||||
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||
info->super_frame_size = m_codec_info.superframeSize;
|
||||
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||
info->next_frame_size = m_superframe_bytes_remain;
|
||||
info->frame_samples = m_codec_info.frameSamples;
|
||||
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
|
||||
}
|
||||
|
||||
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
|
||||
@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span<u8>& in_buf, AjmInstanceGapless& g
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
bool is_reset = false;
|
||||
u32 AjmAt9Decoder::GetMinimumInputSize() const {
|
||||
return m_superframe_bytes_remain;
|
||||
}
|
||||
|
||||
DecoderResult AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
|
||||
*reinterpret_cast<u32*>(in_buf.data()) == 'FFIR') {
|
||||
ParseRIFFHeader(in_buf, gapless);
|
||||
is_reset = true;
|
||||
result.is_reset = true;
|
||||
}
|
||||
|
||||
if (!m_is_initialized) {
|
||||
return {0, 0, is_reset};
|
||||
result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
@ -166,7 +171,14 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
if (ret != At9Status::ERR_SUCCESS) {
|
||||
LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.frames_decoded += 1;
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
@ -196,10 +208,10 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const auto samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
|
||||
result.samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples_written;
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
m_num_frames += 1;
|
||||
@ -209,9 +221,23 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
}
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
} else if (gapless.IsEnd()) {
|
||||
// Drain the remaining superframe
|
||||
std::vector<s16> buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
|
||||
while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
|
||||
ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
|
||||
True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
result.frames_decoded += 1;
|
||||
m_num_frames += 1;
|
||||
}
|
||||
in_buf = in_buf.subspan(m_superframe_bytes_remain);
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
return {1, m_codec_info.frameSamples, is_reset};
|
||||
return result;
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
|
||||
@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
|
||||
: m_codec_info.frameSamples;
|
||||
: m_codec_info.frameSamples - skip_samples;
|
||||
return samples * m_codec_info.channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@ -13,8 +14,6 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||
|
||||
enum AjmAt9CodecFlags : u32 {
|
||||
ParseRiffHeader = 1 << 0,
|
||||
NonInterleavedOutput = 1 << 8,
|
||||
@ -29,16 +28,17 @@ struct AjmSidebandDecAt9CodecInfo {
|
||||
};
|
||||
|
||||
struct AjmAt9Decoder final : AjmCodec {
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32 channels);
|
||||
~AjmAt9Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
|
||||
@ -165,7 +165,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf
|
||||
ASSERT(job_flags.has_value());
|
||||
job.flags = job_flags.value();
|
||||
|
||||
AjmStatisticsJobFlags flags(job.flags);
|
||||
AjmStatisticsJobFlags flags{.raw = job.flags.raw};
|
||||
if (input_control_buffer.has_value()) {
|
||||
AjmBatchBuffer input_batch(input_control_buffer.value());
|
||||
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
|
||||
@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
|
||||
job.input.resample_parameters = input_batch.Consume<AjmSidebandResampleParameters>();
|
||||
}
|
||||
if (True(control_flags & AjmJobControlFlags::Initialize)) {
|
||||
job.input.init_params = AjmDecAt9InitializeParameters{};
|
||||
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
|
||||
input_batch.BytesRemaining());
|
||||
job.input.init_params = input_batch.Consume<AjmSidebandInitParameters>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ namespace Libraries::Ajm {
|
||||
|
||||
struct AjmJob {
|
||||
struct Input {
|
||||
std::optional<AjmDecAt9InitializeParameters> init_params;
|
||||
std::optional<AjmSidebandInitParameters> init_params;
|
||||
std::optional<AjmSidebandResampleParameters> resample_parameters;
|
||||
std::optional<AjmSidebandStatisticsEngineParameters> statistics_engine_parameters;
|
||||
std::optional<AjmSidebandFormat> format;
|
||||
@ -52,6 +52,7 @@ struct AjmBatch {
|
||||
u32 id{};
|
||||
std::atomic_bool waiting{};
|
||||
std::atomic_bool canceled{};
|
||||
std::atomic_bool processed{};
|
||||
std::binary_semaphore finished{0};
|
||||
boost::container::small_vector<AjmJob, 16> jobs;
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr int INSTANCE_ID_MASK = 0x3FFF;
|
||||
|
||||
AjmContext::AjmContext() {
|
||||
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
|
||||
@ -39,7 +40,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
|
||||
batch = *p_batch;
|
||||
}
|
||||
|
||||
batch->canceled = true;
|
||||
if (batch->processed) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool expected = false;
|
||||
batch->canceled.compare_exchange_strong(expected, true);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -58,7 +64,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
|
||||
Common::SetCurrentThreadName("shadPS4:AjmWorker");
|
||||
while (!stop.stop_requested()) {
|
||||
auto batch = batch_queue.PopWait(stop);
|
||||
if (batch != nullptr) {
|
||||
if (batch != nullptr && !batch->canceled) {
|
||||
bool expected = false;
|
||||
batch->processed.compare_exchange_strong(expected, true);
|
||||
ProcessBatch(batch->id, batch->jobs);
|
||||
batch->finished.release();
|
||||
}
|
||||
@ -77,7 +85,7 @@ void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
||||
std::shared_ptr<AjmInstance> instance;
|
||||
{
|
||||
std::shared_lock lock(instances_mutex);
|
||||
auto* p_instance = instances.Get(job.instance_id);
|
||||
auto* p_instance = instances.Get(job.instance_id & INSTANCE_ID_MASK);
|
||||
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
|
||||
instance = *p_instance;
|
||||
}
|
||||
@ -169,15 +177,15 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
if (!opt_index.has_value()) {
|
||||
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
||||
}
|
||||
*out_instance = opt_index.value();
|
||||
*out_instance = opt_index.value() | (static_cast<u32>(codec_type) << 14);
|
||||
|
||||
LOG_INFO(Lib_Ajm, "instance = {}", *out_instance);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AjmContext::InstanceDestroy(u32 instance) {
|
||||
s32 AjmContext::InstanceDestroy(u32 instance_id) {
|
||||
std::unique_lock lock(instances_mutex);
|
||||
if (!instances.Destroy(instance)) {
|
||||
if (!instances.Destroy(instance_id & INSTANCE_ID_MASK)) {
|
||||
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -1,27 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_at9.h"
|
||||
#include "ajm_instance.h"
|
||||
#include "ajm_mp3.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
|
||||
u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
switch (format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
@ -38,13 +27,18 @@ u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||
switch (codec_type) {
|
||||
case AjmCodecType::At9Dec: {
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmAt9CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::Mp3Dec: {
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmMp3CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::M4aacDec: {
|
||||
m_codec = std::make_unique<AjmAacDecoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAacCodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -60,6 +54,7 @@ void AjmInstance::Reset() {
|
||||
|
||||
void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
const auto control_flags = job.flags.control_flags;
|
||||
job.output.p_result->result = 0;
|
||||
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
||||
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
||||
Reset();
|
||||
@ -91,8 +86,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.total_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,61 +100,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.skip_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
u32 frames_decoded = 0;
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
|
||||
out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto [nframes, nsamples, reset] =
|
||||
m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (reset) {
|
||||
if (!job.input.buffer.empty()) {
|
||||
for (;;) {
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
m_gapless.Reset();
|
||||
m_total_samples = 0;
|
||||
}
|
||||
if (!nframes) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
|
||||
m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
}
|
||||
if (in_buf.size() < m_codec->GetMinimumInputSize()) {
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
if (job.output.p_result->result != 0) {
|
||||
break;
|
||||
}
|
||||
const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (result.is_reset) {
|
||||
m_total_samples = 0;
|
||||
} else {
|
||||
m_total_samples += result.samples_written;
|
||||
}
|
||||
frames_decoded += result.frames_decoded;
|
||||
if (result.result != 0) {
|
||||
job.output.p_result->result |= result.result;
|
||||
job.output.p_result->internal_result = result.internal_result;
|
||||
break;
|
||||
}
|
||||
frames_decoded += nframes;
|
||||
m_total_samples += nsamples;
|
||||
|
||||
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto total_decoded_samples = m_total_samples;
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
in_buf = in_buf.subspan(in_buf.size());
|
||||
m_gapless.Reset();
|
||||
m_codec->Reset();
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = total_decoded_samples;
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = m_total_samples;
|
||||
}
|
||||
|
||||
if (job.output.p_format != nullptr) {
|
||||
@ -175,6 +167,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
}
|
||||
|
||||
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
|
||||
if (m_gapless.IsEnd()) {
|
||||
return true;
|
||||
}
|
||||
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,14 @@ struct AjmInstanceGapless {
|
||||
}
|
||||
};
|
||||
|
||||
struct DecoderResult {
|
||||
s32 result = 0;
|
||||
s32 internal_result = 0;
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_written = 0;
|
||||
bool is_reset = false;
|
||||
};
|
||||
|
||||
class AjmCodec {
|
||||
public:
|
||||
virtual ~AjmCodec() = default;
|
||||
@ -81,9 +89,10 @@ public:
|
||||
virtual void Reset() = 0;
|
||||
virtual void GetInfo(void* out_info) const = 0;
|
||||
virtual AjmSidebandFormat GetFormat() const = 0;
|
||||
virtual u32 GetMinimumInputSize() const = 0;
|
||||
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
|
||||
virtual std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
virtual DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
};
|
||||
|
||||
class AjmInstance {
|
||||
@ -94,7 +103,6 @@ public:
|
||||
|
||||
private:
|
||||
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
|
||||
std::optional<u32> GetNumRemainingSamples() const;
|
||||
void Reset();
|
||||
|
||||
AjmInstanceFlags m_flags{};
|
||||
|
||||
@ -8,7 +8,7 @@ namespace Libraries::Ajm {
|
||||
|
||||
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
if (job.output.p_engine) {
|
||||
job.output.p_engine->usage_batch = 0.01;
|
||||
job.output.p_engine->usage_batch = 0.05;
|
||||
const auto ic = job.input.statistics_engine_parameters->interval_count;
|
||||
for (u32 idx = 0; idx < ic; ++idx) {
|
||||
job.output.p_engine->usage_interval[idx] = 0.01;
|
||||
@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
job.output.p_memory->batch_size = 0x4200;
|
||||
job.output.p_memory->input_size = 0x2000;
|
||||
job.output.p_memory->output_size = 0x2000;
|
||||
job.output.p_memory->small_size = 0x200;
|
||||
job.output.p_memory->small_size = 0x400;
|
||||
}
|
||||
}
|
||||
|
||||
void AjmInstanceStatistics::Reset() {}
|
||||
|
||||
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
|
||||
static AjmInstanceStatistics instance;
|
||||
return instance;
|
||||
|
||||
@ -10,6 +10,7 @@ namespace Libraries::Ajm {
|
||||
class AjmInstanceStatistics {
|
||||
public:
|
||||
void ExecuteJob(AjmJob& job);
|
||||
void Reset();
|
||||
|
||||
static AjmInstanceStatistics& Getinstance();
|
||||
};
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm_error.h"
|
||||
#include "ajm_mp3.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_error.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
extern "C" {
|
||||
@ -105,7 +107,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||
m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
|
||||
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
|
||||
@ -138,16 +140,31 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
u32 AjmMp3Decoder::GetMinimumInputSize() const {
|
||||
// 4 bytes is for mp3 header that contains frame_size
|
||||
return 4;
|
||||
}
|
||||
|
||||
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
|
||||
AjmDecMp3ParseFrame info{};
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
|
||||
m_frame_samples = info.samples_per_channel;
|
||||
m_header = std::byteswap(*reinterpret_cast<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),
|
||||
.skipped_samples = 0,
|
||||
};
|
||||
gapless.current = gapless.init;
|
||||
}
|
||||
|
||||
if (in_buf.size() < info.frame_size) {
|
||||
result.result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
|
||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||
@ -155,9 +172,6 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
in_buf = in_buf.subspan(ret);
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_decoded = 0;
|
||||
|
||||
if (pkt->size) {
|
||||
// Send the packet with the compressed data to the decoder
|
||||
pkt->pts = m_parser->pts;
|
||||
@ -177,9 +191,8 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE_MSG("Error during decoding");
|
||||
}
|
||||
frame = ConvertAudioFrame(frame);
|
||||
samples_decoded += u32(frame->nb_samples);
|
||||
|
||||
frames_decoded += 1;
|
||||
result.frames_decoded += 1;
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
|
||||
@ -211,6 +224,7 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples;
|
||||
}
|
||||
result.samples_written += samples;
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
@ -218,16 +232,16 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return {frames_decoded, samples_decoded, false};
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto max_samples = gapless.init.total_samples != 0
|
||||
? std::min(gapless.current.total_samples, m_frame_samples)
|
||||
: m_frame_samples;
|
||||
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
|
||||
return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
|
||||
GetPCMSize(m_format);
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, m_frame_samples);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_frame_samples - skip_samples)
|
||||
: m_frame_samples - skip_samples;
|
||||
return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
class BitReader {
|
||||
@ -264,7 +278,7 @@ private:
|
||||
|
||||
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame) {
|
||||
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
|
||||
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
@ -301,7 +315,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
|
||||
|
||||
BitReader reader(p_current);
|
||||
if (header->protection_type == 0) {
|
||||
reader.Skip(16); // crc = reader.Read<u16>(16);
|
||||
// crc = reader.Read<u16>(16);
|
||||
reader.Skip(16);
|
||||
}
|
||||
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
@ -413,11 +428,7 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
|
||||
frame->encoder_delay = std::byteswap(*reinterpret_cast<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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@ -63,16 +64,17 @@ struct AjmSidebandDecMp3CodecInfo {
|
||||
|
||||
class AjmMp3Decoder : public AjmCodec {
|
||||
public:
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32 channels);
|
||||
~AjmMp3Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
|
||||
17
src/core/libraries/ajm/ajm_result.h
Normal file
17
src/core/libraries/ajm/ajm_result.h
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
|
||||
return AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
|
||||
const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
LOG_INFO(Lib_Audio3d,
|
||||
"called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}",
|
||||
port_id, user_id, type, index, len, freq);
|
||||
@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
|
||||
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) {
|
||||
LOG_DEBUG(Lib_Audio3d, "called");
|
||||
if (params) {
|
||||
*params = OrbisAudio3dOpenParameters{
|
||||
auto default_params = OrbisAudio3dOpenParameters{
|
||||
.size_this = 0x20,
|
||||
.granularity = 0x100,
|
||||
.rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000,
|
||||
@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters*
|
||||
.queue_depth = 2,
|
||||
.buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH,
|
||||
};
|
||||
memcpy(params, &default_params, 0x20);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -421,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id,
|
||||
@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
}
|
||||
|
||||
*port_id = id;
|
||||
std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters));
|
||||
std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -48,7 +48,7 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
title.resize(title_len * 4 + 1);
|
||||
title[title_len * 4] = '\0';
|
||||
|
||||
if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) {
|
||||
if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding");
|
||||
}
|
||||
}
|
||||
@ -59,14 +59,14 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
placeholder[placeholder_len * 4] = '\0';
|
||||
|
||||
if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0],
|
||||
placeholder_len * 4)) {
|
||||
placeholder_len * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding");
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t text_len = std::char_traits<char16_t>::length(text_buffer);
|
||||
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) {
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ bool ImeDialogState::CopyTextToOrbisBuffer() {
|
||||
}
|
||||
|
||||
return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer,
|
||||
max_text_length);
|
||||
static_cast<std::size_t>(max_text_length) + 1);
|
||||
}
|
||||
|
||||
bool ImeDialogState::CallTextFilter() {
|
||||
@ -380,10 +380,12 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||
.timestamp = {0},
|
||||
};
|
||||
|
||||
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) {
|
||||
char16_t tmp_char[2] = {0};
|
||||
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, tmp_char, 2)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed");
|
||||
return 0;
|
||||
}
|
||||
src_keycode.character = tmp_char[0];
|
||||
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}",
|
||||
static_cast<uint16_t>(src_keycode.character));
|
||||
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value
|
||||
|
||||
@ -34,9 +34,11 @@
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace D = Core::Devices;
|
||||
namespace fs = std::filesystem;
|
||||
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
|
||||
|
||||
#define GET_DEVICE_FD(fd) \
|
||||
@ -74,6 +76,7 @@ namespace Libraries::Kernel {
|
||||
|
||||
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode);
|
||||
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
@ -87,6 +90,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(raw_path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
|
||||
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
|
||||
// Flags fsync and sync behave the same
|
||||
@ -121,7 +129,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
bool read_only = false;
|
||||
file->m_guest_name = path;
|
||||
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
|
||||
bool exists = std::filesystem::exists(file->m_host_name);
|
||||
bool exists = fs::exists(file->m_host_name);
|
||||
s32 e = 0;
|
||||
|
||||
if (create) {
|
||||
@ -149,14 +157,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(file->m_host_name) || directory) {
|
||||
if (fs::is_directory(file->m_host_name) || directory) {
|
||||
// Directories can be opened even if the directory flag isn't set.
|
||||
// In these cases, error behavior is identical to the directory code path.
|
||||
directory = true;
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
if (!std::filesystem::is_directory(file->m_host_name)) {
|
||||
if (!fs::is_directory(file->m_host_name)) {
|
||||
// If the opened file is not a directory, return ENOTDIR.
|
||||
// This will trigger when create & directory is specified, this is expected.
|
||||
h->DeleteHandle(handle);
|
||||
@ -554,6 +562,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) {
|
||||
|
||||
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode);
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (path == nullptr) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
@ -563,7 +575,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
bool ro = false;
|
||||
const auto dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (std::filesystem::exists(dir_name)) {
|
||||
if (fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_EEXIST;
|
||||
return -1;
|
||||
}
|
||||
@ -575,12 +587,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
|
||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||
std::error_code ec;
|
||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
|
||||
if (dir_name.empty() || !fs::create_directory(dir_name, ec)) {
|
||||
*__Error() = POSIX_EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dir_name)) {
|
||||
if (!fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
@ -597,28 +609,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
|
||||
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
const fs::path dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (ro) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
|
||||
if (dir_name.empty() || !fs::is_directory(dir_name)) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dir_name)) {
|
||||
if (!fs::exists(dir_name)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
s32 result = std::filesystem::remove_all(dir_name, ec);
|
||||
s32 result = fs::remove_all(dir_name, ec);
|
||||
|
||||
if (ec) {
|
||||
*__Error() = POSIX_EIO;
|
||||
@ -638,11 +654,15 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
|
||||
|
||||
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
||||
LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto path_name = mnt->GetHostPath(path);
|
||||
std::memset(sb, 0, sizeof(OrbisKernelStat));
|
||||
const bool is_dir = std::filesystem::is_directory(path_name);
|
||||
const bool is_file = std::filesystem::is_regular_file(path_name);
|
||||
const bool is_dir = fs::is_directory(path_name);
|
||||
const bool is_file = fs::is_regular_file(path_name);
|
||||
if (!is_dir && !is_file) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
@ -650,12 +670,12 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
||||
|
||||
// get the difference between file clock and system clock
|
||||
const auto now_sys = std::chrono::system_clock::now();
|
||||
const auto now_file = std::filesystem::file_time_type::clock::now();
|
||||
const auto now_file = fs::file_time_type::clock::now();
|
||||
// calculate the file modified time
|
||||
const auto mtime = std::filesystem::last_write_time(path_name);
|
||||
const auto mtime = fs::last_write_time(path_name);
|
||||
const auto mtimestamp = now_sys + (mtime - now_file);
|
||||
|
||||
if (std::filesystem::is_directory(path_name)) {
|
||||
if (fs::is_directory(path_name)) {
|
||||
sb->st_mode = 0000777u | 0040000u;
|
||||
sb->st_size = 65536;
|
||||
sb->st_blksize = 65536;
|
||||
@ -665,7 +685,7 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
|
||||
// TODO incomplete
|
||||
} else {
|
||||
sb->st_mode = 0000777u | 0100000u;
|
||||
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
|
||||
sb->st_size = static_cast<s64>(fs::file_size(path_name));
|
||||
sb->st_blksize = 512;
|
||||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
sb->st_mtim.tv_sec =
|
||||
@ -686,6 +706,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
std::string_view guest_path{path};
|
||||
for (const auto& prefix : available_device | std::views::keys) {
|
||||
@ -694,7 +718,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
|
||||
}
|
||||
}
|
||||
const auto path_name = mnt->GetHostPath(guest_path);
|
||||
if (!std::filesystem::exists(path_name)) {
|
||||
if (!fs::exists(path_name)) {
|
||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
@ -728,6 +752,30 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
|
||||
sb->st_size = file->f.GetSize();
|
||||
sb->st_blksize = 512;
|
||||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
struct stat filestat = {};
|
||||
stat(file->f.GetPath().c_str(), &filestat);
|
||||
sb->st_atim = *reinterpret_cast<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;
|
||||
}
|
||||
@ -807,7 +855,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
bool ro = false;
|
||||
const auto src_path = mnt->GetHostPath(from, &ro);
|
||||
if (!std::filesystem::exists(src_path)) {
|
||||
if (strlen(from) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (strlen(to) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (!fs::exists(src_path)) {
|
||||
*__Error() = POSIX_ENOENT;
|
||||
return -1;
|
||||
}
|
||||
@ -820,32 +876,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
|
||||
*__Error() = POSIX_EROFS;
|
||||
return -1;
|
||||
}
|
||||
const bool src_is_dir = std::filesystem::is_directory(src_path);
|
||||
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (!src_is_dir && dst_is_dir) {
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
}
|
||||
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
|
||||
*__Error() = POSIX_ENOTEMPTY;
|
||||
return -1;
|
||||
const bool src_is_dir = fs::is_directory(src_path);
|
||||
const bool dst_is_dir = fs::is_directory(dst_path);
|
||||
|
||||
if (fs::exists(dst_path)) {
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
*__Error() = POSIX_ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
if (!src_is_dir && dst_is_dir) {
|
||||
*__Error() = POSIX_EISDIR;
|
||||
return -1;
|
||||
}
|
||||
if (dst_is_dir && !fs::is_empty(dst_path)) {
|
||||
*__Error() = POSIX_ENOTEMPTY;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows, std::filesystem::rename will error if the file has been opened before.
|
||||
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
|
||||
// On Windows, fs::rename will error if the file has been opened before.
|
||||
fs::copy(src_path, dst_path,
|
||||
fs::copy_options::overwrite_existing | fs::copy_options::recursive);
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto file = h->GetFile(src_path);
|
||||
if (file) {
|
||||
auto access_mode = file->f.GetAccessMode();
|
||||
file->f.Close();
|
||||
std::filesystem::remove(src_path);
|
||||
fs::remove(src_path);
|
||||
file->f.Open(dst_path, access_mode);
|
||||
} else {
|
||||
std::filesystem::remove(src_path);
|
||||
fs::remove_all(src_path);
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
@ -1098,6 +1158,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
|
||||
if (strlen(path) > 255) {
|
||||
*__Error() = POSIX_ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
if (path == nullptr) {
|
||||
*__Error() = POSIX_EINVAL;
|
||||
return -1;
|
||||
@ -1118,7 +1182,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(host_path)) {
|
||||
if (fs::is_directory(host_path)) {
|
||||
*__Error() = POSIX_EPERM;
|
||||
return -1;
|
||||
}
|
||||
@ -1223,7 +1287,8 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri
|
||||
if (file->type == Core::FileSys::FileType::Regular ||
|
||||
file->type == Core::FileSys::FileType::Device) {
|
||||
// Disk files always ready
|
||||
if (want_read) {
|
||||
// For devices, stdin (fd 0) is never read-ready.
|
||||
if (want_read && i != 0) {
|
||||
FD_SET_POSIX(i, &read_ready);
|
||||
}
|
||||
if (want_write) {
|
||||
@ -1491,6 +1556,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev);
|
||||
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite);
|
||||
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev);
|
||||
LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink);
|
||||
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink);
|
||||
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select);
|
||||
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select);
|
||||
|
||||
@ -5,24 +5,35 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/kernel/kernel.h"
|
||||
#include "core/libraries/kernel/memory.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
static s32 g_sdk_version = -1;
|
||||
static bool g_alias_dmem = false;
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
|
||||
LOG_TRACE(Kernel_Vmm, "called");
|
||||
const auto* memory = Core::Memory::Instance();
|
||||
return memory->GetTotalDirectSize();
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelEnableDmemAliasing() {
|
||||
LOG_DEBUG(Kernel_Vmm, "called");
|
||||
g_alias_dmem = true;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
|
||||
u64 alignment, s32 memoryType, s64* physAddrOut) {
|
||||
if (searchStart < 0 || searchEnd < 0) {
|
||||
@ -78,21 +89,31 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 m
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
return ORBIS_OK;
|
||||
return memory->Free(start, len, true);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
memory->Free(start, len, false);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -197,8 +218,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
|
||||
Core::VMAType::Direct, name, false, phys_addr, alignment);
|
||||
bool should_check = false;
|
||||
if (g_sdk_version >= Common::ElfInfo::FW_25 && False(map_flags & Core::MemoryMapFlags::Stack)) {
|
||||
// Under these conditions, this would normally redirect to sceKernelMapDirectMemory2.
|
||||
should_check = !g_alias_dmem;
|
||||
}
|
||||
const auto ret =
|
||||
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, name,
|
||||
should_check, phys_addr, alignment);
|
||||
|
||||
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr));
|
||||
return ret;
|
||||
@ -244,8 +271,9 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
|
||||
const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
const auto ret = memory->MapMemory(addr, in_addr, len, mem_prot, map_flags,
|
||||
Core::VMAType::Direct, "anon", true, phys_addr, alignment);
|
||||
const auto ret =
|
||||
memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "anon",
|
||||
!g_alias_dmem, phys_addr, alignment);
|
||||
|
||||
if (ret == 0) {
|
||||
// If the map call succeeds, set the direct memory type using the output address.
|
||||
@ -668,10 +696,21 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd,
|
||||
}
|
||||
|
||||
s32 result = ORBIS_OK;
|
||||
if (fd == -1) {
|
||||
if (True(mem_flags & Core::MemoryMapFlags::Anon)) {
|
||||
// Maps flexible memory
|
||||
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
|
||||
Core::VMAType::Flexible, "anon", false);
|
||||
} else if (True(mem_flags & Core::MemoryMapFlags::Stack)) {
|
||||
// Maps stack memory
|
||||
result = memory->MapMemory(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags,
|
||||
Core::VMAType::Stack, "anon", false);
|
||||
} else if (True(mem_flags & Core::MemoryMapFlags::Void)) {
|
||||
// Reserves memory
|
||||
result =
|
||||
memory->MapMemory(&addr_out, aligned_addr, aligned_size, Core::MemoryProt::NoAccess,
|
||||
mem_flags, Core::VMAType::Reserved, "anon", false);
|
||||
} else {
|
||||
// Default to file mapping
|
||||
result = memory->MapFile(&addr_out, aligned_addr, aligned_size, mem_prot, mem_flags, fd,
|
||||
phys_addr);
|
||||
}
|
||||
@ -769,6 +808,12 @@ s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) {
|
||||
}
|
||||
|
||||
void RegisterMemory(Core::Loader::SymbolsResolver* sym) {
|
||||
ASSERT_MSG(sceKernelGetCompiledSdkVersion(&g_sdk_version) == ORBIS_OK,
|
||||
"Failed to get compiled SDK verision.");
|
||||
|
||||
LIB_FUNCTION("usHTMoFoBTM", "libkernel_dmem_aliasing2", 1, "libkernel",
|
||||
sceKernelEnableDmemAliasing);
|
||||
LIB_FUNCTION("usHTMoFoBTM", "libkernel", 1, "libkernel", sceKernelEnableDmemAliasing);
|
||||
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", sceKernelAllocateDirectMemory);
|
||||
LIB_FUNCTION("B+vc2AO2Zrc", "libkernel", 1, "libkernel", sceKernelAllocateMainDirectMemory);
|
||||
LIB_FUNCTION("C0f7TJcbfac", "libkernel", 1, "libkernel", sceKernelAvailableDirectMemorySize);
|
||||
|
||||
@ -36,9 +36,11 @@ s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
|
||||
s32 version = Common::ElfInfo::Instance().CompiledSdkVer();
|
||||
*ver = version;
|
||||
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||
if (!ver) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
*ver = Common::ElfInfo::Instance().CompiledSdkVer();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCpumode() {
|
||||
|
||||
@ -173,6 +173,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", sceKernelDebugRaiseException);
|
||||
LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel",
|
||||
sceKernelDebugRaiseExceptionOnReleaseMode);
|
||||
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
|
||||
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -378,7 +378,8 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
|
||||
if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) {
|
||||
if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck ||
|
||||
type >= PthreadMutexType::Max) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
(*attr)->m_type = type;
|
||||
|
||||
@ -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,
|
||||
|
||||
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,19 +35,23 @@
|
||||
#include "core/libraries/np/np_commerce.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_matching2.h"
|
||||
#include "core/libraries/np/np_partner.h"
|
||||
#include "core/libraries/np/np_party.h"
|
||||
#include "core/libraries/np/np_profile_dialog.h"
|
||||
#include "core/libraries/np/np_score.h"
|
||||
#include "core/libraries/np/np_sns_facebook_dialog.h"
|
||||
#include "core/libraries/np/np_trophy.h"
|
||||
#include "core/libraries/np/np_tus.h"
|
||||
#include "core/libraries/np/np_web_api.h"
|
||||
#include "core/libraries/np/np_web_api2.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/playgo/playgo.h"
|
||||
#include "core/libraries/playgo/playgo_dialog.h"
|
||||
#include "core/libraries/random/random.h"
|
||||
#include "core/libraries/razor_cpu/razor_cpu.h"
|
||||
#include "core/libraries/remote_play/remoteplay.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/rudp/rudp.h"
|
||||
#include "core/libraries/save_data/dialog/savedatadialog.h"
|
||||
#include "core/libraries/save_data/savedata.h"
|
||||
#include "core/libraries/screenshot/screenshot.h"
|
||||
@ -70,7 +74,6 @@
|
||||
#include "core/libraries/web_browser_dialog/webbrowserdialog.h"
|
||||
#include "core/libraries/zlib/zlib_sce.h"
|
||||
#include "fiber/fiber.h"
|
||||
#include "jpeg/jpegenc.h"
|
||||
|
||||
namespace Libraries {
|
||||
|
||||
@ -97,13 +100,17 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::Np::NpCommerce::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpMatching2::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
Libraries::Np::NpTrophy::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi::RegisterLib(sym);
|
||||
Libraries::Np::NpWebApi2::RegisterLib(sym);
|
||||
Libraries::Np::NpProfileDialog::RegisterLib(sym);
|
||||
Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym);
|
||||
Libraries::Np::NpAuth::RegisterLib(sym);
|
||||
Libraries::Np::NpParty::RegisterLib(sym);
|
||||
Libraries::Np::NpPartner::RegisterLib(sym);
|
||||
Libraries::Np::NpTus::RegisterLib(sym);
|
||||
Libraries::ScreenShot::RegisterLib(sym);
|
||||
Libraries::AppContent::RegisterLib(sym);
|
||||
Libraries::PngDec::RegisterLib(sym);
|
||||
@ -117,17 +124,16 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::ErrorDialog::RegisterLib(sym);
|
||||
Libraries::ImeDialog::RegisterLib(sym);
|
||||
Libraries::AvPlayer::RegisterLib(sym);
|
||||
Libraries::Vdec2::RegisterLib(sym);
|
||||
Libraries::Videodec::RegisterLib(sym);
|
||||
Libraries::Videodec2::RegisterLib(sym);
|
||||
Libraries::Audio3d::RegisterLib(sym);
|
||||
Libraries::Ime::RegisterLib(sym);
|
||||
Libraries::GameLiveStreaming::RegisterLib(sym);
|
||||
Libraries::SharePlay::RegisterLib(sym);
|
||||
Libraries::Remoteplay::RegisterLib(sym);
|
||||
Libraries::Videodec::RegisterLib(sym);
|
||||
Libraries::RazorCpu::RegisterLib(sym);
|
||||
Libraries::Move::RegisterLib(sym);
|
||||
Libraries::Fiber::RegisterLib(sym);
|
||||
Libraries::JpegEnc::RegisterLib(sym);
|
||||
Libraries::Mouse::RegisterLib(sym);
|
||||
Libraries::WebBrowserDialog::RegisterLib(sym);
|
||||
Libraries::Zlib::RegisterLib(sym);
|
||||
@ -140,7 +146,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::CompanionHttpd::RegisterLib(sym);
|
||||
Libraries::CompanionUtil::RegisterLib(sym);
|
||||
Libraries::Voice::RegisterLib(sym);
|
||||
Libraries::Rtc::RegisterLib(sym);
|
||||
Libraries::Rudp::RegisterLib(sym);
|
||||
Libraries::VrTracker::RegisterLib(sym);
|
||||
|
||||
// Loading libSceSsl is locked behind a title workaround that currently applies to nothing.
|
||||
|
||||
@ -655,10 +655,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
|
||||
|
||||
switch (file->type) {
|
||||
case Core::FileSys::FileType::Socket: {
|
||||
auto native_handle = file->socket->Native();
|
||||
if (!native_handle) {
|
||||
// P2P socket, cannot be added to epoll
|
||||
LOG_ERROR(Lib_Net, "P2P socket cannot be added to epoll (unimplemented)");
|
||||
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
|
||||
return ORBIS_NET_ERROR_EBADF;
|
||||
}
|
||||
|
||||
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
|
||||
.data = {.fd = id}};
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *file->socket->Native(),
|
||||
&native_event) == 0);
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *native_handle, &native_event) == 0);
|
||||
epoll->events.emplace_back(id, *event);
|
||||
break;
|
||||
}
|
||||
@ -696,10 +703,17 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
|
||||
|
||||
switch (file->type) {
|
||||
case Core::FileSys::FileType::Socket: {
|
||||
auto native_handle = file->socket->Native();
|
||||
if (!native_handle) {
|
||||
// P2P socket, cannot be modified in epoll
|
||||
LOG_ERROR(Lib_Net, "P2P socket cannot be modified in epoll (unimplemented)");
|
||||
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
|
||||
return ORBIS_NET_ERROR_EBADF;
|
||||
}
|
||||
|
||||
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
|
||||
.data = {.fd = id}};
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *file->socket->Native(),
|
||||
&native_event) == 0);
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *native_handle, &native_event) == 0);
|
||||
*it = {id, *event};
|
||||
break;
|
||||
}
|
||||
@ -731,8 +745,15 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
|
||||
|
||||
switch (file->type) {
|
||||
case Core::FileSys::FileType::Socket: {
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *file->socket->Native(), nullptr) ==
|
||||
0);
|
||||
auto native_handle = file->socket->Native();
|
||||
if (!native_handle) {
|
||||
// P2P socket, cannot be removed from epoll
|
||||
LOG_ERROR(Lib_Net, "P2P socket cannot be removed from epoll (unimplemented)");
|
||||
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
|
||||
return ORBIS_NET_ERROR_EBADF;
|
||||
}
|
||||
|
||||
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *native_handle, nullptr) == 0);
|
||||
epoll->events.erase(it);
|
||||
break;
|
||||
}
|
||||
@ -782,6 +803,7 @@ int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid) {
|
||||
LOG_DEBUG(Lib_Net, "called, epollid = {} ({})", epollid, file->epoll->name);
|
||||
|
||||
file->epoll->Destroy();
|
||||
FDTable::Instance()->DeleteHandle(epollid);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -1357,7 +1379,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
|
||||
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
|
||||
const char* safe_name = name ? name : "";
|
||||
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags);
|
||||
|
||||
if (flags != 0) {
|
||||
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
|
||||
@ -1368,8 +1391,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
|
||||
auto* resolver = FDTable::Instance()->GetFile(fd);
|
||||
resolver->is_opened = true;
|
||||
resolver->type = Core::FileSys::FileType::Resolver;
|
||||
resolver->resolver = std::make_shared<Resolver>(name, poolid, flags);
|
||||
resolver->m_guest_name = name;
|
||||
resolver->resolver = std::make_shared<Resolver>(safe_name, poolid, flags);
|
||||
resolver->m_guest_name = safe_name;
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -110,6 +110,15 @@ enum OrbisNetSocketSoOption : u32 {
|
||||
ORBIS_NET_SO_PRIORITY = 0x1203
|
||||
};
|
||||
|
||||
enum OrbisNetFlags : u32 {
|
||||
ORBIS_NET_MSG_PEEK = 0x00000002,
|
||||
ORBIS_NET_MSG_WAITALL = 0x00000040,
|
||||
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
|
||||
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
|
||||
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
|
||||
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
|
||||
switch (o) {
|
||||
case ORBIS_NET_SO_REUSEADDR:
|
||||
|
||||
@ -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 <vector>
|
||||
@ -184,28 +184,104 @@ int PosixSocket::Listen(int backlog) {
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
|
||||
int posix_flags = 0;
|
||||
|
||||
if (sce_flags & ORBIS_NET_MSG_PEEK)
|
||||
posix_flags |= MSG_PEEK;
|
||||
#ifndef _WIN32
|
||||
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
|
||||
posix_flags |= MSG_DONTWAIT;
|
||||
#endif
|
||||
// MSG_WAITALL is only valid for stream sockets
|
||||
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
|
||||
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
|
||||
posix_flags |= MSG_WAITALL;
|
||||
|
||||
return posix_flags;
|
||||
}
|
||||
|
||||
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
|
||||
// This function uses select() with zero timeout to simulate non-blocking behavior.
|
||||
static int socket_is_ready(int sock, bool is_read = true) {
|
||||
fd_set fds{};
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
timeval timeout{0, 0};
|
||||
int res =
|
||||
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
|
||||
if (res == 0) {
|
||||
*Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK;
|
||||
return -1;
|
||||
} else if (res < 0) {
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD bytesSent = 0;
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
int totalSent = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
|
||||
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsamsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, false);
|
||||
if (ready <= 0)
|
||||
return ready;
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
if (use_wsamsg) {
|
||||
// only call WSASendMsg if we have multiple buffers
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to send()
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
} else {
|
||||
DWORD bytesSent = 0;
|
||||
int res = wsasendmsg(
|
||||
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
sent = bytesSent;
|
||||
}
|
||||
} else {
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
if (sent == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalSent += sent;
|
||||
remaining -= sent;
|
||||
buf += sent;
|
||||
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
return totalSent;
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesSent);
|
||||
#else
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
@ -213,37 +289,92 @@ int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (to != nullptr) {
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
return ConvertReturnErrorCode(
|
||||
sendto(sock, (const char*)msg, len, flags, &addr, sizeof(sockaddr_in)));
|
||||
} else {
|
||||
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock, false);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (to == nullptr) {
|
||||
res = send(sock, (const char*)msg, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
int totalReceived = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
|
||||
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsarecvmsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
// emulate DONTWAIT
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, true);
|
||||
if (ready <= 0)
|
||||
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
|
||||
}
|
||||
|
||||
int received = 0;
|
||||
if (use_wsarecvmsg) {
|
||||
// only call WSARecvMsg if multiple buffers + stream
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to recv()
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
} else {
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
|
||||
nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
received = bytesReceived;
|
||||
}
|
||||
} else {
|
||||
// fallback to recv() for UDP or single-buffer
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalReceived += received;
|
||||
remaining -= received;
|
||||
buf += received;
|
||||
|
||||
// stop after first receive if WAITALL is not set
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
|
||||
return totalReceived;
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesReceived);
|
||||
#else
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
@ -251,15 +382,27 @@ int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
if (from != nullptr) {
|
||||
sockaddr addr;
|
||||
int res = recvfrom(sock, (char*)buf, len, flags, &addr, (socklen_t*)fromlen);
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
*fromlen = sizeof(OrbisNetSockaddrIn);
|
||||
return ConvertReturnErrorCode(res);
|
||||
} else {
|
||||
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (from == nullptr) {
|
||||
res = recv(sock, (char*)buf, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
|
||||
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
|
||||
if (res > 0)
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
}
|
||||
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -62,7 +62,7 @@ struct OrbisNetLinger {
|
||||
s32 l_linger;
|
||||
};
|
||||
struct Socket {
|
||||
explicit Socket(int domain, int type, int protocol) {}
|
||||
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
|
||||
virtual ~Socket() = default;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual int Close() = 0;
|
||||
@ -84,6 +84,7 @@ struct Socket {
|
||||
virtual std::optional<net_socket> Native() = 0;
|
||||
std::mutex m_mutex;
|
||||
std::mutex receive_mutex;
|
||||
int socket_type;
|
||||
};
|
||||
|
||||
struct PosixSocket : public Socket {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user