diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh
index 7961f5312..d85aa6c4c 100755
--- a/.github/linux-appimage-sdl.sh
+++ b/.github/linux-appimage-sdl.sh
@@ -8,8 +8,8 @@ if [[ -z $GITHUB_WORKSPACE ]]; then
fi
# Prepare Tools for building the AppImage
-wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
-wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
+wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh
chmod a+x linuxdeploy-x86_64.AppImage
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ffe7c22fb..3d77c5800 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -26,14 +26,14 @@ jobs:
runs-on: ubuntu-24.04
continue-on-error: true
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: fsfe/reuse-action@v5
clang-format:
runs-on: ubuntu-24.04
continue-on-error: true
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install
@@ -46,7 +46,7 @@ jobs:
env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
run: ./.ci/clang-format.sh
-
+
get-info:
runs-on: ubuntu-24.04
outputs:
@@ -54,7 +54,7 @@ jobs:
shorthash: ${{ steps.vars.outputs.shorthash }}
fullhash: ${{ steps.vars.outputs.fullhash }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Get date and git hash
id: vars
run: |
@@ -69,23 +69,23 @@ jobs:
runs-on: windows-2025
needs: get-info
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
- name: Cache CMake Configuration
- uses: actions/cache@v4
+ uses: actions/cache@v5
env:
cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration
with:
- path: |
+ path: |
${{github.workspace}}/build
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
restore-keys: |
${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.19
+ uses: hendrikmuhs/ccache-action@v1.2.21
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
@@ -99,7 +99,7 @@ jobs:
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $env:NUMBER_OF_PROCESSORS
- name: Upload Windows SDL artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: ${{github.workspace}}/build/shadPS4.exe
@@ -108,7 +108,7 @@ jobs:
runs-on: macos-15
needs: get-info
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
@@ -118,18 +118,18 @@ jobs:
xcode-version: latest
- name: Cache CMake Configuration
- uses: actions/cache@v4
- env:
+ uses: actions/cache@v5
+ env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
- with:
- path: |
- ${{github.workspace}}/build
- key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- restore-keys: |
- ${{ env.cache-name }}-
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.19
+ uses: hendrikmuhs/ccache-action@v1.2.21
env:
cache-name: ${{runner.os}}-sdl-cache-cmake-build
with:
@@ -150,7 +150,7 @@ jobs:
mv ${{github.workspace}}/build/shadps4 upload
mv ${{github.workspace}}/build/MoltenVK_icd.json upload
mv ${{github.workspace}}/build/libMoltenVK.dylib upload
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v6
with:
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: upload/
@@ -159,7 +159,7 @@ jobs:
runs-on: ubuntu-24.04
needs: get-info
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
@@ -172,18 +172,18 @@ jobs:
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
- env:
+ uses: actions/cache@v5
+ env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration
- with:
- path: |
- ${{github.workspace}}/build
- key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- restore-keys: |
- ${{ env.cache-name }}-
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.19
+ uses: hendrikmuhs/ccache-action@v1.2.21
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
@@ -195,23 +195,23 @@ jobs:
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc)
-
- - name: Package and Upload Linux(ubuntu64) SDL artifact
+
+ - name: Package and Upload Linux(ubuntu64) SDL artifact
run: |
ls -la ${{ github.workspace }}/build/shadps4
-
- - uses: actions/upload-artifact@v4
+
+ - uses: actions/upload-artifact@v6
with:
name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: ${{ github.workspace }}/build/shadps4
- name: Run AppImage packaging script
run: ./.github/linux-appimage-sdl.sh
-
+
- name: Package and Upload Linux SDL artifact
run: |
tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v6
with:
name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
path: Shadps4-sdl.AppImage
@@ -220,7 +220,7 @@ jobs:
runs-on: ubuntu-24.04
needs: get-info
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
submodules: recursive
@@ -228,18 +228,18 @@ jobs:
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
- env:
+ uses: actions/cache@v5
+ env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-configuration
- with:
- path: |
- ${{github.workspace}}/build
- key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- restore-keys: |
- ${{ env.cache-name }}-
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
- name: Cache CMake Build
- uses: hendrikmuhs/ccache-action@v1.2.19
+ uses: hendrikmuhs/ccache-action@v1.2.21
env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
with:
@@ -258,7 +258,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
path: ./artifacts
@@ -266,7 +266,7 @@ jobs:
run: |
chmod -R a+x ./artifacts/shadps4-linux-sdl-*
chmod -R a+x ./artifacts/shadps4-macos-sdl-*
-
+
- name: Compress individual directories (without parent directory)
run: |
cd ./artifacts
@@ -277,7 +277,7 @@ jobs:
(cd "$dir_name" && zip -r "../${dir_name}.zip" .)
fi
done
-
+
- name: Get latest release information
id: get_latest_release
env:
@@ -351,52 +351,52 @@ jobs:
upload_url="https://uploads.github.com/repos/$REPO/releases/$release_id/assets?name=$filename"
curl -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" --data-binary @"$file" "$upload_url"
done
-
+
- name: Get current pre-release information
env:
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
run: |
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
-
+
# Get all releases (sorted by date)
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
-
+
# Capture the most recent pre-release (assuming the first one is the latest)
current_release=$(echo "$releases" | jq -c '.[] | select(.prerelease == true) | .published_at' | sort -r | head -n 1)
-
+
# Remove extra quotes from captured date
current_release=$(echo $current_release | tr -d '"')
-
+
# Export the current published_at to be available for the next step
echo "CURRENT_PUBLISHED_AT=$current_release" >> $GITHUB_ENV
-
+
- name: Delete old pre-releases and tags
env:
GITHUB_TOKEN: ${{ secrets.SHADPS4_TOKEN_REPO }}
run: |
api_url="https://api.github.com/repos/${{ github.repository }}/releases"
-
+
# Get current pre-releases
releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url")
-
+
# Remove extra quotes from captured date
CURRENT_PUBLISHED_AT=$(echo $CURRENT_PUBLISHED_AT | tr -d '"')
-
+
# Convert CURRENT_PUBLISHED_AT para timestamp Unix
current_published_ts=$(date -d "$CURRENT_PUBLISHED_AT" +%s)
-
+
# Identify pre-releases
echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do
release_date=$(echo "$release" | jq -r '.published_at')
release_id=$(echo "$release" | jq -r '.id')
release_tag=$(echo "$release" | jq -r '.tag_name')
-
+
# Remove extra quotes from captured date
release_date=$(echo $release_date | tr -d '"')
-
+
# Convert release_date para timestamp Unix
release_date_ts=$(date -d "$release_date" +%s)
-
+
# Compare timestamps and delete old pre-releases
if [[ "$release_date_ts" -lt "$current_published_ts" ]]; then
echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 914e25e40..ea5b92bc3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,7 +202,7 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
-set(EMULATOR_VERSION_MINOR "14")
+set(EMULATOR_VERSION_MINOR "15")
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}")
@@ -414,9 +414,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/save_data/dialog/savedatadialog.h
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
src/core/libraries/save_data/dialog/savedatadialog_ui.h
- src/core/libraries/system/sysmodule.cpp
- src/core/libraries/system/sysmodule.h
- src/core/libraries/system/system_error.h
+ src/core/libraries/sysmodule/sysmodule.cpp
+ src/core/libraries/sysmodule/sysmodule.h
+ src/core/libraries/sysmodule/sysmodule_internal.cpp
+ src/core/libraries/sysmodule/sysmodule_internal.h
+ src/core/libraries/sysmodule/sysmodule_error.h
+ src/core/libraries/sysmodule/sysmodule_table.h
src/core/libraries/system/systemservice.cpp
src/core/libraries/system/systemservice.h
src/core/libraries/system/systemservice_error.h
diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml
index 210ca1c5e..8a7fa852b 100644
--- a/dist/net.shadps4.shadPS4.metainfo.xml
+++ b/dist/net.shadps4.shadPS4.metainfo.xml
@@ -38,7 +38,10 @@
Game
-
+
+ https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.15.0
+
+
https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0
diff --git a/src/common/config.cpp b/src/common/config.cpp
index fb1181d62..79d3f799f 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -172,7 +172,7 @@ static ConfigEntry internalScreenWidth(1280);
static ConfigEntry internalScreenHeight(720);
static ConfigEntry isNullGpu(false);
static ConfigEntry shouldCopyGPUBuffers(false);
-static ConfigEntry readbacksEnabled(false);
+static ConfigEntry readbacksMode(GpuReadbacksMode::Disabled);
static ConfigEntry readbackLinearImagesEnabled(false);
static ConfigEntry directMemoryAccessEnabled(false);
static ConfigEntry shouldDumpShaders(false);
@@ -440,8 +440,8 @@ bool copyGPUCmdBuffers() {
return shouldCopyGPUBuffers.get();
}
-bool readbacks() {
- return readbacksEnabled.get();
+int getReadbacksMode() {
+ return readbacksMode.get();
}
bool readbackLinearImages() {
@@ -591,8 +591,8 @@ void setCopyGPUCmdBuffers(bool enable, bool is_game_specific) {
shouldCopyGPUBuffers.set(enable, is_game_specific);
}
-void setReadbacks(bool enable, bool is_game_specific) {
- readbacksEnabled.set(enable, is_game_specific);
+void setReadbacksMode(int mode, bool is_game_specific) {
+ readbacksMode.set(mode, is_game_specific);
}
void setReadbackLinearImages(bool enable, bool is_game_specific) {
@@ -943,7 +943,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific);
isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific);
shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific);
- readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific);
+ readbacksMode.setFromToml(gpu, "readbacksMode", is_game_specific);
readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific);
directMemoryAccessEnabled.setFromToml(gpu, "directMemoryAccess", is_game_specific);
shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific);
@@ -1119,7 +1119,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific);
isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific);
shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific);
- readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific);
+ readbacksMode.setTomlValue(data, "GPU", "readbacksMode", is_game_specific);
readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific);
shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific);
vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific);
@@ -1218,7 +1218,7 @@ void setDefaultValues(bool is_game_specific) {
// Entries with game-specific settings that are in the game-specific setings GUI but not in
// the global settings GUI
if (is_game_specific) {
- readbacksEnabled.set(false, is_game_specific);
+ readbacksMode.set(GpuReadbacksMode::Disabled, is_game_specific);
readbackLinearImagesEnabled.set(false, is_game_specific);
isNeo.set(false, is_game_specific);
isDevKit.set(false, is_game_specific);
diff --git a/src/common/config.h b/src/common/config.h
index eb2b91f52..b341030e0 100644
--- a/src/common/config.h
+++ b/src/common/config.h
@@ -23,6 +23,12 @@ struct GameInstallDir {
enum HideCursorState : int { Never, Idle, Always };
+enum GpuReadbacksMode : int {
+ Disabled,
+ Relaxed,
+ Precise,
+};
+
void load(const std::filesystem::path& path, bool is_game_specific = false);
void save(const std::filesystem::path& path, bool is_game_specific = false);
void resetGameSpecificValue(std::string entry);
@@ -63,8 +69,8 @@ bool nullGpu();
void setNullGpu(bool enable, bool is_game_specific = false);
bool copyGPUCmdBuffers();
void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false);
-bool readbacks();
-void setReadbacks(bool enable, bool is_game_specific = false);
+int getReadbacksMode();
+void setReadbacksMode(int mode, bool is_game_specific = false);
bool readbackLinearImages();
void setReadbackLinearImages(bool enable, bool is_game_specific = false);
bool directMemoryAccess();
diff --git a/src/common/elf_info.h b/src/common/elf_info.h
index 0f2311cb0..b84f36ecb 100644
--- a/src/common/elf_info.h
+++ b/src/common/elf_info.h
@@ -88,7 +88,10 @@ public:
static constexpr u32 FW_50 = 0x5000000;
static constexpr u32 FW_55 = 0x5500000;
static constexpr u32 FW_60 = 0x6000000;
+ static constexpr u32 FW_70 = 0x7000000;
+ static constexpr u32 FW_75 = 0x7500000;
static constexpr u32 FW_80 = 0x8000000;
+ static constexpr u32 FW_115 = 0x11500000;
static ElfInfo& Instance() {
return *Singleton::Instance();
diff --git a/src/core/aerolib/stubs.cpp b/src/core/aerolib/stubs.cpp
index 2634fc46a..7023144d7 100644
--- a/src/core/aerolib/stubs.cpp
+++ b/src/core/aerolib/stubs.cpp
@@ -19,7 +19,7 @@ namespace Core::AeroLib {
// and to longer compile / CI times
//
// Must match STUBS_LIST define
-constexpr u32 MAX_STUBS = 1024;
+constexpr u32 MAX_STUBS = 2048;
u64 UnresolvedStub() {
LOG_ERROR(Core, "Returning zero to {}", __builtin_return_address(0));
@@ -61,8 +61,9 @@ static u32 UsedStubEntries;
#define XREP_256(x) XREP_128(x) XREP_128(x + 128)
#define XREP_512(x) XREP_256(x) XREP_256(x + 256)
#define XREP_1024(x) XREP_512(x) XREP_512(x + 512)
+#define XREP_2048(x) XREP_1024(x) XREP_1024(x + 1024)
-#define STUBS_LIST XREP_1024(0)
+#define STUBS_LIST XREP_2048(0)
static u64 (*stub_handlers[MAX_STUBS])() = {STUBS_LIST};
diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp
index e303417c3..f6a4f7620 100644
--- a/src/core/cpu_patches.cpp
+++ b/src/core/cpu_patches.cpp
@@ -788,14 +788,11 @@ static bool PatchesIllegalInstructionHandler(void* context) {
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
const auto status =
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}", reinterpret_cast(code_address));
- }
- UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
- reinterpret_cast(code_address),
- ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
- : "Failed to decode");
+ LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}",
+ reinterpret_cast(code_address),
+ ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
+ : "Failed to decode");
+ return false;
}
}
diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp
index e647059f0..c5be7410a 100644
--- a/src/core/file_format/psf.cpp
+++ b/src/core/file_format/psf.cpp
@@ -113,6 +113,7 @@ bool PSF::Encode(const std::filesystem::path& filepath) const {
LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written,
psf_buffer.size());
}
+ file.Close();
return written == psf_buffer.size();
}
diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp
index 3f5fdcf78..989679107 100644
--- a/src/core/libraries/audio3d/audio3d.cpp
+++ b/src/core/libraries/audio3d/audio3d.cpp
@@ -1,7 +1,8 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include
+#include
+#include
#include
#include "common/assert.h"
@@ -20,12 +21,21 @@ static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000;
static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT =
AudioOut::OrbisAudioOutParamFormat::S16Stereo;
static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2;
-static constexpr u32 AUDIO3D_OUTPUT_BUFFER_FRAMES = 0x100;
static std::unique_ptr state;
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
LOG_INFO(Lib_Audio3d, "called, handle = {}", handle);
+
+ // Remove from any port that was tracking this handle.
+ if (state) {
+ for (auto& [port_id, port] : state->ports) {
+ std::scoped_lock lock{port.mutex};
+ auto& handles = port.audioout_handles;
+ handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end());
+ }
+ }
+
return AudioOut::sceAudioOutClose(handle);
}
@@ -42,13 +52,21 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
+ std::scoped_lock lock{state->ports[port_id].mutex};
if (len != state->ports[port_id].parameters.granularity) {
LOG_ERROR(Lib_Audio3d, "len != state->ports[port_id].parameters.granularity");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
- return sceAudioOutOpen(user_id, static_cast(type), index, len,
- freq, param);
+ const s32 handle = sceAudioOutOpen(user_id, static_cast(type),
+ index, len, freq, param);
+ if (handle < 0) {
+ return handle;
+ }
+
+ // Track this handle in the port so sceAudio3dPortFlush can use it for sync.
+ state->ports[port_id].audioout_handles.push_back(handle);
+ return handle;
}
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) {
@@ -79,34 +97,31 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p
return AudioOut::sceAudioOutOutputs(param, num);
}
-static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) {
- // Audio3d output is configured for stereo signed 16-bit PCM. Convert the data to match.
- const SDL_AudioSpec src_spec = {
- .format = pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE
- : SDL_AUDIO_F32LE,
- .channels = static_cast(num_channels),
- .freq = AUDIO3D_SAMPLE_RATE,
- };
- constexpr SDL_AudioSpec dst_spec = {
- .format = SDL_AUDIO_S16LE,
- .channels = AUDIO3D_OUTPUT_NUM_CHANNELS,
- .freq = AUDIO3D_SAMPLE_RATE,
- };
- const auto src_size = pcm.num_samples *
- (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? 2 : 4) *
- num_channels;
+static s32 ConvertAndEnqueue(std::deque& queue, const OrbisAudio3dPcm& pcm,
+ const u32 num_channels, const u32 granularity) {
+ if (!pcm.sample_buffer || !pcm.num_samples) {
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
- u8* dst_data;
- int dst_len;
- if (!SDL_ConvertAudioSamples(&src_spec, static_cast(pcm.sample_buffer),
- static_cast(src_size), &dst_spec, &dst_data, &dst_len)) {
- LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError());
+ const u32 bytes_per_sample =
+ (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) ? sizeof(s16) : sizeof(float);
+
+ // Always allocate exactly granularity samples (zeroed = silence for padding).
+ const u32 dst_bytes = granularity * num_channels * bytes_per_sample;
+ u8* copy = static_cast(std::calloc(1, dst_bytes));
+ if (!copy) {
return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
}
- port.queue.emplace_back(AudioData{
- .sample_buffer = dst_data,
- .num_samples = pcm.num_samples,
+ // Copy min(provided, granularity) samples — extra are dropped, shortage stays zero.
+ const u32 samples_to_copy = std::min(pcm.num_samples, granularity);
+ std::memcpy(copy, pcm.sample_buffer, samples_to_copy * num_channels * bytes_per_sample);
+
+ queue.emplace_back(AudioData{
+ .sample_buffer = copy,
+ .num_samples = granularity,
+ .num_channels = num_channels,
+ .format = pcm.format,
});
return ORBIS_OK;
}
@@ -145,8 +160,8 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
- if (num_channels != 2 && num_channels != 8) {
- LOG_ERROR(Lib_Audio3d, "num_channels != 2 && num_channels != 8");
+ if (num_channels != 2 && num_channels != 6 && num_channels != 8) {
+ LOG_ERROR(Lib_Audio3d, "num_channels must be 2, 6, or 8");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
@@ -167,13 +182,14 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32
}
}
- return PortQueueAudio(state->ports[port_id],
- OrbisAudio3dPcm{
- .format = format,
- .sample_buffer = buffer,
- .num_samples = num_samples,
- },
- num_channels);
+ std::scoped_lock lock{state->ports[port_id].mutex};
+ return ConvertAndEnqueue(state->ports[port_id].bed_queue,
+ OrbisAudio3dPcm{
+ .format = format,
+ .sample_buffer = buffer,
+ .num_samples = num_samples,
+ },
+ num_channels, state->ports[port_id].parameters.granularity);
}
s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() {
@@ -237,15 +253,6 @@ s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) {
return init_ret;
}
- AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
- ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
- state->audio_out_handle =
- AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0,
- AUDIO3D_OUTPUT_BUFFER_FRAMES, AUDIO3D_SAMPLE_RATE, ext_info);
- if (state->audio_out_handle < 0) {
- return state->audio_out_handle;
- }
-
return ORBIS_OK;
}
@@ -254,18 +261,84 @@ s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id,
LOG_INFO(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id,
static_cast(object_id));
- if (!state->ports.contains(port_id)) {
- LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
- return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
- }
-
if (!object_id) {
LOG_ERROR(Lib_Audio3d, "!object_id");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
- static int last_id = 0;
- *object_id = ++last_id;
+ *object_id = ORBIS_AUDIO3D_OBJECT_INVALID;
+
+ if (!state->ports.contains(port_id)) {
+ LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
+ }
+
+ auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+
+ // Enforce the max_objects limit set at PortOpen time.
+ if (port.objects.size() >= port.parameters.max_objects) {
+ LOG_ERROR(Lib_Audio3d, "port has no available objects (max_objects = {})",
+ port.parameters.max_objects);
+ return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES;
+ }
+
+ // counter lives in the Port so it resets when the port is closed and reopened,
+ do {
+ ++port.next_object_id;
+ } while (port.next_object_id == 0 || port.next_object_id == ORBIS_AUDIO3D_OBJECT_INVALID ||
+ port.objects.contains(port.next_object_id));
+
+ *object_id = port.next_object_id;
+ port.objects.emplace(*object_id, ObjectState{});
+ LOG_INFO(Lib_Audio3d, "reserved object_id = {}", *object_id);
+
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(const OrbisAudio3dPortId port_id,
+ const OrbisAudio3dObjectId object_id,
+ const OrbisAudio3dAttributeId attribute_id,
+ const void* attribute, const u64 attribute_size) {
+ LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}, attribute_id = {:#x}, size = {}",
+ port_id, object_id, static_cast(attribute_id), attribute_size);
+
+ if (!state->ports.contains(port_id)) {
+ LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
+ }
+
+ auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+ if (!port.objects.contains(object_id)) {
+ LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved (race with Unreserve?), no-op",
+ object_id);
+ return ORBIS_OK;
+ }
+
+ if (!attribute_size &&
+ attribute_id != OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
+ LOG_ERROR(Lib_Audio3d, "!attribute_size for non-reset attribute");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+
+ auto& obj = port.objects[object_id];
+
+ // RESET_STATE clears all attributes and queued PCM; it takes no value.
+ if (attribute_id == OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
+ for (auto& data : obj.pcm_queue) {
+ std::free(data.sample_buffer);
+ }
+ obj.pcm_queue.clear();
+ obj.persistent_attributes.clear();
+ LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id);
+ return ORBIS_OK;
+ }
+
+ // we don't handle any attributes yet, but store them in the ObjectState so they're available
+ // when we do
+ const auto* src = static_cast(attribute);
+ obj.persistent_attributes[static_cast(attribute_id)].assign(src, src + attribute_size);
return ORBIS_OK;
}
@@ -283,32 +356,95 @@ s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id,
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
- auto& port = state->ports[port_id];
+ if (!num_attributes || !attribute_array) {
+ LOG_ERROR(Lib_Audio3d, "!num_attributes || !attribute_array");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+ if (!port.objects.contains(object_id)) {
+ LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved", object_id);
+ return ORBIS_OK;
+ }
+
+ auto& obj = port.objects[object_id];
+
+ for (u64 i = 0; i < num_attributes; i++) {
+ if (attribute_array[i].attribute_id ==
+ OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) {
+ for (auto& data : obj.pcm_queue) {
+ std::free(data.sample_buffer);
+ }
+ obj.pcm_queue.clear();
+ obj.persistent_attributes.clear();
+ LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id);
+ break; // Only one reset is needed even if listed multiple times.
+ }
+ }
+
+ // apply all other attributes.
for (u64 i = 0; i < num_attributes; i++) {
const auto& attribute = attribute_array[i];
switch (attribute.attribute_id) {
+ case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE:
+ break; // Already applied in first pass above.
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_PCM: {
+ if (attribute.value_size < sizeof(OrbisAudio3dPcm)) {
+ LOG_ERROR(Lib_Audio3d, "PCM attribute value_size too small");
+ continue;
+ }
const auto pcm = static_cast(attribute.value);
- // Object audio has 1 channel.
- if (const auto ret = PortQueueAudio(port, *pcm, 1); ret != ORBIS_OK) {
+ // Object audio is always mono (1 channel).
+ if (const auto ret =
+ ConvertAndEnqueue(obj.pcm_queue, *pcm, 1, port.parameters.granularity);
+ ret != ORBIS_OK) {
return ret;
}
break;
}
- default:
- LOG_ERROR(Lib_Audio3d, "Unsupported attribute ID: {:#x}",
- static_cast(attribute.attribute_id));
+ default: {
+ // store the other attributes in the ObjectState so they're available when we implement
+ // them
+ if (attribute.value && attribute.value_size > 0) {
+ const auto* src = static_cast(attribute.value);
+ obj.persistent_attributes[static_cast(attribute.attribute_id)].assign(
+ src, src + attribute.value_size);
+ }
+ LOG_DEBUG(Lib_Audio3d, "Stored attribute {:#x} for object {}",
+ static_cast(attribute.attribute_id), object_id);
break;
}
+ }
}
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve() {
- LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(const OrbisAudio3dPortId port_id,
+ const OrbisAudio3dObjectId object_id) {
+ LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, object_id);
+
+ if (!state->ports.contains(port_id)) {
+ LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
+ }
+
+ auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+
+ if (!port.objects.contains(object_id)) {
+ LOG_ERROR(Lib_Audio3d, "object_id not reserved");
+ return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
+ }
+
+ // Free any queued PCM audio for this object.
+ for (auto& data : port.objects[object_id].pcm_queue) {
+ std::free(data.sample_buffer);
+ }
+
+ port.objects.erase(object_id);
return ORBIS_OK;
}
@@ -320,32 +456,164 @@ s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) {
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
- if (state->ports[port_id].parameters.buffer_mode ==
- OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) {
+ auto& port = state->ports[port_id];
+
+ if (port.parameters.buffer_mode == OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) {
LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability");
return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED;
}
- auto& port = state->ports[port_id];
- if (port.current_buffer.has_value()) {
- // Free existing buffer before replacing.
- SDL_free(port.current_buffer->sample_buffer);
+ if (port.mixed_queue.size() >= port.parameters.queue_depth) {
+ LOG_WARNING(Lib_Audio3d, "mixed queue full (depth={}), dropping advance",
+ port.parameters.queue_depth);
+ return ORBIS_AUDIO3D_ERROR_NOT_READY;
}
- if (!port.queue.empty()) {
- port.current_buffer = port.queue.front();
- port.queue.pop_front();
- } else {
- // Nothing to advance to.
- LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued");
- port.current_buffer = std::nullopt;
+ const u32 granularity = port.parameters.granularity;
+ const u32 out_samples = granularity * AUDIO3D_OUTPUT_NUM_CHANNELS;
+
+ // ---- FLOAT MIX BUFFER ----
+ float* mix_float = static_cast(std::calloc(out_samples, sizeof(float)));
+
+ if (!mix_float)
+ return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
+
+ auto mix_in = [&](std::deque& queue, const float gain) {
+ if (queue.empty())
+ return;
+
+ // default gain is 0.0 — objects with no GAIN set are silent.
+ if (gain == 0.0f) {
+ AudioData data = queue.front();
+ queue.pop_front();
+ std::free(data.sample_buffer);
+ return;
+ }
+
+ AudioData data = queue.front();
+ queue.pop_front();
+
+ const u32 frames = std::min(granularity, data.num_samples);
+ const u32 channels = data.num_channels;
+
+ if (data.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
+ const s16* src = reinterpret_cast(data.sample_buffer);
+
+ for (u32 i = 0; i < frames; i++) {
+ float left = 0.0f;
+ float right = 0.0f;
+
+ if (channels == 1) {
+ float v = src[i] / 32768.0f;
+ left = v;
+ right = v;
+ } else {
+ left = src[i * channels + 0] / 32768.0f;
+ right = src[i * channels + 1] / 32768.0f;
+ }
+
+ mix_float[i * 2 + 0] += left * gain;
+ mix_float[i * 2 + 1] += right * gain;
+ }
+ } else { // FLOAT input
+ const float* src = reinterpret_cast(data.sample_buffer);
+
+ for (u32 i = 0; i < frames; i++) {
+ float left = 0.0f;
+ float right = 0.0f;
+
+ if (channels == 1) {
+ left = src[i];
+ right = src[i];
+ } else {
+ left = src[i * channels + 0];
+ right = src[i * channels + 1];
+ }
+
+ mix_float[i * 2 + 0] += left * gain;
+ mix_float[i * 2 + 1] += right * gain;
+ }
+ }
+
+ std::free(data.sample_buffer);
+ };
+
+ // Bed is mixed at full gain (1.0)
+ mix_in(port.bed_queue, 1.0f);
+
+ // Mix all object PCM queues, applying each object's GAIN persistent attribute.
+ for (auto& [obj_id, obj] : port.objects) {
+ float gain = 0.0f;
+ const auto gain_key =
+ static_cast(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_GAIN);
+ if (obj.persistent_attributes.contains(gain_key)) {
+ const auto& blob = obj.persistent_attributes.at(gain_key);
+ if (blob.size() >= sizeof(float)) {
+ std::memcpy(&gain, blob.data(), sizeof(float));
+ }
+ }
+ mix_in(obj.pcm_queue, gain);
}
+ s16* mix_s16 = static_cast(std::malloc(out_samples * sizeof(s16)));
+
+ if (!mix_s16) {
+ std::free(mix_float);
+ return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (u32 i = 0; i < out_samples; i++) {
+ float v = std::clamp(mix_float[i], -1.0f, 1.0f);
+ mix_s16[i] = static_cast(v * 32767.0f);
+ }
+
+ std::free(mix_float);
+
+ port.mixed_queue.push_back(AudioData{.sample_buffer = reinterpret_cast(mix_s16),
+ .num_samples = granularity,
+ .num_channels = AUDIO3D_OUTPUT_NUM_CHANNELS,
+ .format = OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16});
+
return ORBIS_OK;
}
+s32 PS4_SYSV_ABI sceAudio3dPortClose(const OrbisAudio3dPortId port_id) {
+ LOG_INFO(Lib_Audio3d, "called, port_id = {}", port_id);
-s32 PS4_SYSV_ABI sceAudio3dPortClose() {
- LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ if (!state->ports.contains(port_id)) {
+ LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
+ }
+
+ auto& port = state->ports[port_id];
+ {
+ std::scoped_lock lock{port.mutex};
+
+ if (port.audio_out_handle >= 0) {
+ AudioOut::sceAudioOutClose(port.audio_out_handle);
+ port.audio_out_handle = -1;
+ }
+
+ for (const s32 handle : port.audioout_handles) {
+ AudioOut::sceAudioOutClose(handle);
+ }
+ port.audioout_handles.clear();
+
+ for (auto& data : port.mixed_queue) {
+ std::free(data.sample_buffer);
+ }
+
+ for (auto& data : port.bed_queue) {
+ std::free(data.sample_buffer);
+ }
+
+ for (auto& [obj_id, obj] : port.objects) {
+ for (auto& data : obj.pcm_queue) {
+ std::free(data.sample_buffer);
+ }
+ }
+ }
+
+ state->ports.erase(port_id);
return ORBIS_OK;
}
@@ -359,8 +627,65 @@ s32 PS4_SYSV_ABI sceAudio3dPortDestroy() {
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceAudio3dPortFlush() {
- LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceAudio3dPortFlush(const OrbisAudio3dPortId port_id) {
+ LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id);
+
+ if (!state->ports.contains(port_id)) {
+ LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
+ }
+
+ auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+
+ if (!port.audioout_handles.empty()) {
+ for (const s32 handle : port.audioout_handles) {
+ const s32 ret = AudioOut::sceAudioOutOutput(handle, nullptr);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ return ORBIS_OK;
+ }
+
+ if (port.mixed_queue.empty()) {
+ // Only mix if there's actually something to mix.
+ if (!port.bed_queue.empty() ||
+ std::any_of(port.objects.begin(), port.objects.end(),
+ [](const auto& kv) { return !kv.second.pcm_queue.empty(); })) {
+ const s32 ret = sceAudio3dPortAdvance(port_id);
+ if (ret != ORBIS_OK && ret != ORBIS_AUDIO3D_ERROR_NOT_READY) {
+ return ret;
+ }
+ }
+ }
+
+ if (port.mixed_queue.empty()) {
+ return ORBIS_OK;
+ }
+
+ if (port.audio_out_handle < 0) {
+ AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
+ ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
+ port.audio_out_handle =
+ AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0,
+ port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info);
+ if (port.audio_out_handle < 0) {
+ return port.audio_out_handle;
+ }
+ }
+
+ // Drain all queued mixed frames, blocking on each until consumed.
+ while (!port.mixed_queue.empty()) {
+ AudioData frame = port.mixed_queue.front();
+ port.mixed_queue.pop_front();
+ const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer);
+ std::free(frame.sample_buffer);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
return ORBIS_OK;
}
@@ -398,15 +723,17 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
- const auto port = state->ports[port_id];
- const size_t size = port.queue.size();
+ const auto& port = state->ports[port_id];
+ std::scoped_lock lock{port.mutex};
+ const size_t size = port.mixed_queue.size();
if (queue_level) {
- *queue_level = size;
+ *queue_level = static_cast(size);
}
if (queue_available) {
- *queue_available = port.parameters.queue_depth - size;
+ const u32 depth = port.parameters.queue_depth;
+ *queue_available = (size < depth) ? static_cast(depth - size) : 0u;
}
return ORBIS_OK;
@@ -446,7 +773,10 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServi
}
*port_id = id;
- std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this);
+ auto& port = state->ports[id];
+ std::memcpy(
+ &port.parameters, parameters,
+ std::min(parameters->size_this, static_cast(sizeof(OrbisAudio3dOpenParameters))));
return ORBIS_OK;
}
@@ -461,24 +791,96 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id,
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
- const auto& port = state->ports[port_id];
+ auto& port = state->ports[port_id];
+
if (port.parameters.buffer_mode !=
OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
LOG_ERROR(Lib_Audio3d, "port doesn't have push capability");
return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED;
}
- if (!port.current_buffer.has_value()) {
- // Nothing to push.
- LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready");
+ const u32 depth = port.parameters.queue_depth;
+
+ if (port.audio_out_handle < 0) {
+ AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
+ ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
+
+ port.audio_out_handle =
+ AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0,
+ port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info);
+
+ if (port.audio_out_handle < 0)
+ return port.audio_out_handle;
+ }
+
+ // Function that submits exactly one frame (if available)
+ auto submit_one_frame = [&](bool& submitted) -> s32 {
+ AudioData frame;
+ {
+ std::scoped_lock lock{port.mutex};
+
+ if (port.mixed_queue.empty()) {
+ submitted = false;
+ return ORBIS_OK;
+ }
+
+ frame = port.mixed_queue.front();
+ port.mixed_queue.pop_front();
+ }
+
+ const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer);
+
+ std::free(frame.sample_buffer);
+
+ if (ret < 0)
+ return ret;
+
+ submitted = true;
+ return ORBIS_OK;
+ };
+
+ // if not full, return immediately
+ {
+ std::scoped_lock lock{port.mutex};
+ if (port.mixed_queue.size() < depth) {
+ return ORBIS_OK;
+ }
+ }
+
+ // Submit one frame to free space
+ bool submitted = false;
+ s32 ret = submit_one_frame(submitted);
+ if (ret < 0)
+ return ret;
+
+ if (!submitted)
+ return ORBIS_OK;
+
+ // ASYNC: free exactly one slot and return
+ if (blocking == OrbisAudio3dBlocking::ORBIS_AUDIO3D_BLOCKING_ASYNC) {
return ORBIS_OK;
}
- // TODO: Implement asynchronous blocking mode.
- const auto& [sample_buffer, num_samples] = port.current_buffer.value();
- return AudioOut::sceAudioOutOutput(state->audio_out_handle, sample_buffer);
-}
+ // SYNC: ensure at least one slot is free
+ // (drain until size < depth)
+ while (true) {
+ {
+ std::scoped_lock lock{port.mutex};
+ if (port.mixed_queue.size() < depth)
+ break;
+ }
+ bool drained = false;
+ ret = submit_one_frame(drained);
+ if (ret < 0)
+ return ret;
+
+ if (!drained)
+ break;
+ }
+
+ return ORBIS_OK;
+}
s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
@@ -532,9 +934,15 @@ s32 PS4_SYSV_ABI sceAudio3dTerminate() {
return ORBIS_AUDIO3D_ERROR_NOT_READY;
}
- AudioOut::sceAudioOutOutput(state->audio_out_handle, nullptr);
- AudioOut::sceAudioOutClose(state->audio_out_handle);
- state.release();
+ std::vector port_ids;
+ for (const auto& [id, _] : state->ports) {
+ port_ids.push_back(id);
+ }
+ for (const auto id : port_ids) {
+ sceAudio3dPortClose(id);
+ }
+
+ state.reset();
return ORBIS_OK;
}
@@ -557,6 +965,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
sceAudio3dGetSpeakerArrayMixCoefficients2);
LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dInitialize);
LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectReserve);
+ LIB_FUNCTION("V1FBFpNIAzk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttribute);
LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttributes);
LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectUnreserve);
LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortAdvance);
diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h
index ae20e39a8..0db7fa83b 100644
--- a/src/core/libraries/audio3d/audio3d.h
+++ b/src/core/libraries/audio3d/audio3d.h
@@ -3,7 +3,10 @@
#pragma once
+#include
+#include
#include
+#include
#include
#include "common/types.h"
@@ -15,6 +18,8 @@ class SymbolsResolver;
namespace Libraries::Audio3d {
+constexpr int ORBIS_AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF;
+
enum class OrbisAudio3dRate : u32 {
ORBIS_AUDIO3D_RATE_48000 = 0,
};
@@ -60,10 +65,21 @@ struct OrbisAudio3dPcm {
enum class OrbisAudio3dAttributeId : u32 {
ORBIS_AUDIO3D_ATTRIBUTE_PCM = 1,
+ ORBIS_AUDIO3D_ATTRIBUTE_POSITION = 2,
+ ORBIS_AUDIO3D_ATTRIBUTE_GAIN = 3,
+ ORBIS_AUDIO3D_ATTRIBUTE_SPREAD = 4,
+ ORBIS_AUDIO3D_ATTRIBUTE_PRIORITY = 5,
+ ORBIS_AUDIO3D_ATTRIBUTE_PASSTHROUGH = 6,
+ ORBIS_AUDIO3D_ATTRIBUTE_AMBISONICS = 7,
+ ORBIS_AUDIO3D_ATTRIBUTE_APPLICATION_SPECIFIC = 8,
+ ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE = 9,
+ ORBIS_AUDIO3D_ATTRIBUTE_RESTRICTED = 10,
+ ORBIS_AUDIO3D_ATTRIBUTE_OUTPUT_ROUTE = 11,
};
using OrbisAudio3dPortId = u32;
using OrbisAudio3dObjectId = u32;
+using OrbisAudio3dAmbisonics = u32;
struct OrbisAudio3dAttribute {
OrbisAudio3dAttributeId attribute_id;
@@ -75,17 +91,35 @@ struct OrbisAudio3dAttribute {
struct AudioData {
u8* sample_buffer;
u32 num_samples;
+ u32 num_channels{1}; // channels in sample_buffer
+ OrbisAudio3dFormat format{
+ OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16}; // format of sample_buffer
+};
+
+struct ObjectState {
+ std::deque pcm_queue;
+ std::unordered_map> persistent_attributes;
};
struct Port {
+ mutable std::recursive_mutex mutex;
OrbisAudio3dOpenParameters parameters{};
- std::deque queue; // Only stores PCM buffers for now
- std::optional current_buffer{};
+ // Opened lazily on the first sceAudio3dPortPush call.
+ s32 audio_out_handle{-1};
+ // Handles explicitly opened by the game via sceAudio3dAudioOutOpen.
+ std::vector audioout_handles;
+ // Reserved objects and their state.
+ std::unordered_map objects;
+ // increasing counter for generating unique object IDs within this port.
+ OrbisAudio3dObjectId next_object_id{0};
+ // Bed audio queue
+ std::deque bed_queue;
+ // Mixed stereo frames ready to be consumed by sceAudio3dPortPush.
+ std::deque mixed_queue;
};
struct Audio3dState {
std::unordered_map ports;
- s32 audio_out_handle;
};
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
@@ -109,15 +143,20 @@ s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2();
s32 PS4_SYSV_ABI sceAudio3dInitialize(s64 reserved);
s32 PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId port_id,
OrbisAudio3dObjectId* object_id);
+s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(OrbisAudio3dPortId port_id,
+ OrbisAudio3dObjectId object_id,
+ OrbisAudio3dAttributeId attribute_id,
+ const void* attribute, u64 attribute_size);
s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId port_id,
OrbisAudio3dObjectId object_id, u64 num_attributes,
const OrbisAudio3dAttribute* attribute_array);
-s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve();
+s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId port_id,
+ OrbisAudio3dObjectId object_id);
s32 PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId port_id);
-s32 PS4_SYSV_ABI sceAudio3dPortClose();
+s32 PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId port_id);
s32 PS4_SYSV_ABI sceAudio3dPortCreate();
s32 PS4_SYSV_ABI sceAudio3dPortDestroy();
-s32 PS4_SYSV_ABI sceAudio3dPortFlush();
+s32 PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId port_id);
s32 PS4_SYSV_ABI sceAudio3dPortFreeState();
s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported();
s32 PS4_SYSV_ABI sceAudio3dPortGetList();
diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp
index 138747da4..db32862ad 100644
--- a/src/core/libraries/avplayer/avplayer_impl.cpp
+++ b/src/core/libraries/avplayer/avplayer_impl.cpp
@@ -12,28 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) {
const auto* const self = reinterpret_cast(handle);
const auto allocate = self->m_init_data_original.memory_replacement.allocate;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
- return Core::ExecuteGuest(allocate, ptr, alignment, size);
+ return allocate(ptr, alignment, size);
}
void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) {
const auto* const self = reinterpret_cast(handle);
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
- return Core::ExecuteGuest(deallocate, ptr, memory);
+ return deallocate(ptr, memory);
}
void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) {
const auto* const self = reinterpret_cast(handle);
const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
- return Core::ExecuteGuest(allocate, ptr, alignment, size);
+ return allocate(ptr, alignment, size);
}
void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) {
const auto* const self = reinterpret_cast(handle);
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture;
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
- return Core::ExecuteGuest(deallocate, ptr, memory);
+ return deallocate(ptr, memory);
}
int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
@@ -42,7 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
const auto open = self->m_init_data_original.file_replacement.open;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
- return Core::ExecuteGuest(open, ptr, filename);
+ return open(ptr, filename);
}
int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
@@ -51,7 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
const auto close = self->m_init_data_original.file_replacement.close;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
- return Core::ExecuteGuest(close, ptr);
+ return close(ptr);
}
int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) {
@@ -60,7 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position
const auto read_offset = self->m_init_data_original.file_replacement.read_offset;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
- return Core::ExecuteGuest(read_offset, ptr, buffer, position, length);
+ return read_offset(ptr, buffer, position, length);
}
u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
@@ -69,7 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
const auto size = self->m_init_data_original.file_replacement.size;
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
- return Core::ExecuteGuest(size, ptr);
+ return size(ptr);
}
AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) {
diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp
index e1b11840e..dbaa36d18 100644
--- a/src/core/libraries/avplayer/avplayer_state.cpp
+++ b/src/core/libraries/avplayer/avplayer_state.cpp
@@ -92,7 +92,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, AvPlayerEvents event_id,
const auto callback = self->m_event_replacement.event_callback;
const auto ptr = self->m_event_replacement.object_ptr;
if (callback != nullptr) {
- Core::ExecuteGuest(callback, ptr, event_id, 0, event_data);
+ callback(ptr, event_id, 0, event_data);
}
}
diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp
index 326dc2418..1993d8cd7 100644
--- a/src/core/libraries/gnmdriver/gnmdriver.cpp
+++ b/src/core/libraries/gnmdriver/gnmdriver.cpp
@@ -627,10 +627,30 @@ int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceGnmDrawIndirectMulti() {
- LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
- UNREACHABLE();
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count,
+ u32 shader_stage, u32 vertex_sgpr_offset,
+ u32 instance_sgpr_offset, u32 flags) {
+ LOG_TRACE(Lib_GnmDriver, "called");
+
+ if (cmdbuf && size == 11 && shader_stage < ShaderStages::Max && vertex_sgpr_offset < 0x10 &&
+ instance_sgpr_offset < 0x10) {
+ const auto predicate = flags & 1 ? PM4Predicate::PredEnable : PM4Predicate::PredDisable;
+ cmdbuf = WriteHeader(
+ cmdbuf, 4, PM4ShaderType::ShaderGraphics, predicate);
+
+ const auto sgpr_offset = indirect_sgpr_offsets[shader_stage];
+ cmdbuf[0] = data_offset;
+ cmdbuf[1] = vertex_sgpr_offset == 0 ? 0 : (vertex_sgpr_offset & 0xffffu) + sgpr_offset;
+ cmdbuf[2] = instance_sgpr_offset == 0 ? 0 : (instance_sgpr_offset & 0xffffu) + sgpr_offset;
+ cmdbuf[3] = max_count;
+ cmdbuf[4] = sizeof(DrawIndirectArgs);
+ cmdbuf[5] = sceKernelIsNeoMode() ? flags & 0xe0000000u | 2u : 2u; // auto index
+
+ cmdbuf += 6;
+ WriteTrailingNop<3>(cmdbuf);
+ return ORBIS_OK;
+ }
+ return -1;
}
u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size) {
diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h
index 9f5fde628..4ece58ebd 100644
--- a/src/core/libraries/gnmdriver/gnmdriver.h
+++ b/src/core/libraries/gnmdriver/gnmdriver.h
@@ -60,7 +60,9 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexOffset(u32* cmdbuf, u32 size, u32 index_offset,
s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage,
u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags);
int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti();
-int PS4_SYSV_ABI sceGnmDrawIndirectMulti();
+s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count,
+ u32 shader_stage, u32 vertex_sgpr_offset,
+ u32 instance_sgpr_offset, u32 flags);
u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size);
u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState175(u32* cmdbuf, u32 size);
u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState200(u32* cmdbuf, u32 size);
diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp
index 258cc61e1..96ae446fa 100644
--- a/src/core/libraries/ime/ime.cpp
+++ b/src/core/libraries/ime/ime.cpp
@@ -99,16 +99,16 @@ public:
if (m_ime_mode) {
OrbisImeParam param = m_param.ime;
if (use_param_handler) {
- Core::ExecuteGuest(param.handler, param.arg, event);
+ param.handler(param.arg, event);
} else {
- Core::ExecuteGuest(handler, param.arg, event);
+ handler(param.arg, event);
}
} else {
OrbisImeKeyboardParam param = m_param.key;
if (use_param_handler) {
- Core::ExecuteGuest(param.handler, param.arg, event);
+ param.handler(param.arg, event);
} else {
- Core::ExecuteGuest(handler, param.arg, event);
+ handler(param.arg, event);
}
}
}
diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp
index 4a95c60c9..9611e7c49 100644
--- a/src/core/libraries/ime/ime_dialog_ui.cpp
+++ b/src/core/libraries/ime/ime_dialog_ui.cpp
@@ -131,8 +131,7 @@ bool ImeDialogState::CallTextFilter() {
return false;
}
- int ret =
- Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length);
+ int ret = text_filter(out_text, &out_text_length, src_text, src_text_length);
if (ret != 0) {
return false;
@@ -153,7 +152,7 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16*
return true;
}
- int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr);
+ int ret = keyboard_filter(src_keycode, out_keycode, out_status, nullptr);
return ret == 0;
}
diff --git a/src/core/libraries/kernel/kernel.h b/src/core/libraries/kernel/kernel.h
index ce6446129..315af6b06 100644
--- a/src/core/libraries/kernel/kernel.h
+++ b/src/core/libraries/kernel/kernel.h
@@ -37,7 +37,7 @@ struct OrbisWrapperImpl {
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl::wrap)
-#define CURRENT_FIRMWARE_VERSION 0x13020011
+#define CURRENT_FIRMWARE_VERSION 0x13500011
s32* PS4_SYSV_ABI __Error();
diff --git a/src/core/libraries/kernel/orbis_error.h b/src/core/libraries/kernel/orbis_error.h
index d19b3f3f1..6ebff0ba3 100644
--- a/src/core/libraries/kernel/orbis_error.h
+++ b/src/core/libraries/kernel/orbis_error.h
@@ -106,3 +106,5 @@ constexpr int ORBIS_KERNEL_ERROR_ECAPMODE = 0x8002005E;
constexpr int ORBIS_KERNEL_ERROR_ENOBLK = 0x8002005F;
constexpr int ORBIS_KERNEL_ERROR_EICV = 0x80020060;
constexpr int ORBIS_KERNEL_ERROR_ENOPLAYGOENT = 0x80020061;
+constexpr int ORBIS_KERNEL_ERROR_ESDKVERSION = 0x80020063;
+constexpr int ORBIS_KERNEL_ERROR_ESTART = 0x80020064;
\ No newline at end of file
diff --git a/src/core/libraries/kernel/threads.cpp b/src/core/libraries/kernel/threads.cpp
index 082a52b67..083dc8ee1 100644
--- a/src/core/libraries/kernel/threads.cpp
+++ b/src/core/libraries/kernel/threads.cpp
@@ -7,6 +7,17 @@
namespace Libraries::Kernel {
+void PS4_SYSV_ABI ClearStack() {
+ void* const stackaddr_attr = Libraries::Kernel::g_curthread->attr.stackaddr_attr;
+ void* volatile sp;
+ asm("mov %%rsp, %0" : "=rm"(sp));
+ // leave a safety net of 64 bytes for memset
+ const size_t size = ((uintptr_t)sp - (uintptr_t)stackaddr_attr) - 64;
+ void* volatile buf = alloca(size);
+ memset(buf, 0, size);
+ sp = nullptr;
+}
+
void RegisterThreads(Core::Loader::SymbolsResolver* sym) {
RegisterMutex(sym);
RegisterCond(sym);
diff --git a/src/core/libraries/kernel/threads.h b/src/core/libraries/kernel/threads.h
index 42ab0b13f..81535352c 100644
--- a/src/core/libraries/kernel/threads.h
+++ b/src/core/libraries/kernel/threads.h
@@ -27,6 +27,7 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr
PthreadEntryFunc start_routine, void* arg);
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return);
+int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread);
int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr);
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type);
@@ -40,6 +41,8 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex);
void RegisterThreads(Core::Loader::SymbolsResolver* sym);
+void PS4_SYSV_ABI ClearStack();
+
class Thread {
public:
explicit Thread() = default;
diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp
index 0b27f2bd8..e2fd032f5 100644
--- a/src/core/libraries/kernel/threads/exception.cpp
+++ b/src/core/libraries/kernel/threads/exception.cpp
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
+#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/orbis_error.h"
+#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/kernel/threads/exception.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/libs.h"
@@ -13,23 +15,24 @@
#else
#include
#endif
+#include
namespace Libraries::Kernel {
#ifdef _WIN32
// Windows doesn't have native versions of these, and we don't need to use them either.
-static s32 NativeToOrbisSignal(s32 s) {
+s32 NativeToOrbisSignal(s32 s) {
return s;
}
-static s32 OrbisToNativeSignal(s32 s) {
+s32 OrbisToNativeSignal(s32 s) {
return s;
}
#else
-static s32 NativeToOrbisSignal(s32 s) {
+s32 NativeToOrbisSignal(s32 s) {
switch (s) {
case SIGHUP:
return POSIX_SIGHUP;
@@ -89,12 +92,21 @@ static s32 NativeToOrbisSignal(s32 s) {
return POSIX_SIGUSR1;
case SIGUSR2:
return POSIX_SIGUSR2;
+ case _SIGEMT:
+ return POSIX_SIGEMT;
+ case _SIGINFO:
+ return POSIX_SIGINFO;
+ case 0:
+ return 128;
default:
+ if (s > 0 && s < 128) {
+ return s;
+ }
UNREACHABLE_MSG("Unknown signal {}", s);
}
}
-static s32 OrbisToNativeSignal(s32 s) {
+s32 OrbisToNativeSignal(s32 s) {
switch (s) {
case POSIX_SIGHUP:
return SIGHUP;
@@ -108,6 +120,8 @@ static s32 OrbisToNativeSignal(s32 s) {
return SIGTRAP;
case POSIX_SIGABRT:
return SIGABRT;
+ case POSIX_SIGEMT:
+ return _SIGEMT;
case POSIX_SIGFPE:
return SIGFPE;
case POSIX_SIGKILL:
@@ -150,22 +164,33 @@ static s32 OrbisToNativeSignal(s32 s) {
return SIGPROF;
case POSIX_SIGWINCH:
return SIGWINCH;
+ case POSIX_SIGINFO:
+ return _SIGINFO;
case POSIX_SIGUSR1:
return SIGUSR1;
case POSIX_SIGUSR2:
return SIGUSR2;
+ case 128:
+ return 0;
default:
+ if (s > 0 && s < 128) {
+ return s;
+ }
UNREACHABLE_MSG("Unknown signal {}", s);
}
}
#endif
-std::array Handlers{};
+#ifdef __APPLE__
+#define sigisemptyset(x) (*(x) == 0)
+#endif
+
+std::array Handlers{};
#ifndef _WIN64
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) {
- const auto handler = Handlers[native_signum];
+ const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
if (handler) {
auto ctx = Ucontext{};
#ifdef __APPLE__
@@ -214,6 +239,8 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context
ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr);
#endif
handler(NativeToOrbisSignal(native_signum), &ctx);
+ } else {
+ UNREACHABLE_MSG("Unhandled exception");
}
}
#else
@@ -221,7 +248,7 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
const char* thrName = (char*)arg1;
int native_signum = reinterpret_cast(arg2);
LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName);
- const auto handler = Handlers[native_signum];
+ const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
if (handler) {
auto ctx = Ucontext{};
ctx.uc_mcontext.mc_r8 = context->R8;
@@ -243,73 +270,105 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
ctx.uc_mcontext.mc_fs = context->SegFs;
ctx.uc_mcontext.mc_gs = context->SegGs;
handler(NativeToOrbisSignal(native_signum), &ctx);
+ } else {
+ UNREACHABLE_MSG("Unhandled exception");
}
}
#endif
-int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) {
- if (signum > POSIX_SIGUSR2) {
- return ORBIS_KERNEL_ERROR_EINVAL;
+s32 PS4_SYSV_ABI posix_sigemptyset(Sigset* s) {
+ s->bits[0] = 0;
+ s->bits[1] = 0;
+ return 0;
+}
+
+bool PS4_SYSV_ABI posix_sigisemptyset(Sigset* s) {
+ return s->bits[0] == 0 && s->bits[1] == 0;
+}
+
+s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) {
+ if (sig < 1 || sig > 128 || sig == POSIX_SIGTHR || sig == POSIX_SIGKILL ||
+ sig == POSIX_SIGSTOP) {
+ *__Error() = POSIX_EINVAL;
+ return ORBIS_FAIL;
+ }
+#ifdef _WIN32
+ LOG_ERROR(Lib_Kernel, "(STUBBED) called, sig: {}", sig);
+ Handlers[sig] = reinterpret_cast(
+ act ? act->__sigaction_handler.sigaction : nullptr);
+#else
+ s32 native_sig = OrbisToNativeSignal(sig);
+ if (native_sig == SIGVTALRM) {
+ LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE-reserved signal {}!", sig);
+ *__Error() = POSIX_EINVAL;
+ return ORBIS_FAIL;
+ }
+#ifndef __APPLE__
+ if (native_sig >= __SIGRTMIN && native_sig < SIGRTMIN) {
+ LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE libc-reserved signal {}!", sig);
+ *__Error() = POSIX_EINVAL;
+ return ORBIS_FAIL;
+ }
+#else
+ if (native_sig > SIGUSR2) {
+ LOG_ERROR(Lib_Kernel,
+ "Guest is attempting to use SIGRT signals, which aren't available on this "
+ "platform (signal: {})!",
+ sig);
}
- LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
- int const native_signum = OrbisToNativeSignal(signum);
-#ifdef __APPLE__
- ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!");
#endif
- ASSERT_MSG(!Handlers[native_signum], "Invalid parameters");
- Handlers[native_signum] = handler;
-#ifndef _WIN64
- if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
+ LOG_INFO(Lib_Kernel, "called, sig: {}, native sig: {}", sig, native_sig);
+ struct sigaction native_act{};
+ if (act) {
+ native_act.sa_flags = act->sa_flags; // todo check compatibility, on Linux it seems fine
+ native_act.sa_sigaction =
+ reinterpret_cast(SigactionHandler);
+ if (!posix_sigisemptyset(&act->sa_mask)) {
+ LOG_ERROR(Lib_Kernel, "Unhandled sa_mask: {:x}", act->sa_mask.bits[0]);
+ }
+ }
+ auto const prev_handler = Handlers[sig];
+ Handlers[sig] = reinterpret_cast(
+ act ? act->__sigaction_handler.sigaction : nullptr);
+
+ if (native_sig == SIGSEGV || native_sig == SIGBUS || native_sig == SIGILL) {
return ORBIS_OK; // These are handled in Core::SignalHandler
}
- struct sigaction act = {};
- act.sa_flags = SA_SIGINFO | SA_RESTART;
- act.sa_sigaction = reinterpret_cast(SigactionHandler);
- sigemptyset(&act.sa_mask);
- sigaction(native_signum, &act, nullptr);
-#endif
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
- if (signum > POSIX_SIGUSR2) {
- return ORBIS_KERNEL_ERROR_EINVAL;
+ if (native_sig > 127) {
+ LOG_WARNING(Lib_Kernel, "We can't install a handler for native signal {}!", native_sig);
+ return ORBIS_OK;
}
- int const native_signum = OrbisToNativeSignal(signum);
- ASSERT_MSG(Handlers[native_signum], "Invalid parameters");
- Handlers[native_signum] = nullptr;
-#ifndef _WIN64
- if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
- struct sigaction action{};
- action.sa_sigaction = Core::SignalHandler;
- action.sa_flags = SA_SIGINFO | SA_ONSTACK;
- sigemptyset(&action.sa_mask);
-
- ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0,
- "Failed to reinstate original signal handler for signal {}", native_signum);
- } else {
- struct sigaction act = {};
- act.sa_flags = SA_SIGINFO | SA_RESTART;
- act.sa_sigaction = nullptr;
- sigemptyset(&act.sa_mask);
- sigaction(native_signum, &act, nullptr);
+ struct sigaction native_oact{};
+ s32 ret = sigaction(native_sig, act ? &native_act : nullptr, oact ? &native_oact : nullptr);
+ if (oact) {
+ oact->sa_flags = native_oact.sa_flags;
+ oact->__sigaction_handler.sigaction =
+ reinterpret_cast__sigaction_handler.sigaction)>(prev_handler);
+ if (!sigisemptyset(&native_oact.sa_mask)) {
+ LOG_ERROR(Lib_Kernel, "Unhandled sa_mask");
+ }
+ }
+ if (ret < 0) {
+ LOG_ERROR(Lib_Kernel, "sigaction failed: {}", strerror(errno));
+ *__Error() = ErrnoToSceKernelError(errno);
+ return ORBIS_FAIL;
}
#endif
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
- if (signum != POSIX_SIGUSR1) {
- return ORBIS_KERNEL_ERROR_EINVAL;
+s32 PS4_SYSV_ABI posix_pthread_kill(PthreadT thread, s32 sig) {
+ if (sig < 1 || sig > 128) { // off-by-one error?
+ return POSIX_EINVAL;
}
- LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name);
- int const native_signum = OrbisToNativeSignal(signum);
+ LOG_WARNING(Lib_Kernel, "Raising signal {} on thread '{}'", sig, thread->name);
+ int const native_signum = OrbisToNativeSignal(sig);
#ifndef _WIN64
const auto pthr = reinterpret_cast(thread->native_thr.GetHandle());
const auto ret = pthread_kill(pthr, native_signum);
if (ret != 0) {
LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name,
- strerror(ret));
+ strerror(errno));
}
#else
USER_APC_OPTION option;
@@ -317,12 +376,73 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
u64 res = NtQueueApcThreadEx(reinterpret_cast(thread->native_thr.GetHandle()), option,
ExceptionHandler, (void*)thread->name.c_str(),
- (void*)native_signum, nullptr);
+ (void*)(s64)native_signum, nullptr);
ASSERT(res == 0);
#endif
return ORBIS_OK;
}
+// libkernel has a check in sceKernelInstallExceptionHandler and sceKernelRemoveExceptionHandler for
+// validating if the application requested a handler for an allowed signal or not. However, that is
+// just a wrapper for sigaction, which itself does not have any such restrictions, and therefore
+// this check is ridiculously trivial to go around. This, however, means that we need to support all
+// 127 - 3 possible signals, even if realistically, only homebrew will use most of them.
+static std::unordered_set orbis_allowed_signals{
+ POSIX_SIGHUP, POSIX_SIGILL, POSIX_SIGFPE, POSIX_SIGBUS, POSIX_SIGSEGV, POSIX_SIGUSR1,
+};
+
+int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, OrbisKernelExceptionHandler handler) {
+ if (!orbis_allowed_signals.contains(signum)) {
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+ if (Handlers[signum] != nullptr) {
+ return ORBIS_KERNEL_ERROR_EAGAIN;
+ }
+ LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
+ Sigaction act = {};
+ act.sa_flags = POSIX_SA_SIGINFO | POSIX_SA_RESTART;
+ act.__sigaction_handler.sigaction =
+ reinterpret_cast(handler);
+ posix_sigemptyset(&act.sa_mask);
+ s32 ret = posix_sigaction(signum, &act, nullptr);
+ if (ret < 0) {
+ LOG_ERROR(Lib_Kernel, "Failed to add handler for signal {}: {}", signum,
+ strerror(*__Error()));
+ return ErrnoToSceKernelError(*__Error());
+ }
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
+ if (!orbis_allowed_signals.contains(signum)) {
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+ int const native_signum = OrbisToNativeSignal(signum);
+ Handlers[signum] = nullptr;
+ Sigaction act = {};
+ act.sa_flags = POSIX_SA_SIGINFO;
+ act.__sigaction_handler.sigaction = nullptr;
+ posix_sigemptyset(&act.sa_mask);
+ s32 ret = posix_sigaction(signum, &act, nullptr);
+ if (ret < 0) {
+ LOG_ERROR(Lib_Kernel, "Failed to remove handler for signal {}: {}", signum,
+ strerror(*__Error()));
+ return ErrnoToSceKernelError(*__Error());
+ }
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
+ if (signum != POSIX_SIGUSR1) {
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+ s32 ret = posix_pthread_kill(thread, signum);
+ if (ret < 0) {
+ return ErrnoToSceKernelError(ret);
+ }
+ return ret;
+}
+
s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) {
if (unk != 0) {
return ORBIS_KERNEL_ERROR_EINVAL;
@@ -349,6 +469,13 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
sceKernelDebugRaiseExceptionOnReleaseMode);
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
+
+ LIB_FUNCTION("KiJEPEWRyUY", "libkernel", 1, "libkernel", posix_sigaction);
+ LIB_FUNCTION("+F7C-hdk7+E", "libkernel", 1, "libkernel", posix_sigemptyset);
+ LIB_FUNCTION("yH-uQW3LbX0", "libkernel", 1, "libkernel", posix_pthread_kill);
+ LIB_FUNCTION("KiJEPEWRyUY", "libScePosix", 1, "libkernel", posix_sigaction);
+ LIB_FUNCTION("+F7C-hdk7+E", "libScePosix", 1, "libkernel", posix_sigemptyset);
+ LIB_FUNCTION("yH-uQW3LbX0", "libScePosix", 1, "libkernel", posix_pthread_kill);
}
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/kernel/threads/exception.h b/src/core/libraries/kernel/threads/exception.h
index 42c92ab2e..c07242c1d 100644
--- a/src/core/libraries/kernel/threads/exception.h
+++ b/src/core/libraries/kernel/threads/exception.h
@@ -11,7 +11,7 @@ class SymbolsResolver;
namespace Libraries::Kernel {
-using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
+using OrbisKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
constexpr s32 POSIX_SIGHUP = 1;
constexpr s32 POSIX_SIGINT = 2;
@@ -47,6 +47,23 @@ constexpr s32 POSIX_SIGUSR2 = 31;
constexpr s32 POSIX_SIGTHR = 32;
constexpr s32 POSIX_SIGLIBRT = 33;
+#ifdef __linux__
+constexpr s32 _SIGEMT = 128;
+constexpr s32 _SIGINFO = 129;
+#elif !defined(_WIN32)
+constexpr s32 _SIGEMT = SIGEMT;
+constexpr s32 _SIGINFO = SIGINFO;
+#endif
+
+constexpr s32 POSIX_SA_NOCLDSTOP = 1;
+constexpr s32 POSIX_SA_NOCLDWAIT = 2;
+constexpr s32 POSIX_SA_SIGINFO = 4;
+constexpr s32 POSIX_SA_ONSTACK = 0x08000000;
+constexpr s32 POSIX_SA_RESTART = 0x10000000;
+constexpr s32 POSIX_SA_NODEFER = 0x40000000;
+constexpr s32 POSIX_SA_RESETHAND = 0x80000000;
+constexpr s32 POSIX_SA_RESTORER = 0x04000000;
+
struct Mcontext {
u64 mc_onstack;
u64 mc_rdi;
@@ -101,17 +118,74 @@ struct Sigset {
u64 bits[2];
};
+union Sigval {
+ /* Members as suggested by Annex C of POSIX 1003.1b. */
+ int sival_int;
+ void* sival_ptr;
+ /* 6.0 compatibility */
+ int sigval_int;
+ void* sigval_ptr;
+};
+
+struct Siginfo {
+ int _si_signo; /* signal number */
+ int _si_errno; /* errno association */
+ /*
+ * Cause of signal, one of the SI_ macros or signal-specific
+ * values, i.e. one of the FPE_... values for SIGFPE. This
+ * value is equivalent to the second argument to an old-style
+ * FreeBSD signal handler.
+ */
+ int _si_code; /* signal code */
+ s32 _si_pid; /* sending process */
+ u32 _si_uid; /* sender's ruid */
+ int _si_status; /* exit value */
+ void* _si_addr; /* faulting instruction */
+ union Sigval _si_value; /* signal value */
+ union {
+ struct {
+ int _trapno; /* machine specific trap code */
+ } _fault;
+ struct {
+ int _timerid;
+ int _overrun;
+ } _timer;
+ struct {
+ int _mqd;
+ } _mesgq;
+ struct {
+ long _band; /* band event for SIGPOLL */
+ } _poll; /* was this ever used ? */
+ struct {
+ long __spare1__;
+ int __spare2__[7];
+ } __spare__;
+ } _reason;
+};
+
+struct Sigaction {
+ union {
+ void (*handler)(int);
+ void (*sigaction)(int, struct Siginfo*, void*);
+ } __sigaction_handler;
+ int sa_flags;
+ Sigset sa_mask;
+};
+
struct Ucontext {
struct Sigset uc_sigmask;
int field1_0x10[12];
- struct Mcontext uc_mcontext;
- struct Ucontext* uc_link;
- struct ExStack uc_stack;
+ Mcontext uc_mcontext;
+ Ucontext* uc_link;
+ ExStack uc_stack;
int uc_flags;
int __spare[4];
int field7_0x4f4[3];
};
+s32 NativeToOrbisSignal(s32 s);
+s32 OrbisToNativeSignal(s32 s);
+
void RegisterException(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp
index 20bd20f4b..3742db5cf 100644
--- a/src/core/libraries/kernel/threads/pthread.cpp
+++ b/src/core/libraries/kernel/threads/pthread.cpp
@@ -199,10 +199,17 @@ static void RunThread(void* arg) {
g_curthread = curthread;
Common::SetCurrentThreadName(curthread->name.c_str());
DebugState.AddCurrentThreadToGuestList();
+ Core::InitializeTLS();
+
+ curthread->native_thr.Initialize();
+
+ // Clear the stack before running the guest thread
+ if (False(g_curthread->attr.flags & PthreadAttrFlags::StackUser)) {
+ ClearStack();
+ }
/* Run the current thread's start routine with argument: */
- curthread->native_thr.Initialize();
- void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg);
+ void* ret = curthread->start_routine(curthread->arg);
/* Remove thread from tracking */
DebugState.RemoveCurrentThreadFromGuestList();
@@ -235,7 +242,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
new_thread->attr.sched_policy = curthread->attr.sched_policy;
}
- static int TidCounter = 1;
+ static std::atomic TidCounter = 1;
new_thread->tid = ++TidCounter;
if (new_thread->attr.stackaddr_attr == nullptr) {
@@ -658,6 +665,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
+ LIB_FUNCTION("Jmi+9w9u0E4", "libkernel", 1, "libkernel", posix_pthread_create_name_np);
LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);
diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp
index 094866a5a..38032f174 100644
--- a/src/core/libraries/kernel/threads/pthread_spec.cpp
+++ b/src/core/libraries/kernel/threads/pthread_spec.cpp
@@ -84,7 +84,7 @@ void _thread_cleanupspecific() {
* destructor:
*/
lk.unlock();
- Core::ExecuteGuest(destructor, data);
+ destructor(data);
lk.lock();
}
}
diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp
index eebb991dc..ac35c4b63 100644
--- a/src/core/libraries/libs.cpp
+++ b/src/core/libraries/libs.cpp
@@ -57,10 +57,10 @@
#include "core/libraries/screenshot/screenshot.h"
#include "core/libraries/share_play/shareplay.h"
#include "core/libraries/signin_dialog/signindialog.h"
+#include "core/libraries/sysmodule/sysmodule.h"
#include "core/libraries/system/commondialog.h"
#include "core/libraries/system/msgdialog.h"
#include "core/libraries/system/posix.h"
-#include "core/libraries/system/sysmodule.h"
#include "core/libraries/system/systemservice.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/system_gesture/system_gesture.h"
diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp
index 8bc9b51f0..4d6886908 100644
--- a/src/core/libraries/network/http.cpp
+++ b/src/core/libraries/network/http.cpp
@@ -935,18 +935,24 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
pathLength++;
}
- // Ensure the path starts with '/'
- if (pathLength > 0 && pathStart[0] != '/') {
+ if (pathLength > 0) {
// Prepend '/' to the path
requiredSize += pathLength + 2; // Include '/' and null terminator
if (pool && prepare < requiredSize) {
- LOG_ERROR(Lib_Http, "out of memory");
+ LOG_ERROR(Lib_Http, "out of memory, provided size: {}, required size: {}",
+ prepare, requiredSize);
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
}
if (out && pool) {
out->path = (char*)pool + (requiredSize - pathLength - 2);
+ out->username = (char*)pool + (requiredSize - pathLength - 3);
+ out->password = (char*)pool + (requiredSize - pathLength - 3);
+ out->hostname = (char*)pool + (requiredSize - pathLength - 3);
+ out->query = (char*)pool + (requiredSize - pathLength - 3);
+ out->fragment = (char*)pool + (requiredSize - pathLength - 3);
+ out->username[0] = '\0';
out->path[0] = '/'; // Add leading '/'
memcpy(out->path + 1, pathStart, pathLength);
out->path[pathLength + 1] = '\0';
@@ -969,6 +975,19 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Move past the path
offset += pathLength;
+ } else {
+ // Parse the path (everything after the slashes)
+ char* pathStart = (char*)srcUri + offset;
+ u64 pathLength = 0;
+ while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
+ pathStart[pathLength] != '#') {
+ pathLength++;
+ }
+
+ if (pathLength > 0) {
+ requiredSize += pathLength + 3; // Add '/' and null terminator, and the dummy
+ // null character for the other fields
+ }
}
}
diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp
index 102447952..ca75ad394 100644
--- a/src/core/libraries/network/net.cpp
+++ b/src/core/libraries/network/net.cpp
@@ -887,6 +887,10 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events,
}
file->resolver->Resolve();
+ if (file->resolver->resolution_error != ORBIS_OK) {
+ // Resolution failed, shouldn't appear.
+ continue;
+ }
const auto it =
std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == rid; });
@@ -1402,8 +1406,21 @@ int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid) {
}
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status) {
- LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid);
- *status = 0;
+ if (!status) {
+ LOG_ERROR(Lib_Net, "status == nullptr");
+ *sceNetErrnoLoc() = ORBIS_NET_EINVAL;
+ return ORBIS_NET_ERROR_EINVAL;
+ }
+
+ auto file = FDTable::Instance()->GetResolver(resolverid);
+ if (!file) {
+ LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid);
+ *sceNetErrnoLoc() = ORBIS_NET_EBADF;
+ return ORBIS_NET_ERROR_EBADF;
+ }
+
+ *status = file->resolver->resolution_error;
+ LOG_INFO(Lib_Net, "called rid = {}, error = {:#x}", resolverid, static_cast(*status));
return ORBIS_OK;
}
@@ -1425,10 +1442,17 @@ int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* host
auto file = FDTable::Instance()->GetResolver(resolverid);
if (!file) {
+ LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid);
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
+ if (!Config::getIsConnectedToNetwork()) {
+ *sceNetErrnoLoc() = ORBIS_NET_RESOLVER_ENODNS;
+ file->resolver->resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
+ return ORBIS_NET_ERROR_RESOLVER_ENODNS;
+ }
+
if ((flags & ORBIS_NET_RESOLVER_ASYNC) != 0) {
return file->resolver->ResolveAsync(hostname, addr, timeout, retry, flags);
}
diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp
index a295477b6..a4081cd11 100644
--- a/src/core/libraries/network/net_ctl_obj.cpp
+++ b/src/core/libraries/network/net_ctl_obj.cpp
@@ -50,7 +50,7 @@ void NetCtlInternal::CheckCallback() {
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : callbacks) {
if (func != nullptr) {
- Core::ExecuteGuest(func, event, arg);
+ func(event, arg);
}
}
}
@@ -61,7 +61,7 @@ void NetCtlInternal::CheckNpToolkitCallback() {
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : nptool_callbacks) {
if (func != nullptr) {
- Core::ExecuteGuest(func, event, arg);
+ func(event, arg);
}
}
}
diff --git a/src/core/libraries/network/net_resolver.cpp b/src/core/libraries/network/net_resolver.cpp
index 6571176df..7eb4c4001 100644
--- a/src/core/libraries/network/net_resolver.cpp
+++ b/src/core/libraries/network/net_resolver.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
+#include "common/config.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/libraries/error_codes.h"
@@ -26,11 +27,19 @@ int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeo
}
void Resolver::Resolve() {
+ if (!Config::getIsConnectedToNetwork()) {
+ resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
+ return;
+ }
+
if (async_resolution) {
auto* netinfo = Common::Singleton::Instance();
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
-
resolution_error = ret;
+ if (ret != ORBIS_OK) {
+ // Resolver errors are stored as ORBIS_NET_ERROR values.
+ resolution_error = -ret | ORBIS_NET_ERROR_BASE;
+ }
} else {
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
}
diff --git a/src/core/libraries/network/net_resolver.h b/src/core/libraries/network/net_resolver.h
index 34d7dc591..4c5c2ece8 100644
--- a/src/core/libraries/network/net_resolver.h
+++ b/src/core/libraries/network/net_resolver.h
@@ -18,6 +18,8 @@ public:
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
void Resolve();
+ int resolution_error = ORBIS_OK;
+
private:
struct AsyncResolution {
const char* hostname;
@@ -31,7 +33,6 @@ private:
int poolid;
int flags;
std::optional async_resolution{};
- int resolution_error = ORBIS_OK;
std::mutex m_mutex;
};
diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp
index f2adccf50..164d85896 100644
--- a/src/core/libraries/network/posix_sockets.cpp
+++ b/src/core/libraries/network/posix_sockets.cpp
@@ -430,6 +430,15 @@ int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
sockaddr addr2;
convertOrbisNetSockaddrToPosix(addr, &addr2);
int result = ::connect(sock, &addr2, sizeof(sockaddr_in));
+#ifdef _WIN32
+ // Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS
+ // Step in here on errors to address this.
+ if (result == -1) {
+ if (WSAGetLastError() == WSAEWOULDBLOCK) {
+ WSASetLastError(WSAEINPROGRESS);
+ }
+ }
+#endif
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
result == -1 ? Common::GetLastErrorMsg() : "none");
return ConvertReturnErrorCode(result);
diff --git a/src/core/libraries/network/ssl2.cpp b/src/core/libraries/network/ssl2.cpp
index 682095801..3a7fd71e5 100644
--- a/src/core/libraries/network/ssl2.cpp
+++ b/src/core/libraries/network/ssl2.cpp
@@ -5,6 +5,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/ssl2.h"
+#include "core/libraries/network/ssl2_error.h"
namespace Libraries::Ssl2 {
@@ -108,8 +109,19 @@ int PS4_SYSV_ABI sceSslEnableVerifyOption() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceSslFreeCaCerts() {
- LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
+int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
+ LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
+ if (certs == nullptr) {
+ return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
+ }
+ if (certs->certs != nullptr) {
+ for (s32 data = 0; data < certs->num; data++) {
+ free(certs->certs[data].ptr);
+ }
+ delete (certs->certs);
+ }
+
+ // delete (certs->pool);
return ORBIS_OK;
}
@@ -128,17 +140,18 @@ int PS4_SYSV_ABI sceSslGetAlpnSelected() {
return ORBIS_OK;
}
-struct OrbisSslCaCerts {
- void* certs;
- u64 num;
- void* pool;
-};
-
-int PS4_SYSV_ABI sceSslGetCaCerts(int sslCtxId, OrbisSslCaCerts* certs) {
- // check if it is same as libSceSsl
+int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
- certs->certs = nullptr;
- certs->num = 0;
+ if (certs == nullptr) {
+ return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
+ }
+ // Allocate a buffer to store dummy data in.
+ const char* dummy_data = "dummy";
+ u64 dummy_length = strlen(dummy_data) + 1;
+ char* data = static_cast(malloc(dummy_length));
+ strncpy(data, dummy_data, dummy_length);
+ certs->certs = new OrbisSslData{data, dummy_length};
+ certs->num = 1;
certs->pool = nullptr;
return ORBIS_OK;
}
diff --git a/src/core/libraries/network/ssl2.h b/src/core/libraries/network/ssl2.h
index 754dda40c..18fb205d3 100644
--- a/src/core/libraries/network/ssl2.h
+++ b/src/core/libraries/network/ssl2.h
@@ -10,5 +10,17 @@ class SymbolsResolver;
}
namespace Libraries::Ssl2 {
+
+struct OrbisSslData {
+ char* ptr;
+ u64 size;
+};
+
+struct OrbisSslCaCerts {
+ OrbisSslData* certs;
+ u64 num;
+ void* pool;
+};
+
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Ssl2
\ No newline at end of file
diff --git a/src/core/libraries/network/ssl2_error.h b/src/core/libraries/network/ssl2_error.h
new file mode 100644
index 000000000..03bf94256
--- /dev/null
+++ b/src/core/libraries/network/ssl2_error.h
@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/libraries/error_codes.h"
+
+constexpr int ORBIS_SSL_ERROR_INVALID_ARGUMENT = 0x8095F007;
\ No newline at end of file
diff --git a/src/core/libraries/network/sys_net.cpp b/src/core/libraries/network/sys_net.cpp
index 76107d323..a0fae3a58 100644
--- a/src/core/libraries/network/sys_net.cpp
+++ b/src/core/libraries/network/sys_net.cpp
@@ -28,8 +28,11 @@ int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 add
if (returncode >= 0) {
return returncode;
}
- LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
- (u32)*Libraries::Kernel::__Error());
+ u32 error = *Libraries::Kernel::__Error();
+ // Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication.
+ if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) {
+ LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
+ }
return -1;
}
@@ -59,8 +62,13 @@ int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen)
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
auto new_sock = file->socket->Accept(addr, paddrlen);
if (!new_sock) {
- LOG_ERROR(Lib_Net, "s = {} ({}) returned error code creating new socket for accepting: {}",
- s, file->m_guest_name, (u32)*Libraries::Kernel::__Error());
+ u32 error = *Libraries::Kernel::__Error();
+ // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
+ if (error != ORBIS_NET_EWOULDBLOCK) {
+ LOG_ERROR(Lib_Net,
+ "s = {} ({}) returned error code creating new socket for accepting: {}", s,
+ file->m_guest_name, error);
+ }
return -1;
}
auto fd = FDTable::Instance()->CreateHandle();
@@ -396,8 +404,11 @@ s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, Orbis
if (returncode >= 0) {
return returncode;
}
- LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
- (u32)*Libraries::Kernel::__Error());
+ // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
+ u32 error = *Libraries::Kernel::__Error();
+ if (error != ORBIS_NET_EWOULDBLOCK) {
+ LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
+ }
return -1;
}
diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp
index 2f785f9a0..97d19c352 100644
--- a/src/core/libraries/ngs2/ngs2.cpp
+++ b/src/core/libraries/ngs2/ngs2.cpp
@@ -160,13 +160,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o
result = SystemSetup(option, &bufferInfo, 0, 0);
if (result >= 0) {
uintptr_t sysUserData = allocator->userData;
- result = Core::ExecuteGuest(hostAlloc, &bufferInfo);
+ result = hostAlloc(&bufferInfo);
if (result >= 0) {
OrbisNgs2Handle* handleCopy = outHandle;
result = SystemSetup(option, &bufferInfo, hostFree, handleCopy);
if (result < 0) {
if (hostFree) {
- Core::ExecuteGuest(hostFree, &bufferInfo);
+ hostFree(&bufferInfo);
}
}
}
diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp
index a5199c297..48b086457 100644
--- a/src/core/libraries/save_data/savedata.cpp
+++ b/src/core/libraries/save_data/savedata.cpp
@@ -333,6 +333,9 @@ static bool match(std::string_view str, std::string_view pattern) {
for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) {
if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) {
return true;
+ } else if (str_wild_it == str.end()) {
+ // Avoid incrementing str_wild_it past str.end().
+ break;
}
}
return false;
diff --git a/src/core/libraries/sysmodule/sysmodule.cpp b/src/core/libraries/sysmodule/sysmodule.cpp
new file mode 100644
index 000000000..1ad9075e7
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule.cpp
@@ -0,0 +1,204 @@
+// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+
+#include "common/elf_info.h"
+#include "common/logging/log.h"
+#include "core/libraries/error_codes.h"
+#include "core/libraries/kernel/orbis_error.h"
+#include "core/libraries/kernel/process.h"
+#include "core/libraries/libs.h"
+#include "core/libraries/sysmodule/sysmodule.h"
+#include "core/libraries/sysmodule/sysmodule_error.h"
+#include "core/libraries/sysmodule/sysmodule_internal.h"
+#include "core/linker.h"
+
+namespace Libraries::SysModule {
+
+static std::mutex g_mutex{};
+
+s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle) {
+ LOG_INFO(Lib_SysModule, "called");
+ if ((id & 0x7fffffff) == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ std::scoped_lock lk{g_mutex};
+ return getModuleHandle(id, handle);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
+ Kernel::OrbisModuleInfoForUnwind* info) {
+ LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind called");
+ s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info);
+ if (res != ORBIS_OK) {
+ return res;
+ }
+
+ if (shouldHideName(info->name.data())) {
+ std::ranges::fill(info->name, '\0');
+ }
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) {
+ if (id == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ std::scoped_lock lk{g_mutex};
+ return getModuleHandle(id, nullptr);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) {
+ if ((id & 0x7fffffff) == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ std::scoped_lock lk{g_mutex};
+ return getModuleHandle(id, nullptr);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) {
+ LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
+ s32 result = validateModuleId(id);
+ if (result < ORBIS_OK) {
+ return result;
+ }
+
+ // Only locks for internal loadModule call.
+ {
+ std::scoped_lock lk{g_mutex};
+ result = loadModule(id, 0, nullptr, nullptr);
+ }
+
+ if (result == ORBIS_KERNEL_ERROR_ESTART) {
+ s32 sdk_ver = 0;
+ result = Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver);
+ if (sdk_ver < Common::ElfInfo::FW_115 || result != ORBIS_OK) {
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ } else {
+ return ORBIS_KERNEL_ERROR_ESTART;
+ }
+ }
+
+ // The real library has some weird workaround for CUSA01478 and CUSA01495 here.
+ // Unless this is proven necessary, I don't plan to handle this.
+ return result;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id) {
+ LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
+ s32 result = validateModuleId(id);
+ if (result < ORBIS_OK) {
+ return result;
+ }
+
+ // This specific module ID is loaded unlocked.
+ if (id == 0x80000039) {
+ return loadModule(id, 0, nullptr, nullptr);
+ }
+ std::scoped_lock lk{g_mutex};
+ return loadModule(id, 0, nullptr, nullptr);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc,
+ const void* argv, u64 unk, s32* res_out) {
+ LOG_INFO(Lib_SysModule, "called, id = {:#x}", id);
+ s32 result = validateModuleId(id);
+ if (result < ORBIS_OK) {
+ return result;
+ }
+
+ if (unk != 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ std::scoped_lock lk{g_mutex};
+ return loadModule(id, argc, argv, res_out);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() {
+ LOG_DEBUG(Lib_SysModule, "called");
+ return preloadModulesForLibkernel();
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id) {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called, id = {:#x}", id);
+ if (id == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ std::scoped_lock lk{g_mutex};
+ return unloadModule(id, 0, nullptr, nullptr, false);
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() {
+ LOG_ERROR(Lib_SysModule, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+void RegisterLib(Core::Loader::SymbolsResolver* sym) {
+ LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleGetModuleHandleInternal);
+ LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleGetModuleInfoForUnwind);
+ LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleIsCalledFromSysModule);
+ LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleIsCameraPreloaded);
+ LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded);
+ LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleIsLoadedInternal);
+ LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule);
+ LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleLoadModuleByNameInternal);
+ LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleLoadModuleInternal);
+ LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleLoadModuleInternalWithArg);
+ LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleMapLibcForLibkernel);
+ LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmodulePreloadModuleForLibkernel);
+ LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule);
+ LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleUnloadModuleByNameInternal);
+ LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleUnloadModuleInternal);
+ LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule",
+ sceSysmoduleUnloadModuleInternalWithArg);
+};
+
+} // namespace Libraries::SysModule
diff --git a/src/core/libraries/sysmodule/sysmodule.h b/src/core/libraries/sysmodule/sysmodule.h
new file mode 100644
index 000000000..17ac3188f
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/types.h"
+#include "core/libraries/kernel/process.h"
+
+namespace Core::Loader {
+class SymbolsResolver;
+}
+
+namespace Libraries::SysModule {
+
+using OrbisSysModule = u16;
+using OrbisSysModuleInternal = u32;
+
+s32 PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal(OrbisSysModuleInternal id, s32* handle);
+s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
+ Kernel::OrbisModuleInfoForUnwind* info);
+s32 PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule();
+s32 PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded();
+s32 PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id);
+s32 PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id);
+s32 PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id);
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal();
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternal(OrbisSysModuleInternal id);
+s32 PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg(OrbisSysModuleInternal id, s32 argc,
+ const void* argv, u64 unk, s32* res_out);
+s32 PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel();
+s32 PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel();
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModule(OrbisSysModule id);
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal();
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal();
+s32 PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg();
+
+void RegisterLib(Core::Loader::SymbolsResolver* sym);
+} // namespace Libraries::SysModule
diff --git a/src/core/libraries/sysmodule/sysmodule_error.h b/src/core/libraries/sysmodule/sysmodule_error.h
new file mode 100644
index 000000000..aee14b9df
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule_error.h
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/types.h"
+
+constexpr s32 ORBIS_SYSMODULE_INVALID_ID = 0x805A1000;
+constexpr s32 ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001;
+constexpr s32 ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF;
\ No newline at end of file
diff --git a/src/core/libraries/sysmodule/sysmodule_internal.cpp b/src/core/libraries/sysmodule/sysmodule_internal.cpp
new file mode 100644
index 000000000..def410e25
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule_internal.cpp
@@ -0,0 +1,444 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/config.h"
+#include "common/elf_info.h"
+#include "common/logging/log.h"
+#include "core/file_sys/fs.h"
+#include "core/libraries/disc_map/disc_map.h"
+#include "core/libraries/font/font.h"
+#include "core/libraries/font/fontft.h"
+#include "core/libraries/jpeg/jpegenc.h"
+#include "core/libraries/kernel/kernel.h"
+#include "core/libraries/kernel/orbis_error.h"
+#include "core/libraries/libc_internal/libc_internal.h"
+#include "core/libraries/libpng/pngenc.h"
+#include "core/libraries/libs.h"
+#include "core/libraries/ngs2/ngs2.h"
+#include "core/libraries/rtc/rtc.h"
+#include "core/libraries/sysmodule/sysmodule_error.h"
+#include "core/libraries/sysmodule/sysmodule_internal.h"
+#include "core/libraries/sysmodule/sysmodule_table.h"
+#include "core/linker.h"
+#include "emulator.h"
+
+namespace Libraries::SysModule {
+
+s32 getModuleHandle(s32 id, s32* handle) {
+ if (id == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+ for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
+ if (mod.id != id) {
+ continue;
+ }
+ if (mod.is_loaded < 1) {
+ return ORBIS_SYSMODULE_NOT_LOADED;
+ }
+ if (handle != nullptr) {
+ *handle = mod.handle;
+ }
+ return ORBIS_OK;
+ }
+ return ORBIS_SYSMODULE_INVALID_ID;
+}
+
+bool shouldHideName(const char* module_name) {
+ for (u64 i = 0; i < g_num_modules; i++) {
+ OrbisSysmoduleModuleInternal mod = g_modules_array[i];
+ if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) == 0) {
+ continue;
+ }
+ u64 name_length = std::strlen(mod.name);
+ char name_copy[0x100];
+ std::strncpy(name_copy, mod.name, sizeof(name_copy));
+ // Module table stores names without extensions, so check with .prx appended to the name.
+ std::strncpy(&name_copy[name_length], ".prx", 4);
+ s32 result = std::strncmp(module_name, name_copy, sizeof(name_copy));
+ if (result == 0) {
+ return true;
+ }
+
+ // libSceFios2 and libc are checked as both sprx or prx modules.
+ if (i == 3) {
+ result = std::strncmp(module_name, "libSceFios2.sprx", sizeof(name_copy));
+ } else if (i == 4) {
+ result = std::strncmp(module_name, "libc.sprx", sizeof(name_copy));
+ }
+
+ if (result == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool isDebugModule(s32 id) {
+ for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
+ if (mod.id == id && (mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool validateModuleId(s32 id) {
+ if ((id & 0x7fffffff) == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ s32 sdk_ver = 0;
+ ASSERT_MSG(!Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver),
+ "Failed to retrieve compiled SDK version");
+
+ // libSceGameCustomDialog isn't loadable on SDK >= 7.50
+ if (id == 0xb8 && sdk_ver >= Common::ElfInfo::FW_75) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ // libSceNpSnsFacebookDialog isn't loadable on SDK >= 7.00
+ if (id == 0xb0 && sdk_ver >= Common::ElfInfo::FW_70) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ // libSceJson isn't loadable on SDK >= 3.00
+ if (id == 0x80 && sdk_ver >= Common::ElfInfo::FW_30) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ // Cannot load debug modules on retail hardware.
+ if (isDebugModule(id) && !Config::isDevKitConsole()) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ return ORBIS_OK;
+}
+
+s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) {
+ auto* mnt = Common::Singleton::Instance();
+ auto* linker = Common::Singleton::Instance();
+ auto* game_info = Common::Singleton::Instance();
+
+ // If the module is already loaded, increment is_loaded and return ORBIS_OK.
+ OrbisSysmoduleModuleInternal& mod = g_modules_array[index];
+ if (mod.is_loaded > 0) {
+ mod.is_loaded++;
+ return ORBIS_OK;
+ }
+
+ s32 start_result = 0;
+ // Most of the logic the actual module has here is to get the correct location of this module.
+ // Since we only care about a small subset of LLEs, we can simplify this logic.
+ if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsGame) != 0) {
+ std::string guest_path = std::string("/app0/sce_module/").append(mod.name);
+ guest_path.append(".prx");
+ const auto& host_path = mnt->GetHostPath(guest_path);
+
+ // For convenience, load through linker directly instead of loading through libkernel calls.
+ s32 result = linker->LoadAndStartModule(host_path, argc, argv, &start_result);
+ // If the module is missing, the library prints a very helpful message for developers.
+ // We'll just log an error.
+ if (result < 0) {
+ LOG_ERROR(Lib_SysModule, "Failed to load game library {}", guest_path);
+ return result;
+ } else {
+ // On success, the library validates module params and the module SDK version.
+ // We don't store the information this uses, so skip the proper checks.
+ mod.handle = result;
+ mod.is_loaded++;
+ }
+ } else {
+ // This is not a game library. We'll need to perform some checks,
+ // but we don't need to perform the path resolution logic the actual library has.
+ std::string mod_name = std::string(mod.name);
+
+ // libSceGnmDriver case
+ if (index == 0xd && Config::isDevKitConsole()) {
+ // There are some other checks involved here that I am not familiar with.
+ // Since we're not exactly running libSceGnmDriver LLE, this shouldn't matter too much.
+ mod_name.append("_padebug");
+ }
+
+ // libSceSsl2 case
+ if (index == 0x27 && false /*needs legacy ssl*/) {
+ // Replaces module name with libSceSsl (index 0x15)
+ mod_name.clear();
+ mod_name.append(g_modules_array[0x15].name);
+ }
+
+ // libSceVrTracker case
+ if (index == 0xb3 && Config::isDevKitConsole()) {
+ mod_name.append("_debug");
+ }
+
+ if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) == 0 &&
+ (mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeoMode) != 0 &&
+ Kernel::sceKernelIsNeoMode() == 1) {
+ // PS4 Pro running in enhanced mode
+ mod_name.append("ForNeoMode");
+ } else if ((mod.flags & OrbisSysmoduleModuleInternalFlags::IsNeo) != 0 &&
+ Config::isNeoModeConsole()) {
+ // PS4 Pro running in base mode
+ mod_name.append("ForNeo");
+ }
+
+ // Append .sprx extension.
+ mod_name.append(".sprx");
+
+ // Now we need to check if the requested library is allowed to LLE.
+ // First, we allow all modules from game-specific sys_modules
+ const auto& sys_module_path = Config::getSysModulesPath();
+ const auto& game_specific_module_path =
+ sys_module_path / game_info->GameSerial() / mod_name;
+ if (std::filesystem::exists(game_specific_module_path)) {
+ // The requested module is present in the game-specific sys_modules, load it.
+ LOG_INFO(Loader, "Loading {} from game serial file {}", mod_name,
+ game_info->GameSerial());
+ s32 handle =
+ linker->LoadAndStartModule(game_specific_module_path, argc, argv, &start_result);
+ ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name);
+ mod.handle = handle;
+ mod.is_loaded++;
+ if (res_out != nullptr) {
+ *res_out = start_result;
+ }
+ return ORBIS_OK;
+ }
+
+ // We need to check a few things here.
+ // First, check if this is a module we allow LLE for.
+ static s32 stub_handle = 100;
+ constexpr auto ModulesToLoad = std::to_array(
+ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},
+ {"libSceUlt.sprx", nullptr},
+ {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib},
+ {"libSceJpegDec.sprx", nullptr},
+ {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib},
+ {"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
+ {"libSceJson.sprx", nullptr},
+ {"libSceJson2.sprx", nullptr},
+ {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
+ {"libSceCesCs.sprx", nullptr},
+ {"libSceAudiodec.sprx", nullptr},
+ {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
+ {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
+ {"libSceFreeTypeOt.sprx", nullptr}});
+
+ // Iterate through the allowed array
+ const auto it = std::ranges::find_if(
+ ModulesToLoad, [&](Core::SysModules module) { return mod_name == module.module_name; });
+ if (it == ModulesToLoad.end()) {
+ // Not an allowed LLE, stub success without warning.
+ mod.is_loaded++;
+ // Some internal checks rely on a handle, stub a valid one.
+ mod.handle = stub_handle++;
+ if (res_out != nullptr) {
+ *res_out = ORBIS_OK;
+ }
+ return ORBIS_OK;
+ }
+
+ // Allowed module, check if it exists
+ const auto& module_path = sys_module_path / mod_name;
+ if (std::filesystem::exists(module_path)) {
+ LOG_INFO(Loader, "Loading {}", mod_name);
+ s32 handle = linker->LoadAndStartModule(module_path, argc, argv, &start_result);
+ ASSERT_MSG(handle >= 0, "Failed to load module {}", mod_name);
+ mod.handle = handle;
+ } else {
+ // Allowed LLE that isn't present, log message
+ auto& [name, init_func] = *it;
+ if (init_func) {
+ LOG_INFO(Loader, "Can't Load {} switching to HLE", mod_name);
+ init_func(&linker->GetHLESymbols());
+
+ // When loading HLEs, we need to relocate imports
+ // This ensures later module loads can see our HLE functions.
+ linker->RelocateAllImports();
+ } else {
+ LOG_INFO(Loader, "No HLE available for {} module", mod_name);
+ }
+ mod.handle = stub_handle++;
+ }
+
+ // Mark module as loaded.
+ mod.is_loaded++;
+ }
+
+ // Only successful loads will reach here
+ if (res_out != nullptr) {
+ *res_out = start_result;
+ }
+
+ return ORBIS_OK;
+}
+
+s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out) {
+ // Retrieve the module to load from the table
+ OrbisSysmoduleModuleInternal requested_module{};
+ for (OrbisSysmoduleModuleInternal mod : g_modules_array) {
+ if (mod.id == id) {
+ requested_module = mod;
+ break;
+ }
+ }
+ if (requested_module.id != id || requested_module.id == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ // Every module has a pointer to an array of indexes to modules that need loading.
+ if (requested_module.to_load == nullptr) {
+ // Seems like ORBIS_SYSMODULE_LOCK_FAILED is a generic internal error.
+ return ORBIS_SYSMODULE_LOCK_FAILED;
+ }
+
+ LOG_INFO(Lib_SysModule, "Loading {}", requested_module.name);
+
+ // Loop through every module that requires loading, in reverse order
+ for (s64 i = requested_module.num_to_load - 1; i >= 0; i--) {
+ // Modules flagged as debug modules only load for devkits
+ u32 mod_index = requested_module.to_load[i];
+ if ((!Config::isDevKitConsole() &&
+ g_modules_array[mod_index].flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0) {
+ continue;
+ }
+
+ // Arguments and result should only be applied to the requested module
+ // Dependencies don't receive these values.
+ s32 result = 0;
+ if (i != 0) {
+ result = loadModuleInternal(mod_index, 0, nullptr, nullptr);
+ } else {
+ result = loadModuleInternal(mod_index, argc, argv, res_out);
+ }
+
+ // If loading any module fails, abort there.
+ if (result != ORBIS_OK) {
+ return result;
+ }
+ }
+ return ORBIS_OK;
+}
+
+s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal) {
+ OrbisSysmoduleModuleInternal mod{};
+ for (s32 i = 0; i < g_modules_array.size(); i++) {
+ mod = g_modules_array[i];
+ if (mod.id != id) {
+ continue;
+ }
+
+ // Skips checking libSceDiscMap
+ if (i == 0x22) {
+ continue;
+ }
+
+ // If the module is loaded once, and is part of the second preload list,
+ // then return OK and do nothing.
+ for (s32 index : g_preload_list_2) {
+ if (index == i && mod.is_loaded == 1) {
+ return ORBIS_OK;
+ }
+ }
+
+ // Found the correct module.
+ break;
+ }
+
+ // If we failed to locate the module, return invalid id.
+ if (mod.id != id || mod.id == 0) {
+ return ORBIS_SYSMODULE_INVALID_ID;
+ }
+
+ // If the module has no dependencies, then return an internal error.
+ if (mod.num_to_load == 0 || mod.to_load == nullptr) {
+ return ORBIS_SYSMODULE_LOCK_FAILED;
+ }
+
+ // Unload the module and it's dependencies
+ for (s64 i = 0; i < mod.num_to_load; i++) {
+ OrbisSysmoduleModuleInternal dep_mod = g_modules_array[mod.to_load[i]];
+ // If this is a debug module and we're not emulating a devkit, skip it.
+ if ((dep_mod.flags & OrbisSysmoduleModuleInternalFlags::IsDebug) != 0 &&
+ !Config::isDevKitConsole()) {
+ continue;
+ }
+
+ // If the module to unload is marked as unloaded, then return not loaded
+ if (dep_mod.is_loaded == 0) {
+ return ORBIS_SYSMODULE_NOT_LOADED;
+ }
+
+ // By this point, all necessary checks are performed, decrement the load count.
+ dep_mod.is_loaded--;
+
+ // Normally, this is where the real library would actually unload the module,
+ // through a call to sceKernelStopUnloadModule.
+ // As we don't implement module unloading, this behavior is skipped.
+
+ // Stub success during requested module unload.
+ if (i == 0 && res_out != nullptr) {
+ *res_out = ORBIS_OK;
+ }
+ }
+ return ORBIS_OK;
+}
+
+s32 preloadModulesForLibkernel() {
+ // For now, default to loading g_preload_list_3.
+ // As far as I can tell, g_preload_list_1 seems to be some sort of list with libs
+ // that games don't typically use, and g_preload_list_2 is just a reorganized version of 3.
+ s32 sdk_ver = 0;
+ ASSERT_MSG(Kernel::sceKernelGetCompiledSdkVersion(&sdk_ver) == 0,
+ "Failed to get compiled SDK version");
+ for (u32 module_index : g_preload_list_3) {
+ // As per usual, these are arrays of indexes for g_modules_array
+ // libSceDbg, libScePerf, libSceMat, and libSceRazorCpu_debug.
+ // These are skipped unless this console is a devkit.
+ if ((module_index == 0x12 || module_index == 0x1e || module_index == 0x24 ||
+ module_index == 0x26) &&
+ !Config::isDevKitConsole()) {
+ continue;
+ }
+
+ // libSceDiscMap case, skipped on newer SDK versions.
+ if (module_index == 0x22 && sdk_ver >= Common::ElfInfo::FW_20) {
+ continue;
+ }
+
+ // libSceDbgAssist is skipped on non-testkit consoles.
+ // For now, stub check to non-devkit.
+ if (module_index == 0x23 && !Config::isDevKitConsole()) {
+ continue;
+ }
+
+ // libSceRazorCpu, skipped for old non-devkit consoles.
+ if (module_index == 0x25 && sdk_ver < Common::ElfInfo::FW_45 &&
+ !Config::isDevKitConsole()) {
+ continue;
+ }
+
+ // libSceHttp2, skipped for SDK versions below 7.00.
+ if (module_index == 0x28 && sdk_ver < Common::ElfInfo::FW_70) {
+ continue;
+ }
+
+ // libSceNpWebApi2 and libSceNpGameIntent, skipped for SDK versions below 7.50
+ if ((module_index == 0x29 || module_index == 0x2a) && sdk_ver < Common::ElfInfo::FW_75) {
+ continue;
+ }
+
+ // Load the actual module
+ s32 result = loadModuleInternal(module_index, 0, nullptr, nullptr);
+ if (result != ORBIS_OK) {
+ // On real hardware, module preloading must succeed or the game will abort.
+ // To enable users to test homebrew easier, we'll log a critical error instead.
+ LOG_CRITICAL(Lib_SysModule, "Failed to preload {}, expect crashes",
+ g_modules_array[module_index].name);
+ }
+ }
+ return ORBIS_OK;
+}
+
+} // namespace Libraries::SysModule
diff --git a/src/core/libraries/sysmodule/sysmodule_internal.h b/src/core/libraries/sysmodule/sysmodule_internal.h
new file mode 100644
index 000000000..8f88f85ea
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule_internal.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/types.h"
+#include "core/libraries/kernel/process.h"
+
+namespace Libraries::SysModule {
+
+s32 getModuleHandle(s32 id, s32* handle);
+bool shouldHideName(const char* module_name);
+bool isDebugModule(s32 id);
+bool validateModuleId(s32 id);
+s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out);
+s32 loadModule(s32 id, s32 argc, const void* argv, s32* res_out);
+s32 unloadModule(s32 id, s32 argc, const void* argv, s32* res_out, bool is_internal);
+s32 preloadModulesForLibkernel();
+
+} // namespace Libraries::SysModule
\ No newline at end of file
diff --git a/src/core/libraries/sysmodule/sysmodule_table.h b/src/core/libraries/sysmodule/sysmodule_table.h
new file mode 100644
index 000000000..bd27a8aae
--- /dev/null
+++ b/src/core/libraries/sysmodule/sysmodule_table.h
@@ -0,0 +1,684 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/types.h"
+
+namespace Libraries::SysModule {
+
+/**
+ * libSceSysmodule hardcodes an array of valuable data about loading each PS4 module.
+ * This header stores the contents of this array, as dumped from 12.52's libSceSysmodule,
+ * and altered to fit within my simplified internal module struct.
+ */
+
+// This is an internal struct. Doesn't match the real one exactly.
+struct OrbisSysmoduleModuleInternal {
+ u32 id; // User requested ID
+ s32 handle; // Handle of the module, once loaded
+ s32 is_loaded; // 0 by default, set to 1 once loaded.
+ s32 flags; // Miscellaneous details about the module
+ const char* name; // Name of the actual SPRX/PRX library
+ const u16* to_load; // Pointer to an array of modules to load
+ s32 num_to_load; // Number of indicies in the array of modules
+};
+
+// This enum contains helpful identifiers for some bits used in the flags of a module.
+enum OrbisSysmoduleModuleInternalFlags : s32 {
+ IsCommon = 1, // Module is located in /system/common/lib
+ IsPriv = 2, // Module is located in /system/priv/lib
+ IsGame = 4, // Module is located in /app0/sce_module
+ IsDebug = 8, // Module should only be loaded on devkit/testkit consoles
+ IsNeo = 0x200, // Module should only be loaded on PS4 Pro consoles
+ IsNeoMode = 0x400, // Module should only be loaded for PS4 Pro running in enhanced mode
+ IsCommonEx = 0x1000, // Module is located in /system_ex/common_ex/lib
+ IsPrivEx = 0x2000, // Module is located in /system_ex/priv_ex/lib
+};
+
+// Array of module indexes to load in sceSysmodulePreloadModuleForLibkernel.
+// The library has three versions of this array
+u32 g_preload_list_1[36] = {0x24, 3, 4, 5, 6, 7, 8, 9, 0x25, 0xb, 0xc, 0xd,
+ 0xe, 0xf, 0x10, 0x11, 0x1f, 0x12, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17,
+ 0x2a, 0x18, 0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e, 0x20, 0x21};
+u32 g_preload_list_2[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8,
+ 9, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f, 0x12,
+ 0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18, 0x29,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x25, 0x26, 0x1e};
+u32 g_preload_list_3[38] = {1, 2, 0x24, 0x22, 3, 4, 5, 6, 7, 8,
+ 9, 0x25, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x1f,
+ 0x12, 0x23, 0x13, 0x14, 0x27, 0x28, 0x16, 0x17, 0x2a, 0x18,
+ 0x29, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x26, 0x1e};
+
+// Arrays of modules to load for each module.
+// The stored values are valid indices to modules in g_modules_array.
+u16 g_libSceNet_modules[1] = {5};
+u16 g_libSceIpmi_modules[1] = {6};
+u16 g_libSceMbus_modules[2] = {7, 6};
+u16 g_libSceRegMgr_modules[1] = {8};
+u16 g_libSceRtc_modules[1] = {9};
+u16 g_libSceAvSetting_modules[3] = {11, 7, 6};
+u16 g_libSceVideoOut_modules[3] = {12, 11, 7};
+u16 g_libSceGnmDriver_modules[4] = {13, 12, 8, 37};
+u16 g_libSceAudioOut_modules[4] = {14, 11, 7, 6};
+u16 g_libSceAudioIn_modules[4] = {15, 14, 7, 6};
+u16 g_libSceAjm_modules[1] = {16};
+u16 g_libScePad_modules[2] = {17, 7};
+u16 g_libSceDbg_debug_modules[1] = {18};
+u16 g_libSceNetCtl_modules[2] = {19, 6};
+u16 g_libSceHttp_modules[5] = {20, 39, 9, 19, 5};
+u16 g_libSceSsl_modules[3] = {21, 9, 5};
+u16 g_libSceNpCommon_modules[8] = {22, 20, 39, 19, 9, 8, 6, 5};
+u16 g_libSceNpManager_modules[7] = {23, 22, 20, 39, 19, 9, 5};
+u16 g_libSceNpWebApi_modules[7] = {24, 23, 22, 20, 39, 9, 5};
+u16 g_libSceSaveData_modules[4] = {25, 27, 9, 6};
+u16 g_libSceSystemService_modules[3] = {26, 8, 6};
+u16 g_libSceUserService_modules[2] = {27, 6};
+u16 g_libSceCommonDialog_modules[1] = {28};
+u16 g_libSceSysUtil_modules[2] = {29, 8};
+u16 g_libScePerf_debug_modules[3] = {30, 38, 37};
+u16 g_libSceCamera_modules[2] = {31, 7};
+u16 g_libSceDiscMap_modules[1] = {34};
+u16 g_libSceDbgAssist_modules[1] = {35};
+u16 g_libSceMat_debug_modules[1] = {36};
+u16 g_libSceRazorCpu_modules[1] = {37};
+u16 g_libSceRazorCpu_debug_debug_modules[2] = {38, 37};
+u16 g_libSceSsl2_modules[3] = {39, 9, 5};
+u16 g_libSceHttp2_modules[13] = {40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5};
+u16 g_libSceNpWebApi2_modules[39] = {41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9,
+ 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9,
+ 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5};
+u16 g_libSceNpGameIntent_modules[10] = {42, 22, 20, 39, 19, 9, 8, 6, 5, 6};
+u16 g_libSceFiber_modules[5] = {49, 114, 30, 38, 37};
+u16 g_libSceUlt_modules[6] = {50, 49, 114, 30, 38, 37};
+u16 g_libSceNgs2_modules[2] = {51, 16};
+u16 g_libSceXml_modules[1] = {52};
+u16 g_libSceNpUtility_modules[5] = {53, 22, 20, 19, 5};
+u16 g_libSceVoice_modules[4] = {54, 16, 15, 14};
+u16 g_libSceNpMatching2_modules[7] = {55, 23, 22, 20, 39, 19, 5};
+u16 g_libSceNpScoreRanking_modules[3] = {56, 23, 22};
+u16 g_libSceRudp_modules[1] = {57};
+u16 g_libSceNpTus_modules[3] = {58, 23, 22};
+u16 g_libSceFace_modules[1] = {59};
+u16 g_libSceSmart_modules[1] = {60};
+u16 g_libSceJson_modules[1] = {61};
+u16 g_libSceGameLiveStreaming_modules[2] = {62, 6};
+u16 g_libSceCompanionUtil_modules[3] = {63, 7, 6};
+u16 g_libScePlayGo_modules[1] = {64};
+u16 g_libSceFont_modules[1] = {65};
+u16 g_libSceVideoRecording_modules[2] = {66, 82};
+u16 g_libSceAudiodec_modules[2] = {67, 16};
+u16 g_libSceJpegDec_modules[1] = {68};
+u16 g_libSceJpegEnc_modules[1] = {69};
+u16 g_libScePngDec_modules[1] = {70};
+u16 g_libScePngEnc_modules[1] = {71};
+u16 g_libSceVideodec_modules[3] = {72, 80, 161};
+u16 g_libSceMove_modules[1] = {73};
+u16 g_libScePadTracker_modules[2] = {75, 17};
+u16 g_libSceDepth_modules[2] = {76, 31};
+u16 g_libSceHand_modules[1] = {77};
+u16 g_libSceIme_modules[2] = {78, 6};
+u16 g_libSceImeDialog_modules[2] = {79, 6};
+u16 g_libSceVdecCore_modules[1] = {80};
+u16 g_libSceNpParty_modules[2] = {81, 6};
+u16 g_libSceAvcap_modules[2] = {82, 6};
+u16 g_libSceFontFt_modules[1] = {83};
+u16 g_libSceFreeTypeOt_modules[1] = {84};
+u16 g_libSceFreeTypeOl_modules[1] = {85};
+u16 g_libSceFreeTypeOptOl_modules[1] = {86};
+u16 g_libSceScreenShot_modules[3] = {87, 29, 6};
+u16 g_libSceNpAuth_modules[3] = {88, 22, 23};
+u16 g_libSceVoiceQos_modules[5] = {89, 54, 16, 15, 14};
+u16 g_libSceSysCore_modules[2] = {90, 6};
+u16 g_libSceM4aacEnc_modules[2] = {91, 16};
+u16 g_libSceAudiodecCpu_modules[1] = {92};
+u16 g_libSceCdlgUtilServer_modules[2] = {93, 26};
+u16 g_libSceSulpha_debug_modules[1] = {94};
+u16 g_libSceSaveDataDialog_modules[4] = {95, 9, 28, 26};
+u16 g_libSceInvitationDialog_modules[1] = {96};
+u16 g_libSceKeyboard_debug_modules[1] = {97};
+u16 g_libSceKeyboard_modules[1] = {98};
+u16 g_libSceMsgDialog_modules[1] = {99};
+u16 g_libSceAvPlayer_modules[1] = {100};
+u16 g_libSceContentExport_modules[1] = {101};
+u16 g_libSceVisionManager_modules[1] = {102};
+u16 g_libSceAc3Enc_modules[2] = {103, 16};
+u16 g_libSceAppInstUtil_modules[1] = {104};
+u16 g_libSceVencCore_modules[1] = {105};
+u16 g_libSceAudio3d_modules[1] = {106};
+u16 g_libSceNpCommerce_modules[1] = {107};
+u16 g_libSceHidControl_modules[1] = {108};
+u16 g_libSceMouse_modules[1] = {109};
+u16 g_libSceCompanionHttpd_modules[1] = {110};
+u16 g_libSceWebBrowserDialog_modules[1] = {111};
+u16 g_libSceErrorDialog_modules[1] = {112};
+u16 g_libSceNpTrophy_modules[1] = {113};
+u16 g_ulobjmgr_modules[1] = {114};
+u16 g_libSceVideoCoreInterface_modules[1] = {115};
+u16 g_libSceVideoCoreServerInterface_modules[1] = {116};
+u16 g_libSceNpSns_modules[1] = {117};
+u16 g_libSceNpSnsFacebookDialog_modules[2] = {118, 117};
+u16 g_libSceMoveTracker_modules[1] = {119};
+u16 g_libSceNpProfileDialog_modules[1] = {120};
+u16 g_libSceNpFriendListDialog_modules[1] = {121};
+u16 g_libSceAppContent_modules[1] = {122};
+u16 g_libSceMarlin_modules[1] = {123};
+u16 g_libSceDtsEnc_modules[2] = {124, 16};
+u16 g_libSceNpSignaling_modules[1] = {125};
+u16 g_libSceRemoteplay_modules[1] = {126};
+u16 g_libSceUsbd_modules[1] = {127};
+u16 g_libSceGameCustomDataDialog_modules[1] = {128};
+u16 g_libSceNpEulaDialog_modules[1] = {129};
+u16 g_libSceRandom_modules[1] = {130};
+u16 g_libSceDipsw_modules[1] = {131};
+u16 g_libSceS3DConversion_modules[1] = {132};
+u16 g_libSceOttvCapture_debug_modules[1] = {133};
+u16 g_libSceBgft_modules[1] = {134};
+u16 g_libSceAudiodecCpuDdp_modules[1] = {135};
+u16 g_libSceAudiodecCpuM4aac_modules[1] = {136};
+u16 g_libSceAudiodecCpuDts_modules[1] = {137};
+u16 g_libSceAudiodecCpuDtsHdLbr_modules[1] = {138};
+u16 g_libSceAudiodecCpuDtsHdMa_modules[1] = {139};
+u16 g_libSceAudiodecCpuLpcm_modules[1] = {140};
+u16 g_libSceBemp2sys_modules[1] = {141};
+u16 g_libSceBeisobmf_modules[1] = {142};
+u16 g_libScePlayReady_modules[1] = {143};
+u16 g_libSceVideoNativeExtEssential_modules[1] = {144};
+u16 g_libSceZlib_modules[1] = {145};
+u16 g_libSceIduUtil_modules[1] = {146};
+u16 g_libScePsm_modules[1] = {147};
+u16 g_libSceDtcpIp_modules[1] = {148};
+u16 g_libSceKbEmulate_modules[1] = {149};
+u16 g_libSceAppChecker_modules[1] = {150};
+u16 g_libSceNpGriefReport_modules[1] = {151};
+u16 g_libSceContentSearch_modules[1] = {152};
+u16 g_libSceShareUtility_modules[1] = {153};
+u16 g_libSceWeb_modules[6] = {154, 155, 147, 192, 27, 6};
+u16 g_libSceWebKit2_modules[30] = {155, 266, 90, 6, 8, 255, 192, 116, 266, 90, 6, 8, 12, 11, 7,
+ 17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libSceDeci4h_debug_modules[1] = {156};
+u16 g_libSceHeadTracker_modules[1] = {157};
+u16 g_libSceGameUpdate_modules[2] = {158, 6};
+u16 g_libSceAutoMounterClient_modules[2] = {159, 6};
+u16 g_libSceSystemGesture_modules[1] = {160};
+u16 g_libSceVdecSavc_modules[1] = {161};
+u16 g_libSceVdecSavc2_modules[1] = {162};
+u16 g_libSceVideodec2_modules[3] = {163, 80, 162};
+u16 g_libSceVdecwrap_modules[2] = {164, 80};
+u16 g_libSceVshctl_modules[1] = {165};
+u16 g_libSceAt9Enc_modules[1] = {166};
+u16 g_libSceConvertKeycode_modules[1] = {167};
+u16 g_libSceGpuException_modules[1] = {168};
+u16 g_libSceSharePlay_modules[1] = {169};
+u16 g_libSceAudiodReport_modules[1] = {170};
+u16 g_libSceSulphaDrv_modules[1] = {171};
+u16 g_libSceHmd_modules[1] = {172};
+u16 g_libSceUsbStorage_modules[2] = {173, 6};
+u16 g_libSceVdecShevc_modules[1] = {174};
+u16 g_libSceUsbStorageDialog_modules[1] = {175};
+u16 g_libSceFaceTracker_modules[2] = {176, 59};
+u16 g_libSceHandTracker_modules[1] = {177};
+u16 g_libSceNpSnsYouTubeDialog_modules[2] = {178, 117};
+u16 g_libSceVrTracker_modules[6] = {179, 6, 172, 31, 17, 73};
+u16 g_libSceProfileCacheExternal_modules[2] = {180, 6};
+u16 g_libSceBackupRestoreUtil_modules[1] = {181};
+u16 g_libSceMusicPlayerService_modules[2] = {182, 183};
+u16 g_libSceMusicCoreServerClientJsEx_modules[1] = {183};
+u16 g_libSceSpSysCallWrapper_modules[3] = {184, 19, 6};
+u16 g_libScePs2EmuMenuDialog_modules[1] = {185};
+u16 g_libSceNpSnsDailyMotionDialog_modules[1] = {186};
+u16 g_libSceAudiodecCpuHevag_modules[1] = {187};
+u16 g_libSceLoginDialog_modules[2] = {188, 6};
+u16 g_libSceLoginService_modules[2] = {189, 6};
+u16 g_libSceSigninDialog_modules[2] = {190, 6};
+u16 g_libSceVdecsw_modules[3] = {191, 80, 162};
+u16 g_libSceOrbisCompat_modules[24] = {192, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26,
+ 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libSceCoreIPC_modules[1] = {193};
+u16 g_libSceCustomMusicCore_modules[12] = {194, 29, 8, 27, 6, 14, 11, 7, 6, 11, 7, 6};
+u16 g_libSceJson2_modules[1] = {195};
+u16 g_libSceAudioLatencyEstimation_modules[1] = {196};
+u16 g_libSceWkFontConfig_modules[1] = {197};
+u16 g_libSceVorbisDec_modules[3] = {198, 67, 16};
+u16 g_libSceTtsCoreEnUs_modules[1] = {199};
+u16 g_libSceTtsCoreJp_modules[1] = {200};
+u16 g_libSceOpusCeltEnc_modules[2] = {201, 16};
+u16 g_libSceOpusCeltDec_modules[2] = {202, 16};
+u16 g_libSceLoginMgrServer_modules[1] = {203};
+u16 g_libSceHmdSetupDialog_modules[1] = {204};
+u16 g_libSceVideoOutSecondary_modules[6] = {205, 82, 6, 12, 11, 7};
+u16 g_libSceContentDelete_modules[1] = {206};
+u16 g_libSceImeBackend_modules[1] = {207};
+u16 g_libSceNetCtlApDialog_modules[1] = {208};
+u16 g_libSceGnmResourceRegistration_modules[1] = {209};
+u16 g_libScePlayGoDialog_modules[1] = {210};
+u16 g_libSceSocialScreen_modules[7] = {211, 205, 82, 6, 12, 11, 7};
+u16 g_libSceEditMp4_modules[1] = {212};
+u16 g_libScePsmKitSystem_modules[1] = {221};
+u16 g_libSceTextToSpeech_modules[1] = {222};
+u16 g_libSceNpToolkit_modules[1] = {223};
+u16 g_libSceCustomMusicService_modules[2] = {224, 183};
+u16 g_libSceClSysCallWrapper_modules[11] = {225, 20, 39, 9, 19, 5, 39, 9, 5, 67, 16};
+u16 g_libSceScm_modules[1] = {226};
+u16 g_libSceSystemLogger_modules[2] = {227, 6};
+u16 g_libSceBluetoothHid_modules[1] = {228};
+u16 g_libSceAvPlayerStreaming_modules[1] = {229};
+u16 g_libSceAudiodecCpuAlac_modules[1] = {230};
+u16 g_libSceVideoDecoderArbitration_modules[1] = {231};
+u16 g_libSceVrServiceDialog_modules[1] = {232};
+u16 g_libSceJobManager_modules[2] = {233, 114};
+u16 g_libSceAudiodecCpuFlac_modules[1] = {234};
+u16 g_libSceSrcUtl_modules[2] = {235, 16};
+u16 g_libSceS3da_modules[1] = {236};
+u16 g_libSceDseehx_modules[1] = {237};
+u16 g_libSceShareFactoryUtil_modules[1] = {238};
+u16 g_libSceDataTransfer_modules[1] = {239};
+u16 g_libSceSocialScreenDialog_modules[1] = {240};
+u16 g_libSceAbstractStorage_modules[1] = {241};
+u16 g_libSceImageUtil_modules[1] = {242};
+u16 g_libSceMetadataReaderWriter_modules[1] = {243};
+u16 g_libSceJpegParser_modules[1] = {244};
+u16 g_libSceGvMp4Parser_modules[1] = {245};
+u16 g_libScePngParser_modules[1] = {246};
+u16 g_libSceGifParser_modules[1] = {247};
+u16 g_libSceNpSnsDialog_modules[2] = {248, 117};
+u16 g_libSceAbstractLocal_modules[1] = {249};
+u16 g_libSceAbstractFacebook_modules[1] = {250};
+u16 g_libSceAbstractYoutube_modules[1] = {251};
+u16 g_libSceAbstractTwitter_modules[1] = {252};
+u16 g_libSceAbstractDailymotion_modules[1] = {253};
+u16 g_libSceNpToolkit2_modules[1] = {254};
+u16 g_libScePrecompiledShaders_modules[1] = {255};
+u16 g_libSceDiscId_modules[1] = {256};
+u16 g_libSceLibreSsl_modules[2] = {257, 130};
+u16 g_libSceFsInternalForVsh_modules[1] = {258};
+u16 g_libSceNpUniversalDataSystem_modules[1] = {259};
+u16 g_libSceDolbyVision_modules[1] = {260};
+u16 g_libSceOpusSilkEnc_modules[2] = {261, 16};
+u16 g_libSceOpusDec_modules[2] = {262, 16};
+u16 g_libSceWebKit2Secure_modules[34] = {263, 265, 26, 8, 6, 266, 90, 6, 8, 255, 192, 116,
+ 266, 90, 6, 8, 12, 11, 7, 17, 7, 26, 8, 6,
+ 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libSceJscCompiler_modules[1] = {264};
+u16 g_libSceJitBridge_modules[4] = {265, 26, 8, 6};
+u16 g_libScePigletv2VSH_modules[4] = {266, 90, 6, 8};
+u16 g_libSceJitBridge_common_ex_modules[4] = {267, 26, 8, 6};
+u16 g_libSceJscCompiler_common_ex_modules[1] = {268};
+u16 g_libSceOrbisCompat_common_ex_modules[24] = {269, 116, 266, 90, 6, 8, 12, 11, 7, 17, 7, 26,
+ 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libSceWeb_common_ex_modules[6] = {270, 271, 147, 269, 27, 6};
+u16 g_libSceWebKit2_common_ex_modules[30] = {271, 266, 90, 6, 8, 273, 269, 116, 266, 90,
+ 6, 8, 12, 11, 7, 17, 7, 26, 8, 6,
+ 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libSceWebKit2Secure_common_ex_modules[34] = {
+ 272, 267, 26, 8, 6, 266, 90, 6, 8, 273, 269, 116, 266, 90, 6, 8, 12,
+ 11, 7, 17, 7, 26, 8, 6, 257, 130, 39, 9, 5, 19, 6, 5, 8, 9};
+u16 g_libScePrecompiledShaders_common_ex_modules[1] = {273};
+u16 g_libSceGic_modules[1] = {274};
+u16 g_libSceRnpsAppMgr_modules[1] = {275};
+u16 g_libSceAsyncStorageInternal_modules[1] = {276};
+u16 g_libSceHttpCache_modules[1] = {277};
+u16 g_libScePlayReady2_modules[1] = {278};
+u16 g_libSceHdrScopes_debug_modules[1] = {279};
+u16 g_libSceNKWeb_modules[1] = {280};
+u16 g_libSceNKWebKit_modules[2] = {281, 282};
+u16 g_libSceNKWebKitRequirements_modules[1] = {282};
+u16 g_libSceVnaInternal_modules[1] = {283};
+u16 g_libSceVnaWebsocket_modules[1] = {284};
+u16 g_libSceCesCs_modules[1] = {285};
+u16 g_libSceComposite_modules[1] = {286};
+u16 g_libSceCompositeExt_modules[1] = {287};
+u16 g_libSceHubAppUtil_modules[1] = {288};
+u16 g_libScePosixForWebKit_modules[1] = {289};
+u16 g_libSceNpPartner001_modules[1] = {290};
+u16 g_libSceNpSessionSignaling_modules[75] = {
+ 291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9,
+ 5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22,
+ 20, 39, 19, 9, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9};
+u16 g_libScePlayerInvitationDialog_modules[1] = {292};
+u16 g_libSceNpCppWebApi_modules[42] = {293, 195, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19,
+ 9, 8, 6, 5, 40, 20, 39, 9, 19, 5, 39, 9, 5, 9,
+ 19, 6, 5, 20, 39, 9, 19, 5, 39, 9, 5, 9, 5, 9};
+u16 g_libSceNpEntitlementAccess_modules[1] = {294};
+u16 g_libSceNpRemotePlaySessionSignaling_modules[76] = {
+ 295, 291, 41, 23, 22, 20, 39, 19, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 40,
+ 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 20, 39, 9, 19, 5, 39, 9,
+ 5, 9, 5, 22, 20, 39, 19, 9, 8, 6, 5, 23, 22, 20, 39, 19, 9, 5, 40,
+ 20, 39, 9, 19, 5, 39, 9, 5, 9, 19, 6, 5, 39, 9, 5, 19, 6, 5, 9};
+u16 g_libSceLibreSsl3_modules[2] = {296, 130};
+u16 g_libcurl_modules[2] = {297, 289};
+u16 g_libicu_modules[2] = {298, 289};
+u16 g_libcairo_modules[9] = {299, 300, 301, 302, 303, 289, 298, 289, 289};
+u16 g_libfontconfig_modules[1] = {300};
+u16 g_libfreetype_modules[1] = {301};
+u16 g_libharfbuzz_modules[1] = {302};
+u16 g_libpng16_modules[2] = {303, 289};
+u16 g_libSceFontGs_modules[1] = {304};
+u16 g_libSceGLSlimClientVSH_modules[1] = {305};
+u16 g_libSceGLSlimServerVSH_modules[1] = {306};
+u16 g_libSceFontGsm_modules[1] = {307};
+u16 g_libSceNpPartnerSubscription_modules[1] = {308};
+u16 g_libSceNpAuthAuthorizedAppDialog_modules[1] = {309};
+
+// This is the actual array of modules.
+constexpr u64 g_num_modules = 310;
+std::array g_modules_array = std::to_array<
+ OrbisSysmoduleModuleInternal>(
+ {{0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 1, "libkernel", nullptr, 0},
+ {0x0, -1, 0, 1, "libSceLibcInternal", nullptr, 0},
+ {0x0, -1, 0, 4, "libSceFios2", nullptr, 0},
+ {0x0, -1, 0, 4, "libc", nullptr, 0},
+ {0x8000001c, -1, 0, 1, "libSceNet", g_libSceNet_modules, 1},
+ {0x8000001d, -1, 0, 1, "libSceIpmi", g_libSceIpmi_modules, 1},
+ {0x8000001e, -1, 0, 1, "libSceMbus", g_libSceMbus_modules, 2},
+ {0x8000001f, -1, 0, 1, "libSceRegMgr", g_libSceRegMgr_modules, 1},
+ {0x80000020, -1, 0, 1, "libSceRtc", g_libSceRtc_modules, 1},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x80000021, -1, 0, 1, "libSceAvSetting", g_libSceAvSetting_modules, 3},
+ {0x80000022, -1, 0, 1, "libSceVideoOut", g_libSceVideoOut_modules, 3},
+ {0x80000052, -1, 0, 1025, "libSceGnmDriver", g_libSceGnmDriver_modules, 4},
+ {0x80000001, -1, 0, 1, "libSceAudioOut", g_libSceAudioOut_modules, 4},
+ {0x80000002, -1, 0, 1, "libSceAudioIn", g_libSceAudioIn_modules, 4},
+ {0x80000023, -1, 0, 1, "libSceAjm", g_libSceAjm_modules, 1},
+ {0x80000024, -1, 0, 1, "libScePad", g_libScePad_modules, 2},
+ {0x80000025, -1, 0, 9, "libSceDbg", g_libSceDbg_debug_modules, 1},
+ {0x80000009, -1, 0, 1, "libSceNetCtl", g_libSceNetCtl_modules, 2},
+ {0x8000000a, -1, 0, 1, "libSceHttp", g_libSceHttp_modules, 5},
+ {0x0, -1, 0, 1, "libSceSsl", g_libSceSsl_modules, 3},
+ {0x8000000c, -1, 0, 1, "libSceNpCommon", g_libSceNpCommon_modules, 8},
+ {0x8000000d, -1, 0, 1, "libSceNpManager", g_libSceNpManager_modules, 7},
+ {0x8000000e, -1, 0, 1, "libSceNpWebApi", g_libSceNpWebApi_modules, 7},
+ {0x8000000f, -1, 0, 1, "libSceSaveData", g_libSceSaveData_modules, 4},
+ {0x80000010, -1, 0, 1, "libSceSystemService", g_libSceSystemService_modules, 3},
+ {0x80000011, -1, 0, 1, "libSceUserService", g_libSceUserService_modules, 2},
+ {0x80000018, -1, 0, 1, "libSceCommonDialog", g_libSceCommonDialog_modules, 1},
+ {0x80000026, -1, 0, 1, "libSceSysUtil", g_libSceSysUtil_modules, 2},
+ {0x80000019, -1, 0, 9, "libScePerf", g_libScePerf_debug_modules, 3},
+ {0x8000001a, -1, 0, 1, "libSceCamera", g_libSceCamera_modules, 2},
+ {0x0, -1, 0, 1, "libSceWebKit2ForVideoService", nullptr, 0},
+ {0x0, -1, 0, 1, "libSceOrbisCompatForVideoService", nullptr, 0},
+ {0xd7, -1, 0, 1, "libSceDiscMap", g_libSceDiscMap_modules, 1},
+ {0x8000003d, -1, 0, 129, "libSceDbgAssist", g_libSceDbgAssist_modules, 1},
+ {0x80000048, -1, 0, 9, "libSceMat", g_libSceMat_debug_modules, 1},
+ {0x0, -1, 0, 1, "libSceRazorCpu", g_libSceRazorCpu_modules, 1},
+ {0x80000075, -1, 0, 9, "libSceRazorCpu_debug", g_libSceRazorCpu_debug_debug_modules, 2},
+ {0x8000000b, -1, 0, 1, "libSceSsl2", g_libSceSsl2_modules, 3},
+ {0x8000008c, -1, 0, 1, "libSceHttp2", g_libSceHttp2_modules, 13},
+ {0x8000008f, -1, 0, 1, "libSceNpWebApi2", g_libSceNpWebApi2_modules, 39},
+ {0x8000008d, -1, 0, 1, "libSceNpGameIntent", g_libSceNpGameIntent_modules, 10},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x6, -1, 0, 1, "libSceFiber", g_libSceFiber_modules, 5},
+ {0x7, -1, 0, 1, "libSceUlt", g_libSceUlt_modules, 6},
+ {0xb, -1, 0, 1, "libSceNgs2", g_libSceNgs2_modules, 2},
+ {0x17, -1, 0, 1, "libSceXml", g_libSceXml_modules, 1},
+ {0x19, -1, 0, 1, "libSceNpUtility", g_libSceNpUtility_modules, 5},
+ {0x1a, -1, 0, 1, "libSceVoice", g_libSceVoice_modules, 4},
+ {0x1c, -1, 0, 1, "libSceNpMatching2", g_libSceNpMatching2_modules, 7},
+ {0x1e, -1, 0, 1, "libSceNpScoreRanking", g_libSceNpScoreRanking_modules, 3},
+ {0x21, -1, 0, 1, "libSceRudp", g_libSceRudp_modules, 1},
+ {0x2c, -1, 0, 1, "libSceNpTus", g_libSceNpTus_modules, 3},
+ {0x38, -1, 0, 4, "libSceFace", g_libSceFace_modules, 1},
+ {0x39, -1, 0, 4, "libSceSmart", g_libSceSmart_modules, 1},
+ {0x80, -1, 0, 1, "libSceJson", g_libSceJson_modules, 1},
+ {0x81, -1, 0, 1, "libSceGameLiveStreaming", g_libSceGameLiveStreaming_modules, 2},
+ {0x82, -1, 0, 1, "libSceCompanionUtil", g_libSceCompanionUtil_modules, 3},
+ {0x83, -1, 0, 1, "libScePlayGo", g_libScePlayGo_modules, 1},
+ {0x84, -1, 0, 1, "libSceFont", g_libSceFont_modules, 1},
+ {0x85, -1, 0, 1, "libSceVideoRecording", g_libSceVideoRecording_modules, 2},
+ {0x88, -1, 0, 1, "libSceAudiodec", g_libSceAudiodec_modules, 2},
+ {0x8a, -1, 0, 1, "libSceJpegDec", g_libSceJpegDec_modules, 1},
+ {0x8b, -1, 0, 1, "libSceJpegEnc", g_libSceJpegEnc_modules, 1},
+ {0x8c, -1, 0, 1, "libScePngDec", g_libScePngDec_modules, 1},
+ {0x8d, -1, 0, 1, "libScePngEnc", g_libScePngEnc_modules, 1},
+ {0x8e, -1, 0, 2049, "libSceVideodec", g_libSceVideodec_modules, 3},
+ {0x8f, -1, 0, 1, "libSceMove", g_libSceMove_modules, 1},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x91, -1, 0, 1, "libScePadTracker", g_libScePadTracker_modules, 2},
+ {0x92, -1, 0, 1, "libSceDepth", g_libSceDepth_modules, 2},
+ {0x93, -1, 0, 4, "libSceHand", g_libSceHand_modules, 1},
+ {0x95, -1, 0, 1, "libSceIme", g_libSceIme_modules, 2},
+ {0x96, -1, 0, 1, "libSceImeDialog", g_libSceImeDialog_modules, 2},
+ {0x80000015, -1, 0, 1, "libSceVdecCore", g_libSceVdecCore_modules, 1},
+ {0x97, -1, 0, 1, "libSceNpParty", g_libSceNpParty_modules, 2},
+ {0x80000003, -1, 0, 1, "libSceAvcap", g_libSceAvcap_modules, 2},
+ {0x98, -1, 0, 1, "libSceFontFt", g_libSceFontFt_modules, 1},
+ {0x99, -1, 0, 1, "libSceFreeTypeOt", g_libSceFreeTypeOt_modules, 1},
+ {0x9a, -1, 0, 1, "libSceFreeTypeOl", g_libSceFreeTypeOl_modules, 1},
+ {0x9b, -1, 0, 1, "libSceFreeTypeOptOl", g_libSceFreeTypeOptOl_modules, 1},
+ {0x9c, -1, 0, 1, "libSceScreenShot", g_libSceScreenShot_modules, 3},
+ {0x9d, -1, 0, 1, "libSceNpAuth", g_libSceNpAuth_modules, 3},
+ {0x1b, -1, 0, 1, "libSceVoiceQos", g_libSceVoiceQos_modules, 5},
+ {0x80000004, -1, 0, 1, "libSceSysCore", g_libSceSysCore_modules, 2},
+ {0xbc, -1, 0, 1, "libSceM4aacEnc", g_libSceM4aacEnc_modules, 2},
+ {0xbd, -1, 0, 1, "libSceAudiodecCpu", g_libSceAudiodecCpu_modules, 1},
+ {0x80000007, -1, 0, 1, "libSceCdlgUtilServer", g_libSceCdlgUtilServer_modules, 2},
+ {0x9f, -1, 0, 9, "libSceSulpha", g_libSceSulpha_debug_modules, 1},
+ {0xa0, -1, 0, 1, "libSceSaveDataDialog", g_libSceSaveDataDialog_modules, 4},
+ {0xa2, -1, 0, 1, "libSceInvitationDialog", g_libSceInvitationDialog_modules, 1},
+ {0xa3, -1, 0, 2057, "libSceKeyboard", g_libSceKeyboard_debug_modules, 1},
+ {0x106, -1, 0, 2049, "libSceKeyboard", g_libSceKeyboard_modules, 1},
+ {0xa4, -1, 0, 1, "libSceMsgDialog", g_libSceMsgDialog_modules, 1},
+ {0xa5, -1, 0, 1, "libSceAvPlayer", g_libSceAvPlayer_modules, 1},
+ {0xa6, -1, 0, 1, "libSceContentExport", g_libSceContentExport_modules, 1},
+ {0x80000012, -1, 0, 2, "libSceVisionManager", g_libSceVisionManager_modules, 1},
+ {0x80000013, -1, 0, 2, "libSceAc3Enc", g_libSceAc3Enc_modules, 2},
+ {0x80000014, -1, 0, 1, "libSceAppInstUtil", g_libSceAppInstUtil_modules, 1},
+ {0x80000016, -1, 0, 514, "libSceVencCore", g_libSceVencCore_modules, 1},
+ {0xa7, -1, 0, 1, "libSceAudio3d", g_libSceAudio3d_modules, 1},
+ {0xa8, -1, 0, 1, "libSceNpCommerce", g_libSceNpCommerce_modules, 1},
+ {0x80000017, -1, 0, 1, "libSceHidControl", g_libSceHidControl_modules, 1},
+ {0xa9, -1, 0, 1, "libSceMouse", g_libSceMouse_modules, 1},
+ {0xaa, -1, 0, 1, "libSceCompanionHttpd", g_libSceCompanionHttpd_modules, 1},
+ {0xab, -1, 0, 1, "libSceWebBrowserDialog", g_libSceWebBrowserDialog_modules, 1},
+ {0xac, -1, 0, 1, "libSceErrorDialog", g_libSceErrorDialog_modules, 1},
+ {0xad, -1, 0, 1, "libSceNpTrophy", g_libSceNpTrophy_modules, 1},
+ {0x0, -1, 0, 1, "ulobjmgr", g_ulobjmgr_modules, 1},
+ {0xae, -1, 0, 1, "libSceVideoCoreInterface", g_libSceVideoCoreInterface_modules, 1},
+ {0xaf, -1, 0, 1, "libSceVideoCoreServerInterface", g_libSceVideoCoreServerInterface_modules,
+ 1},
+ {0x8000001b, -1, 0, 1, "libSceNpSns", g_libSceNpSns_modules, 1},
+ {0xb0, -1, 0, 1, "libSceNpSnsFacebookDialog", g_libSceNpSnsFacebookDialog_modules, 2},
+ {0xb1, -1, 0, 1, "libSceMoveTracker", g_libSceMoveTracker_modules, 1},
+ {0xb2, -1, 0, 1, "libSceNpProfileDialog", g_libSceNpProfileDialog_modules, 1},
+ {0xb3, -1, 0, 1, "libSceNpFriendListDialog", g_libSceNpFriendListDialog_modules, 1},
+ {0xb4, -1, 0, 1, "libSceAppContent", g_libSceAppContent_modules, 1},
+ {0x80000027, -1, 0, 2, "libSceMarlin", g_libSceMarlin_modules, 1},
+ {0x80000028, -1, 0, 2, "libSceDtsEnc", g_libSceDtsEnc_modules, 2},
+ {0xb5, -1, 0, 1, "libSceNpSignaling", g_libSceNpSignaling_modules, 1},
+ {0xb6, -1, 0, 1, "libSceRemoteplay", g_libSceRemoteplay_modules, 1},
+ {0xb7, -1, 0, 1, "libSceUsbd", g_libSceUsbd_modules, 1},
+ {0xb8, -1, 0, 1, "libSceGameCustomDataDialog", g_libSceGameCustomDataDialog_modules, 1},
+ {0xb9, -1, 0, 1, "libSceNpEulaDialog", g_libSceNpEulaDialog_modules, 1},
+ {0xba, -1, 0, 1, "libSceRandom", g_libSceRandom_modules, 1},
+ {0x80000029, -1, 0, 2, "libSceDipsw", g_libSceDipsw_modules, 1},
+ {0x86, -1, 0, 4, "libSceS3DConversion", g_libSceS3DConversion_modules, 1},
+ {0x8000003e, -1, 0, 9, "libSceOttvCapture", g_libSceOttvCapture_debug_modules, 1},
+ {0x8000002a, -1, 0, 1, "libSceBgft", g_libSceBgft_modules, 1},
+ {0xbe, -1, 0, 1, "libSceAudiodecCpuDdp", g_libSceAudiodecCpuDdp_modules, 1},
+ {0xc0, -1, 0, 1, "libSceAudiodecCpuM4aac", g_libSceAudiodecCpuM4aac_modules, 1},
+ {0x8000002b, -1, 0, 2, "libSceAudiodecCpuDts", g_libSceAudiodecCpuDts_modules, 1},
+ {0xc9, -1, 0, 1, "libSceAudiodecCpuDtsHdLbr", g_libSceAudiodecCpuDtsHdLbr_modules, 1},
+ {0x8000002d, -1, 0, 2, "libSceAudiodecCpuDtsHdMa", g_libSceAudiodecCpuDtsHdMa_modules, 1},
+ {0x8000002e, -1, 0, 2, "libSceAudiodecCpuLpcm", g_libSceAudiodecCpuLpcm_modules, 1},
+ {0xc1, -1, 0, 1, "libSceBemp2sys", g_libSceBemp2sys_modules, 1},
+ {0xc2, -1, 0, 1, "libSceBeisobmf", g_libSceBeisobmf_modules, 1},
+ {0xc3, -1, 0, 1, "libScePlayReady", g_libScePlayReady_modules, 1},
+ {0xc4, -1, 0, 1, "libSceVideoNativeExtEssential", g_libSceVideoNativeExtEssential_modules, 1},
+ {0xc5, -1, 0, 1, "libSceZlib", g_libSceZlib_modules, 1},
+ {0x8000002f, -1, 0, 1, "libSceIduUtil", g_libSceIduUtil_modules, 1},
+ {0x80000030, -1, 0, 1, "libScePsm", g_libScePsm_modules, 1},
+ {0xc6, -1, 0, 1, "libSceDtcpIp", g_libSceDtcpIp_modules, 1},
+ {0x80000031, -1, 0, 1, "libSceKbEmulate", g_libSceKbEmulate_modules, 1},
+ {0x80000032, -1, 0, 2, "libSceAppChecker", g_libSceAppChecker_modules, 1},
+ {0x80000033, -1, 0, 1, "libSceNpGriefReport", g_libSceNpGriefReport_modules, 1},
+ {0xc7, -1, 0, 1, "libSceContentSearch", g_libSceContentSearch_modules, 1},
+ {0xc8, -1, 0, 1, "libSceShareUtility", g_libSceShareUtility_modules, 1},
+ {0x80000034, -1, 0, 1, "libSceWeb", g_libSceWeb_modules, 6},
+ {0x8000006a, -1, 0, 1, "libSceWebKit2", g_libSceWebKit2_modules, 30},
+ {0xca, -1, 0, 9, "libSceDeci4h", g_libSceDeci4h_debug_modules, 1},
+ {0xcb, -1, 0, 4, "libSceHeadTracker", g_libSceHeadTracker_modules, 1},
+ {0xcc, -1, 0, 1, "libSceGameUpdate", g_libSceGameUpdate_modules, 2},
+ {0xcd, -1, 0, 1, "libSceAutoMounterClient", g_libSceAutoMounterClient_modules, 2},
+ {0xce, -1, 0, 1, "libSceSystemGesture", g_libSceSystemGesture_modules, 1},
+ {0x80000035, -1, 0, 1, "libSceVdecSavc", g_libSceVdecSavc_modules, 1},
+ {0x80000036, -1, 0, 1, "libSceVdecSavc2", g_libSceVdecSavc2_modules, 1},
+ {0xcf, -1, 0, 2049, "libSceVideodec2", g_libSceVideodec2_modules, 3},
+ {0xd0, -1, 0, 1, "libSceVdecwrap", g_libSceVdecwrap_modules, 2},
+ {0x80000037, -1, 0, 1, "libSceVshctl", g_libSceVshctl_modules, 1},
+ {0xd1, -1, 0, 1, "libSceAt9Enc", g_libSceAt9Enc_modules, 1},
+ {0xd2, -1, 0, 1, "libSceConvertKeycode", g_libSceConvertKeycode_modules, 1},
+ {0x80000039, -1, 0, 1, "libSceGpuException", g_libSceGpuException_modules, 1},
+ {0xd3, -1, 0, 1, "libSceSharePlay", g_libSceSharePlay_modules, 1},
+ {0x8000003a, -1, 0, 2, "libSceAudiodReport", g_libSceAudiodReport_modules, 1},
+ {0x8000003b, -1, 0, 2, "libSceSulphaDrv", g_libSceSulphaDrv_modules, 1},
+ {0xd4, -1, 0, 1, "libSceHmd", g_libSceHmd_modules, 1},
+ {0xd5, -1, 0, 1, "libSceUsbStorage", g_libSceUsbStorage_modules, 2},
+ {0x8000003c, -1, 0, 1, "libSceVdecShevc", g_libSceVdecShevc_modules, 1},
+ {0xd6, -1, 0, 1, "libSceUsbStorageDialog", g_libSceUsbStorageDialog_modules, 1},
+ {0xd8, -1, 0, 4, "libSceFaceTracker", g_libSceFaceTracker_modules, 2},
+ {0xd9, -1, 0, 4, "libSceHandTracker", g_libSceHandTracker_modules, 1},
+ {0xda, -1, 0, 1, "libSceNpSnsYouTubeDialog", g_libSceNpSnsYouTubeDialog_modules, 2},
+ {0xed, -1, 0, 1, "libSceVrTracker", g_libSceVrTracker_modules, 6},
+ {0xdc, -1, 0, 1, "libSceProfileCacheExternal", g_libSceProfileCacheExternal_modules, 2},
+ {0x8000003f, -1, 0, 1, "libSceBackupRestoreUtil", g_libSceBackupRestoreUtil_modules, 1},
+ {0xdd, -1, 0, 1, "libSceMusicPlayerService", g_libSceMusicPlayerService_modules, 2},
+ {0x0, -1, 0, 1, "libSceMusicCoreServerClientJsEx", g_libSceMusicCoreServerClientJsEx_modules,
+ 1},
+ {0xde, -1, 0, 1, "libSceSpSysCallWrapper", g_libSceSpSysCallWrapper_modules, 3},
+ {0xdf, -1, 0, 1, "libScePs2EmuMenuDialog", g_libScePs2EmuMenuDialog_modules, 1},
+ {0xe0, -1, 0, 1, "libSceNpSnsDailyMotionDialog", g_libSceNpSnsDailyMotionDialog_modules, 1},
+ {0xe1, -1, 0, 1, "libSceAudiodecCpuHevag", g_libSceAudiodecCpuHevag_modules, 1},
+ {0xe2, -1, 0, 1, "libSceLoginDialog", g_libSceLoginDialog_modules, 2},
+ {0xe3, -1, 0, 1, "libSceLoginService", g_libSceLoginService_modules, 2},
+ {0xe4, -1, 0, 1, "libSceSigninDialog", g_libSceSigninDialog_modules, 2},
+ {0xe5, -1, 0, 1, "libSceVdecsw", g_libSceVdecsw_modules, 3},
+ {0x8000006d, -1, 0, 1, "libSceOrbisCompat", g_libSceOrbisCompat_modules, 24},
+ {0x0, -1, 0, 1, "libSceCoreIPC", g_libSceCoreIPC_modules, 1},
+ {0xe6, -1, 0, 1, "libSceCustomMusicCore", g_libSceCustomMusicCore_modules, 12},
+ {0xe7, -1, 0, 1, "libSceJson2", g_libSceJson2_modules, 1},
+ {0xe8, -1, 0, 4, "libSceAudioLatencyEstimation", g_libSceAudioLatencyEstimation_modules, 1},
+ {0xe9, -1, 0, 1, "libSceWkFontConfig", g_libSceWkFontConfig_modules, 1},
+ {0xea, -1, 0, 2, "libSceVorbisDec", g_libSceVorbisDec_modules, 3},
+ {0x80000041, -1, 0, 1, "libSceTtsCoreEnUs", g_libSceTtsCoreEnUs_modules, 1},
+ {0x80000042, -1, 0, 1, "libSceTtsCoreJp", g_libSceTtsCoreJp_modules, 1},
+ {0x80000043, -1, 0, 1, "libSceOpusCeltEnc", g_libSceOpusCeltEnc_modules, 2},
+ {0x80000044, -1, 0, 1, "libSceOpusCeltDec", g_libSceOpusCeltDec_modules, 2},
+ {0x80000045, -1, 0, 2, "libSceLoginMgrServer", g_libSceLoginMgrServer_modules, 1},
+ {0xeb, -1, 0, 1, "libSceHmdSetupDialog", g_libSceHmdSetupDialog_modules, 1},
+ {0x80000046, -1, 0, 1, "libSceVideoOutSecondary", g_libSceVideoOutSecondary_modules, 6},
+ {0xee, -1, 0, 1, "libSceContentDelete", g_libSceContentDelete_modules, 1},
+ {0xef, -1, 0, 1, "libSceImeBackend", g_libSceImeBackend_modules, 1},
+ {0xf0, -1, 0, 1, "libSceNetCtlApDialog", g_libSceNetCtlApDialog_modules, 1},
+ {0x80000047, -1, 0, 1, "libSceGnmResourceRegistration",
+ g_libSceGnmResourceRegistration_modules, 1},
+ {0xf1, -1, 0, 1, "libScePlayGoDialog", g_libScePlayGoDialog_modules, 1},
+ {0xf2, -1, 0, 1, "libSceSocialScreen", g_libSceSocialScreen_modules, 7},
+ {0xf3, -1, 0, 1, "libSceEditMp4", g_libSceEditMp4_modules, 1},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0x0, -1, 0, 0, nullptr, nullptr, 0},
+ {0xf5, -1, 0, 1, "libScePsmKitSystem", g_libScePsmKitSystem_modules, 1},
+ {0xf6, -1, 0, 1, "libSceTextToSpeech", g_libSceTextToSpeech_modules, 1},
+ {0xf7, -1, 0, 2052, "libSceNpToolkit", g_libSceNpToolkit_modules, 1},
+ {0xf8, -1, 0, 1, "libSceCustomMusicService", g_libSceCustomMusicService_modules, 2},
+ {0xf9, -1, 0, 1, "libSceClSysCallWrapper", g_libSceClSysCallWrapper_modules, 11},
+ {0x80000049, -1, 0, 1, "libSceScm", g_libSceScm_modules, 1},
+ {0xfa, -1, 0, 1, "libSceSystemLogger", g_libSceSystemLogger_modules, 2},
+ {0xfb, -1, 0, 1, "libSceBluetoothHid", g_libSceBluetoothHid_modules, 1},
+ {0x80000050, -1, 0, 1, "libSceAvPlayerStreaming", g_libSceAvPlayerStreaming_modules, 1},
+ {0x80000051, -1, 0, 2, "libSceAudiodecCpuAlac", g_libSceAudiodecCpuAlac_modules, 1},
+ {0xfc, -1, 0, 1, "libSceVideoDecoderArbitration", g_libSceVideoDecoderArbitration_modules, 1},
+ {0xfd, -1, 0, 1, "libSceVrServiceDialog", g_libSceVrServiceDialog_modules, 1},
+ {0xfe, -1, 0, 4, "libSceJobManager", g_libSceJobManager_modules, 2},
+ {0x80000053, -1, 0, 2, "libSceAudiodecCpuFlac", g_libSceAudiodecCpuFlac_modules, 1},
+ {0x103, -1, 0, 1, "libSceSrcUtl", g_libSceSrcUtl_modules, 2},
+ {0x80000055, -1, 0, 2, "libSceS3da", g_libSceS3da_modules, 1},
+ {0x80000056, -1, 0, 2, "libSceDseehx", g_libSceDseehx_modules, 1},
+ {0xff, -1, 0, 1, "libSceShareFactoryUtil", g_libSceShareFactoryUtil_modules, 1},
+ {0x80000057, -1, 0, 1, "libSceDataTransfer", g_libSceDataTransfer_modules, 1},
+ {0x100, -1, 0, 1, "libSceSocialScreenDialog", g_libSceSocialScreenDialog_modules, 1},
+ {0x80000058, -1, 0, 1, "libSceAbstractStorage", g_libSceAbstractStorage_modules, 1},
+ {0x80000059, -1, 0, 1, "libSceImageUtil", g_libSceImageUtil_modules, 1},
+ {0x8000005a, -1, 0, 1, "libSceMetadataReaderWriter", g_libSceMetadataReaderWriter_modules, 1},
+ {0x8000005b, -1, 0, 1, "libSceJpegParser", g_libSceJpegParser_modules, 1},
+ {0x8000005c, -1, 0, 1, "libSceGvMp4Parser", g_libSceGvMp4Parser_modules, 1},
+ {0x8000005d, -1, 0, 1, "libScePngParser", g_libScePngParser_modules, 1},
+ {0x8000005e, -1, 0, 1, "libSceGifParser", g_libSceGifParser_modules, 1},
+ {0x101, -1, 0, 1, "libSceNpSnsDialog", g_libSceNpSnsDialog_modules, 2},
+ {0x8000005f, -1, 0, 1, "libSceAbstractLocal", g_libSceAbstractLocal_modules, 1},
+ {0x80000060, -1, 0, 1, "libSceAbstractFacebook", g_libSceAbstractFacebook_modules, 1},
+ {0x80000061, -1, 0, 1, "libSceAbstractYoutube", g_libSceAbstractYoutube_modules, 1},
+ {0x80000062, -1, 0, 1, "libSceAbstractTwitter", g_libSceAbstractTwitter_modules, 1},
+ {0x80000063, -1, 0, 1, "libSceAbstractDailymotion", g_libSceAbstractDailymotion_modules, 1},
+ {0x102, -1, 0, 2052, "libSceNpToolkit2", g_libSceNpToolkit2_modules, 1},
+ {0x80000064, -1, 0, 1, "libScePrecompiledShaders", g_libScePrecompiledShaders_modules, 1},
+ {0x104, -1, 0, 1, "libSceDiscId", g_libSceDiscId_modules, 1},
+ {0x80000065, -1, 0, 1, "libSceLibreSsl", g_libSceLibreSsl_modules, 2},
+ {0x80000066, -1, 0, 2, "libSceFsInternalForVsh", g_libSceFsInternalForVsh_modules, 1},
+ {0x105, -1, 0, 1, "libSceNpUniversalDataSystem", g_libSceNpUniversalDataSystem_modules, 1},
+ {0x80000067, -1, 0, 1, "libSceDolbyVision", g_libSceDolbyVision_modules, 1},
+ {0x80000068, -1, 0, 1, "libSceOpusSilkEnc", g_libSceOpusSilkEnc_modules, 2},
+ {0x80000069, -1, 0, 1, "libSceOpusDec", g_libSceOpusDec_modules, 2},
+ {0x8000006b, -1, 0, 1, "libSceWebKit2Secure", g_libSceWebKit2Secure_modules, 34},
+ {0x8000006c, -1, 0, 1, "libSceJscCompiler", g_libSceJscCompiler_modules, 1},
+ {0x8000006e, -1, 0, 1, "libSceJitBridge", g_libSceJitBridge_modules, 4},
+ {0x0, -1, 0, 1, "libScePigletv2VSH", g_libScePigletv2VSH_modules, 4},
+ {0x8000006f, -1, 0, 4096, "libSceJitBridge", g_libSceJitBridge_common_ex_modules, 4},
+ {0x80000070, -1, 0, 4096, "libSceJscCompiler", g_libSceJscCompiler_common_ex_modules, 1},
+ {0x80000071, -1, 0, 4096, "libSceOrbisCompat", g_libSceOrbisCompat_common_ex_modules, 24},
+ {0x80000072, -1, 0, 4096, "libSceWeb", g_libSceWeb_common_ex_modules, 6},
+ {0x80000073, -1, 0, 4096, "libSceWebKit2", g_libSceWebKit2_common_ex_modules, 30},
+ {0x80000074, -1, 0, 4096, "libSceWebKit2Secure", g_libSceWebKit2Secure_common_ex_modules, 34},
+ {0x0, -1, 0, 4096, "libScePrecompiledShaders", g_libScePrecompiledShaders_common_ex_modules,
+ 1},
+ {0x107, -1, 0, 1, "libSceGic", g_libSceGic_modules, 1},
+ {0x80000076, -1, 0, 1, "libSceRnpsAppMgr", g_libSceRnpsAppMgr_modules, 1},
+ {0x80000077, -1, 0, 1, "libSceAsyncStorageInternal", g_libSceAsyncStorageInternal_modules, 1},
+ {0x80000078, -1, 0, 1, "libSceHttpCache", g_libSceHttpCache_modules, 1},
+ {0x108, -1, 0, 1, "libScePlayReady2", g_libScePlayReady2_modules, 1},
+ {0x109, -1, 0, 9, "libSceHdrScopes", g_libSceHdrScopes_debug_modules, 1},
+ {0x80000079, -1, 0, 1, "libSceNKWeb", g_libSceNKWeb_modules, 1},
+ {0x8000007a, -1, 0, 1, "libSceNKWebKit", g_libSceNKWebKit_modules, 2},
+ {0x0, -1, 0, 1, "libSceNKWebKitRequirements", g_libSceNKWebKitRequirements_modules, 1},
+ {0x8000007c, -1, 0, 1, "libSceVnaInternal", g_libSceVnaInternal_modules, 1},
+ {0x8000007d, -1, 0, 1, "libSceVnaWebsocket", g_libSceVnaWebsocket_modules, 1},
+ {0x10c, -1, 0, 1, "libSceCesCs", g_libSceCesCs_modules, 1},
+ {0x8000008a, -1, 0, 2, "libSceComposite", g_libSceComposite_modules, 1},
+ {0x8000008b, -1, 0, 1, "libSceCompositeExt", g_libSceCompositeExt_modules, 1},
+ {0x116, -1, 0, 1, "libSceHubAppUtil", g_libSceHubAppUtil_modules, 1},
+ {0x80000098, -1, 0, 1, "libScePosixForWebKit", g_libScePosixForWebKit_modules, 1},
+ {0x11a, -1, 0, 1, "libSceNpPartner001", g_libSceNpPartner001_modules, 1},
+ {0x112, -1, 0, 1, "libSceNpSessionSignaling", g_libSceNpSessionSignaling_modules, 75},
+ {0x10d, -1, 0, 1, "libScePlayerInvitationDialog", g_libScePlayerInvitationDialog_modules, 1},
+ {0x115, -1, 0, 4, "libSceNpCppWebApi", g_libSceNpCppWebApi_modules, 42},
+ {0x113, -1, 0, 1, "libSceNpEntitlementAccess", g_libSceNpEntitlementAccess_modules, 1},
+ {0x8000009a, -1, 0, 2, "libSceNpRemotePlaySessionSignaling",
+ g_libSceNpRemotePlaySessionSignaling_modules, 76},
+ {0x800000b8, -1, 0, 1, "libSceLibreSsl3", g_libSceLibreSsl3_modules, 2},
+ {0x800000b1, -1, 0, 1, "libcurl", g_libcurl_modules, 2},
+ {0x800000aa, -1, 0, 1, "libicu", g_libicu_modules, 2},
+ {0x800000ac, -1, 0, 1, "libcairo", g_libcairo_modules, 9},
+ {0x0, -1, 0, 1, "libfontconfig", g_libfontconfig_modules, 1},
+ {0x0, -1, 0, 1, "libfreetype", g_libfreetype_modules, 1},
+ {0x0, -1, 0, 1, "libharfbuzz", g_libharfbuzz_modules, 1},
+ {0x800000ab, -1, 0, 1, "libpng16", g_libpng16_modules, 2},
+ {0x12f, -1, 0, 1, "libSceFontGs", g_libSceFontGs_modules, 1},
+ {0x800000c0, -1, 0, 1, "libSceGLSlimClientVSH", g_libSceGLSlimClientVSH_modules, 1},
+ {0x800000c1, -1, 0, 1, "libSceGLSlimServerVSH", g_libSceGLSlimServerVSH_modules, 1},
+ {0x135, -1, 0, 4, "libSceFontGsm", g_libSceFontGsm_modules, 1},
+ {0x138, -1, 0, 1, "libSceNpPartnerSubscription", g_libSceNpPartnerSubscription_modules, 1},
+ {0x139, -1, 0, 1, "libSceNpAuthAuthorizedAppDialog", g_libSceNpAuthAuthorizedAppDialog_modules,
+ 1}});
+
+} // namespace Libraries::SysModule
diff --git a/src/core/libraries/system/sysmodule.cpp b/src/core/libraries/system/sysmodule.cpp
deleted file mode 100644
index 50d030065..000000000
--- a/src/core/libraries/system/sysmodule.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#define MAGIC_ENUM_RANGE_MIN 0
-#define MAGIC_ENUM_RANGE_MAX 300
-#include
-
-#include "common/logging/log.h"
-#include "core/libraries/error_codes.h"
-#include "core/libraries/kernel/process.h"
-#include "core/libraries/libs.h"
-#include "core/libraries/system/sysmodule.h"
-#include "core/libraries/system/system_error.h"
-
-namespace Libraries::SysModule {
-
-int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
- Kernel::OrbisModuleInfoForUnwind* info) {
- LOG_TRACE(Lib_SysModule, "sceSysmoduleGetModuleInfoForUnwind(addr=0x{:X}, flags=0x{:X})", addr,
- flags);
-
- s32 res = Kernel::sceKernelGetModuleInfoForUnwind(addr, flags, info);
- if (res != 0) {
- return res;
- }
-
- static constexpr std::array modules_to_hide = {
- "libc.prx",
- "libc.sprx",
- "libSceAudioLatencyEstimation.prx",
- "libSceFace.prx",
- "libSceFaceTracker.prx",
- "libSceFios2.prx",
- "libSceFios2.sprx",
- "libSceFontGsm.prx",
- "libSceHand.prx",
- "libSceHandTracker.prx",
- "libSceHeadTracker.prx",
- "libSceJobManager.prx",
- "libSceNpCppWebApi.prx",
- "libSceNpToolkit.prx",
- "libSceNpToolkit2.prx",
- "libSceS3DConversion.prx",
- "libSceSmart.prx",
- };
-
- const std::string_view module_name = info->name.data();
- if (std::ranges::find(modules_to_hide, module_name) != modules_to_hide.end()) {
- std::ranges::fill(info->name, '\0');
- }
- return res;
-}
-
-int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id) {
- LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id));
- if (static_cast(id) == 0) {
- LOG_ERROR(Lib_SysModule, "Invalid sysmodule ID: {:#x}", static_cast(id));
- return ORBIS_SYSMODULE_INVALID_ID;
- }
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id) {
- LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {:#x}", static_cast(id));
- if ((static_cast(id) & 0x7FFFFFFF) == 0) {
- LOG_ERROR(Lib_SysModule, "Invalid internal sysmodule ID: {:#x}", static_cast(id));
- return ORBIS_SYSMODULE_INVALID_ID;
- }
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id) {
- LOG_ERROR(Lib_SysModule, "(DUMMY) called module = {}", magic_enum::enum_name(id));
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleUnloadModule() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg() {
- LOG_ERROR(Lib_SysModule, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-void RegisterLib(Core::Loader::SymbolsResolver* sym) {
- LIB_FUNCTION("D8cuU4d72xM", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleGetModuleHandleInternal);
- LIB_FUNCTION("4fU5yvOkVG4", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleGetModuleInfoForUnwind);
- LIB_FUNCTION("ctfO7dQ7geg", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleIsCalledFromSysModule);
- LIB_FUNCTION("no6T3EfiS3E", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleIsCameraPreloaded);
- LIB_FUNCTION("fMP5NHUOaMk", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleIsLoaded);
- LIB_FUNCTION("ynFKQ5bfGks", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleIsLoadedInternal);
- LIB_FUNCTION("g8cM39EUZ6o", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleLoadModule);
- LIB_FUNCTION("CU8m+Qs+HN4", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleLoadModuleByNameInternal);
- LIB_FUNCTION("39iV5E1HoCk", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleLoadModuleInternal);
- LIB_FUNCTION("hHrGoGoNf+s", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleLoadModuleInternalWithArg);
- LIB_FUNCTION("lZ6RvVl0vo0", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleMapLibcForLibkernel);
- LIB_FUNCTION("DOO+zuW1lrE", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmodulePreloadModuleForLibkernel);
- LIB_FUNCTION("eR2bZFAAU0Q", "libSceSysmodule", 1, "libSceSysmodule", sceSysmoduleUnloadModule);
- LIB_FUNCTION("vpTHmA6Knvg", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleUnloadModuleByNameInternal);
- LIB_FUNCTION("vXZhrtJxkGc", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleUnloadModuleInternal);
- LIB_FUNCTION("aKa6YfBKZs4", "libSceSysmodule", 1, "libSceSysmodule",
- sceSysmoduleUnloadModuleInternalWithArg);
-};
-
-} // namespace Libraries::SysModule
diff --git a/src/core/libraries/system/sysmodule.h b/src/core/libraries/system/sysmodule.h
deleted file mode 100644
index 3f1328e4b..000000000
--- a/src/core/libraries/system/sysmodule.h
+++ /dev/null
@@ -1,194 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include "common/types.h"
-#include "core/libraries/kernel/process.h"
-
-namespace Core::Loader {
-class SymbolsResolver;
-}
-
-namespace Libraries::SysModule {
-
-enum class OrbisSysModule : u16 {
- ORBIS_SYSMODULE_INVALID = 0x0000,
- ORBIS_SYSMODULE_FIBER = 0x0006, // libSceFiber.sprx
- ORBIS_SYSMODULE_ULT = 0x0007, // libSceUlt.sprx
- ORBIS_SYSMODULE_NGS2 = 0x000B, // libSceNgs2.sprx
- ORBIS_SYSMODULE_XML = 0x0017, // libSceXml.sprx
- ORBIS_SYSMODULE_NP_UTILITY = 0x0019, // libSceNpUtility.sprx
- ORBIS_SYSMODULE_VOICE = 0x001A, // libSceVoice.sprx
- ORBIS_SYSMODULE_VOICEQOS = 0x001B, // libSceVoiceQos.sprx
- ORBIS_SYSMODULE_NP_MATCHING2 = 0x001C, // libSceNpMatching2.sprx
- ORBIS_SYSMODULE_NP_SCORE_RANKING = 0x001E, // libSceNpScoreRanking.sprx
- ORBIS_SYSMODULE_RUDP = 0x0021, // libSceRudp.sprx
- ORBIS_SYSMODULE_NP_TUS = 0x002C, // libSceNpTus.sprx
- ORBIS_SYSMODULE_FACE = 0x0038, // libSceFace.sprx
- ORBIS_SYSMODULE_SMART = 0x0039, // libSceSmart.sprx
- ORBIS_SYSMODULE_JSON = 0x0080, // libSceJson.sprx
- ORBIS_SYSMODULE_GAME_LIVE_STREAMING = 0x0081, // libSceGameLiveStreaming.sprx
- ORBIS_SYSMODULE_COMPANION_UTIL = 0x0082, // libSceCompanionUtil.sprx
- ORBIS_SYSMODULE_PLAYGO = 0x0083, // libScePlayGo.sprx
- ORBIS_SYSMODULE_FONT = 0x0084, // libSceFont.sprx
- ORBIS_SYSMODULE_VIDEO_RECORDING = 0x0085, // libSceVideoRecording.sprx
- ORBIS_SYSMODULE_S3DCONVERSION = 0x0086, // libSceS3DConversion
- ORBIS_SYSMODULE_AUDIODEC = 0x0088, // libSceAudiodec.sprx
- ORBIS_SYSMODULE_JPEG_DEC = 0x008A, // libSceJpegDec.sprx
- ORBIS_SYSMODULE_JPEG_ENC = 0x008B, // libSceJpegEnc.sprx
- ORBIS_SYSMODULE_PNG_DEC = 0x008C, // libScePngDec.sprx
- ORBIS_SYSMODULE_PNG_ENC = 0x008D, // libScePngEnc.sprx
- ORBIS_SYSMODULE_VIDEODEC = 0x008E, // libSceVideodec.sprx
- ORBIS_SYSMODULE_MOVE = 0x008F, // libSceMove.sprx
- ORBIS_SYSMODULE_PAD_TRACKER = 0x0091, // libScePadTracker.sprx
- ORBIS_SYSMODULE_DEPTH = 0x0092, // libSceDepth.sprx
- ORBIS_SYSMODULE_HAND = 0x0093, // libSceHand.sprx
- ORBIS_SYSMODULE_LIBIME = 0x0095, // libSceIme.sprx
- ORBIS_SYSMODULE_IME_DIALOG = 0x0096, // libSceImeDialog.sprx
- ORBIS_SYSMODULE_NP_PARTY = 0x0097, // libSceNpParty.sprx
- ORBIS_SYSMODULE_FONT_FT = 0x0098, // libSceFontFt.sprx
- ORBIS_SYSMODULE_FREETYPE_OT = 0x0099, // libSceFreeTypeOt.sprx
- ORBIS_SYSMODULE_FREETYPE_OL = 0x009A, // libSceFreeTypeOl.sprx
- ORBIS_SYSMODULE_FREETYPE_OPT_OL = 0x009B, // libSceFreeTypeOptOl.sprx
- ORBIS_SYSMODULE_SCREEN_SHOT = 0x009C, // libSceScreenShot.sprx
- ORBIS_SYSMODULE_NP_AUTH = 0x009D, // libSceNpAuth.sprx
- ORBIS_SYSMODULE_SULPHA = 0x009F,
- ORBIS_SYSMODULE_SAVE_DATA_DIALOG = 0x00A0, // libSceSaveDataDialog.sprx
- ORBIS_SYSMODULE_INVITATION_DIALOG = 0x00A2, // libSceInvitationDialog.sprx
- ORBIS_SYSMODULE_DEBUG_KEYBOARD = 0x00A3,
- ORBIS_SYSMODULE_MESSAGE_DIALOG = 0x00A4, // libSceMsgDialog.sprx
- ORBIS_SYSMODULE_AV_PLAYER = 0x00A5, // libSceAvPlayer.sprx
- ORBIS_SYSMODULE_CONTENT_EXPORT = 0x00A6, // libSceContentExport.sprx
- ORBIS_SYSMODULE_AUDIO_3D = 0x00A7, // libSceAudio3d.sprx
- ORBIS_SYSMODULE_NP_COMMERCE = 0x00A8, // libSceNpCommerce.sprx
- ORBIS_SYSMODULE_MOUSE = 0x00A9, // libSceMouse.sprx
- ORBIS_SYSMODULE_COMPANION_HTTPD = 0x00AA, // libSceCompanionHttpd.sprx
- ORBIS_SYSMODULE_WEB_BROWSER_DIALOG = 0x00AB, // libSceWebBrowserDialog.sprx
- ORBIS_SYSMODULE_ERROR_DIALOG = 0x00AC, // libSceErrorDialog.sprx
- ORBIS_SYSMODULE_NP_TROPHY = 0x00AD, // libSceNpTrophy.sprx
- ORBIS_SYSMODULE_VIDEO_CORE_IF = 0x00AE, // libSceVideoCoreInterface.sprx
- ORBIS_SYSMODULE_VIDEO_CORE_SERVER_IF = 0x00AF, // libSceVideoCoreServerInterface.sprx
- ORBIS_SYSMODULE_NP_SNS_FACEBOOK = 0x00B0, // libSceNpSnsFacebookDialog.sprx
- ORBIS_SYSMODULE_MOVE_TRACKER = 0x00B1, // libSceMoveTracker.sprx
- ORBIS_SYSMODULE_NP_PROFILE_DIALOG = 0x00B2, // libSceNpProfileDialog.sprx
- ORBIS_SYSMODULE_NP_FRIEND_LIST_DIALOG = 0x00B3, // libSceNpFriendListDialog.sprx
- ORBIS_SYSMODULE_APP_CONTENT = 0x00B4, // libSceAppContent.sprx
- ORBIS_SYSMODULE_NP_SIGNALING = 0x00B5, // libSceNpSignaling.sprx
- ORBIS_SYSMODULE_REMOTE_PLAY = 0x00B6, // libSceRemoteplay.sprx
- ORBIS_SYSMODULE_USBD = 0x00B7, // libSceUsbd.sprx
- ORBIS_SYSMODULE_GAME_CUSTOM_DATA_DIALOG = 0x00B8, // libSceGameCustomDataDialog.sprx
- ORBIS_SYSMODULE_NP_EULA_DIALOG = 0x00B9, // libSceNpEulaDialog.sprx
- ORBIS_SYSMODULE_RANDOM = 0x00BA, // libSceRandom.sprx
- ORBIS_SYSMODULE_RESERVED2 = 0x00BB,
- ORBIS_SYSMODULE_M4AAC_ENC = 0x00BC, // libSceM4aacEnc.sprx
- ORBIS_SYSMODULE_AUDIODEC_CPU = 0x00BD, // libSceAudiodecCpu.sprx
- ORBIS_SYSMODULE_AUDIODEC_CPU_DDP = 0x00BE, // libSceAudiodecCpuDdp.sprx
- ORBIS_SYSMODULE_AUDIODEC_CPU_M4AAC = 0x00C0, // libSceAudiodecCpuM4aac.sprx
- ORBIS_SYSMODULE_BEMP2_SYS = 0x00C1, // libSceBemp2sys.sprx
- ORBIS_SYSMODULE_BEISOBMF = 0x00C2, // libSceBeisobmf.sprx
- ORBIS_SYSMODULE_PLAY_READY = 0x00C3, // libScePlayReady.sprx
- ORBIS_SYSMODULE_VIDEO_NATIVE_EXT_ESSENTIAL = 0x00C4, // libSceVideoNativeExtEssential.sprx
- ORBIS_SYSMODULE_ZLIB = 0x00C5, // libSceZlib.sprx
- ORBIS_SYSMODULE_DTCP_IP = 0x00C6, // libSceDtcpIp.sprx
- ORBIS_SYSMODULE_CONTENT_SEARCH = 0x00C7, // libSceContentSearch.sprx
- ORBIS_SYSMODULE_SHARE_UTILITY = 0x00C8, // libSceShareUtility.sprx
- ORBIS_SYSMODULE_AUDIODEC_CPU_DTS_HD_LBR = 0x00C9, // libSceAudiodecCpuDtsHdLbr.sprx
- ORBIS_SYSMODULE_DECI4H = 0x00CA,
- ORBIS_SYSMODULE_HEAD_TRACKER = 0x00CB, // libSceHeadTracker.sprx
- ORBIS_SYSMODULE_GAME_UPDATE = 0x00CC, // libSceGameUpdate.sprx
- ORBIS_SYSMODULE_AUTO_MOUNTER_CLIENT = 0x00CD, // libSceAutoMounterClient.sprx
- ORBIS_SYSMODULE_SYSTEM_GESTURE = 0x00CE, // libSceSystemGesture.sprx
- ORBIS_SYSMODULE_VIDEODEC2 = 0x00CF, // libSceVideodec2.sprx
- ORBIS_SYSMODULE_VDECWRAP = 0x00D0, // libSceVdecwrap.sprx
- ORBIS_SYSMODULE_AT9_ENC = 0x00D1, // libSceAt9Enc.sprx
- ORBIS_SYSMODULE_CONVERT_KEYCODE = 0x00D2, // libSceConvertKeycode.sprx
- ORBIS_SYSMODULE_SHARE_PLAY = 0x00D3, // libSceSharePlay.sprx
- ORBIS_SYSMODULE_HMD = 0x00D4, // libSceHmd.sprx
- ORBIS_SYSMODULE_USB_STORAGE = 0x00D5, // libSceUsbStorage.sprx
- ORBIS_SYSMODULE_USB_STORAGE_DIALOG = 0x00D6, // libSceUsbStorageDialog.sprx
- ORBIS_SYSMODULE_DISC_MAP = 0x00D7, // libSceDiscMap.sprx
- ORBIS_SYSMODULE_FACE_TRACKER = 0x00D8, // libSceFaceTracker.sprx
- ORBIS_SYSMODULE_HAND_TRACKER = 0x00D9, // libSceHandTracker.sprx
- ORBIS_SYSMODULE_NP_SNS_YOUTUBE_DIALOG = 0x00DA, // libSceNpSnsYouTubeDialog.sprx
- ORBIS_SYSMODULE_PROFILE_CACHE_EXTERNAL = 0x00DC, // libSceProfileCacheExternal.sprx
- ORBIS_SYSMODULE_MUSIC_PLAYER_SERVICE = 0x00DD, // libSceMusicPlayerService.sprx
- ORBIS_SYSMODULE_SP_SYS_CALL_WRAPPER = 0x00DE, // libSceSpSysCallWrapper.sprx
- ORBIS_SYSMODULE_PS2_EMU_MENU_DIALOG = 0x00DF, // libScePs2EmuMenuDialog.sprx
- ORBIS_SYSMODULE_NP_SNS_DAILYMOTION_DIALOG = 0x00E0, // libSceNpSnsDailyMotionDialog.sprx
- ORBIS_SYSMODULE_AUDIODEC_CPU_HEVAG = 0x00E1, // libSceAudiodecCpuHevag.sprx
- ORBIS_SYSMODULE_LOGIN_DIALOG = 0x00E2, // libSceLoginDialog.sprx
- ORBIS_SYSMODULE_LOGIN_SERVICE = 0x00E3, // libSceLoginService.sprx
- ORBIS_SYSMODULE_SIGNIN_DIALOG = 0x00E4, // libSceSigninDialog.sprx
- ORBIS_SYSMODULE_VDECSW = 0x00E5, // libSceVdecsw.sprx
- ORBIS_SYSMODULE_CUSTOM_MUSIC_CORE = 0x00E6, // libSceCustomMusicCore.sprx
- ORBIS_SYSMODULE_JSON2 = 0x00E7, // libSceJson2.sprx
- ORBIS_SYSMODULE_AUDIO_LATENCY_ESTIMATION = 0x00E8, // libSceAudioLatencyEstimation.sprx
- ORBIS_SYSMODULE_WK_FONT_CONFIG = 0x00E9, // libSceWkFontConfig.sprx
- ORBIS_SYSMODULE_VORBIS_DEC = 0x00EA, // libSceVorbisDec.sprx
- ORBIS_SYSMODULE_HMD_SETUP_DIALOG = 0x00EB, // libSceHmdSetupDialog.sprx
- ORBIS_SYSMODULE_RESERVED28 = 0x00EC,
- ORBIS_SYSMODULE_VR_TRACKER = 0x00ED, // libSceVrTracker.sprx
- ORBIS_SYSMODULE_CONTENT_DELETE = 0x00EE, // libSceContentDelete.sprx
- ORBIS_SYSMODULE_IME_BACKEND = 0x00EF, // libSceImeBackend.sprx
- ORBIS_SYSMODULE_NET_CTL_AP_DIALOG = 0x00F0, // libSceNetCtlApDialog.sprx
- ORBIS_SYSMODULE_PLAYGO_DIALOG = 0x00F1, // libScePlayGoDialog.sprx
- ORBIS_SYSMODULE_SOCIAL_SCREEN = 0x00F2, // libSceSocialScreen.sprx
- ORBIS_SYSMODULE_EDIT_MP4 = 0x00F3, // libSceEditMp4.sprx
- ORBIS_SYSMODULE_PSM_KIT_SYSTEM = 0x00F5, // libScePsmKitSystem.sprx
- ORBIS_SYSMODULE_TEXT_TO_SPEECH = 0x00F6, // libSceTextToSpeech.sprx
- ORBIS_SYSMODULE_NP_TOOLKIT = 0x00F7, // libSceNpToolkit.sprx
- ORBIS_SYSMODULE_CUSTOM_MUSIC_SERVICE = 0x00F8, // libSceCustomMusicService.sprx
- ORBIS_SYSMODULE_CL_SYS_CALL_WRAPPER = 0x00F9, // libSceClSysCallWrapper.sprx
- ORBIS_SYSMODULE_SYSTEM_LOGGER = 0x00FA, // libSceSystemLogger.sprx
- ORBIS_SYSMODULE_BLUETOOTH_HID = 0x00FB, // libSceBluetoothHid.sprx
- ORBIS_SYSMODULE_VIDEO_DECODER_ARBITRATION = 0x00FC, // libSceVideoDecoderArbitration.sprx
- ORBIS_SYSMODULE_VR_SERVICE_DIALOG = 0x00FD, // libSceVrServiceDialog.sprx
- ORBIS_SYSMODULE_JOB_MANAGER = 0x00FE, // libSceJobManager.sprx
- ORBIS_SYSMODULE_SHARE_FACTORY_UTIL = 0x00FF, // libSceShareFactoryUtil.sprx
- ORBIS_SYSMODULE_SOCIAL_SCREEN_DIALOG = 0x0100, // libSceSocialScreenDialog.sprx
- ORBIS_SYSMODULE_NP_SNS_DIALOG = 0x0101, // libSceNpSnsDialog.sprx
- ORBIS_SYSMODULE_NP_TOOLKIT2 = 0x0102, // libSceNpToolkit2.sprx
- ORBIS_SYSMODULE_SRC_UTL = 0x0103, // libSceSrcUtl.sprx
- ORBIS_SYSMODULE_DISC_ID = 0x0104, // libSceDiscId.sprx
- ORBIS_SYSMODULE_NP_UNIVERSAL_DATA_SYSTEM = 0x0105, // libSceNpUniversalDataSystem.sprx
- ORBIS_SYSMODULE_KEYBOARD = 0x0106, // libSceKeyboard.sprx
- ORBIS_SYSMODULE_GIC = 0x0107, // libSceGic.sprx
- ORBIS_SYSMODULE_PLAY_READY2 = 0x0108, // libScePlayReady2.sprx
- ORBIS_SYSMODULE_CES_CS = 0x010c, // libSceCesCs.sprx
- ORBIS_SYSMODULE_PLAYER_INVITATION_DIALOG = 0x010d, // libScePlayerInvitationDialog.sprx
- ORBIS_SYSMODULE_NP_SESSION_SIGNALING = 0x0112, // libSceNpSessionSignaling.sprx
- ORBIS_SYSMODULE_NP_ENTITLEMENT_ACCESS = 0x0113, // libSceNpEntitlementAccess.sprx
- ORBIS_SYSMODULE_NP_CPP_WEB_API = 0x0115, // libSceNpCppWebApi.sprx
- ORBIS_SYSMODULE_HUB_APP_UTIL = 0x0116, // libSceHubAppUtil.sprx
- ORBIS_SYSMODULE_NP_PARTNER001 = 0x011a, // libSceNpPartner001.sprx
- ORBIS_SYSMODULE_FONT_GS = 0x012f, // libSceFontGs.sprx
- ORBIS_SYSMODULE_FONT_GSM = 0x0135, // libSceFontGsm.sprx
- ORBIS_SYSMODULE_NP_PARTNER_SUBSCRIPTION = 0x0138, // libSceNpPartnerSubscription.sprx
- ORBIS_SYSMODULE_NP_AUTH_AUTHORIZED_APP_DIALOG = 0x0139, // libSceNpAuthAuthorizedAppDialog.sprx
-};
-
-enum class OrbisSysModuleInternal : u32 {
- ORBIS_SYSMODULE_INTERNAL_RAZOR_CPU = 0x80000019, // libSceRazorCpu.sprx
-};
-
-int PS4_SYSV_ABI sceSysmoduleGetModuleHandleInternal();
-s32 PS4_SYSV_ABI sceSysmoduleGetModuleInfoForUnwind(VAddr addr, s32 flags,
- Kernel::OrbisModuleInfoForUnwind* info);
-int PS4_SYSV_ABI sceSysmoduleIsCalledFromSysModule();
-int PS4_SYSV_ABI sceSysmoduleIsCameraPreloaded();
-int PS4_SYSV_ABI sceSysmoduleIsLoaded(OrbisSysModule id);
-int PS4_SYSV_ABI sceSysmoduleIsLoadedInternal(OrbisSysModuleInternal id);
-int PS4_SYSV_ABI sceSysmoduleLoadModule(OrbisSysModule id);
-int PS4_SYSV_ABI sceSysmoduleLoadModuleByNameInternal();
-int PS4_SYSV_ABI sceSysmoduleLoadModuleInternal();
-int PS4_SYSV_ABI sceSysmoduleLoadModuleInternalWithArg();
-int PS4_SYSV_ABI sceSysmoduleMapLibcForLibkernel();
-int PS4_SYSV_ABI sceSysmodulePreloadModuleForLibkernel();
-int PS4_SYSV_ABI sceSysmoduleUnloadModule();
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleByNameInternal();
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternal();
-int PS4_SYSV_ABI sceSysmoduleUnloadModuleInternalWithArg();
-
-void RegisterLib(Core::Loader::SymbolsResolver* sym);
-} // namespace Libraries::SysModule
diff --git a/src/core/libraries/system/system_error.h b/src/core/libraries/system/system_error.h
deleted file mode 100644
index 615e4cd5f..000000000
--- a/src/core/libraries/system/system_error.h
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-constexpr int ORBIS_SYSMODULE_INVALID_ID = 0x805A1000;
-constexpr int ORBIS_SYSMODULE_NOT_LOADED = 0x805A1001;
-constexpr int ORBIS_SYSMODULE_LOCK_FAILED = 0x805A10FF;
\ No newline at end of file
diff --git a/src/core/libraries/usbd/emulated/dimensions.cpp b/src/core/libraries/usbd/emulated/dimensions.cpp
index 272f2f649..452968840 100644
--- a/src/core/libraries/usbd/emulated/dimensions.cpp
+++ b/src/core/libraries/usbd/emulated/dimensions.cpp
@@ -3,6 +3,9 @@
#include "dimensions.h"
+#include "core/libraries/kernel/threads.h"
+#include "core/tls.h"
+
#include
#include
@@ -619,22 +622,46 @@ libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* t
return LIBUSB_TRANSFER_COMPLETED;
}
+struct WriteThreadArgs {
+ DimensionsBackend* self;
+ libusb_transfer* transfer;
+};
+
+void* PS4_SYSV_ABI DimensionsBackend::WriteThread(void* arg) {
+ auto* args = reinterpret_cast(arg);
+
+ auto* self = args->self;
+ auto* transfer = args->transfer;
+
+ self->HandleAsyncTransfer(transfer);
+
+ const u8 flags = transfer->flags;
+ transfer->status = LIBUSB_TRANSFER_COMPLETED;
+ transfer->actual_length = transfer->length;
+ if (transfer->callback) {
+ transfer->callback(transfer);
+ }
+ if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
+ libusb_free_transfer(transfer);
+ }
+ delete args;
+ return nullptr;
+}
+
s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) {
if (transfer->endpoint == 0x01) {
- std::thread write_thread([this, transfer] {
- HandleAsyncTransfer(transfer);
+ using namespace Libraries::Kernel;
+
+ PthreadAttrT attr{};
+ posix_pthread_attr_init(&attr);
+ PthreadT thread{};
+ auto* args = new WriteThreadArgs();
+ args->self = this;
+ args->transfer = transfer;
+ posix_pthread_create(&thread, &attr, HOST_CALL(DimensionsBackend::WriteThread), args);
+ posix_pthread_attr_destroy(&attr);
+ posix_pthread_detach(thread);
- const u8 flags = transfer->flags;
- transfer->status = LIBUSB_TRANSFER_COMPLETED;
- transfer->actual_length = transfer->length;
- if (transfer->callback) {
- transfer->callback(transfer);
- }
- if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
- libusb_free_transfer(transfer);
- }
- });
- write_thread.detach();
return LIBUSB_SUCCESS;
}
diff --git a/src/core/libraries/usbd/emulated/dimensions.h b/src/core/libraries/usbd/emulated/dimensions.h
index d9573b5f4..bc409f7c3 100644
--- a/src/core/libraries/usbd/emulated/dimensions.h
+++ b/src/core/libraries/usbd/emulated/dimensions.h
@@ -103,6 +103,8 @@ protected:
std::queue> m_queries;
private:
+ static void* PS4_SYSV_ABI WriteThread(void* arg);
+
std::shared_ptr m_dimensions_toypad = std::make_shared();
std::array m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
diff --git a/src/core/linker.cpp b/src/core/linker.cpp
index 7a0653e9f..3f410e926 100644
--- a/src/core/linker.cpp
+++ b/src/core/linker.cpp
@@ -16,11 +16,16 @@
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/threads.h"
+#include "core/libraries/sysmodule/sysmodule.h"
#include "core/linker.h"
#include "core/memory.h"
#include "core/tls.h"
#include "ipc/ipc.h"
+#ifndef _WIN32
+#include
+#endif
+
namespace Core {
static PS4_SYSV_ABI void ProgramExitFunc() {
@@ -106,11 +111,17 @@ void Linker::Execute(const std::vector& args) {
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("Game:Main");
+#ifndef _WIN32 // Clear any existing signal mask for game threads.
+ sigset_t emptyset;
+ sigemptyset(&emptyset);
+ pthread_sigmask(SIG_SETMASK, &emptyset, nullptr);
+#endif
if (auto& ipc = IPC::Instance()) {
ipc.WaitForStart();
}
- LoadSharedLibraries();
+ // Have libSceSysmodule preload our libraries.
+ Libraries::SysModule::sceSysmodulePreloadModuleForLibkernel();
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
// Some games fail without accurately emulating this behavior.
@@ -135,7 +146,8 @@ void Linker::Execute(const std::vector& args) {
}
}
params.entry_addr = module->GetEntryAddress();
- ExecuteGuest(RunMainEntry, ¶ms);
+ Libraries::Kernel::ClearStack();
+ RunMainEntry(¶ms);
});
}
@@ -349,8 +361,10 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
return_info->virtual_address = AeroLib::GetStub(sr.name.c_str());
return_info->name = "Unknown !!!";
}
- LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name,
- return_info->name, library->name, module->name);
+ if (library->name != "libc" && library->name != "libSceFios2") {
+ LOG_WARNING(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name,
+ return_info->name, library->name, module->name);
+ }
return false;
}
@@ -379,8 +393,7 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
if (!addr) {
// Module was just loaded by above code. Allocate TLS block for it.
const u32 init_image_size = module->tls.init_image_size;
- u8* dest = reinterpret_cast(
- Core::ExecuteGuest(heap_api->heap_malloc, module->tls.image_size));
+ u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size));
const u8* src = reinterpret_cast(module->tls.image_virtual_addr);
std::memcpy(dest, src, init_image_size);
std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size);
@@ -412,7 +425,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) {
ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread");
} else {
if (heap_api) {
- addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size);
+ addr_out = heap_api->heap_malloc(total_tls_size);
} else {
addr_out = std::malloc(total_tls_size);
}
@@ -422,7 +435,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) {
void Linker::FreeTlsForNonPrimaryThread(void* pointer) {
if (heap_api) {
- Core::ExecuteGuest(heap_api->heap_free, pointer);
+ heap_api->heap_free(pointer);
} else {
std::free(pointer);
}
diff --git a/src/core/linker.h b/src/core/linker.h
index 8ffcd9d45..3cb59d9ee 100644
--- a/src/core/linker.h
+++ b/src/core/linker.h
@@ -125,11 +125,10 @@ public:
}
}
- void LoadSharedLibraries() {
+ void RelocateAllImports() {
+ std::scoped_lock lk{mutex};
for (auto& module : m_modules) {
- if (module->IsSharedLib()) {
- module->Start(0, nullptr, nullptr);
- }
+ Relocate(module.get());
}
}
diff --git a/src/core/module.cpp b/src/core/module.cpp
index 127e74293..7e9d74a09 100644
--- a/src/core/module.cpp
+++ b/src/core/module.cpp
@@ -97,7 +97,7 @@ Module::~Module() = default;
s32 Module::Start(u64 args, const void* argp, void* param) {
LOG_INFO(Core_Linker, "Module started : {}", name);
const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress();
- return ExecuteGuest(reinterpret_cast(addr), args, argp, param);
+ return reinterpret_cast(addr)(args, argp, param);
}
void Module::LoadModuleToMemory(u32& max_tls_index) {
diff --git a/src/core/signals.cpp b/src/core/signals.cpp
index 70b431d39..f9b45bab7 100644
--- a/src/core/signals.cpp
+++ b/src/core/signals.cpp
@@ -21,7 +21,7 @@
#ifndef _WIN32
namespace Libraries::Kernel {
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context);
-extern std::array Handlers;
+extern std::array Handlers;
} // namespace Libraries::Kernel
#endif
@@ -86,7 +86,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
// If the guest has installed a custom signal handler, and the access violation didn't
// come from HLE memory tracking, pass the signal on
- if (Libraries::Kernel::Handlers[sig]) {
+ if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
Libraries::Kernel::SigactionHandler(sig, info,
reinterpret_cast(raw_context));
return;
@@ -99,7 +99,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
}
case SIGILL:
if (!signals->DispatchIllegalInstruction(raw_context)) {
- if (Libraries::Kernel::Handlers[sig]) {
+ if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
Libraries::Kernel::SigactionHandler(sig, info,
reinterpret_cast(raw_context));
return;
diff --git a/src/core/signals.h b/src/core/signals.h
index 90801debb..011383693 100644
--- a/src/core/signals.h
+++ b/src/core/signals.h
@@ -10,10 +10,8 @@
#ifdef _WIN32
#define SIGSLEEP -1
-#elif defined(__APPLE__)
-#define SIGSLEEP SIGVTALRM
#else
-#define SIGSLEEP SIGRTMAX
+#define SIGSLEEP SIGVTALRM
#endif
namespace Core {
diff --git a/src/core/tls.cpp b/src/core/tls.cpp
index 57ed20f38..8b926cb39 100644
--- a/src/core/tls.cpp
+++ b/src/core/tls.cpp
@@ -198,7 +198,7 @@ Tcb* GetTcbBase() {
thread_local std::once_flag init_tls_flag;
-void EnsureThreadInitialized() {
+void InitializeTLS() {
std::call_once(init_tls_flag, [] { SetTcbBase(Libraries::Kernel::g_curthread->tcb); });
}
diff --git a/src/core/tls.h b/src/core/tls.h
index 27de518ea..2d94488f7 100644
--- a/src/core/tls.h
+++ b/src/core/tls.h
@@ -43,30 +43,7 @@ void SetTcbBase(void* image_address);
Tcb* GetTcbBase();
/// Makes sure TLS is initialized for the thread before entering guest.
-void EnsureThreadInitialized();
-
-template
-#ifdef __clang__
-__attribute__((optnone))
-#else
-__attribute__((optimize("O0")))
-#endif
-void ClearStack() {
- volatile void* buf = alloca(size);
- memset(const_cast(buf), 0, size);
- buf = nullptr;
-}
-
-template
-ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) {
- EnsureThreadInitialized();
- // clear stack to avoid trash from EnsureThreadInitialized
- auto* tcb = GetTcbBase();
- if (tcb != nullptr && tcb->tcb_fiber == nullptr) {
- ClearStack<12_KB>();
- }
- return func(std::forward(args)...);
-}
+void InitializeTLS();
template
struct HostCallWrapperImpl;
diff --git a/src/emulator.cpp b/src/emulator.cpp
index 27def3565..cc862f8ab 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -32,17 +32,9 @@
#include "core/file_format/psf.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
-#include "core/libraries/disc_map/disc_map.h"
-#include "core/libraries/font/font.h"
-#include "core/libraries/font/fontft.h"
-#include "core/libraries/jpeg/jpegenc.h"
#include "core/libraries/kernel/kernel.h"
-#include "core/libraries/libc_internal/libc_internal.h"
-#include "core/libraries/libpng/pngenc.h"
#include "core/libraries/libs.h"
-#include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np/np_trophy.h"
-#include "core/libraries/rtc/rtc.h"
#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h"
#include "core/memory.h"
@@ -245,7 +237,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args,
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
- LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks());
+ LOG_INFO(Config, "GPU readbacksMode: {}", Config::getReadbacksMode());
LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages());
LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
@@ -405,17 +397,6 @@ void Emulator::Run(std::filesystem::path file, std::vector args,
std::quick_exit(0);
}
- // check if we have system modules to load
- LoadSystemModules(game_info.game_serial);
-
- // Load all prx from game's sce_module folder
- mnt->IterateDirectory("/app0/sce_module", [this](const auto& path, const auto is_file) {
- if (is_file) {
- LOG_INFO(Loader, "Loading {}", fmt::UTF(path.u8string()));
- linker->LoadModule(path);
- }
- });
-
#ifdef ENABLE_DISCORD_RPC
// Discord RPC
if (Config::getEnableDiscordRPC()) {
@@ -556,54 +537,6 @@ void Emulator::Restart(std::filesystem::path eboot_path,
std::quick_exit(0);
}
-void Emulator::LoadSystemModules(const std::string& game_serial) {
- constexpr auto ModulesToLoad = std::to_array(
- {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib},
- {"libSceUlt.sprx", nullptr},
- {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib},
- {"libSceJpegDec.sprx", nullptr},
- {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib},
- {"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
- {"libSceJson.sprx", nullptr},
- {"libSceJson2.sprx", nullptr},
- {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
- {"libSceCesCs.sprx", nullptr},
- {"libSceAudiodec.sprx", nullptr},
- {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
- {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
- {"libSceFreeTypeOt.sprx", nullptr}});
-
- std::vector found_modules;
- const auto& sys_module_path = Config::getSysModulesPath();
- for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) {
- found_modules.push_back(entry.path());
- }
- for (const auto& [module_name, init_func] : ModulesToLoad) {
- const auto it = std::ranges::find_if(
- found_modules, [&](const auto& path) { return path.filename() == module_name; });
- if (it != found_modules.end()) {
- LOG_INFO(Loader, "Loading {}", it->string());
- if (linker->LoadModule(*it) != -1) {
- continue;
- }
- }
- if (init_func) {
- LOG_INFO(Loader, "Can't Load {} switching to HLE", module_name);
- init_func(&linker->GetHLESymbols());
- } else {
- LOG_INFO(Loader, "No HLE available for {} module", module_name);
- }
- }
- if (!game_serial.empty() && std::filesystem::exists(sys_module_path / game_serial)) {
- for (const auto& entry :
- std::filesystem::directory_iterator(sys_module_path / game_serial)) {
- LOG_INFO(Loader, "Loading {} from game serial file {}", entry.path().string(),
- game_serial);
- linker->LoadModule(entry.path());
- }
- }
-}
-
void Emulator::UpdatePlayTime(const std::string& serial) {
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
const auto filePath = (user_dir / "play_time.txt").string();
diff --git a/src/main.cpp b/src/main.cpp
index d3799e2ec..d2804ee62 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -142,6 +142,14 @@ int main(int argc, char* argv[]) {
return 1;
}
}
+ if (!gameArgs.empty()) {
+ if (gameArgs.front() == "--") {
+ gameArgs.erase(gameArgs.begin());
+ } else {
+ std::cerr << "Error: unhandled flags\n";
+ return 1;
+ }
+ }
// ---- Apply flags ----
if (patchFile)
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
index 6dd1637dd..1055bf081 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
@@ -140,6 +140,15 @@ Id ImageAtomicF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id va
const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.F32[1], pointer, scope, semantics, value);
}
+
+Id ImageAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value,
+ Id cmp_value,
+ Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) {
+ const auto& texture = ctx.images[handle & 0xFFFF];
+ const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, texture.id, coords, ctx.ConstU32(0U))};
+ const auto [scope, semantics]{AtomicArgs(ctx)};
+ return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, semantics, value, cmp_value);
+}
} // Anonymous namespace
Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value) {
@@ -420,6 +429,12 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicExchange);
}
+Id EmitImageAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value,
+ Id cmp_value) {
+ return ImageAtomicU32CmpSwap(ctx, inst, handle, coords, value, cmp_value,
+ &Sirit::Module::OpAtomicCompareExchange);
+}
+
Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) {
const auto& buffer = ctx.buffers[binding];
const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index e2a969b61..0b05dcef4 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -220,20 +220,33 @@ Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms) {
const auto& texture = ctx.images[handle & 0xFFFF];
- const Id image = ctx.OpLoad(texture.image_type, texture.id);
const Id color_type = texture.data_types->Get(4);
ImageOperands operands;
operands.Add(spv::ImageOperandsMask::Sample, ms);
Id texel;
if (!texture.is_storage) {
+ const Id image = ctx.OpLoad(texture.image_type, texture.id);
operands.Add(spv::ImageOperandsMask::Lod, lod);
texel = ctx.OpImageFetch(color_type, image, coords, operands.mask, operands.operands);
} else {
+ Id image_ptr = texture.id;
if (ctx.profile.supports_image_load_store_lod) {
operands.Add(spv::ImageOperandsMask::Lod, lod);
} else if (Sirit::ValidId(lod)) {
- LOG_WARNING(Render, "Image read with LOD not supported by driver");
+#if 1
+ // It's confusing what interactions will cause this code path so leave it as
+ // unreachable until a case is found.
+ // Normally IMAGE_LOAD_MIP should translate -> OpImageFetch
+ UNREACHABLE_MSG("Unsupported ImageRead with Lod");
+#else
+ LOG_WARNING(Render, "Fallback for ImageRead with LOD");
+ ASSERT(texture.mip_fallback_mode == MipStorageFallbackMode::DynamicIndex);
+ const Id single_image_ptr_type =
+ ctx.TypePointer(spv::StorageClass::UniformConstant, texture.image_type);
+ image_ptr = ctx.OpAccessChain(single_image_ptr_type, image_ptr, std::array{lod});
+#endif
}
+ const Id image = ctx.OpLoad(texture.image_type, image_ptr);
texel = ctx.OpImageRead(color_type, image, coords, operands.mask, operands.operands);
}
return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], texel) : texel;
@@ -242,15 +255,20 @@ Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms,
Id color) {
const auto& texture = ctx.images[handle & 0xFFFF];
- const Id image = ctx.OpLoad(texture.image_type, texture.id);
+ Id image_ptr = texture.id;
const Id color_type = texture.data_types->Get(4);
ImageOperands operands;
operands.Add(spv::ImageOperandsMask::Sample, ms);
if (ctx.profile.supports_image_load_store_lod) {
operands.Add(spv::ImageOperandsMask::Lod, lod);
} else if (Sirit::ValidId(lod)) {
- LOG_WARNING(Render, "Image write with LOD not supported by driver");
+ LOG_WARNING(Render, "Fallback for ImageWrite with LOD");
+ ASSERT(texture.mip_fallback_mode == MipStorageFallbackMode::DynamicIndex);
+ const Id single_image_ptr_type =
+ ctx.TypePointer(spv::StorageClass::UniformConstant, texture.image_type);
+ image_ptr = ctx.OpAccessChain(single_image_ptr_type, image_ptr, std::array{lod});
}
+ const Id image = ctx.OpLoad(texture.image_type, image_ptr);
const Id texel = texture.is_integer ? ctx.OpBitcast(color_type, color) : color;
ctx.OpImageWrite(image, coords, texel, operands.mask, operands.operands);
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 80968eaf0..69fa36eaa 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -456,6 +456,8 @@ Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords,
Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value);
Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value);
Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value);
+Id EmitImageAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
+ Id cmp_value);
Id EmitCubeFaceIndex(EmitContext& ctx, IR::Inst* inst, Id cube_coords);
Id EmitLaneId(EmitContext& ctx);
Id EmitWarpId(EmitContext& ctx);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 261155ab5..c0e469964 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -961,23 +961,33 @@ void EmitContext::DefineImagesAndSamplers() {
const auto nfmt = sharp.GetNumberFmt();
const bool is_integer = AmdGpu::IsInteger(nfmt);
const bool is_storage = image_desc.is_written;
+ const MipStorageFallbackMode mip_fallback_mode = image_desc.mip_fallback_mode;
const VectorIds& data_types = GetAttributeType(*this, nfmt);
const Id sampled_type = data_types[1];
const Id image_type{ImageType(*this, image_desc, sampled_type)};
- const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
+
+ const u32 num_bindings = image_desc.NumBindings(info);
+ Id pointee_type = image_type;
+ if (mip_fallback_mode == MipStorageFallbackMode::DynamicIndex) {
+ pointee_type = TypeArray(pointee_type, ConstU32(num_bindings));
+ }
+
+ const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, pointee_type)};
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
- Decorate(id, spv::Decoration::Binding, binding.unified++);
+ Decorate(id, spv::Decoration::Binding, binding.unified);
+ binding.unified += num_bindings;
Decorate(id, spv::Decoration::DescriptorSet, 0U);
+ // TODO better naming for resources (flattened sharp_idx is not informative)
Name(id, fmt::format("{}_{}{}", stage, "img", image_desc.sharp_idx));
images.push_back({
.data_types = &data_types,
.id = id,
.sampled_type = is_storage ? sampled_type : TypeSampledImage(image_type),
- .pointer_type = pointer_type,
.image_type = image_type,
.view_type = sharp.GetViewType(image_desc.is_array),
.is_integer = is_integer,
.is_storage = is_storage,
+ .mip_fallback_mode = mip_fallback_mode,
});
interfaces.push_back(id);
}
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index 9bb2b7d7a..a9c6f0968 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -293,11 +293,11 @@ public:
const VectorIds* data_types;
Id id;
Id sampled_type;
- Id pointer_type;
Id image_type;
AmdGpu::ImageType view_type;
bool is_integer = false;
bool is_storage = false;
+ MipStorageFallbackMode mip_fallback_mode{};
};
enum class PointerType : u32 {
diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp
index d26873396..4a90fe358 100644
--- a/src/shader_recompiler/frontend/format.cpp
+++ b/src/shader_recompiler/frontend/format.cpp
@@ -3430,8 +3430,8 @@ constexpr std::array InstructionFormatMIMG = {{
{InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Uint32},
// 16 = IMAGE_ATOMIC_CMPSWAP
- {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
- ScalarType::Undefined},
+ {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
+ ScalarType::Uint32},
// 17 = IMAGE_ATOMIC_ADD
{InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Uint32},
diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp
index 634486fc4..b1aca83d3 100644
--- a/src/shader_recompiler/frontend/translate/data_share.cpp
+++ b/src/shader_recompiler/frontend/translate/data_share.cpp
@@ -277,12 +277,21 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) {
const u8 offset0 = inst.control.ds.offset0;
const u8 offset1 = inst.control.ds.offset1;
const IR::U32 src{GetSrc(inst.src[0])};
- // ASSERT(offset1 & 0x80);
const IR::U32 lane_id = ir.LaneId();
- const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11));
- const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1));
- const IR::U32 index = ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2));
- SetDst(inst.dst[0], ir.QuadShuffle(src, index));
+ if (offset1 & 0x80) {
+ const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11));
+ const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1));
+ const IR::U32 index = ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2));
+ SetDst(inst.dst[0], ir.QuadShuffle(src, index));
+ } else {
+ const u8 and_mask = (offset0 & 0x1f) | (~u8{0} << 5);
+ const u8 or_mask = (offset0 >> 5) | ((offset1 & 0x3) << 3);
+ const u8 xor_mask = offset1 >> 2;
+ const IR::U32 index = ir.BitwiseXor(
+ ir.BitwiseOr(ir.BitwiseAnd(lane_id, ir.Imm32(and_mask)), ir.Imm32(or_mask)),
+ ir.Imm32(xor_mask));
+ SetDst(inst.dst[0], ir.ReadLane(src, index));
+ }
}
void Translator::DS_APPEND(const GcnInst& inst) {
diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp
index 3aa70e2ec..611070a86 100644
--- a/src/shader_recompiler/frontend/translate/translate.cpp
+++ b/src/shader_recompiler/frontend/translate/translate.cpp
@@ -352,10 +352,10 @@ T Translator::GetSrc(const InstOperand& operand) {
}
} else {
if (operand.input_modifier.abs) {
- value = ir.IAbs(value);
+ value = ir.BitwiseAnd(value, ir.Imm32(0x7FFFFFFFu));
}
if (operand.input_modifier.neg) {
- value = ir.INeg(value);
+ value = ir.BitwiseXor(value, ir.Imm32(0x80000000u));
}
}
return value;
@@ -453,6 +453,23 @@ T Translator::GetSrc64(const InstOperand& operand) {
if (operand.input_modifier.neg) {
value = ir.FPNeg(value);
}
+ } else {
+ // GCN VOP3 abs/neg modifier bits operate on the sign bit (bit 63 for
+ // 64-bit values). Unpack, modify the high dword's bit 31, repack.
+ if (operand.input_modifier.abs) {
+ const auto unpacked = ir.UnpackUint2x32(value);
+ const auto lo = IR::U32{ir.CompositeExtract(unpacked, 0)};
+ const auto hi = IR::U32{ir.CompositeExtract(unpacked, 1)};
+ const auto hi_abs = ir.BitwiseAnd(hi, ir.Imm32(0x7FFFFFFFu));
+ value = ir.PackUint2x32(ir.CompositeConstruct(lo, hi_abs));
+ }
+ if (operand.input_modifier.neg) {
+ const auto unpacked = ir.UnpackUint2x32(value);
+ const auto lo = IR::U32{ir.CompositeExtract(unpacked, 0)};
+ const auto hi = IR::U32{ir.CompositeExtract(unpacked, 1)};
+ const auto hi_neg = ir.BitwiseXor(hi, ir.Imm32(0x80000000u));
+ value = ir.PackUint2x32(ir.CompositeConstruct(lo, hi_neg));
+ }
}
return value;
}
diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h
index 08b0192f5..5ee75e336 100644
--- a/src/shader_recompiler/frontend/translate/translate.h
+++ b/src/shader_recompiler/frontend/translate/translate.h
@@ -153,6 +153,7 @@ public:
void V_SUB_F32(const GcnInst& inst);
void V_SUBREV_F32(const GcnInst& inst);
void V_MUL_F32(const GcnInst& inst);
+ void V_MUL_LEGACY_F32(const GcnInst& inst);
void V_MUL_I32_I24(const GcnInst& inst, bool is_signed);
void V_MIN_F32(const GcnInst& inst, bool is_legacy = false);
void V_MAX_F32(const GcnInst& inst, bool is_legacy = false);
diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp
index 08a0f6527..23236b702 100644
--- a/src/shader_recompiler/frontend/translate/vector_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp
@@ -25,7 +25,7 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
case Opcode::V_MAC_LEGACY_F32:
return V_MAC_F32(inst);
case Opcode::V_MUL_LEGACY_F32:
- return V_MUL_F32(inst);
+ return V_MUL_LEGACY_F32(inst);
case Opcode::V_MUL_F32:
return V_MUL_F32(inst);
case Opcode::V_MUL_I32_I24:
@@ -493,6 +493,19 @@ void Translator::V_MUL_F32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1])));
}
+void Translator::V_MUL_LEGACY_F32(const GcnInst& inst) {
+ // GCN V_MUL_LEGACY_F32: if either source is zero, the result is +0.0
+ // regardless of the other operand (even if NaN or Inf).
+ // Standard IEEE multiply would produce NaN for 0 * Inf.
+ const IR::F32 src0{GetSrc(inst.src[0])};
+ const IR::F32 src1{GetSrc(inst.src[1])};
+ const IR::F32 zero{ir.Imm32(0.0f)};
+ const IR::U1 src0_zero{ir.FPEqual(src0, zero)};
+ const IR::U1 src1_zero{ir.FPEqual(src1, zero)};
+ const IR::U1 either_zero{ir.LogicalOr(src0_zero, src1_zero)};
+ SetDst(inst.dst[0], IR::F32{ir.Select(either_zero, zero, ir.FPMul(src0, src1))});
+}
+
void Translator::V_MUL_I32_I24(const GcnInst& inst, bool is_signed) {
const IR::U32 src0{
ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), is_signed)};
diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp
index e0c64ff4a..0d9e8f220 100644
--- a/src/shader_recompiler/frontend/translate/vector_memory.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp
@@ -137,6 +137,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
// Image atomic operations
case Opcode::IMAGE_ATOMIC_SWAP:
return IMAGE_ATOMIC(AtomicOp::Swap, inst);
+ case Opcode::IMAGE_ATOMIC_CMPSWAP:
+ return IMAGE_ATOMIC(AtomicOp::CmpSwap, inst);
case Opcode::IMAGE_ATOMIC_ADD:
return IMAGE_ATOMIC(AtomicOp::Add, inst);
case Opcode::IMAGE_ATOMIC_SMIN:
@@ -458,6 +460,7 @@ void Translator::IMAGE_STORE(bool has_mip, const GcnInst& inst) {
IR::TextureInstInfo info{};
info.has_lod.Assign(has_mip);
info.is_array.Assign(mimg.da);
+ info.is_r128.Assign(mimg.r128);
boost::container::static_vector comps;
for (u32 i = 0; i < 4; i++) {
@@ -519,6 +522,10 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) {
switch (op) {
case AtomicOp::Swap:
return ir.ImageAtomicExchange(handle, body, value, {});
+ case AtomicOp::CmpSwap: {
+ const IR::Value cmp_val = ir.GetVectorReg(val_reg + 1);
+ return ir.ImageAtomicCmpSwap(handle, body, value, cmp_val, info);
+ }
case AtomicOp::Add:
return ir.ImageAtomicIAdd(handle, body, value, info);
case AtomicOp::Smin:
diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp
index 1e77dc677..c681c3120 100644
--- a/src/shader_recompiler/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/ir/ir_emitter.cpp
@@ -2055,6 +2055,11 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c
return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value);
}
+Value IREmitter::ImageAtomicCmpSwap(const Value& handle, const Value& coords, const Value& value,
+ const Value& cmp_value, TextureInstInfo info) {
+ return Inst(Opcode::ImageAtomicCmpSwap32, Flags{info}, handle, coords, value, cmp_value);
+}
+
Value IREmitter::ImageSampleRaw(const Value& image_handle, const Value& sampler_handle,
const Value& address1, const Value& address2, const Value& address3,
const Value& address4, TextureInstInfo info) {
diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h
index 6f20d5780..adc8f5fb1 100644
--- a/src/shader_recompiler/ir/ir_emitter.h
+++ b/src/shader_recompiler/ir/ir_emitter.h
@@ -360,6 +360,9 @@ public:
TextureInstInfo info);
[[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords,
const Value& value, TextureInstInfo info);
+ [[nodiscard]] Value ImageAtomicCmpSwap(const Value& handle, const Value& coords,
+ const Value& value, const Value& cmp_value,
+ TextureInstInfo info);
[[nodiscard]] Value ImageSampleRaw(const Value& image_handle, const Value& sampler_handle,
const Value& address1, const Value& address2,
diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp
index 40ce69df8..cd0131770 100644
--- a/src/shader_recompiler/ir/microinstruction.cpp
+++ b/src/shader_recompiler/ir/microinstruction.cpp
@@ -123,6 +123,7 @@ bool Inst::MayHaveSideEffects() const noexcept {
case Opcode::ImageAtomicOr32:
case Opcode::ImageAtomicXor32:
case Opcode::ImageAtomicExchange32:
+ case Opcode::ImageAtomicCmpSwap32:
case Opcode::DebugPrint:
case Opcode::EmitVertex:
case Opcode::EmitPrimitive:
diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc
index 4875375bc..6304a96fa 100644
--- a/src/shader_recompiler/ir/opcodes.inc
+++ b/src/shader_recompiler/ir/opcodes.inc
@@ -436,6 +436,7 @@ OPCODE(ImageAtomicAnd32, U32, Opaq
OPCODE(ImageAtomicOr32, U32, Opaque, Opaque, U32, )
OPCODE(ImageAtomicXor32, U32, Opaque, Opaque, U32, )
OPCODE(ImageAtomicExchange32, U32, Opaque, Opaque, U32, )
+OPCODE(ImageAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, )
// Cube operations - optional, usable if profile.supports_native_cube_calc
OPCODE(CubeFaceIndex, F32, F32x3, )
diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h
index f103b6736..1b14a1c6b 100644
--- a/src/shader_recompiler/ir/passes/ir_passes.h
+++ b/src/shader_recompiler/ir/passes/ir_passes.h
@@ -19,7 +19,7 @@ void DeadCodeEliminationPass(IR::Program& program);
void ConstantPropagationPass(IR::BlockList& program);
void FlattenExtendedUserdataPass(IR::Program& program);
void ReadLaneEliminationPass(IR::Program& program);
-void ResourceTrackingPass(IR::Program& program);
+void ResourceTrackingPass(IR::Program& program, const Profile& profile);
void CollectShaderInfoPass(IR::Program& program, const Profile& profile);
void LowerBufferFormatToRaw(IR::Program& program);
void LowerFp64ToFp32(IR::Program& program);
diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
index 93129ac0e..3b7888ab3 100644
--- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
+++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
@@ -9,6 +9,7 @@
#include "shader_recompiler/ir/operand_helper.h"
#include "shader_recompiler/ir/program.h"
#include "shader_recompiler/ir/reinterpret.h"
+#include "shader_recompiler/profile.h"
#include "video_core/amdgpu/resource.h"
namespace Shader::Optimization {
@@ -214,6 +215,7 @@ bool IsImageAtomicInstruction(const IR::Inst& inst) {
case IR::Opcode::ImageAtomicOr32:
case IR::Opcode::ImageAtomicXor32:
case IR::Opcode::ImageAtomicExchange32:
+ case IR::Opcode::ImageAtomicCmpSwap32:
return true;
default:
return false;
@@ -254,7 +256,9 @@ public:
u32 Add(const ImageResource& desc) {
const u32 index{Add(image_resources, desc, [&desc](const auto& existing) {
- return desc.sharp_idx == existing.sharp_idx && desc.is_array == existing.is_array;
+ return desc.sharp_idx == existing.sharp_idx && desc.is_array == existing.is_array &&
+ desc.mip_fallback_mode == existing.mip_fallback_mode &&
+ desc.constant_mip_index == existing.constant_mip_index;
})};
auto& image = image_resources[index];
image.is_atomic |= desc.is_atomic;
@@ -528,14 +532,21 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
inst.SetArg(0, ir.Imm32(buffer_binding));
}
-void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) {
+void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors,
+ const Profile& profile) {
// Read image sharp.
const auto inst_info = inst.Flags();
const IR::Inst* image_handle = inst.Arg(0).InstRecursive();
const auto tsharp = TrackSharp(image_handle, block, inst_info.pc);
const bool is_atomic = IsImageAtomicInstruction(inst);
const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite || is_atomic;
- const ImageResource image_res = {
+ const bool is_storage =
+ inst.GetOpcode() == IR::Opcode::ImageRead || inst.GetOpcode() == IR::Opcode::ImageWrite;
+ // ImageRead with !is_written gets emitted as OpImageFetch with LOD operand, doesn't
+ // need fallback (TODO is this 100% true?)
+ const bool needs_mip_storage_fallback =
+ inst_info.has_lod && is_written && !profile.supports_image_load_store_lod;
+ ImageResource image_res = {
.sharp_idx = tsharp,
.is_depth = bool(inst_info.is_depth),
.is_atomic = is_atomic,
@@ -543,9 +554,42 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
.is_written = is_written,
.is_r128 = bool(inst_info.is_r128),
};
+
auto image = image_res.GetSharp(info);
ASSERT(image.GetType() != AmdGpu::ImageType::Invalid);
+ if (needs_mip_storage_fallback) {
+ // If the mip level to IMAGE_(LOAD/STORE)_MIP is a constant, set up ImageResource
+ // so that we will only bind a single level.
+ // If index is dynamic, we will bind levels as an array
+ const auto view_type = image.GetViewType(image_res.is_array);
+
+ IR::Inst* body = inst.Arg(1).InstRecursive();
+ const auto lod_arg = [&] -> IR::Value {
+ switch (view_type) {
+ case AmdGpu::ImageType::Color1D: // x, [lod]
+ return body->Arg(1);
+ case AmdGpu::ImageType::Color1DArray: // x, slice, [lod]
+ case AmdGpu::ImageType::Color2D: // x, y, [lod]
+ return body->Arg(2);
+ case AmdGpu::ImageType::Color2DArray: // x, y, slice, [lod]
+ case AmdGpu::ImageType::Color3D: // x, y, z, [lod]
+ return body->Arg(3);
+ case AmdGpu::ImageType::Color2DMsaa:
+ case AmdGpu::ImageType::Color2DMsaaArray:
+ default:
+ UNREACHABLE_MSG("Invalid image type {}", view_type);
+ }
+ }();
+
+ if (lod_arg.IsImmediate()) {
+ image_res.mip_fallback_mode = MipStorageFallbackMode::ConstantIndex;
+ image_res.constant_mip_index = lod_arg.U32();
+ } else {
+ image_res.mip_fallback_mode = MipStorageFallbackMode::DynamicIndex;
+ }
+ }
+
// Patch image instruction if image is FMask.
if (AmdGpu::IsFmask(image.GetDataFmt())) {
ASSERT_MSG(!is_written, "FMask storage instructions are not supported");
@@ -1079,7 +1123,11 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) {
const auto has_ms = view_type == AmdGpu::ImageType::Color2DMsaa ||
view_type == AmdGpu::ImageType::Color2DMsaaArray;
ASSERT(!inst_info.has_lod || !has_ms);
- const auto lod = inst_info.has_lod ? IR::U32{arg} : IR::U32{};
+ // If we are binding a single mip level as fallback, drop the argument
+ const auto lod =
+ (inst_info.has_lod && image_res.mip_fallback_mode != MipStorageFallbackMode::ConstantIndex)
+ ? IR::U32{arg}
+ : IR::U32{};
const auto ms = has_ms ? IR::U32{arg} : IR::U32{};
const auto is_storage = image_res.is_written;
@@ -1110,7 +1158,7 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) {
}
}
-void ResourceTrackingPass(IR::Program& program) {
+void ResourceTrackingPass(IR::Program& program, const Profile& profile) {
// Iterate resource instructions and patch them after finding the sharp.
auto& info = program.info;
@@ -1121,7 +1169,7 @@ void ResourceTrackingPass(IR::Program& program) {
if (IsBufferInstruction(inst)) {
PatchBufferSharp(*block, inst, info, descriptors);
} else if (IsImageInstruction(inst)) {
- PatchImageSharp(*block, inst, info, descriptors);
+ PatchImageSharp(*block, inst, info, descriptors, profile);
}
}
}
diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp
index f4fa45afc..d6efa2890 100644
--- a/src/shader_recompiler/recompiler.cpp
+++ b/src/shader_recompiler/recompiler.cpp
@@ -80,7 +80,7 @@ IR::Program TranslateProgram(const std::span& code, Pools& pools, Inf
Shader::Optimization::RingAccessElimination(program, runtime_info);
Shader::Optimization::ReadLaneEliminationPass(program);
Shader::Optimization::FlattenExtendedUserdataPass(program);
- Shader::Optimization::ResourceTrackingPass(program);
+ Shader::Optimization::ResourceTrackingPass(program, profile);
Shader::Optimization::LowerBufferFormatToRaw(program);
Shader::Optimization::SharedMemorySimplifyPass(program, profile);
Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile);
diff --git a/src/shader_recompiler/resource.h b/src/shader_recompiler/resource.h
index 5d9965105..82a861e2a 100644
--- a/src/shader_recompiler/resource.h
+++ b/src/shader_recompiler/resource.h
@@ -71,6 +71,8 @@ struct BufferResource {
};
using BufferResourceList = boost::container::static_vector;
+enum class MipStorageFallbackMode : u32 { None, DynamicIndex, ConstantIndex };
+
struct ImageResource {
u32 sharp_idx;
bool is_depth{};
@@ -78,6 +80,8 @@ struct ImageResource {
bool is_array{};
bool is_written{};
bool is_r128{};
+ MipStorageFallbackMode mip_fallback_mode{};
+ u32 constant_mip_index{};
constexpr AmdGpu::Image GetSharp(const auto& info) const noexcept {
AmdGpu::Image image{};
@@ -86,6 +90,7 @@ struct ImageResource {
} else {
const auto raw = info.template ReadUdSharp(sharp_idx);
std::memcpy(&image, &raw, sizeof(raw));
+ image.pitch = image.width;
}
if (!image.Valid()) {
LOG_DEBUG(Render_Vulkan, "Encountered invalid image sharp");
@@ -101,6 +106,13 @@ struct ImageResource {
}
return image;
}
+
+ u32 NumBindings(const auto& info) const {
+ const AmdGpu::Image tsharp = GetSharp(info);
+ return (mip_fallback_mode == MipStorageFallbackMode::DynamicIndex)
+ ? (tsharp.last_level - tsharp.base_level + 1)
+ : 1;
+ }
};
using ImageResourceList = boost::container::static_vector;
diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h
index 4f6bb44bf..fa14583af 100644
--- a/src/shader_recompiler/specialization.h
+++ b/src/shader_recompiler/specialization.h
@@ -52,6 +52,8 @@ struct ImageSpecialization {
bool is_srgb = false;
AmdGpu::CompMapping dst_select{};
AmdGpu::NumberConversion num_conversion{};
+ // FIXME any pipeline cache changes needed?
+ u32 num_bindings = 0;
bool operator==(const ImageSpecialization&) const = default;
};
@@ -133,7 +135,7 @@ struct StageSpecialization {
}
});
ForEachSharp(binding, images, info->images,
- [](auto& spec, const auto& desc, AmdGpu::Image sharp) {
+ [&](auto& spec, const auto& desc, AmdGpu::Image sharp) {
spec.type = sharp.GetViewType(desc.is_array);
spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt());
spec.is_storage = desc.is_written;
@@ -144,6 +146,7 @@ struct StageSpecialization {
spec.is_srgb = sharp.GetNumberFmt() == AmdGpu::NumberFormat::Srgb;
}
spec.num_conversion = sharp.GetNumberConversion();
+ spec.num_bindings = desc.NumBindings(*info);
});
ForEachSharp(binding, fmasks, info->fmasks,
[](auto& spec, const auto& desc, AmdGpu::Image sharp) {
diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp
index b2a4d7a61..c4f1d6695 100644
--- a/src/video_core/amdgpu/liverpool.cpp
+++ b/src/video_core/amdgpu/liverpool.cpp
@@ -229,6 +229,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data());
while (!dcb.empty()) {
@@ -267,27 +269,27 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2;
- const std::string_view label{reinterpret_cast(&nop->data_block[1]),
- marker_sz};
- if (rasterizer) {
+ if (guest_markers_enabled) {
+ const auto marker_sz = nop->header.count.Value() * 2;
+ const std::string_view label{
+ reinterpret_cast(&nop->data_block[1]), marker_sz};
rasterizer->ScopeMarkerBegin(label, true);
}
break;
}
case PM4CmdNop::PayloadType::DebugColorMarkerPush: {
- const auto marker_sz = nop->header.count.Value() * 2;
- const std::string_view label{reinterpret_cast(&nop->data_block[1]),
- marker_sz};
- const u32 color = *reinterpret_cast(
- reinterpret_cast(&nop->data_block[1]) + marker_sz);
- if (rasterizer) {
+ if (guest_markers_enabled) {
+ const auto marker_sz = nop->header.count.Value() * 2;
+ const std::string_view label{
+ reinterpret_cast(&nop->data_block[1]), marker_sz};
+ const u32 color = *reinterpret_cast(
+ reinterpret_cast(&nop->data_block[1]) + marker_sz);
rasterizer->ScopedMarkerInsertColor(label, color, true);
}
break;
}
case PM4CmdNop::PayloadType::DebugMarkerPop: {
- if (rasterizer) {
+ if (guest_markers_enabled) {
rasterizer->ScopeMarkerEnd(true);
}
break;
@@ -427,9 +429,13 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address));
- rasterizer->Draw(true);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address));
+ rasterizer->Draw(true);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->Draw(true);
+ }
}
break;
}
@@ -444,10 +450,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("gfx:{}:DrawIndexOffset2", cmd_address));
- rasterizer->Draw(true, draw_index_off->index_offset);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndexOffset2", cmd_address));
+ rasterizer->Draw(true, draw_index_off->index_offset);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->Draw(true, draw_index_off->index_offset);
+ }
}
break;
}
@@ -460,9 +470,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndexAuto", cmd_address));
- rasterizer->Draw(false);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndexAuto", cmd_address));
+ rasterizer->Draw(false);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->Draw(false);
+ }
}
break;
}
@@ -475,9 +490,36 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndirect", cmd_address));
- rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndirect", cmd_address));
+ rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0);
+ }
+ }
+ break;
+ }
+ case PM4ItOpcode::DrawIndirectMulti: {
+ const auto* draw_indirect =
+ reinterpret_cast(header);
+ const auto offset = draw_indirect->data_offset;
+ if (DebugState.DumpingCurrentReg()) {
+ DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs);
+ }
+ if (rasterizer) {
+ const auto cmd_address = reinterpret_cast(header);
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndirectMulti", cmd_address));
+ rasterizer->DrawIndirect(false, indirect_args_addr, offset,
+ draw_indirect->stride, draw_indirect->count, 0);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DrawIndirect(false, indirect_args_addr, offset,
+ draw_indirect->stride, draw_indirect->count, 0);
+ }
}
break;
}
@@ -491,10 +533,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("gfx:{}:DrawIndexIndirect", cmd_address));
- rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndexIndirect", cmd_address));
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0);
+ }
}
break;
}
@@ -507,12 +553,18 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address));
- rasterizer->DrawIndirect(true, indirect_args_addr, offset,
- draw_index_indirect->stride,
- draw_index_indirect->count, 0);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address));
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset,
+ draw_index_indirect->stride,
+ draw_index_indirect->count, 0);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset,
+ draw_index_indirect->stride,
+ draw_index_indirect->count, 0);
+ }
}
break;
}
@@ -525,15 +577,24 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address));
- rasterizer->DrawIndirect(true, indirect_args_addr, offset,
- draw_index_indirect->stride,
- draw_index_indirect->count,
- draw_index_indirect->count_indirect_enable.Value()
- ? draw_index_indirect->count_addr
- : 0);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address));
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset,
+ draw_index_indirect->stride,
+ draw_index_indirect->count,
+ draw_index_indirect->count_indirect_enable.Value()
+ ? draw_index_indirect->count_addr
+ : 0);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DrawIndirect(true, indirect_args_addr, offset,
+ draw_index_indirect->stride,
+ draw_index_indirect->count,
+ draw_index_indirect->count_indirect_enable.Value()
+ ? draw_index_indirect->count_addr
+ : 0);
+ }
}
break;
}
@@ -550,9 +611,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DispatchDirect", cmd_address));
- rasterizer->DispatchDirect();
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DispatchDirect", cmd_address));
+ rasterizer->DispatchDirect();
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DispatchDirect();
+ }
}
break;
}
@@ -568,10 +634,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("gfx:{}:DispatchIndirect", cmd_address));
- rasterizer->DispatchIndirect(indirect_args_addr, offset, size);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("gfx:{}:DispatchIndirect", cmd_address));
+ rasterizer->DispatchIndirect(indirect_args_addr, offset, size);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DispatchIndirect(indirect_args_addr, offset, size);
+ }
}
break;
}
@@ -829,6 +899,7 @@ template
Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) {
FIBER_ENTER(acb_task_name[vqid]);
auto& queue = asc_queues[{vqid}];
+ const bool host_markers_enabled = rasterizer && Config::getVkHostMarkersEnabled();
struct IndirectPatch {
const PM4Header* header;
@@ -881,6 +952,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) {
}
const PM4ItOpcode opcode = header->type3.opcode;
+
const auto* it_body = reinterpret_cast(header) + 1;
switch (opcode) {
case PM4ItOpcode::Nop: {
@@ -998,10 +1070,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) {
}
if (rasterizer && (cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address));
- rasterizer->DispatchDirect();
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address));
+ rasterizer->DispatchDirect();
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DispatchDirect();
+ }
}
break;
}
@@ -1017,10 +1093,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) {
}
if (rasterizer && (cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast(header);
- rasterizer->ScopeMarkerBegin(
- fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address));
- rasterizer->DispatchIndirect(ib_address, 0, size);
- rasterizer->ScopeMarkerEnd();
+ if (host_markers_enabled) {
+ rasterizer->ScopeMarkerBegin(
+ fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address));
+ rasterizer->DispatchIndirect(ib_address, 0, size);
+ rasterizer->ScopeMarkerEnd();
+ } else {
+ rasterizer->DispatchIndirect(ib_address, 0, size);
+ }
}
break;
}
diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h
index 17511d0a2..abf58ad89 100644
--- a/src/video_core/amdgpu/pm4_cmds.h
+++ b/src/video_core/amdgpu/pm4_cmds.h
@@ -1051,6 +1051,24 @@ struct PM4CmdDrawIndirect {
u32 draw_initiator; ///< Draw Initiator Register
};
+struct PM4CmdDrawIndirectMulti {
+ PM4Type3Header header; ///< header
+ u32 data_offset; ///< Byte aligned offset where the required data structure starts
+ union {
+ u32 dw2;
+ BitField<0, 16, u32> base_vtx_loc; ///< Offset where the CP will write the
+ ///< BaseVertexLocation it fetched from memory
+ };
+ union {
+ u32 dw3;
+ BitField<0, 16, u32> start_inst_loc; ///< Offset where the CP will write the
+ ///< StartInstanceLocation it fetched from memory
+ };
+ u32 count; ///< Count of data structures to loop through before going to next packet
+ u32 stride; ///< Stride in memory from one data structure to the next
+ u32 draw_initiator; ///< Draw Initiator Register
+};
+
struct DrawIndexedIndirectArgs {
u32 index_count_per_instance;
u32 instance_count;
diff --git a/src/video_core/buffer_cache/memory_tracker.h b/src/video_core/buffer_cache/memory_tracker.h
index ec0878c3b..2ec86de35 100644
--- a/src/video_core/buffer_cache/memory_tracker.h
+++ b/src/video_core/buffer_cache/memory_tracker.h
@@ -5,8 +5,10 @@
#include
#include
+#include
#include
#include
+
#include "common/debug.h"
#include "common/types.h"
#include "video_core/buffer_cache/region_manager.h"
@@ -71,7 +73,7 @@ public:
// modified. If we need to flush the flush function is going to perform CPU
// state change.
std::scoped_lock lk{manager->lock};
- if (Config::readbacks() &&
+ if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled &&
manager->template IsRegionModified(offset, size)) {
return true;
}
diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h
index 608b16fb3..ecf9406af 100644
--- a/src/video_core/buffer_cache/region_manager.h
+++ b/src/video_core/buffer_cache/region_manager.h
@@ -95,7 +95,7 @@ public:
}
if constexpr (type == Type::CPU) {
UpdateProtection();
- } else if (Config::readbacks()) {
+ } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precise) {
UpdateProtection();
}
}
@@ -126,7 +126,7 @@ public:
bits.UnsetRange(start_page, end_page);
if constexpr (type == Type::CPU) {
UpdateProtection();
- } else if (Config::readbacks()) {
+ } else if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled) {
UpdateProtection();
}
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 35eda86da..ba0a3afa2 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -21,7 +21,11 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
info = &info_;
const auto debug_str = GetDebugString();
+ const vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo subgroup_size_ci = {
+ .requiredSubgroupSize = 64,
+ };
const vk::PipelineShaderStageCreateInfo shader_ci = {
+ .pNext = instance.IsSubgroupSize64Supported() ? &subgroup_size_ci : nullptr,
.stage = vk::ShaderStageFlagBits::eCompute,
.module = module,
.pName = "main",
@@ -44,13 +48,15 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
});
}
for (const auto& image : info->images) {
+ const u32 num_bindings = image.NumBindings(*info);
bindings.push_back({
- .binding = binding++,
+ .binding = binding,
.descriptorType = image.is_written ? vk::DescriptorType::eStorageImage
: vk::DescriptorType::eSampledImage,
- .descriptorCount = 1,
+ .descriptorCount = num_bindings,
.stageFlags = vk::ShaderStageFlagBits::eCompute,
});
+ binding += num_bindings;
}
for (const auto& sampler : info->samplers) {
bindings.push_back({
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 242c9b6f2..bc9ef571b 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -457,13 +457,15 @@ void GraphicsPipeline::BuildDescSetLayout(bool preloading) {
});
}
for (const auto& image : stage->images) {
+ const u32 num_bindings = image.NumBindings(*stage);
bindings.push_back({
- .binding = binding++,
+ .binding = binding,
.descriptorType = image.is_written ? vk::DescriptorType::eStorageImage
: vk::DescriptorType::eSampledImage,
- .descriptorCount = 1,
+ .descriptorCount = num_bindings,
.stageFlags = stage_bit,
});
+ binding += num_bindings;
}
for (const auto& sampler : stage->samplers) {
bindings.push_back({
diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp
index 44aa79d98..6898df97a 100644
--- a/src/video_core/renderer_vulkan/vk_instance.cpp
+++ b/src/video_core/renderer_vulkan/vk_instance.cpp
@@ -220,9 +220,11 @@ bool Instance::CreateDevice() {
const vk::StructureChain properties_chain = physical_device.getProperties2<
vk::PhysicalDeviceProperties2, vk::PhysicalDeviceVulkan11Properties,
- vk::PhysicalDeviceVulkan12Properties, vk::PhysicalDevicePushDescriptorPropertiesKHR>();
+ vk::PhysicalDeviceVulkan12Properties, vk::PhysicalDeviceVulkan13Properties,
+ vk::PhysicalDevicePushDescriptorPropertiesKHR>();
vk11_props = properties_chain.get();
vk12_props = properties_chain.get();
+ vk13_props = properties_chain.get();
push_descriptor_props = properties_chain.get();
LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", vk11_props.subgroupSize);
@@ -367,7 +369,7 @@ bool Instance::CreateDevice() {
feature_chain.get();
const auto vk11_features = feature_chain.get();
vk12_features = feature_chain.get();
- const auto vk13_features = feature_chain.get();
+ vk13_features = feature_chain.get();
vk::StructureChain device_chain = {
vk::DeviceCreateInfo{
.queueCreateInfoCount = 1u,
@@ -429,6 +431,7 @@ bool Instance::CreateDevice() {
vk::PhysicalDeviceVulkan13Features{
.robustImageAccess = vk13_features.robustImageAccess,
.shaderDemoteToHelperInvocation = vk13_features.shaderDemoteToHelperInvocation,
+ .subgroupSizeControl = vk13_features.subgroupSizeControl,
.synchronization2 = vk13_features.synchronization2,
.dynamicRendering = vk13_features.dynamicRendering,
.maintenance4 = vk13_features.maintenance4,
diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h
index 8975669bb..7a8a906d5 100644
--- a/src/video_core/renderer_vulkan/vk_instance.h
+++ b/src/video_core/renderer_vulkan/vk_instance.h
@@ -233,6 +233,11 @@ public:
return vk12_features.shaderSharedInt64Atomics;
}
+ /// Returns true if the subgroup size can be set to match guest subgroup size
+ bool IsSubgroupSize64Supported() const {
+ return vk13_features.subgroupSizeControl && vk13_props.maxSubgroupSize >= 64;
+ }
+
/// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported.
bool IsWorkgroupMemoryExplicitLayoutSupported() const {
return workgroup_memory_explicit_layout &&
@@ -455,9 +460,11 @@ private:
vk::PhysicalDeviceMemoryProperties memory_properties;
vk::PhysicalDeviceVulkan11Properties vk11_props;
vk::PhysicalDeviceVulkan12Properties vk12_props;
+ vk::PhysicalDeviceVulkan13Properties vk13_props;
vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props;
vk::PhysicalDeviceFeatures features;
vk::PhysicalDeviceVulkan12Features vk12_features;
+ vk::PhysicalDeviceVulkan13Features vk13_features;
vk::PhysicalDevicePortabilitySubsetFeaturesKHR portability_features;
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features;
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 737c9feed..80af19372 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -662,6 +662,13 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding
void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindings& binding) {
image_bindings.clear();
+ const u32 first_image_idx = image_infos.size();
+ // For loading/storing to explicit mip levels, when no native instruction support, bind an array
+ // of descriptors consecutively, 1 for each mip level. The shader can index this with LOD
+ // operand.
+ // This array holds the size of each consecutive array with the number of bindings consumed.
+ // This is currently always 1 for anything other than mip fallback arrays.
+ boost::container::small_vector image_descriptor_array_sizes;
for (const auto& image_desc : stage.images) {
const auto tsharp = image_desc.GetSharp(stage);
@@ -671,25 +678,43 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
if (tsharp.GetDataFmt() == AmdGpu::DataFormat::FormatInvalid) {
image_bindings.emplace_back(std::piecewise_construct, std::tuple{}, std::tuple{});
+ image_descriptor_array_sizes.push_back(1);
continue;
}
- auto& [image_id, desc] = image_bindings.emplace_back(std::piecewise_construct, std::tuple{},
- std::tuple{tsharp, image_desc});
- image_id = texture_cache.FindImage(desc);
- auto* image = &texture_cache.GetImage(image_id);
- if (image->depth_id) {
- // If this image has an associated depth image, it's a stencil attachment.
- // Redirect the access to the actual depth-stencil buffer.
- image_id = image->depth_id;
- image = &texture_cache.GetImage(image_id);
+ const Shader::MipStorageFallbackMode mip_fallback_mode = image_desc.mip_fallback_mode;
+ const u32 num_bindings = image_desc.NumBindings(stage);
+
+ for (auto i = 0; i < num_bindings; i++) {
+ auto& [image_id, desc] = image_bindings.emplace_back(
+ std::piecewise_construct, std::tuple{}, std::tuple{tsharp, image_desc});
+
+ if (mip_fallback_mode == Shader::MipStorageFallbackMode::ConstantIndex) {
+ ASSERT(num_bindings == 1);
+ desc.view_info.range.base.level += image_desc.constant_mip_index;
+ desc.view_info.range.extent.levels = 1;
+ } else if (mip_fallback_mode == Shader::MipStorageFallbackMode::DynamicIndex) {
+ desc.view_info.range.base.level += i;
+ desc.view_info.range.extent.levels = 1;
+ }
+
+ image_id = texture_cache.FindImage(desc);
+ auto* image = &texture_cache.GetImage(image_id);
+ if (image->depth_id) {
+ // If this image has an associated depth image, it's a stencil attachment.
+ // Redirect the access to the actual depth-stencil buffer.
+ image_id = image->depth_id;
+ image = &texture_cache.GetImage(image_id);
+ }
+ if (image->binding.is_bound) {
+ // The image is already bound. In case if it is about to be used as storage we
+ // need to force general layout on it.
+ image->binding.force_general |= image_desc.is_written;
+ }
+ image->binding.is_bound = 1u;
}
- if (image->binding.is_bound) {
- // The image is already bound. In case if it is about to be used as storage we need
- // to force general layout on it.
- image->binding.force_general |= image_desc.is_written;
- }
- image->binding.is_bound = 1u;
+
+ image_descriptor_array_sizes.push_back(num_bindings);
}
// Second pass to re-bind images that were updated after binding
@@ -749,16 +774,26 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view,
image.backing->state.layout);
}
+ }
+ u32 image_info_idx = first_image_idx;
+ u32 image_binding_idx = 0;
+ for (u32 array_size : image_descriptor_array_sizes) {
+ const auto& [_, desc] = image_bindings[image_binding_idx];
+ const bool is_storage = desc.type == VideoCore::TextureCache::BindingType::Storage;
set_writes.push_back({
.dstSet = VK_NULL_HANDLE,
- .dstBinding = binding.unified++,
+ .dstBinding = binding.unified,
.dstArrayElement = 0,
- .descriptorCount = 1,
+ .descriptorCount = array_size,
.descriptorType =
is_storage ? vk::DescriptorType::eStorageImage : vk::DescriptorType::eSampledImage,
- .pImageInfo = &image_infos.back(),
+ .pImageInfo = &image_infos[image_info_idx],
});
+
+ image_info_idx += array_size;
+ image_binding_idx += array_size;
+ binding.unified += array_size;
}
for (const auto& sampler : stage.samplers) {
diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp
index 418641bc3..ffa5168c5 100644
--- a/src/video_core/texture_cache/image.cpp
+++ b/src/video_core/texture_cache/image.cpp
@@ -239,7 +239,13 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout dst_layout, vk::AccessFlags2
ASSERT(subres_idx < subresource_states.size());
auto& state = subresource_states[subres_idx];
- if (state.layout != dst_layout || state.access_mask != dst_mask) {
+ if (state.layout != dst_layout || state.access_mask != dst_mask ||
+ static_cast(dst_mask &
+ (vk::AccessFlagBits2::eTransferWrite |
+ vk::AccessFlagBits2::eShaderWrite |
+ vk::AccessFlagBits2::eColorAttachmentWrite |
+ vk::AccessFlagBits2::eDepthStencilAttachmentWrite |
+ vk::AccessFlagBits2::eMemoryWrite))) {
barriers.emplace_back(vk::ImageMemoryBarrier2{
.srcStageMask = state.pl_stage,
.srcAccessMask = state.access_mask,
@@ -269,7 +275,12 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout dst_layout, vk::AccessFlags2
subresource_states.clear();
}
} else { // Full resource transition
- if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) {
+ constexpr auto write_flags =
+ vk::AccessFlagBits2::eTransferWrite | vk::AccessFlagBits2::eShaderWrite |
+ vk::AccessFlagBits2::eColorAttachmentWrite |
+ vk::AccessFlagBits2::eDepthStencilAttachmentWrite | vk::AccessFlagBits2::eMemoryWrite;
+ const bool is_write = static_cast(dst_mask & write_flags);
+ if (last_state.layout == dst_layout && last_state.access_mask == dst_mask && !is_write) {
return {};
}
@@ -461,33 +472,36 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I
void Image::CopyImage(Image& src_image) {
const auto& src_info = src_image.info;
+
const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels);
- // Check format compatibility
+ // Format mismatch warning (safe but useful)
if (src_info.pixel_format != info.pixel_format) {
LOG_DEBUG(Render_Vulkan,
- "Copy between different formats: src={}, dst={}. Color may be incorrect.",
+ "Copy between different formats: src={}, dst={}. "
+ "Result may be undefined.",
vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format));
}
- const u32 width = src_info.size.width;
- const u32 height = src_info.size.height;
+ const u32 base_width = src_info.size.width;
+ const u32 base_height = src_info.size.height;
const u32 base_depth =
info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth;
- auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth);
-
- ASSERT(test_src_layers == test_dst_layers || num_mips == 1 ||
- (ConvertImageType(src_info.type) != ConvertImageType(info.type) &&
- (test_src_layers == 1 || test_dst_layers == 1)));
-
+ // Match sample count before copying
SetBackingSamples(info.num_samples, false);
src_image.SetBackingSamples(src_info.num_samples);
- boost::container::small_vector image_copies;
+ boost::container::small_vector regions;
+
+ const vk::ImageAspectFlags src_aspect =
+ src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil;
+
+ const vk::ImageAspectFlags dst_aspect = aspect_mask & ~vk::ImageAspectFlagBits::eStencil;
const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D;
const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D;
+
const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D;
const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D;
@@ -495,103 +509,68 @@ void Image::CopyImage(Image& src_image) {
const bool is_3d_to_2d = src_is_3d && dst_is_2d;
const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d;
- // Determine aspect mask - exclude stencil
- vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
-
- // For depth/stencil images, only copy the depth aspect (skip stencil)
- if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) {
- aspect = vk::ImageAspectFlagBits::eDepth;
- }
-
for (u32 mip = 0; mip < num_mips; ++mip) {
- const auto mip_w = std::max(width >> mip, 1u);
- const auto mip_h = std::max(height >> mip, 1u);
- const auto mip_d = std::max(base_depth >> mip, 1u);
+ const u32 mip_w = std::max(base_width >> mip, 1u);
+ const u32 mip_h = std::max(base_height >> mip, 1u);
+ const u32 mip_d = std::max(base_depth >> mip, 1u);
auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d);
+ vk::ImageCopy region{};
+
+ region.srcSubresource.aspectMask = src_aspect;
+ region.srcSubresource.mipLevel = mip;
+ region.srcSubresource.baseArrayLayer = 0;
+
+ region.dstSubresource.aspectMask = dst_aspect;
+ region.dstSubresource.mipLevel = mip;
+ region.dstSubresource.baseArrayLayer = 0;
+
if (is_same_type) {
- u32 copy_layers = std::min(src_layers, dst_layers);
-
- if (src_is_3d)
- src_layers = 1;
- if (dst_is_3d)
- dst_layers = 1;
-
- vk::ImageCopy copy_region = {
- .srcSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = copy_layers,
- },
- .dstSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = copy_layers,
- },
- .extent = vk::Extent3D(mip_w, mip_h, mip_d),
- };
- image_copies.push_back(copy_region);
+ // 2D->2D OR 3D->3D
+ if (src_is_3d) {
+ // 3D images must use layerCount=1
+ region.srcSubresource.layerCount = 1;
+ region.dstSubresource.layerCount = 1;
+ region.extent = vk::Extent3D(mip_w, mip_h, mip_d);
+ } else {
+ // Array images
+ const u32 copy_layers = std::min(src_layers, dst_layers);
+ region.srcSubresource.layerCount = copy_layers;
+ region.dstSubresource.layerCount = copy_layers;
+ region.extent = vk::Extent3D(mip_w, mip_h, 1);
+ }
} else if (is_2d_to_3d) {
- vk::ImageCopy copy_region = {
- .srcSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = src_layers,
- },
- .dstSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- .extent = vk::Extent3D(mip_w, mip_h, src_layers),
- };
- image_copies.push_back(copy_region);
+ // 2D array -> 3D volume
+ region.srcSubresource.layerCount = src_layers;
+ region.dstSubresource.layerCount = 1;
+ region.extent = vk::Extent3D(mip_w, mip_h, src_layers);
} else if (is_3d_to_2d) {
- vk::ImageCopy copy_region = {
- .srcSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- .dstSubresource{
- .aspectMask = aspect,
- .mipLevel = mip,
- .baseArrayLayer = 0,
- .layerCount = dst_layers,
- },
- .extent = vk::Extent3D(mip_w, mip_h, dst_layers),
- };
- image_copies.push_back(copy_region);
+ // 3D volume -> 2D array
+ region.srcSubresource.layerCount = 1;
+ region.dstSubresource.layerCount = dst_layers;
+ region.extent = vk::Extent3D(mip_w, mip_h, dst_layers);
}
+
+ regions.push_back(region);
}
scheduler->EndRendering();
- // Remove the pipeline stage flags - they don't belong here
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {});
auto cmdbuf = scheduler->CommandBuffer();
- if (!image_copies.empty()) {
- cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(),
- vk::ImageLayout::eTransferDstOptimal, image_copies);
+ if (!regions.empty()) {
+ cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(),
+ backing->state.layout, regions);
}
- // Remove pipeline stage flags here too
- src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead,
- {});
-
- Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {});
+ Transit(vk::ImageLayout::eGeneral,
+ vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {});
}
-
void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) {
const auto& src_info = src_image.info;
const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels);
@@ -668,6 +647,10 @@ void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset)
cmdbuf.copyBufferToImage(buffer, GetImage(), vk::ImageLayout::eTransferDstOptimal,
buffer_copies);
+
+ // Match CopyImage: transition to general so shaders can sample the result.
+ Transit(vk::ImageLayout::eGeneral,
+ vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {});
}
void Image::CopyMip(Image& src_image, u32 mip, u32 slice) {
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
index 0da9c8bfb..543e144d2 100644
--- a/src/video_core/texture_cache/image_info.h
+++ b/src/video_core/texture_cache/image_info.h
@@ -48,7 +48,7 @@ struct ImageInfo {
}
Extent2D BlockDim() const {
const auto dim = props.is_block ? 2 : 0;
- return Extent2D{size.width >> dim, size.height >> dim};
+ return Extent2D{pitch >> dim, size.height >> dim};
}
s32 MipOf(const ImageInfo& info) const;
diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp
index a8d846cfc..8163902cc 100644
--- a/src/video_core/texture_cache/texture_cache.cpp
+++ b/src/video_core/texture_cache/texture_cache.cpp
@@ -297,14 +297,6 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag
if (image_info.guest_address == cache_image.info.guest_address) {
const u32 lhs_block_size = image_info.num_bits * image_info.num_samples;
const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples;
-
- if (image_info.pitch != cache_image.info.pitch) {
- if (safe_to_delete) {
- FreeImage(cache_image_id);
- }
- return {merged_image_id, -1, -1};
- }
-
if (image_info.BlockDim() != cache_image.info.BlockDim() ||
lhs_block_size != rhs_block_size) {
// Very likely this kind of overlap is caused by allocation from a pool.