diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml
index 922b30f12..466554ca6 100644
--- a/.github/workflows/linux-qt.yml
+++ b/.github/workflows/linux-qt.yml
@@ -31,8 +31,27 @@ jobs:
arch: linux_gcc_64
version: 6.7.1
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake dependency build objects
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-builds
+ with:
+ append-timestamp: false
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+
- name: Configure CMake
- run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
@@ -50,4 +69,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: shadps4-linux-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }}
- path: Shadps4-qt.AppImage
\ No newline at end of file
+ path: Shadps4-qt.AppImage
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index ee1340984..d4402472a 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -25,8 +25,27 @@ jobs:
run: >
sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake dependency build objects
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-builds
+ with:
+ append-timestamp: false
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+
- name: Configure CMake
- run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml
index f04d3091c..beb927a79 100644
--- a/.github/workflows/macos-qt.yml
+++ b/.github/workflows/macos-qt.yml
@@ -40,8 +40,29 @@ jobs:
arch: clang_64
archives: qtbase qttools
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake dependency build objects
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{runner.os}}-qt-cache-cmake-dependency-builds
+ with:
+ append-timestamp: false
+ create-symlink: true
+ key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ variant: sccache
+
- name: Configure CMake
- run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 0eb0ad17a..9526c6fd6 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -31,8 +31,29 @@ jobs:
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install molten-vk
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
+ - name: Cache CMake dependency build objects
+ uses: hendrikmuhs/ccache-action@v1.2.14
+ env:
+ cache-name: ${{runner.os}}-sdl-cache-cmake-dependency-builds
+ with:
+ append-timestamp: false
+ create-symlink: true
+ key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ variant: sccache
+
- name: Configure CMake
- run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu)
diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml
index 83b1a908b..fee202b5c 100644
--- a/.github/workflows/windows-qt.yml
+++ b/.github/workflows/windows-qt.yml
@@ -30,6 +30,17 @@ jobs:
arch: win64_msvc2019_64
archives: qtbase qttools
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 413277927..4bea63b16 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -20,6 +20,17 @@ jobs:
with:
submodules: recursive
+ - name: Cache CMake dependency source code
+ uses: actions/cache@v4
+ env:
+ cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources
+ with:
+ path: |
+ ${{github.workspace}}/build
+ key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
+ restore-keys: |
+ ${{ env.cache-name }}-
+
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL
diff --git a/.gitmodules b/.gitmodules
index 95b0fc0bb..b2543534f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -85,3 +85,11 @@
[submodule "externals/half"]
path = externals/half
url = https://github.com/ROCm/half.git
+[submodule "externals/dear_imgui"]
+ path = externals/dear_imgui
+ url = https://github.com/shadps4-emu/ext-imgui.git
+ shallow = true
+ branch = docking
+[submodule "externals/pugixml"]
+ path = externals/pugixml
+ url = https://github.com/zeux/pugixml.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7250dd50f..20e2143b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,22 @@ endif()
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
+# First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR.
+if (APPLE AND CMAKE_OSX_ARCHITECTURES)
+ set(BASE_ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
+else()
+ set(BASE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
+endif()
+
+# Next, match common architecture strings down to a known common value.
+if (BASE_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
+ set(ARCHITECTURE "x86_64")
+elseif (BASE_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
+ set(ARCHITECTURE "arm64")
+else()
+ message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}")
+endif()
+
# This function should be passed a list of all files in a target. It will automatically generate file groups
# following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem.
function(create_target_directory_groups target_name)
@@ -92,6 +108,7 @@ find_package(xbyak 7.07 CONFIG)
find_package(xxHash 0.8.2 MODULE)
find_package(zlib-ng 2.1.7 MODULE)
find_package(Zydis 5.0.0 CONFIG)
+find_package(pugixml 1.14 CONFIG)
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC)
find_package(cryptopp 8.9.0 MODULE)
@@ -204,6 +221,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/system/commondialog.h
src/core/libraries/system/msgdialog.cpp
src/core/libraries/system/msgdialog.h
+ src/core/libraries/system/msgdialog_ui.cpp
src/core/libraries/system/posix.cpp
src/core/libraries/system/posix.h
src/core/libraries/save_data/error_codes.h
@@ -243,6 +261,11 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/ngs2/ngs2_impl.cpp
src/core/libraries/ngs2/ngs2_impl.h
src/core/libraries/ajm/ajm_error.h
+ src/core/libraries/audio3d/audio3d.cpp
+ src/core/libraries/audio3d/audio3d.h
+ src/core/libraries/audio3d/audio3d_error.h
+ src/core/libraries/audio3d/audio3d_impl.cpp
+ src/core/libraries/audio3d/audio3d_impl.h
)
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
@@ -291,6 +314,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp
src/core/libraries/np_score/np_score.h
src/core/libraries/np_trophy/np_trophy.cpp
src/core/libraries/np_trophy/np_trophy.h
+ src/core/libraries/np_trophy/trophy_ui.cpp
+ src/core/libraries/np_trophy/trophy_ui.h
)
set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
@@ -308,6 +333,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/logging/text_formatter.h
src/common/logging/types.h
src/common/alignment.h
+ src/common/arch.h
src/common/assert.cpp
src/common/assert.h
src/common/bit_field.h
@@ -325,6 +351,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/error.cpp
src/common/error.h
src/common/scope_exit.h
+ src/common/fixed_value.h
src/common/func_traits.h
src/common/native_clock.cpp
src/common/native_clock.h
@@ -356,8 +383,6 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/aerolib/aerolib.h
src/core/address_space.cpp
src/core/address_space.h
- src/core/cpu_patches.cpp
- src/core/cpu_patches.h
src/core/crypto/crypto.cpp
src/core/crypto/crypto.h
src/core/crypto/keys.h
@@ -415,6 +440,12 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/virtual_memory.h
)
+if (ARCHITECTURE STREQUAL "x86_64")
+ set(CORE ${CORE}
+ src/core/cpu_patches.cpp
+ src/core/cpu_patches.h)
+endif()
+
set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/profile.h
src/shader_recompiler/recompiler.cpp
@@ -553,6 +584,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h
+ src/video_core/texture_cache/host_compatibility.h
src/video_core/page_manager.cpp
src/video_core/page_manager.h
src/video_core/multi_level_page_table.h
@@ -560,6 +592,19 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/renderdoc.h
)
+set(IMGUI src/imgui/imgui_config.h
+ src/imgui/imgui_layer.h
+ src/imgui/imgui_std.h
+ src/imgui/layer/video_info.cpp
+ src/imgui/layer/video_info.h
+ src/imgui/renderer/imgui_core.cpp
+ src/imgui/renderer/imgui_core.h
+ src/imgui/renderer/imgui_impl_sdl3.cpp
+ src/imgui/renderer/imgui_impl_sdl3.h
+ src/imgui/renderer/imgui_impl_vulkan.cpp
+ src/imgui/renderer/imgui_impl_vulkan.h
+)
+
set(INPUT src/input/controller.cpp
src/input/controller.h
src/input/keys_constants.h
@@ -620,6 +665,7 @@ endif()
if (ENABLE_QT_GUI)
qt_add_executable(shadps4
${AUDIO_CORE}
+ ${IMGUI}
${INPUT}
${QT_GUI}
${COMMON}
@@ -632,6 +678,7 @@ if (ENABLE_QT_GUI)
else()
add_executable(shadps4
${AUDIO_CORE}
+ ${IMGUI}
${INPUT}
${COMMON}
${CORE}
@@ -648,8 +695,11 @@ endif()
create_target_directory_groups(shadps4)
-target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg)
-target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3)
+target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui)
+target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
+
+target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
+target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
if (APPLE)
option(USE_SYSTEM_VULKAN_LOADER "Enables using the system Vulkan loader instead of directly linking with MoltenVK. Useful for loading validation layers." OFF)
@@ -661,8 +711,10 @@ if (APPLE)
target_link_libraries(shadps4 PRIVATE ${MOLTENVK})
endif()
- # Reserve system-managed memory space.
- target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x20000000000)
+ if (ARCHITECTURE STREQUAL "x86_64")
+ # Reserve system-managed memory space.
+ target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x20000000000)
+ endif()
# Replacement for std::chrono::time_zone
target_link_libraries(shadps4 PRIVATE date::date-tz)
diff --git a/README.md b/README.md
index db898e565..2127a5791 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ If you encounter problems or have doubts, do not hesitate to look at the [**Quic
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).
-To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/MyZRaBngxA).
+To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
@@ -159,6 +159,20 @@ Open a PR and we'll check it :)
+
+# Special Thanks
+
+A few noteworthy teams/projects who've helped us along the way are:
+
+- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. They have been incredibly helpful in understanding and solving problems that came up from natively executing the x64 code of PS4 binaries
+
+- [**fpPS4**](https://github.com/red-prig/fpPS4): The fpPS4 team has assisted massively with understanding some of the more complex parts of the PS4 operating system and libraries, by helping with reverse engineering work and research.
+
+- **yuzu**: Our shader compiler has been designed with yuzu's Hades compiler as a blueprint. This allowed us to focus on the challenges of emulating a modern AMD GPU while having a high-quality optimizing shader compiler implementation as a base.
+
+- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris.
+
+
# Sister Projects
- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat.
diff --git a/documents/building-linux.md b/documents/building-linux.md
index 9645d8b4f..622de543b 100644
--- a/documents/building-linux.md
+++ b/documents/building-linux.md
@@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#### Debian & Ubuntu
```
-sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev
+sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev
```
#### Fedora
@@ -34,9 +34,9 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git
cd shadPS4
```
-Generate the build directory in the shadPS4 directory:
+Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag:
```
-cmake -S . -B build/
+cmake -S . -B build/ -DENABLE_QT_GUI=ON
```
Enter the directory:
@@ -49,8 +49,11 @@ Use make to build the project:
cmake --build . --parallel$(nproc)
```
-Now run the emulator:
-
+Now run the emulator. If QT is enabled:
+```
+./shadps4
+```
+Otherwise, specify the path to your PKG's boot file:
```
./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin
```
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index de0317ff9..5410f37eb 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -43,7 +43,6 @@ else()
endif()
if (NOT TARGET FFmpeg::ffmpeg)
- set(ARCHITECTURE "x86_64")
add_subdirectory(ffmpeg-core)
add_library(FFmpeg::ffmpeg ALIAS ffmpeg)
endif()
@@ -155,6 +154,17 @@ if (APPLE)
endif()
endif()
+# Dear ImGui
+add_library(Dear_ImGui
+ dear_imgui/imgui.cpp
+ dear_imgui/imgui_demo.cpp
+ dear_imgui/imgui_draw.cpp
+ dear_imgui/imgui_internal.h
+ dear_imgui/imgui_tables.cpp
+ dear_imgui/imgui_widgets.cpp
+)
+target_include_directories(Dear_ImGui INTERFACE dear_imgui/)
+
# Tracy
option(TRACY_ENABLE "" ON)
option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash
@@ -168,3 +178,8 @@ option(TRACY_NO_SAMPLING "" ON)
option(TRACY_ONLY_LOCALHOST "" ON)
option(TRACY_NO_CONTEXT_SWITCH "" ON)
add_subdirectory(tracy)
+
+# pugixml
+if (NOT TARGET pugixml::pugixml)
+ add_subdirectory(pugixml)
+endif()
\ No newline at end of file
diff --git a/externals/dear_imgui b/externals/dear_imgui
new file mode 160000
index 000000000..636cd4a7d
--- /dev/null
+++ b/externals/dear_imgui
@@ -0,0 +1 @@
+Subproject commit 636cd4a7d623a2bc9bf59bb3acbb4ca075befba3
diff --git a/externals/pugixml b/externals/pugixml
new file mode 160000
index 000000000..30cc354fe
--- /dev/null
+++ b/externals/pugixml
@@ -0,0 +1 @@
+Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed
diff --git a/src/common/arch.h b/src/common/arch.h
new file mode 100644
index 000000000..b22366cb7
--- /dev/null
+++ b/src/common/arch.h
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#if defined(__x86_64__) || defined(_M_X64)
+#define ARCH_X86_64 1
+#elif defined(__aarch64__) || defined(_M_ARM64)
+#define ARCH_ARM64 1
+#endif
diff --git a/src/common/assert.cpp b/src/common/assert.cpp
index 3a49c9396..be0feb71d 100644
--- a/src/common/assert.cpp
+++ b/src/common/assert.cpp
@@ -1,10 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/arch.h"
#include "common/assert.h"
#include "common/logging/backend.h"
+#if defined(ARCH_X86_64)
#define Crash() __asm__ __volatile__("int $3")
+#elif defined(ARCH_ARM64)
+#define Crash() __asm__ __volatile__("brk 0")
+#else
+#error "Missing Crash() implementation for target CPU architecture."
+#endif
void assert_fail_impl() {
Common::Log::Stop();
@@ -18,3 +25,8 @@ void assert_fail_impl() {
Crash();
throw std::runtime_error("Unreachable code");
}
+
+void assert_fail_debug_msg(const char* msg) {
+ LOG_CRITICAL(Debug, "Assertion Failed!\n{}", msg);
+ assert_fail_impl();
+}
diff --git a/src/common/fixed_value.h b/src/common/fixed_value.h
new file mode 100644
index 000000000..e32a795f2
--- /dev/null
+++ b/src/common/fixed_value.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+/**
+ * @brief A template class that encapsulates a fixed, compile-time constant value.
+ *
+ * @tparam T The type of the value.
+ * @tparam Value The fixed value of type T.
+ *
+ * This class provides a way to encapsulate a value that is constant and known at compile-time.
+ * The value is stored as a private member and cannot be changed. Any attempt to assign a new
+ * value to an object of this class will reset it to the fixed value.
+ */
+template
+class FixedValue {
+ T m_value{Value};
+
+public:
+ constexpr FixedValue() = default;
+
+ constexpr explicit(false) operator T() const {
+ return m_value;
+ }
+
+ FixedValue& operator=(const T&) {
+ m_value = Value;
+ return *this;
+ }
+ FixedValue& operator=(T&&) noexcept {
+ m_value = {Value};
+ return *this;
+ }
+};
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index ab3468ca0..c3088f926 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -113,10 +113,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, ImeDialog) \
SUB(Lib, AvPlayer) \
SUB(Lib, Ngs2) \
+ SUB(Lib, Audio3d) \
CLS(Frontend) \
CLS(Render) \
SUB(Render, Vulkan) \
SUB(Render, Recompiler) \
+ CLS(ImGui) \
CLS(Input) \
CLS(Tty) \
CLS(Loader)
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index dd2376ea6..749568da1 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -80,10 +80,12 @@ enum class Class : u8 {
Lib_ImeDialog, ///< The LibSceImeDialog implementation.
Lib_AvPlayer, ///< The LibSceAvPlayer implementation.
Lib_Ngs2, ///< The LibSceNgs2 implementation.
+ Lib_Audio3d, ///< The LibSceAudio3d implementation.
Frontend, ///< Emulator UI
Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend
Render_Recompiler, ///< Shader recompiler
+ ImGui, ///< ImGui
Loader, ///< ROM loader
Input, ///< Input emulation
Tty, ///< Debug output from emu
diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp
index e6c1fc1af..cce12ebcf 100644
--- a/src/common/path_util.cpp
+++ b/src/common/path_util.cpp
@@ -116,6 +116,7 @@ static auto UserPaths = [] {
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
create_path(PathType::AddonsDir, user_dir / ADDONS_DIR);
+ create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
return paths;
}();
diff --git a/src/common/path_util.h b/src/common/path_util.h
index bee93c1b9..623b285ed 100644
--- a/src/common/path_util.h
+++ b/src/common/path_util.h
@@ -23,6 +23,7 @@ enum class PathType {
CheatsDir, // Where cheats are stored.
PatchesDir, // Where patches are stored.
AddonsDir, // Where additional content is stored.
+ MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
};
constexpr auto PORTABLE_DIR = "user";
@@ -41,6 +42,7 @@ constexpr auto CAPTURES_DIR = "captures";
constexpr auto CHEATS_DIR = "cheats";
constexpr auto PATCHES_DIR = "patches";
constexpr auto ADDONS_DIR = "addcont";
+constexpr auto METADATA_DIR = "game_data";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";
diff --git a/src/common/rdtsc.h b/src/common/rdtsc.h
index 3180273e5..4e4d58436 100644
--- a/src/common/rdtsc.h
+++ b/src/common/rdtsc.h
@@ -3,6 +3,8 @@
#pragma once
+#include "common/arch.h"
+
#ifdef _MSC_VER
#include
#endif
@@ -13,15 +15,20 @@ namespace Common {
#ifdef _MSC_VER
__forceinline static u64 FencedRDTSC() {
+#ifdef ARCH_X86_64
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
+#else
+#error "Missing FencedRDTSC() implementation for target CPU architecture."
+#endif
}
#else
static inline u64 FencedRDTSC() {
+#ifdef ARCH_X86_64
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
@@ -29,6 +36,16 @@ static inline u64 FencedRDTSC() {
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
+#elif defined(ARCH_ARM64)
+ u64 ret;
+ asm volatile("isb\n\t"
+ "mrs %0, cntvct_el0\n\t"
+ "isb\n\t"
+ : "=r"(ret)::"memory");
+ return ret;
+#else
+#error "Missing FencedRDTSC() implementation for target CPU architecture."
+#endif
}
#endif
diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp
index 235113700..3950bd5fe 100644
--- a/src/core/address_space.cpp
+++ b/src/core/address_space.cpp
@@ -3,11 +3,13 @@
#include
#include "common/alignment.h"
+#include "common/arch.h"
#include "common/assert.h"
#include "common/error.h"
#include "core/address_space.h"
#include "core/libraries/kernel/memory_management.h"
#include "core/memory.h"
+#include "libraries/error_codes.h"
#ifdef _WIN32
#include
@@ -16,7 +18,7 @@
#include
#endif
-#ifdef __APPLE__
+#if defined(__APPLE__) && defined(ARCH_X86_64)
// Reserve space for the system address space using a zerofill section.
asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000");
#endif
@@ -231,27 +233,36 @@ struct AddressSpace::Impl {
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
DWORD new_flags{};
- if (read && write) {
+
+ if (read && write && execute) {
+ new_flags = PAGE_EXECUTE_READWRITE;
+ } else if (read && write) {
new_flags = PAGE_READWRITE;
} else if (read && !write) {
new_flags = PAGE_READONLY;
- } else if (!read && !write) {
+ } else if (execute && !read && not write) {
+ new_flags = PAGE_EXECUTE;
+ } else if (!read && !write && !execute) {
new_flags = PAGE_NOACCESS;
} else {
- UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write);
+ LOG_CRITICAL(Common_Memory,
+ "Unsupported protection flag combination for address {:#x}, size {}",
+ virtual_addr, size);
+ return;
}
- const VAddr virtual_end = virtual_addr + size;
- auto [it, end] = placeholders.equal_range({virtual_addr, virtual_end});
- while (it != end) {
- const size_t offset = std::max(it->lower(), virtual_addr);
- const size_t protect_length = std::min(it->upper(), virtual_end) - offset;
- DWORD old_flags{};
- if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) {
- LOG_CRITICAL(Common_Memory, "Failed to change virtual memory protect rules");
- }
- ++it;
+ DWORD old_flags{};
+ bool success =
+ VirtualProtect(reinterpret_cast(virtual_addr), size, new_flags, &old_flags);
+
+ if (!success) {
+ LOG_ERROR(Common_Memory,
+ "Failed to change virtual memory protection for address {:#x}, size {}",
+ virtual_addr, size);
}
+
+ // Use assert to ensure success in debug builds
+ DEBUG_ASSERT(success && "Failed to change virtual memory protection");
}
HANDLE process{};
@@ -298,12 +309,12 @@ struct AddressSpace::Impl {
constexpr int protection_flags = PROT_READ | PROT_WRITE;
constexpr int base_map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
-#ifdef __APPLE__
- // On ARM64 Macs, we run into limitations due to the commpage from 0xFC0000000 - 0xFFFFFFFFF
- // and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. We can allocate the system
- // managed region, as well as system reserved if reduced in size slightly, but we cannot map
- // the user region where we want, so we must let the OS put it wherever possible and hope
- // the game won't rely on its location.
+#if defined(__APPLE__) && defined(ARCH_X86_64)
+ // On ARM64 Macs under Rosetta 2, we run into limitations due to the commpage from
+ // 0xFC0000000 - 0xFFFFFFFFF and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF.
+ // We can allocate the system managed region, as well as system reserved if reduced in size
+ // slightly, but we cannot map the user region where we want, so we must let the OS put it
+ // wherever possible and hope the game won't rely on its location.
system_managed_base = reinterpret_cast(
mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), system_managed_size, protection_flags,
base_map_flags | MAP_FIXED, -1, 0));
@@ -315,12 +326,22 @@ struct AddressSpace::Impl {
protection_flags, base_map_flags, -1, 0));
#else
const auto virtual_size = system_managed_size + system_reserved_size + user_size;
+#if defined(ARCH_X86_64)
const auto virtual_base =
reinterpret_cast(mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), virtual_size,
protection_flags, base_map_flags | MAP_FIXED, -1, 0));
system_managed_base = virtual_base;
system_reserved_base = reinterpret_cast(SYSTEM_RESERVED_MIN);
user_base = reinterpret_cast(USER_MIN);
+#else
+ // Map memory wherever possible and instruction translation can handle offsetting to the
+ // base.
+ const auto virtual_base = reinterpret_cast(
+ mmap(nullptr, virtual_size, protection_flags, base_map_flags, -1, 0));
+ system_managed_base = virtual_base;
+ system_reserved_base = virtual_base + SYSTEM_RESERVED_MIN - SYSTEM_MANAGED_MIN;
+ user_base = virtual_base + USER_MIN - SYSTEM_MANAGED_MIN;
+#endif
#endif
if (system_managed_base == MAP_FAILED || system_reserved_base == MAP_FAILED ||
user_base == MAP_FAILED) {
@@ -420,9 +441,11 @@ struct AddressSpace::Impl {
if (write) {
flags |= PROT_WRITE;
}
+#ifdef ARCH_X86_64
if (execute) {
flags |= PROT_EXEC;
}
+#endif
int ret = mprotect(reinterpret_cast(virtual_addr), size, flags);
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
}
@@ -453,8 +476,14 @@ AddressSpace::~AddressSpace() = default;
void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr,
bool is_exec) {
- return impl->Map(virtual_addr, phys_addr, size,
- is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
+#if ARCH_X86_64
+ const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
+#else
+ // On non-native architectures, we can simplify things by ignoring the execute flag for the
+ // canonical copy of the memory and rely on the JIT to map translated code as executable.
+ constexpr auto prot = PAGE_READWRITE;
+#endif
+ return impl->Map(virtual_addr, phys_addr, size, prot);
}
void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot,
@@ -493,7 +522,10 @@ void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VA
}
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
- return impl->Protect(virtual_addr, size, true, true, true);
+ const bool read = True(perms & MemoryPermission::Read);
+ const bool write = True(perms & MemoryPermission::Write);
+ const bool execute = True(perms & MemoryPermission::Execute);
+ return impl->Protect(virtual_addr, size, read, write, execute);
}
} // namespace Core
diff --git a/src/core/address_space.h b/src/core/address_space.h
index 2a3488d57..3233c7588 100644
--- a/src/core/address_space.h
+++ b/src/core/address_space.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include "common/arch.h"
#include "common/enum.h"
#include "common/types.h"
@@ -23,7 +24,7 @@ constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL;
constexpr VAddr SYSTEM_MANAGED_MIN = 0x00000400000ULL;
constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL;
constexpr VAddr SYSTEM_RESERVED_MIN = 0x07FFFFC000ULL;
-#ifdef __APPLE__
+#if defined(__APPLE__) && defined(ARCH_X86_64)
// Can only comfortably reserve the first 0x7C0000000 of system reserved space.
constexpr VAddr SYSTEM_RESERVED_MAX = 0xFBFFFFFFFULL;
#else
diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp
index f31ff18cb..91b3bcd40 100644
--- a/src/core/cpu_patches.cpp
+++ b/src/core/cpu_patches.cpp
@@ -315,14 +315,12 @@ static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat
SaveRegisters(c, {scratch});
// BLSI sets CF to zero if source is zero, otherwise it sets CF to one.
- Xbyak::Label set_carry, clear_carry, end;
+ Xbyak::Label clear_carry, end;
c.mov(scratch, *src);
c.neg(scratch); // NEG, like BLSI, clears CF if the source is zero and sets it otherwise
- c.jc(set_carry);
- c.jmp(clear_carry);
+ c.jnc(clear_carry);
- c.L(set_carry);
c.and_(scratch, *src);
c.stc(); // setting/clearing carry needs to happen after the AND because that clears CF
c.jmp(end);
@@ -345,15 +343,13 @@ static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGener
SaveRegisters(c, {scratch});
- Xbyak::Label set_carry, clear_carry, end;
+ Xbyak::Label clear_carry, end;
// BLSMSK sets CF to zero if source is NOT zero, otherwise it sets CF to one.
c.mov(scratch, *src);
c.test(scratch, scratch);
- c.jz(set_carry);
- c.jmp(clear_carry);
+ c.jnz(clear_carry);
- c.L(set_carry);
c.dec(scratch);
c.xor_(scratch, *src);
c.stc();
@@ -378,15 +374,13 @@ static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat
SaveRegisters(c, {scratch});
- Xbyak::Label set_carry, clear_carry, end;
+ Xbyak::Label clear_carry, end;
// BLSR sets CF to zero if source is NOT zero, otherwise it sets CF to one.
c.mov(scratch, *src);
c.test(scratch, scratch);
- c.jz(set_carry);
- c.jmp(clear_carry);
+ c.jnz(clear_carry);
- c.L(set_carry);
c.dec(scratch);
c.and_(scratch, *src);
c.stc();
diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp
index f122709e4..86865fe63 100644
--- a/src/core/file_format/trp.cpp
+++ b/src/core/file_format/trp.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/path_util.h"
#include "trp.h"
TRP::TRP() = default;
@@ -48,8 +49,9 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
return false;
s64 seekPos = sizeof(TrpHeader);
- std::filesystem::path trpFilesPath(std::filesystem::current_path() / "user/game_data" /
- title / "TrophyFiles" / it.path().stem());
+ std::filesystem::path trpFilesPath(
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title / "TrophyFiles" /
+ it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp
new file mode 100644
index 000000000..63815a068
--- /dev/null
+++ b/src/core/libraries/audio3d/audio3d.cpp
@@ -0,0 +1,344 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio3d.h"
+#include "audio3d_error.h"
+#include "audio3d_impl.h"
+
+#include "common/logging/log.h"
+#include "core/libraries/audio/audioout.h"
+#include "core/libraries/error_codes.h"
+#include "core/libraries/libs.h"
+
+namespace Libraries::Audio3d {
+
+// Audio3d
+
+int PS4_SYSV_ABI sceAudio3dInitialize(s64 iReserved) {
+ LOG_INFO(Lib_Audio3d, "iReserved = {}", iReserved);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dTerminate() {
+ // TODO: When not initialized or some ports still open, return ORBIS_AUDIO3D_ERROR_NOT_READY
+ LOG_INFO(Lib_Audio3d, "called");
+ return ORBIS_OK;
+}
+
+void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters) {
+ if (sParameters != NULL) {
+ sParameters->szSizeThis = sizeof(OrbisAudio3dOpenParameters);
+ sParameters->uiGranularity = 256;
+ sParameters->eRate = ORBIS_AUDIO3D_RATE_48000;
+ sParameters->uiMaxObjects = 512;
+ sParameters->uiQueueDepth = 2;
+ sParameters->eBufferMode = ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH;
+ sParameters->uiNumBeds = 2;
+ } else {
+ LOG_ERROR(Lib_Audio3d, "Invalid OpenParameters ptr");
+ }
+}
+
+int PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId iUserId,
+ const OrbisAudio3dOpenParameters* pParameters,
+ OrbisAudio3dPortId* pId) {
+ LOG_INFO(Lib_Audio3d, "iUserId = {}", iUserId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId uiPortId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId uiPortId,
+ OrbisAudio3dAttributeId uiAttributeId,
+ const void* pAttribute, size_t szAttribute) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiAttributeId = {}, szAttribute = {}", uiPortId,
+ uiAttributeId, szAttribute);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId uiPortId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId uiPortId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId uiPortId, OrbisAudio3dBlocking eBlocking) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId uiPortId,
+ OrbisAudio3dAttributeId* pCapabilities,
+ unsigned int* pNumCapabilities) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId uiPortId, unsigned int* pQueueLevel,
+ unsigned int* pQueueAvailable) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId uiPortId, OrbisAudio3dObjectId* pId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId uiPortId,
+ OrbisAudio3dObjectId uiObjectId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}", uiPortId, uiObjectId);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId uiPortId,
+ OrbisAudio3dObjectId uiObjectId,
+ size_t szNumAttributes,
+ const OrbisAudio3dAttribute* pAttributeArray) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}, szNumAttributes = {}", uiPortId,
+ uiObjectId, szNumAttributes);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels,
+ OrbisAudio3dFormat eFormat, const void* pBuffer,
+ unsigned int uiNumSamples) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}", uiPortId,
+ uiNumChannels, uiNumSamples);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels,
+ OrbisAudio3dFormat eFormat, const void* pBuffer,
+ unsigned int uiNumSamples,
+ OrbisAudio3dOutputRoute eOutputRoute, bool bRestricted) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}, bRestricted = {}",
+ uiPortId, uiNumChannels, uiNumSamples, bRestricted);
+ return ORBIS_OK;
+}
+
+size_t PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize(unsigned int uiNumSpeakers, bool bIs3d) {
+ LOG_INFO(Lib_Audio3d, "uiNumSpeakers = {}, bIs3d = {}", uiNumSpeakers, bIs3d);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI
+sceAudio3dCreateSpeakerArray(OrbisAudio3dSpeakerArrayHandle* pHandle,
+ const OrbisAudio3dSpeakerArrayParameters* pParameters) {
+ if (pHandle == nullptr || pParameters == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid SpeakerArray parameters");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ LOG_INFO(Lib_Audio3d, "called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray(OrbisAudio3dSpeakerArrayHandle handle) {
+ if (handle == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ LOG_INFO(Lib_Audio3d, "called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients(OrbisAudio3dSpeakerArrayHandle handle,
+ OrbisAudio3dPosition pos, float fSpread,
+ float* pCoefficients,
+ unsigned int uiNumCoefficients) {
+ LOG_INFO(Lib_Audio3d, "fSpread = {}, uiNumCoefficients = {}", fSpread, uiNumCoefficients);
+ if (handle == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(OrbisAudio3dSpeakerArrayHandle handle,
+ OrbisAudio3dPosition pos, float fSpread,
+ float* pCoefficients,
+ unsigned int uiNumCoefficients,
+ bool bHeightAware,
+ float fDownmixSpreadRadius) {
+ LOG_INFO(Lib_Audio3d,
+ "fSpread = {}, uiNumCoefficients = {}, bHeightAware = {}, fDownmixSpreadRadius = {}",
+ fSpread, uiNumCoefficients, bHeightAware, fDownmixSpreadRadius);
+ if (handle == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId uiPortId, OrbisUserServiceUserId userId,
+ s32 type, s32 index, u32 len, u32 freq, u32 param) {
+ LOG_INFO(Lib_Audio3d,
+ "uiPortId = {}, userId = {}, type = {}, index = {}, len = {}, freq = {}, param = {}",
+ uiPortId, userId, type, index, len, freq, param);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle) {
+ LOG_INFO(Lib_Audio3d, "handle = {}", handle);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, const void* ptr) {
+ LOG_INFO(Lib_Audio3d, "handle = {}", handle);
+ if (ptr == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid Output ptr");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(::Libraries::AudioOut::OrbisAudioOutOutputParam* param,
+ s32 num) {
+ LOG_INFO(Lib_Audio3d, "num = {}", num);
+ if (param == nullptr) {
+ LOG_ERROR(Lib_Audio3d, "invalid OutputParam ptr");
+ return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
+ }
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortCreate(unsigned int uiGranularity, OrbisAudio3dRate eRate,
+ s64 iReserved, OrbisAudio3dPortId* pId) {
+ LOG_INFO(Lib_Audio3d, "uiGranularity = {}, iReserved = {}", uiGranularity, iReserved);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortDestroy(OrbisAudio3dPortId uiPortId) {
+ LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId);
+ return ORBIS_OK;
+}
+
+// Audio3dPrivate
+
+const char* PS4_SYSV_ABI sceAudio3dStrError(int iErrorCode) {
+ LOG_ERROR(Lib_Audio3d, "(PRIVATE) called, iErrorCode = {}", iErrorCode);
+ return "NOT_IMPLEMENTED";
+}
+
+// Audio3dDebug
+
+int PS4_SYSV_ABI sceAudio3dPortQueryDebug() {
+ LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortQueryDebug called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetList() {
+ LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetList called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetParameters() {
+ LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetParameters called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetState() {
+ LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetState called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortFreeState() {
+ LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortFreeState called");
+ return ORBIS_OK;
+}
+
+// Unknown
+
+int PS4_SYSV_ABI sceAudio3dPortGetBufferLevel() {
+ LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dPortGetStatus() {
+ LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dReportRegisterHandler() {
+ LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() {
+ LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudio3dSetGpuRenderer() {
+ LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym) {
+ LIB_FUNCTION("-R1DukFq7Dk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dGetSpeakerArrayMixCoefficients);
+ LIB_FUNCTION("-Re+pCWvwjQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dGetSpeakerArrayMixCoefficients2);
+ LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortQueryDebug);
+ LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dObjectUnreserve);
+ LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dObjectSetAttributes);
+ LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dAudioOutOutput);
+ LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dDeleteSpeakerArray);
+ LIB_FUNCTION("9ZA23Ia46Po", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortGetAttributesSupported);
+ LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite);
+ LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dStrError);
+ LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetState);
+ LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dAudioOutOutputs);
+ LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dGetDefaultOpenParameters);
+ LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortDestroy);
+ LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortClose);
+ LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dReportRegisterHandler);
+ LIB_FUNCTION("QqgTQQdzEMY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortGetBufferLevel);
+ LIB_FUNCTION("SEggctIeTcI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetList);
+ LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortCreate);
+ LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dInitialize);
+ LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortPush);
+ LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dTerminate);
+ LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortOpen);
+ LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortGetQueueLevel);
+ LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortSetAttribute);
+ LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFlush);
+ LIB_FUNCTION("flPcUaXVXcw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dPortGetParameters);
+ LIB_FUNCTION("iRX6GJs9tvE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetStatus);
+ LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectReserve);
+ LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dGetSpeakerArrayMemorySize);
+ LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dCreateSpeakerArray);
+ LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortAdvance);
+ LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutClose);
+ LIB_FUNCTION("psv2gbihC1A", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dReportUnregisterHandler);
+ LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFreeState);
+ LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutOpen);
+ LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite2);
+ LIB_FUNCTION("yEYXcbAGK14", "libSceAudio3d", 1, "libSceAudio3d", 1, 1,
+ sceAudio3dSetGpuRenderer);
+};
+
+} // namespace Libraries::Audio3d
\ No newline at end of file
diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h
new file mode 100644
index 000000000..6cbe2d02f
--- /dev/null
+++ b/src/core/libraries/audio3d/audio3d.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/types.h"
+
+#include
+
+namespace Core::Loader {
+class SymbolsResolver;
+}
+
+namespace Libraries::Audio3d {
+
+class Audio3d;
+
+typedef int OrbisUserServiceUserId;
+typedef unsigned int OrbisAudio3dPortId;
+typedef unsigned int OrbisAudio3dObjectId;
+typedef unsigned int OrbisAudio3dAttributeId;
+
+enum OrbisAudio3dFormat {
+ ORBIS_AUDIO3D_FORMAT_S16 = 0, // s16
+ ORBIS_AUDIO3D_FORMAT_FLOAT = 1 // f32
+};
+
+enum OrbisAudio3dRate { ORBIS_AUDIO3D_RATE_48000 = 0 };
+
+enum OrbisAudio3dBufferMode {
+ ORBIS_AUDIO3D_BUFFER_NO_ADVANCE = 0,
+ ORBIS_AUDIO3D_BUFFER_ADVANCE_NO_PUSH = 1,
+ ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2
+};
+
+enum OrbisAudio3dBlocking { ORBIS_AUDIO3D_BLOCKING_ASYNC = 0, ORBIS_AUDIO3D_BLOCKING_SYNC = 1 };
+
+enum OrbisAudio3dPassthrough {
+ ORBIS_AUDIO3D_PASSTHROUGH_NONE = 0,
+ ORBIS_AUDIO3D_PASSTHROUGH_LEFT = 1,
+ ORBIS_AUDIO3D_PASSTHROUGH_RIGHT = 2
+};
+
+enum OrbisAudio3dOutputRoute {
+ ORBIS_AUDIO3D_OUTPUT_BOTH = 0,
+ ORBIS_AUDIO3D_OUTPUT_HMU_ONLY = 1,
+ ORBIS_AUDIO3D_OUTPUT_TV_ONLY = 2
+};
+
+enum OrbisAudio3dAmbisonics {
+ ORBIS_AUDIO3D_AMBISONICS_NONE = ~0,
+ ORBIS_AUDIO3D_AMBISONICS_W = 0,
+ ORBIS_AUDIO3D_AMBISONICS_X = 1,
+ ORBIS_AUDIO3D_AMBISONICS_Y = 2,
+ ORBIS_AUDIO3D_AMBISONICS_Z = 3,
+ ORBIS_AUDIO3D_AMBISONICS_R = 4,
+ ORBIS_AUDIO3D_AMBISONICS_S = 5,
+ ORBIS_AUDIO3D_AMBISONICS_T = 6,
+ ORBIS_AUDIO3D_AMBISONICS_U = 7,
+ ORBIS_AUDIO3D_AMBISONICS_V = 8,
+ ORBIS_AUDIO3D_AMBISONICS_K = 9,
+ ORBIS_AUDIO3D_AMBISONICS_L = 10,
+ ORBIS_AUDIO3D_AMBISONICS_M = 11,
+ ORBIS_AUDIO3D_AMBISONICS_N = 12,
+ ORBIS_AUDIO3D_AMBISONICS_O = 13,
+ ORBIS_AUDIO3D_AMBISONICS_P = 14,
+ ORBIS_AUDIO3D_AMBISONICS_Q = 15
+};
+
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributePcm = 0x00000001;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributePriority = 0x00000002;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributePosition = 0x00000003;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeSpread = 0x00000004;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeGain = 0x00000005;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributePassthrough = 0x00000006;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeResetState = 0x00000007;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeApplicationSpecific = 0x00000008;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeAmbisonics = 0x00000009;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeRestricted = 0x0000000A;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeOutputRoute = 0x0000000B;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeLateReverbLevel = 0x00010001;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadRadius = 0x00010002;
+static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadHeightAware = 0x00010003;
+
+struct OrbisAudio3dSpeakerArray;
+using OrbisAudio3dSpeakerArrayHandle = OrbisAudio3dSpeakerArray*; // head
+
+struct OrbisAudio3dOpenParameters {
+ size_t szSizeThis;
+ unsigned int uiGranularity;
+ OrbisAudio3dRate eRate;
+ unsigned int uiMaxObjects;
+ unsigned int uiQueueDepth;
+ OrbisAudio3dBufferMode eBufferMode;
+ char padding[32];
+ unsigned int uiNumBeds;
+};
+
+struct OrbisAudio3dAttribute {
+ OrbisAudio3dAttributeId uiAttributeId;
+ char padding[32];
+ const void* pValue;
+ size_t szValue;
+};
+
+struct OrbisAudio3dPosition {
+ float fX;
+ float fY;
+ float fZ;
+};
+
+struct OrbisAudio3dPcm {
+ OrbisAudio3dFormat eFormat;
+ const void* pSampleBuffer;
+ unsigned int uiNumSamples;
+};
+
+struct OrbisAudio3dSpeakerArrayParameters {
+ OrbisAudio3dPosition* pSpeakerPosition;
+ unsigned int uiNumSpeakers;
+ bool bIs3d;
+ void* pBuffer;
+ size_t szSize;
+};
+
+struct OrbisAudio3dApplicationSpecific {
+ size_t szSizeThis;
+ u8 cApplicationSpecific[32];
+};
+
+void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters);
+
+void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym);
+} // namespace Libraries::Audio3d
\ No newline at end of file
diff --git a/src/core/libraries/audio3d/audio3d_error.h b/src/core/libraries/audio3d/audio3d_error.h
new file mode 100644
index 000000000..ff9d9749c
--- /dev/null
+++ b/src/core/libraries/audio3d/audio3d_error.h
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+constexpr int ORBIS_AUDIO3D_ERROR_UNKNOWN = 0x80EA0001;
+constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PORT = 0x80EA0002;
+constexpr int ORBIS_AUDIO3D_ERROR_INVALID_OBJECT = 0x80EA0003;
+constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER = 0x80EA0004;
+constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY = 0x80EA0005;
+constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES = 0x80EA0006;
+constexpr int ORBIS_AUDIO3D_ERROR_NOT_READY = 0x80EA0007;
+constexpr int ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED = 0x80EA0008;
diff --git a/src/core/libraries/audio3d/audio3d_impl.cpp b/src/core/libraries/audio3d/audio3d_impl.cpp
new file mode 100644
index 000000000..c267c096f
--- /dev/null
+++ b/src/core/libraries/audio3d/audio3d_impl.cpp
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio3d_error.h"
+#include "audio3d_impl.h"
+
+#include "common/logging/log.h"
+#include "core/libraries/error_codes.h"
+#include "core/libraries/kernel/libkernel.h"
+
+using namespace Libraries::Kernel;
+
+namespace Libraries::Audio3d {} // namespace Libraries::Audio3d
diff --git a/src/core/libraries/audio3d/audio3d_impl.h b/src/core/libraries/audio3d/audio3d_impl.h
new file mode 100644
index 000000000..4e6342b1b
--- /dev/null
+++ b/src/core/libraries/audio3d/audio3d_impl.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio3d.h"
+
+namespace Libraries::Audio3d {
+
+class Audio3d {
+public:
+private:
+ typedef unsigned int OrbisAudio3dPluginId;
+};
+
+} // namespace Libraries::Audio3d
diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp
index 99ba2e8b6..603d55014 100644
--- a/src/core/libraries/avplayer/avplayer_source.cpp
+++ b/src/core/libraries/avplayer/avplayer_source.cpp
@@ -20,6 +20,17 @@ extern "C" {
#include
}
+// The av_err2str macro in libavutil/error.h does not play nice with C++
+#ifdef av_err2str
+#undef av_err2str
+#include
+av_always_inline std::string av_err2string(int errnum) {
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
+ return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum);
+}
+#define av_err2str(err) av_err2string(err).c_str()
+#endif // av_err2str
+
namespace Libraries::AvPlayer {
using namespace Kernel;
diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h
index 094ea6603..b9896b6c3 100644
--- a/src/core/libraries/error_codes.h
+++ b/src/core/libraries/error_codes.h
@@ -440,11 +440,47 @@ constexpr int ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT = 0x8096000A;
constexpr int ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER = 0x80A10003;
// NpTrophy library
+constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600;
+constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601;
+constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602;
+constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604;
+constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605;
+constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606;
+constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607;
constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608;
-constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B;
+constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C;
+constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D;
+constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E;
+constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F;
+constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610;
+constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611;
+constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612;
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613;
-constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622;
+constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616;
+constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618;
+constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619;
+constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A;
+constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C;
+constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D;
+constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E;
+constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621;
+constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622;
+constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623;
+constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
+constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625;
+constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626;
+constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627;
+constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628;
+constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629;
+constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B;
+constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D;
// AvPlayer library
constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001;
diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp
index ffec70300..645bcf423 100644
--- a/src/core/libraries/gnmdriver/gnmdriver.cpp
+++ b/src/core/libraries/gnmdriver/gnmdriver.cpp
@@ -2155,6 +2155,7 @@ int PS4_SYSV_ABI sceGnmSubmitCommandBuffersForWorkload() {
int PS4_SYSV_ABI sceGnmSubmitDone() {
LOG_DEBUG(Lib_GnmDriver, "called");
+ WaitGpuIdle();
if (!liverpool->IsGpuIdle()) {
submission_lock = true;
}
diff --git a/src/core/libraries/kernel/cpu_management.cpp b/src/core/libraries/kernel/cpu_management.cpp
index 93dc60bd0..3bf609dfe 100644
--- a/src/core/libraries/kernel/cpu_management.cpp
+++ b/src/core/libraries/kernel/cpu_management.cpp
@@ -8,7 +8,7 @@
namespace Libraries::Kernel {
int PS4_SYSV_ABI sceKernelIsNeoMode() {
- LOG_INFO(Kernel_Sce, "called");
+ LOG_DEBUG(Kernel_Sce, "called");
return Config::isNeoMode();
}
diff --git a/src/core/libraries/kernel/event_flag/event_flag.cpp b/src/core/libraries/kernel/event_flag/event_flag.cpp
index 0fd0c3bb7..4d3925127 100644
--- a/src/core/libraries/kernel/event_flag/event_flag.cpp
+++ b/src/core/libraries/kernel/event_flag/event_flag.cpp
@@ -78,7 +78,7 @@ int PS4_SYSV_ABI sceKernelCloseEventFlag() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelClearEventFlag(OrbisKernelEventFlag ef, u64 bitPattern) {
- LOG_INFO(Kernel_Event, "called");
+ LOG_DEBUG(Kernel_Event, "called");
ef->Clear(bitPattern);
return ORBIS_OK;
}
@@ -97,7 +97,7 @@ int PS4_SYSV_ABI sceKernelSetEventFlag(OrbisKernelEventFlag ef, u64 bitPattern)
}
int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode,
u64* pResultPat) {
- LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode);
+ LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode);
if (ef == nullptr) {
return ORBIS_KERNEL_ERROR_ESRCH;
@@ -145,7 +145,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern,
}
int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode,
u64* pResultPat, OrbisKernelUseconds* pTimeout) {
- LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode);
+ LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode);
if (ef == nullptr) {
return ORBIS_KERNEL_ERROR_ESRCH;
}
diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp
index 2634e25c8..d56f4dc41 100644
--- a/src/core/libraries/kernel/libkernel.cpp
+++ b/src/core/libraries/kernel/libkernel.cpp
@@ -454,6 +454,8 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("F6e0kwo4cnk", "libkernel", 1, "libkernel", 1, 1, sceKernelTriggerUserEvent);
LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent);
LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId);
+ LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect);
+ LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect);
LIB_FUNCTION("23CPPI1tyBY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventFilter);
// misc
diff --git a/src/core/libraries/kernel/libkernel.h b/src/core/libraries/kernel/libkernel.h
index c28a548ff..73705cdc2 100644
--- a/src/core/libraries/kernel/libkernel.h
+++ b/src/core/libraries/kernel/libkernel.h
@@ -33,6 +33,8 @@ typedef struct {
} OrbisKernelUuid;
int* PS4_SYSV_ABI __Error();
+int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
+ struct OrbisTimesec* st, unsigned long* dst_sec);
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver);
void LibKernel_Register(Core::Loader::SymbolsResolver* sym);
diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp
index a5288a656..af3542912 100644
--- a/src/core/libraries/kernel/memory_management.cpp
+++ b/src/core/libraries/kernel/memory_management.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
+#include "core/address_space.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/memory_management.h"
#include "core/linker.h"
@@ -218,6 +219,19 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void**
return memory->QueryProtection(std::bit_cast(addr), start, end, prot);
}
+int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) {
+ Core::MemoryManager* memory_manager = Core::Memory::Instance();
+ Core::MemoryProt protection_flags = static_cast(prot);
+ return memory_manager->Protect(std::bit_cast(addr), size, protection_flags);
+}
+
+int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) {
+ Core::MemoryManager* memory_manager = Core::Memory::Instance();
+ Core::MemoryProt protection_flags = static_cast(prot);
+ return memory_manager->MTypeProtect(std::bit_cast(addr), size,
+ static_cast(mtype), protection_flags);
+}
+
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
size_t infoSize) {
LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
@@ -282,6 +296,12 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
entries[i].operation, entries[i].length, result);
break;
}
+ case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: {
+ result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection);
+ LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
+ entries[i].operation, entries[i].length, result);
+ break;
+ }
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE: {
result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length,
entries[i].protection, flags, "");
@@ -292,11 +312,10 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
break;
}
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: {
- // By now, ignore protection and log it instead
- LOG_WARNING(Kernel_Vmm,
- "entry = {}, operation = {}, len = {:#x}, type = {} "
- "is UNSUPPORTED and skipped",
- i, entries[i].operation, entries[i].length, (u8)entries[i].type);
+ result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type,
+ entries[i].protection);
+ LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
+ entries[i].operation, entries[i].length, result);
break;
}
default: {
diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h
index 761cb0844..205b2274f 100644
--- a/src/core/libraries/kernel/memory_management.h
+++ b/src/core/libraries/kernel/memory_management.h
@@ -95,6 +95,10 @@ s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len,
int flags);
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot);
+int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot);
+
+int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot);
+
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
size_t infoSize);
s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut);
diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp
index 11d472a49..ec8c15afa 100644
--- a/src/core/libraries/kernel/thread_management.cpp
+++ b/src/core/libraries/kernel/thread_management.cpp
@@ -6,6 +6,7 @@
#include
#include "common/alignment.h"
+#include "common/arch.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/logging/log.h"
@@ -53,11 +54,12 @@ void init_pthreads() {
}
void pthreadInitSelfMainThread() {
+ const char* name = "Main_Thread";
auto* pthread_pool = g_pthread_cxt->GetPthreadPool();
- g_pthread_self = pthread_pool->Create();
+ g_pthread_self = pthread_pool->Create(name);
scePthreadAttrInit(&g_pthread_self->attr);
g_pthread_self->pth = pthread_self();
- g_pthread_self->name = "Main_Thread";
+ g_pthread_self->name = name;
}
int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr) {
@@ -295,7 +297,7 @@ ScePthread PS4_SYSV_ABI scePthreadSelf() {
int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
const /*SceKernelCpumask*/ u64 mask) {
- LOG_INFO(Kernel_Pthread, "called");
+ LOG_DEBUG(Kernel_Pthread, "called");
if (pattr == nullptr || *pattr == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
@@ -387,7 +389,7 @@ int PS4_SYSV_ABI posix_pthread_attr_setstacksize(ScePthreadAttr* attr, size_t st
}
int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask) {
- LOG_INFO(Kernel_Pthread, "called");
+ LOG_DEBUG(Kernel_Pthread, "called");
if (thread == nullptr) {
return SCE_KERNEL_ERROR_ESRCH;
@@ -414,11 +416,6 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
if (addr == nullptr || *addr != nullptr) {
return addr;
}
- static std::mutex mutex;
- std::scoped_lock lk{mutex};
- if (*addr != nullptr) {
- return addr;
- }
const VAddr vaddr = reinterpret_cast(addr);
std::string name = fmt::format("mutex{:#x}", vaddr);
scePthreadMutexInit(addr, nullptr, name.c_str());
@@ -584,8 +581,7 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) {
}
int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) {
- mutex = createMutex(mutex);
- if (mutex == nullptr) {
+ if (mutex == nullptr || *mutex == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
}
@@ -995,7 +991,9 @@ static void cleanup_thread(void* arg) {
static void* run_thread(void* arg) {
auto* thread = static_cast(arg);
Common::SetCurrentThreadName(thread->name.c_str());
+#ifdef ARCH_X86_64
Core::InitializeThreadPatchStack();
+#endif
auto* linker = Common::Singleton::Instance();
linker->InitTlsForThread(false);
void* ret = nullptr;
@@ -1019,7 +1017,7 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr
attr = g_pthread_cxt->GetDefaultAttr();
}
- *thread = pthread_pool->Create();
+ *thread = pthread_pool->Create(name);
if ((*thread)->attr != nullptr) {
scePthreadAttrDestroy(&(*thread)->attr);
@@ -1061,11 +1059,11 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr
}
}
-ScePthread PThreadPool::Create() {
+ScePthread PThreadPool::Create(const char* name) {
std::scoped_lock lock{m_mutex};
for (auto* p : m_threads) {
- if (p->is_free) {
+ if (p->is_free && p->name == name) {
p->is_free = false;
return p;
}
@@ -1188,6 +1186,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) {
int result = pthread_condattr_destroy(&(*attr)->cond_attr);
LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result);
+ delete *attr;
switch (result) {
case 0:
@@ -1493,6 +1492,8 @@ int PS4_SYSV_ABI scePthreadOnce(int* once_control, void (*init_routine)(void)) {
}
[[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr) {
+ g_pthread_self->is_free = true;
+
pthread_exit(value_ptr);
UNREACHABLE();
}
diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h
index a2b2f6fea..7385b55ce 100644
--- a/src/core/libraries/kernel/thread_management.h
+++ b/src/core/libraries/kernel/thread_management.h
@@ -119,7 +119,7 @@ struct PthreadSemInternal {
class PThreadPool {
public:
- ScePthread Create();
+ ScePthread Create(const char* name);
private:
std::vector m_threads;
diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h
index a28f8c133..a28e6e558 100644
--- a/src/core/libraries/kernel/time_management.h
+++ b/src/core/libraries/kernel/time_management.h
@@ -3,6 +3,8 @@
#pragma once
+#include
+
#include "common/types.h"
namespace Core::Loader {
@@ -50,7 +52,10 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency();
u64 PS4_SYSV_ABI sceKernelReadTsc();
-
+int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp);
+s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz);
+int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds,
+ OrbisKernelTimezone* timezone, int* dst_seconds);
void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp
index e91a51e68..da41eaf00 100644
--- a/src/core/libraries/libs.cpp
+++ b/src/core/libraries/libs.cpp
@@ -6,6 +6,7 @@
#include "core/libraries/app_content/app_content.h"
#include "core/libraries/audio/audioin.h"
#include "core/libraries/audio/audioout.h"
+#include "core/libraries/audio3d/audio3d.h"
#include "core/libraries/avplayer/avplayer.h"
#include "core/libraries/dialogs/error_dialog.h"
#include "core/libraries/dialogs/ime_dialog.h"
@@ -75,6 +76,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::ErrorDialog::RegisterlibSceErrorDialog(sym);
Libraries::ImeDialog::RegisterlibSceImeDialog(sym);
Libraries::AvPlayer::RegisterlibSceAvPlayer(sym);
+ Libraries::Audio3d::RegisterlibSceAudio3d(sym);
}
} // namespace Libraries
diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h
index 965b76809..eababdb67 100644
--- a/src/core/libraries/network/net.h
+++ b/src/core/libraries/network/net.h
@@ -10,8 +10,12 @@ class SymbolsResolver;
}
// Define our own htonll and ntohll because its not available in some systems/platforms
+#ifndef HTONLL
#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))
+#endif
+#ifndef NTOHLL
#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))
+#endif
namespace Libraries::Net {
diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp
index ed25322b4..c3f83341a 100644
--- a/src/core/libraries/np_trophy/np_trophy.cpp
+++ b/src/core/libraries/np_trophy/np_trophy.cpp
@@ -4,13 +4,20 @@
#include
#include "common/logging/log.h"
+#include "common/path_util.h"
#include "common/slot_vector.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
+#include "externals/pugixml/src/pugixml.hpp"
#include "np_trophy.h"
+#include "trophy_ui.h"
namespace Libraries::NpTrophy {
+static TrophyUI g_trophy_ui;
+
+std::string game_serial;
+
static constexpr auto MaxTrophyHandles = 4u;
static constexpr auto MaxTrophyContexts = 8u;
@@ -24,11 +31,50 @@ struct ContextKeyHash {
struct TrophyContext {
u32 context_id;
};
-static Common::SlotVector trophy_handles{};
+static Common::SlotVector trophy_handles{};
static Common::SlotVector trophy_contexts{};
static std::unordered_map contexts_internal{};
-int PS4_SYSV_ABI sceNpTrophyAbortHandle() {
+void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p) {
+ for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) {
+ uint32_t array_index = i / 32;
+ uint32_t bit_position = i % 32;
+
+ p->flag_bits[array_index] &= ~(1U << bit_position);
+ }
+}
+
+void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
+ uint32_t array_index = trophyId / 32;
+ uint32_t bit_position = trophyId % 32;
+
+ p->flag_bits[array_index] |= (1U << bit_position);
+}
+
+void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p) {
+ for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) {
+ uint32_t array_index = i / 32;
+ uint32_t bit_position = i % 32;
+
+ p->flag_bits[array_index] |= (1U << bit_position);
+ }
+}
+
+void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
+ uint32_t array_index = trophyId / 32;
+ uint32_t bit_position = trophyId % 32;
+
+ p->flag_bits[array_index] &= ~(1U << bit_position);
+}
+
+bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p) {
+ uint32_t array_index = trophyId / 32;
+ uint32_t bit_position = trophyId % 32;
+
+ return (p->flag_bits[array_index] & (1U << bit_position)) ? 1 : 0;
+}
+
+int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
@@ -83,8 +129,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label,
- u64 options) {
+s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id,
+ uint32_t service_label, uint64_t options) {
ASSERT(options == 0ull);
if (!context) {
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
@@ -107,7 +153,7 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) {
+s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
if (!handle) {
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
@@ -122,55 +168,120 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyDestroyContext() {
- LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
+int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
+ LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context);
+
+ if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
+
+ Common::SlotId contextId;
+ contextId.index = context;
+
+ ContextKey contextkey = trophy_contexts[contextId];
+ trophy_contexts.erase(contextId);
+ contexts_internal.erase(contextkey);
+
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle) {
- if (!trophy_handles.is_allocated({handle})) {
+s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
+ if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
+
+ if (!trophy_handles.is_allocated({static_cast(handle)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
- trophy_handles.erase({handle});
+ trophy_handles.erase({static_cast(handle)});
LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetGameIcon() {
+int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetGameInfo() {
+int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGameDetails* details,
+ OrbisNpTrophyGameData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetGroupIcon() {
+int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGroupId groupId, void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetGroupInfo() {
+int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGroupId groupId,
+ OrbisNpTrophyGroupDetails* details,
+ OrbisNpTrophyGroupData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon() {
+int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, void* buffer, size_t* size) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo() {
+int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details,
+ OrbisNpTrophyData* data) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count) {
- LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
- *flags = 0u;
- *count = 0;
+s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle,
+ OrbisNpTrophyFlagArray* flags, u32* count) {
+ LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState called");
+
+ if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
+
+ if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
+
+ if (flags == nullptr || count == nullptr)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
+
+ ORBIS_NP_TROPHY_FLAG_ZERO(flags);
+
+ const auto trophyDir =
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+
+ pugi::xml_document doc;
+ pugi::xml_parse_result result =
+ doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+
+ int numTrophies = 0;
+
+ if (result) {
+ auto trophyconf = doc.child("trophyconf");
+ for (pugi::xml_node_iterator it = trophyconf.children().begin();
+ it != trophyconf.children().end(); ++it) {
+
+ std::string currentTrophyId = it->attribute("id").value();
+ std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
+
+ if (std::string(it->name()) == "trophy") {
+ numTrophies++;
+ }
+
+ if (currentTrophyUnlockState == "unlocked") {
+ ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
+ }
+ }
+ } else
+ LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+
+ *count = numTrophies;
return ORBIS_OK;
}
@@ -239,8 +350,16 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyRegisterContext() {
+int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle, uint64_t options) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
+
+ if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
+
+ if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
+
return ORBIS_OK;
}
@@ -254,7 +373,8 @@ int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyShowTrophyList() {
+int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle) {
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
return ORBIS_OK;
}
@@ -474,8 +594,132 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceNpTrophyUnlockTrophy() {
- LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
+int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
+ LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
+
+ if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
+
+ if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
+
+ if (trophyId >= 127)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
+
+ if (platinumId == nullptr)
+ return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
+
+ const auto trophyDir =
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+
+ pugi::xml_document doc;
+ pugi::xml_parse_result result =
+ doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+
+ *platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
+
+ int numTrophies = 0;
+ int numTrophiesUnlocked = 0;
+
+ pugi::xml_node_iterator platinumIt;
+ int platinumTrophyGroup = -1;
+
+ if (result) {
+ auto trophyconf = doc.child("trophyconf");
+ for (pugi::xml_node_iterator it = trophyconf.children().begin();
+ it != trophyconf.children().end(); ++it) {
+
+ std::string currentTrophyId = it->attribute("id").value();
+ std::string currentTrophyName = it->child("name").text().as_string();
+ std::string currentTrophyDescription = it->child("detail").text().as_string();
+ std::string currentTrophyType = it->attribute("ttype").value();
+ std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
+
+ if (currentTrophyType == "P") {
+ platinumIt = it;
+
+ if (std::string(platinumIt->attribute("gid").value()).empty()) {
+ platinumTrophyGroup = -1;
+ } else {
+ platinumTrophyGroup =
+ std::stoi(std::string(platinumIt->attribute("gid").value()));
+ }
+
+ if (trophyId == std::stoi(currentTrophyId)) {
+ return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
+ }
+ }
+
+ if (std::string(it->name()) == "trophy") {
+ if (platinumTrophyGroup == -1) {
+ if (std::string(it->attribute("gid").value()).empty()) {
+ numTrophies++;
+ if (currentTrophyUnlockState == "unlocked") {
+ numTrophiesUnlocked++;
+ }
+ }
+ } else {
+ if (!std::string(it->attribute("gid").value()).empty()) {
+ if (std::stoi(std::string(it->attribute("gid").value())) ==
+ platinumTrophyGroup) {
+ numTrophies++;
+ if (currentTrophyUnlockState == "unlocked") {
+ numTrophiesUnlocked++;
+ }
+ }
+ }
+ }
+
+ if (std::stoi(currentTrophyId) == trophyId) {
+ LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}",
+ it->child("name").text().as_string(),
+ it->child("detail").text().as_string());
+ if (currentTrophyUnlockState == "unlocked") {
+ LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
+ return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
+ } else {
+ if (std::string(it->attribute("unlockstate").value()).empty()) {
+ it->append_attribute("unlockstate") = "unlocked";
+ } else {
+ it->attribute("unlockstate").set_value("unlocked");
+ }
+
+ g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName);
+ }
+ }
+ }
+ }
+
+ if (std::string(platinumIt->attribute("unlockstate").value()).empty()) {
+ if ((numTrophies - 2) == numTrophiesUnlocked) {
+
+ platinumIt->append_attribute("unlockstate") = "unlocked";
+
+ std::string platinumTrophyId = platinumIt->attribute("id").value();
+ std::string platinumTrophyName = platinumIt->child("name").text().as_string();
+
+ *platinumId = std::stoi(platinumTrophyId);
+ g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
+ }
+ } else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") {
+ if ((numTrophies - 2) == numTrophiesUnlocked) {
+
+ platinumIt->attribute("unlockstate").set_value("unlocked");
+
+ std::string platinumTrophyId = platinumIt->attribute("id").value();
+ std::string platinumTrophyName = platinumIt->child("name").text().as_string();
+
+ *platinumId = std::stoi(platinumTrophyId);
+ g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName);
+ }
+ }
+
+ doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+
+ } else
+ LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+
return ORBIS_OK;
}
diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h
index d05d353f1..ae08b2969 100644
--- a/src/core/libraries/np_trophy/np_trophy.h
+++ b/src/core/libraries/np_trophy/np_trophy.h
@@ -4,6 +4,7 @@
#pragma once
#include "common/types.h"
+#include "core/libraries/rtc/rtc.h"
namespace Core::Loader {
class SymbolsResolver;
@@ -11,7 +12,116 @@ class SymbolsResolver;
namespace Libraries::NpTrophy {
-int PS4_SYSV_ABI sceNpTrophyAbortHandle();
+extern std::string game_serial;
+
+constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
+constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;
+
+constexpr int ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE = 128;
+constexpr int ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024;
+constexpr int ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE = 128;
+constexpr int ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE = 1024;
+constexpr int ORBIS_NP_TROPHY_NAME_MAX_SIZE = 128;
+constexpr int ORBIS_NP_TROPHY_DESCR_MAX_SIZE = 1024;
+constexpr int ORBIS_NP_TROPHY_NUM_MAX = 128;
+
+constexpr int ORBIS_NP_TROPHY_INVALID_HANDLE = -1;
+constexpr int ORBIS_NP_TROPHY_INVALID_CONTEXT = -1;
+constexpr int ORBIS_NP_TROPHY_INVALID_TROPHY_ID = -1;
+
+typedef int32_t OrbisNpTrophyHandle;
+typedef int32_t OrbisNpTrophyContext;
+typedef int32_t OrbisNpTrophyId;
+typedef uint32_t OrbisNpTrophyFlagMask;
+
+struct OrbisNpTrophyFlagArray {
+ OrbisNpTrophyFlagMask
+ flag_bits[ORBIS_NP_TROPHY_FLAG_SETSIZE >> ORBIS_NP_TROPHY_FLAG_BITS_SHIFT];
+};
+
+void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p);
+void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
+void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p);
+void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p);
+bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
+
+struct OrbisNpTrophyData {
+ size_t size;
+ OrbisNpTrophyId trophyId;
+ bool unlocked;
+ uint8_t reserved[3];
+ Rtc::OrbisRtcTick timestamp;
+};
+
+typedef int32_t OrbisNpTrophyGrade;
+constexpr int ORBIS_NP_TROPHY_GRADE_UNKNOWN = 0;
+constexpr int ORBIS_NP_TROPHY_GRADE_PLATINUM = 1;
+constexpr int ORBIS_NP_TROPHY_GRADE_GOLD = 2;
+constexpr int ORBIS_NP_TROPHY_GRADE_SILVER = 3;
+constexpr int ORBIS_NP_TROPHY_GRADE_BRONZE = 4;
+
+typedef int32_t OrbisNpTrophyGroupId;
+constexpr int ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID = -1;
+constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
+
+struct OrbisNpTrophyDetails {
+ size_t size;
+ OrbisNpTrophyId trophyId;
+ OrbisNpTrophyGrade trophyGrade;
+ OrbisNpTrophyGroupId groupId;
+ bool hidden;
+ uint8_t reserved[3];
+ char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
+ char description[ORBIS_NP_TROPHY_DESCR_MAX_SIZE];
+};
+
+struct OrbisNpTrophyGameData {
+ size_t size;
+ uint32_t unlockedTrophies;
+ uint32_t unlockedPlatinum;
+ uint32_t unlockedGold;
+ uint32_t unlockedSilver;
+ uint32_t unlockedBronze;
+ uint32_t progressPercentage;
+};
+
+struct OrbisNpTrophyGameDetails {
+ size_t size;
+ uint32_t numGroups;
+ uint32_t numTrophies;
+ uint32_t numPlatinum;
+ uint32_t numGold;
+ uint32_t numSilver;
+ uint32_t numBronze;
+ char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
+ char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
+};
+
+struct OrbisNpTrophyGroupData {
+ size_t size;
+ OrbisNpTrophyGroupId groupId;
+ uint32_t unlockedTrophies;
+ uint32_t unlockedPlatinum;
+ uint32_t unlockedGold;
+ uint32_t unlockedSilver;
+ uint32_t unlockedBronze;
+ uint32_t progressPercentage;
+ uint8_t reserved[4];
+};
+
+struct OrbisNpTrophyGroupDetails {
+ size_t size;
+ OrbisNpTrophyGroupId groupId;
+ uint32_t numTrophies;
+ uint32_t numPlatinum;
+ uint32_t numGold;
+ uint32_t numSilver;
+ uint32_t numBronze;
+ char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
+ char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
+};
+
+int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray();
@@ -22,18 +132,30 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails();
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature();
-s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label,
- u64 options);
-s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle);
-int PS4_SYSV_ABI sceNpTrophyDestroyContext();
-s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle);
-int PS4_SYSV_ABI sceNpTrophyGetGameIcon();
-int PS4_SYSV_ABI sceNpTrophyGetGameInfo();
-int PS4_SYSV_ABI sceNpTrophyGetGroupIcon();
-int PS4_SYSV_ABI sceNpTrophyGetGroupInfo();
-int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon();
-int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo();
-s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count);
+s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id,
+ uint32_t service_label, uint64_t options);
+s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle);
+int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context);
+s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle);
+int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ void* buffer, size_t* size);
+int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGameDetails* details,
+ OrbisNpTrophyGameData* data);
+int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGroupId groupId, void* buffer, size_t* size);
+int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyGroupId groupId,
+ OrbisNpTrophyGroupDetails* details,
+ OrbisNpTrophyGroupData* data);
+int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, void* buffer, size_t* size);
+int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details,
+ OrbisNpTrophyData* data);
+s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle,
+ OrbisNpTrophyFlagArray* flags, u32* count);
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum();
int PS4_SYSV_ABI sceNpTrophyIntAbortHandle();
int PS4_SYSV_ABI sceNpTrophyIntCheckNetSyncTitles();
@@ -47,10 +169,12 @@ int PS4_SYSV_ABI sceNpTrophyIntGetTrpIconByUri();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitle();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal();
-int PS4_SYSV_ABI sceNpTrophyRegisterContext();
+int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle, uint64_t options);
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray();
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum();
-int PS4_SYSV_ABI sceNpTrophyShowTrophyList();
+int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context,
+ OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophySystemAbortHandle();
int PS4_SYSV_ABI sceNpTrophySystemBuildGroupIconUri();
int PS4_SYSV_ABI sceNpTrophySystemBuildNetTrophyIconUri();
@@ -94,7 +218,8 @@ int PS4_SYSV_ABI sceNpTrophySystemRemoveTitleData();
int PS4_SYSV_ABI sceNpTrophySystemRemoveUserData();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParam();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt();
-int PS4_SYSV_ABI sceNpTrophyUnlockTrophy();
+int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
+ OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId);
int PS4_SYSV_ABI Func_149656DA81D41C59();
int PS4_SYSV_ABI Func_9F80071876FFA5F6();
int PS4_SYSV_ABI Func_F8EF6F5350A91990();
diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp
new file mode 100644
index 000000000..d23500102
--- /dev/null
+++ b/src/core/libraries/np_trophy/trophy_ui.cpp
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include
+#include "common/assert.h"
+#include "imgui/imgui_std.h"
+#include "trophy_ui.h"
+
+using namespace ImGui;
+using namespace Libraries::NpTrophy;
+
+TrophyUI::TrophyUI() {
+ AddLayer(this);
+}
+
+TrophyUI::~TrophyUI() {
+ Finish();
+}
+
+void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) {
+ TrophyInfo newInfo;
+ newInfo.trophyId = trophyId;
+ newInfo.trophyName = trophyName;
+ trophyQueue.push_back(newInfo);
+}
+
+void TrophyUI::Finish() {
+ RemoveLayer(this);
+}
+
+bool displayingTrophy;
+std::chrono::steady_clock::time_point trophyStartedTime;
+
+void TrophyUI::Draw() {
+ const auto& io = GetIO();
+
+ const ImVec2 window_size{
+ std::min(io.DisplaySize.x, 200.f),
+ std::min(io.DisplaySize.y, 75.f),
+ };
+
+ if (trophyQueue.size() != 0) {
+ if (!displayingTrophy) {
+ displayingTrophy = true;
+ trophyStartedTime = std::chrono::steady_clock::now();
+ }
+
+ std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now();
+ std::chrono::seconds duration =
+ std::chrono::duration_cast(timeNow - trophyStartedTime);
+
+ if (duration.count() >= 5) {
+ trophyQueue.erase(trophyQueue.begin());
+ displayingTrophy = false;
+ }
+
+ if (trophyQueue.size() != 0) {
+ SetNextWindowSize(window_size);
+ SetNextWindowCollapsed(false);
+ SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50));
+ KeepNavHighlight();
+
+ TrophyInfo currentTrophyInfo = trophyQueue[0];
+ if (Begin("Trophy Window", nullptr,
+ ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoInputs)) {
+ Text("Trophy earned!");
+ TextWrapped(currentTrophyInfo.trophyName.c_str());
+ }
+ End();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h
new file mode 100644
index 000000000..d730aca57
--- /dev/null
+++ b/src/core/libraries/np_trophy/trophy_ui.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "common/fixed_value.h"
+#include "common/types.h"
+#include "core/libraries/np_trophy/np_trophy.h"
+#include "imgui/imgui_layer.h"
+
+namespace Libraries::NpTrophy {
+
+struct TrophyInfo {
+ int trophyId = -1;
+ std::string trophyName;
+};
+
+class TrophyUI final : public ImGui::Layer {
+ std::vector trophyQueue;
+
+public:
+ TrophyUI();
+ ~TrophyUI() override;
+
+ void AddTrophyToQueue(int trophyId, std::string trophyName);
+
+ void Finish();
+
+ void Draw() override;
+
+ bool ShouldGrabGamepad() override {
+ return false;
+ }
+};
+
+}; // namespace Libraries::NpTrophy
\ No newline at end of file
diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp
index cb0da552a..b8a27fdc2 100644
--- a/src/core/libraries/pad/pad.cpp
+++ b/src/core/libraries/pad/pad.cpp
@@ -250,7 +250,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
} else {
- if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
+ if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
return 1; // dummy
@@ -263,7 +263,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
} else {
- if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
+ if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
return 1; // dummy
diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h
index b18bbc355..f94a642cf 100644
--- a/src/core/libraries/pad/pad.h
+++ b/src/core/libraries/pad/pad.h
@@ -16,6 +16,7 @@ constexpr int ORBIS_PAD_MAX_DEVICE_UNIQUE_DATA_SIZE = 12;
constexpr int ORBIS_PAD_PORT_TYPE_STANDARD = 0;
constexpr int ORBIS_PAD_PORT_TYPE_SPECIAL = 2;
+constexpr int ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL = 16;
enum OrbisPadDeviceClass {
ORBIS_PAD_DEVICE_CLASS_INVALID = -1,
diff --git a/src/core/libraries/rtc/rtc.cpp b/src/core/libraries/rtc/rtc.cpp
index 387a8558b..7a46a1e31 100644
--- a/src/core/libraries/rtc/rtc.cpp
+++ b/src/core/libraries/rtc/rtc.cpp
@@ -5,156 +5,827 @@
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
+#include "core/libraries/kernel/libkernel.h"
+#include "core/libraries/kernel/time_management.h"
#include "core/libraries/libs.h"
#include "rtc.h"
#include "rtc_error.h"
namespace Libraries::Rtc {
-int PS4_SYSV_ABI sceRtcCheckValid() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ if (pTime->year == 0 || pTime->year > 9999)
+ return ORBIS_RTC_ERROR_INVALID_YEAR;
+
+ if (pTime->month == 0 || pTime->month > 12)
+ return ORBIS_RTC_ERROR_INVALID_MONTH;
+
+ if (pTime->day == 0)
+ return ORBIS_RTC_ERROR_INVALID_DAY;
+
+ using namespace std::chrono;
+ year chronoYear = year(pTime->year);
+ month chronoMonth = month(pTime->month);
+ int lastDay =
+ static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day()));
+
+ if (pTime->day > lastDay)
+ return ORBIS_RTC_ERROR_INVALID_DAY;
+
+ if (pTime->hour >= 24)
+ return ORBIS_RTC_ERROR_INVALID_HOUR;
+
+ if (pTime->minute >= 60)
+ return ORBIS_RTC_ERROR_INVALID_MINUTE;
+
+ if (pTime->second >= 60)
+ return ORBIS_RTC_ERROR_INVALID_SECOND;
+
+ if (pTime->microsecond >= 1000000)
+ return ORBIS_RTC_ERROR_INVALID_MICROSECOND;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcCompareTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ if (pTick1->tick <= pTick2->tick)
+ return 1;
+ else
+ return 0;
+
+ return ORBIS_FAIL;
}
-int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTickLocal == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ time_t seconds;
+ Kernel::OrbisKernelTimezone timezone;
+
+ int convertValue = Kernel::sceKernelConvertLocaltimeToUtc(
+ (pTickLocal->tick - UNIX_EPOCH_TICKS) / 1000000, 0xffffffff, &seconds, &timezone, 0);
+
+ if (convertValue >= 0) {
+ convertValue = sceRtcTickAddMinutes(
+ pTickUtc, pTickLocal, -(((timezone.tz_dsttime * 60) - timezone.tz_minuteswest)));
+ }
+
+ return convertValue;
}
-int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTickUtc == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ Kernel::OrbisKernelTimezone timeZone;
+ int returnValue = Kernel::sceKernelGettimezone(&timeZone);
+
+ sceRtcTickAddMinutes(pTickLocal, pTickUtc,
+ -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)));
+
+ return 0;
}
int PS4_SYSV_ABI sceRtcEnd() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcFormatRFC2822() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc,
+ int iTimeZoneMinutes) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pszDateTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ OrbisRtcTick formatTick;
+
+ if (pTickUtc == nullptr) {
+ sceRtcGetCurrentTick(&formatTick);
+ } else {
+ formatTick.tick = pTickUtc->tick;
+ }
+
+ sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes);
+
+ OrbisRtcDateTime formatTime;
+ sceRtcSetTick(&formatTime, &formatTick);
+
+ int validTime = sceRtcCheckValid(&formatTime);
+
+ std::string formattedString;
+
+ if (validTime >= 0) {
+ int weekDay = sceRtcGetDayOfWeek(formatTime.year, formatTime.month, formatTime.day);
+ switch (weekDay) {
+ case 0:
+ formattedString = "Sun, ";
+ break;
+ case 1:
+ formattedString = "Mon, ";
+ break;
+ case 2:
+ formattedString = "Tue, ";
+ break;
+ case 3:
+ formattedString = "Wed, ";
+ break;
+ case 4:
+ formattedString = "Thu, ";
+ break;
+ case 5:
+ formattedString = "Fri, ";
+ break;
+ case 6:
+ formattedString = "Sat, ";
+ break;
+ }
+
+ if (formatTime.day < 10) {
+ formattedString += "0" + std::to_string(formatTime.day) + " ";
+ } else {
+ formattedString += std::to_string(formatTime.day) + " ";
+ }
+
+ switch (formatTime.month) {
+ case 1:
+ formattedString += "Jan ";
+ break;
+ case 2:
+ formattedString += "Feb ";
+ break;
+ case 3:
+ formattedString += "Mar ";
+ break;
+ case 4:
+ formattedString += "Apr ";
+ break;
+ case 5:
+ formattedString += "May ";
+ break;
+ case 6:
+ formattedString += "Jun ";
+ break;
+ case 7:
+ formattedString += "Jul ";
+ break;
+ case 8:
+ formattedString += "Aug ";
+ break;
+ case 9:
+ formattedString += "Sep ";
+ break;
+ case 10:
+ formattedString += "Oct ";
+ break;
+ case 11:
+ formattedString += "Nov ";
+ break;
+ case 12:
+ formattedString += "Dec ";
+ break;
+ }
+
+ formattedString += std::to_string(formatTime.year) + " ";
+
+ if (formatTime.hour < 10) {
+ formattedString += "0" + std::to_string(formatTime.hour) + ":";
+ } else {
+ formattedString += std::to_string(formatTime.hour) + ":";
+ }
+
+ if (formatTime.minute < 10) {
+ formattedString += "0" + std::to_string(formatTime.minute) + ":";
+ } else {
+ formattedString += std::to_string(formatTime.minute) + ":";
+ }
+
+ if (formatTime.second < 10) {
+ formattedString += "0" + std::to_string(formatTime.second) + " ";
+ } else {
+ formattedString += std::to_string(formatTime.second) + " ";
+ }
+
+ if (iTimeZoneMinutes == 0) {
+ formattedString += "+0000";
+ } else {
+ int timeZoneHours = iTimeZoneMinutes / 60;
+ int timeZoneRemainder = iTimeZoneMinutes % 60;
+
+ if (timeZoneHours < 0) {
+ formattedString += "-";
+ timeZoneHours *= -1;
+ } else {
+ formattedString += "+";
+ }
+
+ if (timeZoneHours < 10) {
+ formattedString += "0" + std::to_string(timeZoneHours);
+ } else {
+ formattedString += std::to_string(timeZoneHours);
+ }
+
+ if (timeZoneRemainder == 0) {
+ formattedString += "00";
+ } else {
+ if (timeZoneRemainder < 0)
+ timeZoneRemainder *= -1;
+ formattedString += std::to_string(timeZoneRemainder);
+ }
+ }
+
+ for (int i = 0; i < formattedString.size() + 1; ++i) {
+ pszDateTime[i] = formattedString.c_str()[i];
+ }
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ Kernel::OrbisKernelTimezone timeZone;
+ Kernel::sceKernelGettimezone(&timeZone);
+
+ return sceRtcFormatRFC2822(pszDateTime, pTickUtc,
+ -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)));
}
-int PS4_SYSV_ABI sceRtcFormatRFC3339() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc,
+ int iTimeZoneMinutes) {
+ LOG_TRACE(Lib_Rtc, "called");
+ return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, iTimeZoneMinutes);
}
-int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ Kernel::OrbisKernelTimezone timeZone;
+ Kernel::sceKernelGettimezone(&timeZone);
+
+ return sceRtcFormatRFC3339(pszDateTime, pTickUtc,
+ -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)));
}
-int PS4_SYSV_ABI sceRtcFormatRFC3339Precise() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc,
+ int iTimeZoneMinutes) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pszDateTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ OrbisRtcTick formatTick;
+
+ if (pTickUtc == nullptr) {
+ sceRtcGetCurrentTick(&formatTick);
+ } else {
+ formatTick.tick = pTickUtc->tick;
+ }
+
+ sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes);
+
+ OrbisRtcDateTime formatTime;
+
+ sceRtcSetTick(&formatTime, &formatTick);
+
+ std::string formattedString;
+ formattedString = std::to_string(formatTime.year) + "-";
+
+ if (formatTime.month < 10) {
+ formattedString += "0" + std::to_string(formatTime.month) + "-";
+ } else {
+ formattedString += std::to_string(formatTime.month) + "-";
+ }
+
+ if (formatTime.day < 10) {
+ formattedString += "0" + std::to_string(formatTime.day) + "T";
+ } else {
+ formattedString += std::to_string(formatTime.day) + "T";
+ }
+
+ if (formatTime.hour < 10) {
+ formattedString += "0" + std::to_string(formatTime.hour) + ":";
+ } else {
+ formattedString += std::to_string(formatTime.hour) + ":";
+ }
+
+ if (formatTime.minute < 10) {
+ formattedString += "0" + std::to_string(formatTime.minute) + ":";
+ } else {
+ formattedString += std::to_string(formatTime.minute) + ":";
+ }
+
+ if (formatTime.second < 10) {
+ formattedString += "0" + std::to_string(formatTime.second);
+ } else {
+ formattedString += std::to_string(formatTime.second);
+ }
+
+ if (formatTime.microsecond != 0) {
+ formattedString += "." + std::to_string(formatTime.microsecond / 1000).substr(0, 2);
+ } else {
+ formattedString += ".00";
+ }
+
+ if (iTimeZoneMinutes == 0) {
+ formattedString += "Z";
+ } else {
+ int timeZoneHours = iTimeZoneMinutes / 60;
+ int timeZoneRemainder = iTimeZoneMinutes % 60;
+
+ if (timeZoneHours < 0) {
+ formattedString += "-";
+ timeZoneHours *= -1;
+ } else {
+ formattedString += "+";
+ }
+
+ if (timeZoneHours < 10) {
+ formattedString += "0" + std::to_string(timeZoneHours);
+ } else {
+ formattedString += std::to_string(timeZoneHours);
+ }
+
+ if (timeZoneRemainder == 0) {
+ formattedString += ":00";
+ } else {
+ if (timeZoneRemainder < 0)
+ timeZoneRemainder *= -1;
+ formattedString += ":" + std::to_string(timeZoneRemainder);
+ }
+ }
+
+ for (int i = 0; i < formattedString.size() + 1; ++i) {
+ pszDateTime[i] = formattedString.c_str()[i];
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime,
+ const OrbisRtcTick* pTickUtc) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ Kernel::OrbisKernelTimezone timeZone;
+ Kernel::sceKernelGettimezone(&timeZone);
+
+ return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc,
+ -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)));
}
-int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue == SCE_OK) {
+ pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+ } else {
+ return ORBIS_RTC_ERROR_NOT_INITIALIZED;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetCurrentClock() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue == SCE_OK) {
+ OrbisRtcTick clockTick;
+ clockTick.tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+
+ sceRtcTickAddMinutes(&clockTick, &clockTick, timeZone);
+ sceRtcSetTick(pTime, &clockTick);
+ }
+
+ return returnValue;
}
-int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED;
+
+ Kernel::OrbisKernelTimezone timeZone;
+ int returnValue = Kernel::sceKernelGettimezone(&timeZone);
+
+ if (returnValue >= 0) {
+ Kernel::OrbisKernelTimespec clocktime;
+
+ // calculate total timezone offset for converting UTC to local time
+ uint64_t tzOffset = -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60));
+
+ if (returnValue >= 0) {
+ OrbisRtcTick newTick;
+ sceRtcGetCurrentTick(&newTick);
+ sceRtcTickAddMinutes(&newTick, &newTick, tzOffset);
+ sceRtcSetTick(pTime, &newTick);
+ }
+ }
+
+ return returnValue;
}
-int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue == SCE_OK) {
+ pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+ } else {
+ return ORBIS_RTC_ERROR_NOT_INITIALIZED;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue == SCE_OK) {
+ pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+ } else {
+ return ORBIS_RTC_ERROR_NOT_INITIALIZED;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue == SCE_OK) {
+ pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+ } else {
+ return ORBIS_RTC_ERROR_NOT_INITIALIZED;
+ }
+
+ return SCE_OK;
}
int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick) {
- pTick->tick = std::chrono::duration_cast(
- std::chrono::high_resolution_clock::now().time_since_epoch())
- .count();
- return ORBIS_OK;
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick == nullptr)
+ return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED;
+
+ Kernel::OrbisKernelTimespec clocktime;
+ int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime);
+
+ if (returnValue >= 0) {
+ pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetDayOfWeek() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ int sdk_version = 0;
+ int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version);
+ if (sdkResult != ORBIS_OK) {
+ sdk_version = 0;
+ }
+
+ if (sdk_version < 0x3000000) {
+ if (year < 1) {
+ return ORBIS_RTC_ERROR_INVALID_YEAR;
+ }
+ if (month > 12 || month <= 0) {
+ return ORBIS_RTC_ERROR_INVALID_MONTH;
+ }
+ } else {
+ if (year > 9999 || year < 1) {
+ return ORBIS_RTC_ERROR_INVALID_YEAR;
+ }
+ if (month > 12 || month <= 0) {
+ return ORBIS_RTC_ERROR_INVALID_MONTH;
+ }
+ }
+
+ int daysInMonth = sceRtcGetDaysInMonth(year, month);
+
+ if (day <= 0 || day > daysInMonth)
+ return ORBIS_RTC_ERROR_INVALID_DAY;
+
+ std::chrono::sys_days chrono_time{std::chrono::year(year) / std::chrono::month(month) /
+ std::chrono::day(day)};
+ std::chrono::weekday chrono_weekday{chrono_time};
+
+ return chrono_weekday.c_encoding();
}
-int PS4_SYSV_ABI sceRtcGetDaysInMonth() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (year <= 0)
+ return ORBIS_RTC_ERROR_INVALID_YEAR;
+
+ if (month <= 0 || month > 12)
+ return ORBIS_RTC_ERROR_INVALID_MONTH;
+
+ std::chrono::year chronoYear = std::chrono::year(year);
+ std::chrono::month chronoMonth = std::chrono::month(month);
+ int lastDay = static_cast(unsigned(
+ std::chrono::year_month_day_last{chronoYear / chronoMonth / std::chrono::last}.day()));
+
+ return lastDay;
}
-int PS4_SYSV_ABI sceRtcGetDosTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr || dosTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ int isValid = sceRtcCheckValid(pTime);
+ if (isValid != SCE_OK) {
+ return isValid;
+ }
+
+ *dosTime |= (pTime->second / 2) & 0x1F;
+ *dosTime |= (pTime->minute & 0x3F) << 5;
+ *dosTime |= (pTime->hour & 0x1F) << 11;
+ *dosTime |= (pTime->day & 0x1F) << 16;
+ *dosTime |= (pTime->month & 0x0F) << 21;
+ *dosTime |= ((pTime->year - 1980) & 0x7F) << 25;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr || pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ int isTimeValid = sceRtcCheckValid(pTime);
+ if (isTimeValid != 0)
+ return isTimeValid;
+
+ if (pTime->month > 2) {
+ pTime->month -= 3;
+ } else {
+ pTime->month += 9;
+ pTime->year -= 1;
+ }
+
+ int c = pTime->year / 100;
+ int ya = pTime->year - 100 * c;
+
+ u64 days;
+ u64 msec;
+
+ days = ((146097 * c) >> 2) + ((1461 * ya) >> 2) + (153 * pTime->month + 2) / 5 + pTime->day;
+ days -= 307;
+ days *= 86400000000;
+
+ msec = pTime->hour * 3600000000 + pTime->minute * 60000000 + pTime->second * 1000000 +
+ pTime->microsecond;
+
+ pTick->tick = days + msec;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetTickResolution() {
+unsigned int PS4_SYSV_ABI sceRtcGetTickResolution() {
+ LOG_TRACE(Lib_Rtc, "called");
+
return 1000000;
}
-int PS4_SYSV_ABI sceRtcGetTime_t() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr || llTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ int isValid = sceRtcCheckValid(pTime);
+ if (isValid != SCE_OK) {
+ return isValid;
+ }
+
+ OrbisRtcTick timeTick;
+ sceRtcGetTick(pTime, &timeTick);
+
+ if (timeTick.tick < UNIX_EPOCH_TICKS) {
+ *llTime = 0;
+ } else {
+ *llTime = (timeTick.tick - UNIX_EPOCH_TICKS) / 1000000;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcGetWin32FileTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr || ulWin32Time == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ int isValid = sceRtcCheckValid(pTime);
+ if (isValid != SCE_OK) {
+ return isValid;
+ }
+
+ OrbisRtcTick timeTick;
+ sceRtcGetTick(pTime, &timeTick);
+
+ if (timeTick.tick < WIN32_FILETIME_EPOCH_TICKS) {
+ *ulWin32Time = 0;
+ } else {
+ *ulWin32Time = (timeTick.tick - WIN32_FILETIME_EPOCH_TICKS) * 10;
+ }
+
+ return SCE_OK;
}
int PS4_SYSV_ABI sceRtcInit() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcIsLeapYear() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (yearInt < 1)
+ return ORBIS_RTC_ERROR_INVALID_YEAR;
+
+ using namespace std::chrono;
+
+ year_month_day_last ymdl{year(yearInt) / February / last};
+ return (ymdl.day() == 29d);
}
-int PS4_SYSV_ABI sceRtcParseDateTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int GetMonthFromString(std::string monthStr) {
+ if (monthStr == "Jan")
+ return 1;
+
+ if (monthStr == "Feb")
+ return 2;
+
+ if (monthStr == "Mar")
+ return 3;
+
+ if (monthStr == "Apr")
+ return 4;
+
+ if (monthStr == "May")
+ return 5;
+
+ if (monthStr == "Jun")
+ return 6;
+
+ if (monthStr == "Jul")
+ return 7;
+
+ if (monthStr == "Aug")
+ return 8;
+
+ if (monthStr == "Sep")
+ return 9;
+
+ if (monthStr == "Oct")
+ return 10;
+
+ if (monthStr == "Nov")
+ return 11;
+
+ if (monthStr == "Dec")
+ return 12;
+
+ return 1;
}
-int PS4_SYSV_ABI sceRtcParseRFC3339() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTickUtc == nullptr || pszDateTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ std::string dateTimeString = std::string(pszDateTime);
+
+ char formatKey = dateTimeString[22];
+ OrbisRtcDateTime dateTime;
+
+ if (formatKey == 'Z' || formatKey == '-' || formatKey == '+') {
+ // RFC3339
+ sceRtcParseRFC3339(pTickUtc, pszDateTime);
+ } else if (formatKey == ':') {
+ // RFC2822
+ dateTime.day = std::stoi(dateTimeString.substr(5, 2));
+ dateTime.month = GetMonthFromString(dateTimeString.substr(8, 3));
+ dateTime.year = std::stoi(dateTimeString.substr(12, 4));
+ dateTime.hour = std::stoi(dateTimeString.substr(17, 2));
+ dateTime.minute = std::stoi(dateTimeString.substr(20, 2));
+ dateTime.second = std::stoi(dateTimeString.substr(23, 2));
+ dateTime.microsecond = 0;
+
+ sceRtcGetTick(&dateTime, pTickUtc);
+
+ if (dateTimeString[26] == '+') {
+ int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60;
+ timeZoneOffset += std::stoi(dateTimeString.substr(29, 2));
+ sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset);
+ } else if (dateTimeString[26] == '-') {
+ int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60;
+ timeZoneOffset += std::stoi(dateTimeString.substr(29, 2));
+ timeZoneOffset *= -1;
+ sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset);
+ }
+
+ } else {
+ // asctime
+ dateTime.month = GetMonthFromString(dateTimeString.substr(4, 3));
+ dateTime.day = std::stoi(dateTimeString.substr(8, 2));
+ dateTime.hour = std::stoi(dateTimeString.substr(11, 2));
+ dateTime.minute = std::stoi(dateTimeString.substr(14, 2));
+ dateTime.second = std::stoi(dateTimeString.substr(17, 2));
+ dateTime.year = std::stoi(dateTimeString.substr(20, 4));
+ dateTime.microsecond = 0;
+
+ sceRtcGetTick(&dateTime, pTickUtc);
+ }
+
+ return SCE_OK;
+}
+
+int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTickUtc == nullptr || pszDateTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ std::string dateTimeString = std::string(pszDateTime);
+
+ OrbisRtcDateTime dateTime;
+ dateTime.year = std::stoi(dateTimeString.substr(0, 4));
+ dateTime.month = std::stoi(dateTimeString.substr(5, 2));
+ dateTime.day = std::stoi(dateTimeString.substr(8, 2));
+ dateTime.hour = std::stoi(dateTimeString.substr(11, 2));
+ dateTime.minute = std::stoi(dateTimeString.substr(14, 2));
+ dateTime.second = std::stoi(dateTimeString.substr(17, 2));
+ dateTime.microsecond = std::stoi(dateTimeString.substr(20, 2));
+
+ sceRtcGetTick(&dateTime, pTickUtc);
+
+ if (dateTimeString[22] != 'Z') {
+ if (dateTimeString[22] == '-') {
+ int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60;
+ timeZoneOffset += std::stoi(dateTimeString.substr(26, 2));
+ timeZoneOffset *= -1;
+ sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset);
+ } else if (dateTimeString[22] == '+') {
+ int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60;
+ timeZoneOffset += std::stoi(dateTimeString.substr(26, 2));
+ sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset);
+ }
+ }
+
+ return SCE_OK;
}
int PS4_SYSV_ABI sceRtcSetConf() {
@@ -162,89 +833,294 @@ int PS4_SYSV_ABI sceRtcSetConf() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick() {
+int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick) {
LOG_ERROR(Lib_Rtc, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick() {
+int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick) {
LOG_ERROR(Lib_Rtc, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick() {
+int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick) {
LOG_ERROR(Lib_Rtc, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceRtcSetCurrentTick() {
+int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick) {
LOG_ERROR(Lib_Rtc, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceRtcSetDosTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTime->microsecond = 0;
+ pTime->second = (dosTime << 1) & 0x3e;
+ pTime->minute = (dosTime >> 5) & 0x3f;
+ pTime->hour = (dosTime & 0xf800) >> 0xb;
+
+ int16_t days = dosTime >> 0x10;
+
+ pTime->day = days & 0x1f;
+ pTime->month = (days >> 5) & 0x0f;
+ pTime->year = (days >> 9) + 1980;
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcSetTick() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr || pTick == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ u32 ly, ld, lm, j;
+ u64 days, msec;
+
+ days = pTick->tick / 86400000000;
+ msec = pTick->tick % 86400000000;
+
+ days += 307;
+
+ j = (days << 2) - 1;
+ ly = j / 146097;
+
+ j -= (146097 * ly);
+ ld = j >> 2;
+
+ j = ((ld << 2) + 3) / 1461;
+ ld = (((ld << 2) + 7) - 1461 * j) >> 2;
+
+ lm = (5 * ld - 3) / 153;
+ ld = (5 * ld + 2 - 153 * lm) / 5;
+ ly = 100 * ly + j;
+
+ if (lm < 10) {
+ lm += 3;
+ } else {
+ lm -= 9;
+ ly++;
+ }
+
+ pTime->year = ly;
+ pTime->month = lm;
+ pTime->day = ld;
+
+ pTime->hour = msec / 3600000000;
+ msec %= 3600000000;
+ pTime->minute = msec / 60000000;
+ msec %= 60000000;
+ pTime->second = msec / 1000000;
+ msec %= 1000000;
+ pTime->microsecond = msec;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcSetTime_t() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ int sdk_version;
+ int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version);
+ if (sdkResult != ORBIS_OK) {
+ sdk_version = 0;
+ }
+
+ OrbisRtcTick newTick;
+ if (sdk_version < 0x3000000) {
+ newTick.tick = (llTime & 0xffffffff) * 1000000;
+ } else {
+ if (llTime < 0) {
+ return ORBIS_RTC_ERROR_INVALID_VALUE;
+ }
+ newTick.tick = llTime * 1000000;
+ }
+
+ newTick.tick += UNIX_EPOCH_TICKS;
+ sceRtcSetTick(pTime, &newTick);
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcSetWin32FileTime() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTime == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ u64 convertedTime = (ulWin32Time / 10) + WIN32_FILETIME_EPOCH_TICKS;
+
+ OrbisRtcTick convertedTick;
+ convertedTick.tick = convertedTime;
+
+ sceRtcSetTick(pTime, &convertedTick);
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddDays() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = (lAdd * 86400000000) + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddHours() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = (lAdd * 3600000000) + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddMicroseconds() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2,
+ int64_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = lAdd + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddMinutes() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = (lAdd * 60000000) + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddMonths() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ if (lAdd == 0) {
+ pTick1->tick = pTick2->tick;
+ return SCE_OK;
+ }
+
+ OrbisRtcDateTime time;
+ s64 s;
+ s64 tempMonth;
+
+ sceRtcSetTick(&time, pTick1);
+
+ if (lAdd >= 0) {
+ s = 1;
+ } else {
+ s = -1;
+ }
+
+ time.year += (lAdd / 12);
+ tempMonth = time.month + (lAdd % 12) - 1;
+
+ if (tempMonth > 11 || tempMonth < 0) {
+ tempMonth -= s * 12;
+ time.year += s;
+ }
+
+ time.month = tempMonth + 1;
+
+ using namespace std::chrono;
+ year chronoYear = year(time.year);
+ month chronoMonth = month(time.month);
+ int lastDay =
+ static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day()));
+
+ if (time.day > lastDay) {
+ time.day = lastDay;
+ }
+
+ int timeIsValid = sceRtcCheckValid(&time);
+ if (timeIsValid == SCE_OK) {
+ sceRtcGetTick(&time, pTick1);
+ } else {
+ return timeIsValid;
+ }
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddSeconds() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = (lAdd * 1000000) + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddTicks() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = lAdd + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddWeeks() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ pTick1->tick = (lAdd * 604800000000) + pTick2->tick;
+
+ return SCE_OK;
}
-int PS4_SYSV_ABI sceRtcTickAddYears() {
- LOG_ERROR(Lib_Rtc, "(STUBBED) called");
- return ORBIS_OK;
+int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) {
+ LOG_TRACE(Lib_Rtc, "called");
+
+ if (pTick1 == nullptr || pTick2 == nullptr)
+ return ORBIS_RTC_ERROR_INVALID_POINTER;
+
+ OrbisRtcDateTime time;
+
+ if (lAdd == 0) {
+ pTick1->tick = pTick2->tick;
+ return SCE_OK;
+ }
+
+ sceRtcSetTick(&time, pTick1);
+
+ time.year += lAdd;
+
+ int timeIsValid = sceRtcCheckValid(&time);
+ if (timeIsValid == SCE_OK) {
+ sceRtcGetTick(&time, pTick1);
+ } else {
+ return timeIsValid;
+ }
+
+ return SCE_OK;
}
void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym) {
diff --git a/src/core/libraries/rtc/rtc.h b/src/core/libraries/rtc/rtc.h
index ee6afa70e..c41040863 100644
--- a/src/core/libraries/rtc/rtc.h
+++ b/src/core/libraries/rtc/rtc.h
@@ -11,57 +11,81 @@ class SymbolsResolver;
namespace Libraries::Rtc {
+constexpr int ORBIS_RTC_DAYOFWEEK_SUNDAY = 0;
+constexpr int ORBIS_RTC_DAYOFWEEK_MONDAY = 1;
+constexpr int ORBIS_RTC_DAYOFWEEK_TUESDAY = 2;
+constexpr int ORBIS_RTC_DAYOFWEEK_WEDNESDAY = 3;
+constexpr int ORBIS_RTC_DAYOFWEEK_THURSDAY = 4;
+constexpr int ORBIS_RTC_DAYOFWEEK_FRIDAY = 5;
+constexpr int ORBIS_RTC_DAYOFWEEK_SATURDAY = 6;
+
+constexpr int64_t UNIX_EPOCH_TICKS = 0xdcbffeff2bc000;
+constexpr int64_t WIN32_FILETIME_EPOCH_TICKS = 0xb36168b6a58000;
+
struct OrbisRtcTick {
- u64 tick;
+ uint64_t tick;
};
-int PS4_SYSV_ABI sceRtcCheckValid();
-int PS4_SYSV_ABI sceRtcCompareTick();
-int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc();
-int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime();
+struct OrbisRtcDateTime {
+ uint16_t year;
+ uint16_t month;
+ uint16_t day;
+ uint16_t hour;
+ uint16_t minute;
+ uint16_t second;
+ uint32_t microsecond;
+};
+
+int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime);
+int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2);
+int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc);
+int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal);
int PS4_SYSV_ABI sceRtcEnd();
-int PS4_SYSV_ABI sceRtcFormatRFC2822();
-int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime();
-int PS4_SYSV_ABI sceRtcFormatRFC3339();
-int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime();
-int PS4_SYSV_ABI sceRtcFormatRFC3339Precise();
-int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime();
-int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick();
-int PS4_SYSV_ABI sceRtcGetCurrentClock();
-int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime();
-int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick();
-int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick();
-int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick();
+int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes);
+int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc);
+int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes);
+int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc);
+int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc,
+ int minutes);
+int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime,
+ const OrbisRtcTick* pTickUtc);
+int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone);
+int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime);
+int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick);
int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick);
-int PS4_SYSV_ABI sceRtcGetDayOfWeek();
-int PS4_SYSV_ABI sceRtcGetDaysInMonth();
-int PS4_SYSV_ABI sceRtcGetDosTime();
-int PS4_SYSV_ABI sceRtcGetTick();
-int PS4_SYSV_ABI sceRtcGetTickResolution();
-int PS4_SYSV_ABI sceRtcGetTime_t();
-int PS4_SYSV_ABI sceRtcGetWin32FileTime();
+int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day);
+int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month);
+int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime);
+int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick);
+unsigned int PS4_SYSV_ABI sceRtcGetTickResolution();
+int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime);
+int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time);
int PS4_SYSV_ABI sceRtcInit();
-int PS4_SYSV_ABI sceRtcIsLeapYear();
-int PS4_SYSV_ABI sceRtcParseDateTime();
-int PS4_SYSV_ABI sceRtcParseRFC3339();
+int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt);
+int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime);
+int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime);
int PS4_SYSV_ABI sceRtcSetConf();
-int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick();
-int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick();
-int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick();
-int PS4_SYSV_ABI sceRtcSetCurrentTick();
-int PS4_SYSV_ABI sceRtcSetDosTime();
-int PS4_SYSV_ABI sceRtcSetTick();
-int PS4_SYSV_ABI sceRtcSetTime_t();
-int PS4_SYSV_ABI sceRtcSetWin32FileTime();
-int PS4_SYSV_ABI sceRtcTickAddDays();
-int PS4_SYSV_ABI sceRtcTickAddHours();
-int PS4_SYSV_ABI sceRtcTickAddMicroseconds();
-int PS4_SYSV_ABI sceRtcTickAddMinutes();
-int PS4_SYSV_ABI sceRtcTickAddMonths();
-int PS4_SYSV_ABI sceRtcTickAddSeconds();
-int PS4_SYSV_ABI sceRtcTickAddTicks();
-int PS4_SYSV_ABI sceRtcTickAddWeeks();
-int PS4_SYSV_ABI sceRtcTickAddYears();
+int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime);
+int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick);
+int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime);
+int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time);
+int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2,
+ int64_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd);
+int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd);
void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Rtc
\ No newline at end of file
diff --git a/src/core/libraries/rtc/rtc_error.h b/src/core/libraries/rtc/rtc_error.h
index 04eecbbdf..3af5a68fd 100644
--- a/src/core/libraries/rtc/rtc_error.h
+++ b/src/core/libraries/rtc/rtc_error.h
@@ -3,6 +3,7 @@
#pragma once
+constexpr int ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED = 0x7FFEF9FE;
constexpr int ORBIS_RTC_ERROR_INVALID_PARAMETER = 0x80010602;
constexpr int ORBIS_RTC_ERROR_INVALID_TICK_PARAMETER = 0x80010603;
constexpr int ORBIS_RTC_ERROR_INVALID_DATE_PARAMETER = 0x80010604;
@@ -14,4 +15,18 @@ constexpr int ORBIS_RTC_ERROR_INVALID_DAYS_PARAMETER = 0x80010623;
constexpr int ORBIS_RTC_ERROR_INVALID_HOURS_PARAMETER = 0x80010624;
constexpr int ORBIS_RTC_ERROR_INVALID_MINUTES_PARAMETER = 0x80010625;
constexpr int ORBIS_RTC_ERROR_INVALID_SECONDS_PARAMETER = 0x80010626;
-constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627;
\ No newline at end of file
+constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627;
+constexpr int ORBIS_RTC_ERROR_NOT_INITIALIZED = 0x80B50001;
+constexpr int ORBIS_RTC_ERROR_INVALID_POINTER = 0x80B50002;
+constexpr int ORBIS_RTC_ERROR_INVALID_VALUE = 0x80B50003;
+constexpr int ORBIS_RTC_ERROR_INVALID_ARG = 0x80B50004;
+constexpr int ORBIS_RTC_ERROR_NOT_SUPPORTED = 0x80B50005;
+constexpr int ORBIS_RTC_ERROR_NO_CLOCK = 0x80B50006;
+constexpr int ORBIS_RTC_ERROR_BAD_PARSE = 0x80B50007;
+constexpr int ORBIS_RTC_ERROR_INVALID_YEAR = 0x80B50008;
+constexpr int ORBIS_RTC_ERROR_INVALID_MONTH = 0x80B50009;
+constexpr int ORBIS_RTC_ERROR_INVALID_DAY = 0x80B5000A;
+constexpr int ORBIS_RTC_ERROR_INVALID_HOUR = 0x80B5000B;
+constexpr int ORBIS_RTC_ERROR_INVALID_MINUTE = 0x80B5000C;
+constexpr int ORBIS_RTC_ERROR_INVALID_SECOND = 0x80B5000D;
+constexpr int ORBIS_RTC_ERROR_INVALID_MICROSECOND = 0x80B5000E;
\ No newline at end of file
diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp
index 959a75705..779c922e6 100644
--- a/src/core/libraries/save_data/savedata.cpp
+++ b/src/core/libraries/save_data/savedata.cpp
@@ -179,15 +179,21 @@ int PS4_SYSV_ABI sceSaveDataDeleteUser() {
int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
OrbisSaveDataDirNameSearchResult* result) {
- if (cond == nullptr)
+ if (cond == nullptr || result == nullptr)
return ORBIS_SAVE_DATA_ERROR_PARAMETER;
- LOG_INFO(Lib_SaveData, "called");
+ LOG_INFO(Lib_SaveData, "Number of directories = {}", result->dirNamesNum);
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
std::to_string(cond->userId) / game_serial;
if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) {
- if (cond->dirName == nullptr || std::string_view(cond->dirName->data)
- .empty()) { // look for all dirs if no dir is provided.
- for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) {
+ int maxDirNum = result->dirNamesNum; // Games set a maximum of directories to search for
+ int i = 0;
+
+ if (cond->dirName == nullptr || std::string_view(cond->dirName->data).empty()) {
+ // Look for all dirs if no dir is provided.
+ for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) {
+ if (i >= maxDirNum)
+ break;
+
if (std::filesystem::is_directory(entry.path()) &&
entry.path().filename().string() != "sdmemory") {
// sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs.
@@ -199,13 +205,50 @@ int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond*
result->setNum = i;
}
}
- } else { // Need a game to test.
- LOG_ERROR(Lib_SaveData, "Check Me. sceSaveDataDirNameSearch: dirName = {}",
- cond->dirName->data);
- strncpy(result->dirNames[0].data, cond->dirName->data, 32);
- result->hitNum = 1;
- result->dirNamesNum = 1;
- result->setNum = 1;
+ } else {
+ // Game checks for a specific directory.
+ LOG_INFO(Lib_SaveData, "dirName = {}", cond->dirName->data);
+
+ // Games can pass '%' as a wildcard
+ // e.g. `SAVELIST%` searches for all folders with names starting with `SAVELIST`
+ std::string baseName(cond->dirName->data);
+ u64 wildcardPos = baseName.find('%');
+ if (wildcardPos != std::string::npos) {
+ baseName = baseName.substr(0, wildcardPos);
+ }
+
+ for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) {
+ if (i >= maxDirNum)
+ break;
+
+ if (std::filesystem::is_directory(entry.path())) {
+ std::string dirName = entry.path().filename().string();
+
+ if (wildcardPos != std::string::npos) {
+ if (dirName.compare(0, baseName.size(), baseName) != 0) {
+ continue;
+ }
+ } else if (wildcardPos == std::string::npos && dirName != cond->dirName->data) {
+ continue;
+ }
+
+ strncpy(result->dirNames[i].data, cond->dirName->data, 32);
+
+ i++;
+ result->hitNum = i;
+ result->dirNamesNum = i;
+ result->setNum = i;
+ }
+ }
+ }
+
+ if (result->params != nullptr) {
+ Common::FS::IOFile file(mount_dir / cond->dirName->data / "param.txt",
+ Common::FS::FileAccessMode::Read);
+ if (file.IsOpen()) {
+ file.ReadRaw((void*)result->params, sizeof(OrbisSaveDataParam));
+ file.Close();
+ }
}
} else {
result->hitNum = 0;
diff --git a/src/core/libraries/system/commondialog.cpp b/src/core/libraries/system/commondialog.cpp
index e32e3bb3f..cb9ce0442 100644
--- a/src/core/libraries/system/commondialog.cpp
+++ b/src/core/libraries/system/commondialog.cpp
@@ -8,6 +8,9 @@
namespace Libraries::CommonDialog {
+bool g_isInitialized = false;
+bool g_isUsed = false;
+
int PS4_SYSV_ABI _ZN3sce16CommonDialogUtil12getSelfAppIdEv() {
LOG_ERROR(Lib_CommonDlg, "(STUBBED) called");
return ORBIS_OK;
@@ -83,14 +86,19 @@ int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceCommonDialogInitialize() {
- LOG_ERROR(Lib_CommonDlg, "(DUMMY) called");
- return ORBIS_OK;
+Error PS4_SYSV_ABI sceCommonDialogInitialize() {
+ if (g_isInitialized) {
+ LOG_INFO(Lib_CommonDlg, "already initialized");
+ return Error::ALREADY_SYSTEM_INITIALIZED;
+ }
+ LOG_DEBUG(Lib_CommonDlg, "initialized");
+ g_isInitialized = true;
+ return Error::OK;
}
-int PS4_SYSV_ABI sceCommonDialogIsUsed() {
- LOG_ERROR(Lib_CommonDlg, "(STUBBED) called");
- return ORBIS_OK;
+bool PS4_SYSV_ABI sceCommonDialogIsUsed() {
+ LOG_TRACE(Lib_CommonDlg, "called");
+ return g_isUsed;
}
int PS4_SYSV_ABI Func_0FF577E4E8457883() {
diff --git a/src/core/libraries/system/commondialog.h b/src/core/libraries/system/commondialog.h
index 110e9cc99..6b8ea3d95 100644
--- a/src/core/libraries/system/commondialog.h
+++ b/src/core/libraries/system/commondialog.h
@@ -11,9 +11,44 @@ class SymbolsResolver;
namespace Libraries::CommonDialog {
-struct OrbisCommonDialogBaseParam {
+enum class Status : u32 {
+ NONE = 0,
+ INITIALIZED = 1,
+ RUNNING = 2,
+ FINISHED = 3,
+};
+
+enum class Result : u32 {
+ OK = 0,
+ USER_CANCELED = 1,
+};
+
+enum class Error : u32 {
+ OK = 0,
+ NOT_SYSTEM_INITIALIZED = 0x80B80001,
+ ALREADY_SYSTEM_INITIALIZED = 0x80B80002,
+ NOT_INITIALIZED = 0x80B80003,
+ ALREADY_INITIALIZED = 0x80B80004,
+ NOT_FINISHED = 0x80B80005,
+ INVALID_STATE = 0x80B80006,
+ RESULT_NONE = 0x80B80007,
+ BUSY = 0x80B80008,
+ OUT_OF_MEMORY = 0x80B80009,
+ PARAM_INVALID = 0x80B8000A,
+ NOT_RUNNING = 0x80B8000B,
+ ALREADY_CLOSE = 0x80B8000C,
+ ARG_NULL = 0x80B8000D,
+ UNEXPECTED_FATAL = 0x80B8000E,
+ NOT_SUPPORTED = 0x80B8000F,
+ INHIBIT_SHAREPLAY_CLIENT = 0x80B80010,
+};
+
+extern bool g_isInitialized;
+extern bool g_isUsed;
+
+struct BaseParam {
std::size_t size;
- u8 reserved[36];
+ std::array reserved;
u32 magic;
};
@@ -32,8 +67,8 @@ int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8getAppIdEv();
int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8isFinishEv();
int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client9getResultEv();
int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE();
-int PS4_SYSV_ABI sceCommonDialogInitialize();
-int PS4_SYSV_ABI sceCommonDialogIsUsed();
+Error PS4_SYSV_ABI sceCommonDialogInitialize();
+bool PS4_SYSV_ABI sceCommonDialogIsUsed();
int PS4_SYSV_ABI Func_0FF577E4E8457883();
int PS4_SYSV_ABI Func_41716C2CE379416C();
int PS4_SYSV_ABI Func_483A427D8F6E0748();
diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp
index 452feec93..94c122d9b 100644
--- a/src/core/libraries/system/msgdialog.cpp
+++ b/src/core/libraries/system/msgdialog.cpp
@@ -1,79 +1,157 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include
+#include
+
+#include "common/assert.h"
#include "common/logging/log.h"
-#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/msgdialog.h"
-
-#include
+#include "imgui_internal.h"
+#include "msgdialog_ui.h"
namespace Libraries::MsgDialog {
-int PS4_SYSV_ABI sceMsgDialogClose() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
-}
+using CommonDialog::Error;
+using CommonDialog::Result;
+using CommonDialog::Status;
-int PS4_SYSV_ABI sceMsgDialogGetResult() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
-}
+static auto g_status = Status::NONE;
+static MsgDialogState g_state{};
+static DialogResult g_result{};
+static MsgDialogUi g_msg_dialog_ui;
-int PS4_SYSV_ABI sceMsgDialogGetStatus() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceMsgDialogInitialize() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param) {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- switch (param->mode) {
- case ORBIS_MSG_DIALOG_MODE_USER_MSG:
- LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen userMsg type = %s msg = %s",
- magic_enum::enum_name(param->userMsgParam->buttonType), param->userMsgParam->msg);
- break;
- case ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR:
- LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen progressBar type = %s msg = %s",
- magic_enum::enum_name(param->progBarParam->barType), param->progBarParam->msg);
- break;
- case ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG:
- LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen systemMsg type: %s",
- magic_enum::enum_name(param->sysMsgParam->sysMsgType));
- break;
- default:
- break;
+Error PS4_SYSV_ABI sceMsgDialogClose() {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status != Status::RUNNING) {
+ return Error::NOT_RUNNING;
}
- return ORBIS_OK;
+ g_msg_dialog_ui.Finish(ButtonId::INVALID);
+ return Error::OK;
}
-int PS4_SYSV_ABI sceMsgDialogProgressBarInc() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
+Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status != Status::FINISHED) {
+ return Error::NOT_FINISHED;
+ }
+ if (result == nullptr) {
+ return Error::ARG_NULL;
+ }
+ for (const auto v : result->reserved) {
+ if (v != 0) {
+ return Error::PARAM_INVALID;
+ }
+ }
+ *result = g_result;
+ return Error::OK;
}
-int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
+Status PS4_SYSV_ABI sceMsgDialogGetStatus() {
+ LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
+ return g_status;
}
-int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
+Error PS4_SYSV_ABI sceMsgDialogInitialize() {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (!CommonDialog::g_isInitialized) {
+ return Error::NOT_SYSTEM_INITIALIZED;
+ }
+ if (g_status != Status::NONE) {
+ return Error::ALREADY_INITIALIZED;
+ }
+ if (CommonDialog::g_isUsed) {
+ return Error::BUSY;
+ }
+ g_status = Status::INITIALIZED;
+ CommonDialog::g_isUsed = true;
+
+ return Error::OK;
}
-int PS4_SYSV_ABI sceMsgDialogTerminate() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
+Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param) {
+ if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
+ LOG_INFO(Lib_MsgDlg, "called without initialize");
+ return Error::INVALID_STATE;
+ }
+ if (param == nullptr) {
+ LOG_DEBUG(Lib_MsgDlg, "called param:(NULL)");
+ return Error::ARG_NULL;
+ }
+ LOG_DEBUG(Lib_MsgDlg, "called param->mode: {}", magic_enum::enum_name(param->mode));
+ ASSERT(param->size == sizeof(OrbisParam));
+ ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam));
+ g_result = {};
+ g_state = MsgDialogState{*param};
+ g_status = Status::RUNNING;
+ g_msg_dialog_ui = MsgDialogUi(&g_state, &g_status, &g_result);
+ return Error::OK;
}
-int PS4_SYSV_ABI sceMsgDialogUpdateStatus() {
- LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
- return ORBIS_OK;
+Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget target, u32 delta) {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status != Status::RUNNING) {
+ return Error::NOT_RUNNING;
+ }
+ if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
+ return Error::NOT_SUPPORTED;
+ }
+ if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
+ return Error::PARAM_INVALID;
+ }
+ g_state.GetState().progress += delta;
+ return Error::OK;
+}
+
+Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget target,
+ const char* msg) {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status != Status::RUNNING) {
+ return Error::NOT_RUNNING;
+ }
+ if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
+ return Error::NOT_SUPPORTED;
+ }
+ if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
+ return Error::PARAM_INVALID;
+ }
+ g_state.GetState().msg = msg;
+ return Error::OK;
+}
+
+Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget target,
+ u32 value) {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status != Status::RUNNING) {
+ return Error::NOT_RUNNING;
+ }
+ if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
+ return Error::NOT_SUPPORTED;
+ }
+ if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
+ return Error::PARAM_INVALID;
+ }
+ g_state.GetState().progress = value;
+ return Error::OK;
+}
+
+Error PS4_SYSV_ABI sceMsgDialogTerminate() {
+ LOG_DEBUG(Lib_MsgDlg, "called");
+ if (g_status == Status::RUNNING) {
+ sceMsgDialogClose();
+ }
+ if (g_status == Status::NONE) {
+ return Error::NOT_INITIALIZED;
+ }
+ g_status = Status::NONE;
+ CommonDialog::g_isUsed = false;
+ return Error::OK;
+}
+
+Status PS4_SYSV_ABI sceMsgDialogUpdateStatus() {
+ LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
+ return g_status;
}
void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym) {
diff --git a/src/core/libraries/system/msgdialog.h b/src/core/libraries/system/msgdialog.h
index 28d379138..b8a1f3f07 100644
--- a/src/core/libraries/system/msgdialog.h
+++ b/src/core/libraries/system/msgdialog.h
@@ -3,7 +3,6 @@
#pragma once
-#include "common/types.h"
#include "core/libraries/system/commondialog.h"
namespace Core::Loader {
@@ -12,95 +11,23 @@ class SymbolsResolver;
namespace Libraries::MsgDialog {
-using OrbisUserServiceUserId = s32;
+struct DialogResult;
+struct OrbisParam;
+enum class OrbisMsgDialogProgressBarTarget : u32;
-enum OrbisCommonDialogStatus {
- ORBIS_COMMON_DIALOG_STATUS_NONE = 0,
- ORBIS_COMMON_DIALOG_STATUS_INITIALIZED = 1,
- ORBIS_COMMON_DIALOG_STATUS_RUNNING = 2,
- ORBIS_COMMON_DIALOG_STATUS_FINISHED = 3
-};
-
-enum OrbisMsgDialogMode {
- ORBIS_MSG_DIALOG_MODE_USER_MSG = 1,
- ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR = 2,
- ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG = 3,
-};
-
-enum OrbisMsgDialogButtonType {
- ORBIS_MSG_DIALOG_BUTTON_TYPE_OK = 0,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO = 1,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_NONE = 2,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL = 3,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT = 5,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT_CANCEL = 6,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO = 7,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL_FOCUS_CANCEL = 8,
- ORBIS_MSG_DIALOG_BUTTON_TYPE_2BUTTONS = 9,
-};
-
-enum OrbisMsgDialogProgressBarType {
- ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE = 0,
- ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE_CANCEL = 1,
-};
-
-enum OrbisMsgDialogSystemMessageType {
- ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_EMPTY_STORE = 0,
- ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION = 1,
- ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_UGC_RESTRICTION = 2,
- ORBIS_MSG_DIALOG_SYSMSG_TYPE_CAMERA_NOT_CONNECTED = 4,
- ORBIS_MSG_DIALOG_SYSMSG_TYPE_WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5,
-};
-
-struct OrbisMsgDialogButtonsParam {
- const char* msg1;
- const char* msg2;
- char reserved[32];
-};
-
-struct OrbisMsgDialogUserMessageParam {
- OrbisMsgDialogButtonType buttonType;
- s32 : 32;
- const char* msg;
- OrbisMsgDialogButtonsParam* buttonsParam;
- char reserved[24];
-};
-
-struct OrbisMsgDialogProgressBarParam {
- OrbisMsgDialogProgressBarType barType;
- int32_t : 32;
- const char* msg;
- char reserved[64];
-};
-
-struct OrbisMsgDialogSystemMessageParam {
- OrbisMsgDialogSystemMessageType sysMsgType;
- char reserved[32];
-};
-
-struct OrbisMsgDialogParam {
- CommonDialog::OrbisCommonDialogBaseParam baseParam;
- std::size_t size;
- OrbisMsgDialogMode mode;
- s32 : 32;
- OrbisMsgDialogUserMessageParam* userMsgParam;
- OrbisMsgDialogProgressBarParam* progBarParam;
- OrbisMsgDialogSystemMessageParam* sysMsgParam;
- OrbisUserServiceUserId userId;
- char reserved[40];
- s32 : 32;
-};
-
-int PS4_SYSV_ABI sceMsgDialogClose();
-int PS4_SYSV_ABI sceMsgDialogGetResult();
-int PS4_SYSV_ABI sceMsgDialogGetStatus();
-int PS4_SYSV_ABI sceMsgDialogInitialize();
-s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param);
-int PS4_SYSV_ABI sceMsgDialogProgressBarInc();
-int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg();
-int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue();
-int PS4_SYSV_ABI sceMsgDialogTerminate();
-int PS4_SYSV_ABI sceMsgDialogUpdateStatus();
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogClose();
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result);
+CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus();
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogInitialize();
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param);
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget,
+ u32 delta);
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget,
+ const char* msg);
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget,
+ u32 value);
+CommonDialog::Error PS4_SYSV_ABI sceMsgDialogTerminate();
+CommonDialog::Status PS4_SYSV_ABI sceMsgDialogUpdateStatus();
void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::MsgDialog
diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp
new file mode 100644
index 000000000..63b3390c9
--- /dev/null
+++ b/src/core/libraries/system/msgdialog_ui.cpp
@@ -0,0 +1,272 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include "common/assert.h"
+#include "imgui/imgui_std.h"
+#include "msgdialog_ui.h"
+
+using namespace ImGui;
+using namespace Libraries::CommonDialog;
+using namespace Libraries::MsgDialog;
+
+static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
+static constexpr float PROGRESS_BAR_WIDTH{0.8f};
+
+struct {
+ int count = 0;
+ const char* text1;
+ const char* text2;
+} static constexpr user_button_texts[] = {
+ {1, "OK"}, // 0 OK
+ {2, "Yes", "No"}, // 1 YESNO
+ {0}, // 2 NONE
+ {2, "OK", "Cancel"}, // 3 OK_CANCEL
+ {}, // 4 !!NOP
+ {1, "Wait"}, // 5 WAIT
+ {2, "Wait", "Cancel"}, // 6 WAIT_CANCEL
+ {2, "Yes", "No"}, // 7 YESNO_FOCUS_NO
+ {2, "OK", "Cancel"}, // 8 OK_CANCEL_FOCUS_CANCEL
+ {0xFF}, // 9 TWO_BUTTONS
+};
+static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1);
+
+static void DrawCenteredText(const char* text) {
+ const auto ws = GetWindowSize();
+ const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f);
+ PushTextWrapPos(ws.x - 30.0f);
+ SetCursorPos({
+ (ws.x - text_size.x) / 2.0f,
+ (ws.y - text_size.y) / 2.0f - 50.0f,
+ });
+ Text("%s", text);
+ PopTextWrapPos();
+}
+
+MsgDialogState::MsgDialogState(const OrbisParam& param) {
+ this->mode = param.mode;
+ switch (mode) {
+ case MsgDialogMode::USER_MSG: {
+ ASSERT(param.userMsgParam);
+ const auto& v = *param.userMsgParam;
+ auto state = UserState{
+ .type = v.buttonType,
+ .msg = std::string(v.msg),
+ };
+ if (v.buttonType == ButtonType::TWO_BUTTONS) {
+ ASSERT(v.buttonsParam);
+ state.btn_param1 = std::string(v.buttonsParam->msg1);
+ state.btn_param2 = std::string(v.buttonsParam->msg2);
+ }
+ this->state = state;
+ } break;
+ case MsgDialogMode::PROGRESS_BAR: {
+ ASSERT(param.progBarParam);
+ const auto& v = *param.progBarParam;
+ this->state = ProgressState{
+ .type = v.barType,
+ .msg = std::string(v.msg),
+ .progress = 0,
+ };
+ } break;
+ case MsgDialogMode::SYSTEM_MSG: {
+ ASSERT(param.sysMsgParam);
+ const auto& v = *param.sysMsgParam;
+ this->state = SystemState{
+ .type = v.sysMsgType,
+ };
+ } break;
+ default:
+ UNREACHABLE_MSG("Unknown dialog mode");
+ }
+}
+
+void MsgDialogUi::DrawUser() {
+ const auto& [button_type, msg, btn_param1, btn_param2] =
+ state->GetState();
+ const auto ws = GetWindowSize();
+ DrawCenteredText(msg.c_str());
+ ASSERT(button_type <= ButtonType::TWO_BUTTONS);
+ auto [count, text1, text2] = user_button_texts[static_cast(button_type)];
+ if (count == 0xFF) { // TWO_BUTTONS -> User defined message
+ count = 2;
+ text1 = btn_param1.c_str();
+ text2 = btn_param2.c_str();
+ }
+ const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO;
+ SetCursorPos({
+ ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count),
+ ws.y - 10.0f - BUTTON_SIZE.y,
+ });
+ BeginGroup();
+ if (count > 0) {
+ // First button at the right, so we render the second button first
+ if (count == 2) {
+ PushID(2);
+ if (Button(text2, BUTTON_SIZE)) {
+ switch (button_type) {
+ case ButtonType::OK_CANCEL:
+ case ButtonType::WAIT_CANCEL:
+ case ButtonType::OK_CANCEL_FOCUS_CANCEL:
+ Finish(ButtonId::INVALID, Result::USER_CANCELED);
+ break;
+ default:
+ Finish(ButtonId::BUTTON2);
+ break;
+ }
+ }
+ if (first_render && !focus_first) {
+ SetItemCurrentNavFocus();
+ }
+ PopID();
+ SameLine();
+ }
+ PushID(1);
+ if (Button(text1, BUTTON_SIZE)) {
+ Finish(ButtonId::BUTTON1);
+ }
+ if (first_render && focus_first) {
+ SetItemCurrentNavFocus();
+ }
+ PopID();
+ SameLine();
+ }
+ EndGroup();
+}
+
+void MsgDialogUi::DrawProgressBar() {
+ const auto& [bar_type, msg, progress_bar_value] =
+ state->GetState();
+ DrawCenteredText(msg.c_str());
+ const auto ws = GetWindowSize();
+ SetCursorPos({
+ ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f),
+ ws.y - 10.0f - BUTTON_SIZE.y,
+ });
+ const bool has_cancel = bar_type == ProgressBarType::PERCENTAGE_CANCEL;
+ float bar_width = PROGRESS_BAR_WIDTH * ws.x;
+ if (has_cancel) {
+ bar_width -= BUTTON_SIZE.x - 10.0f;
+ }
+ BeginGroup();
+ ProgressBar(static_cast(progress_bar_value) / 100.0f, {bar_width, BUTTON_SIZE.y});
+ if (has_cancel) {
+ SameLine();
+ if (Button("Cancel", BUTTON_SIZE)) {
+ Finish(ButtonId::INVALID, Result::USER_CANCELED);
+ }
+ if (first_render) {
+ SetItemCurrentNavFocus();
+ }
+ }
+ EndGroup();
+}
+
+struct {
+ const char* text;
+} static constexpr system_message_texts[] = {
+ "No product available in the store.", // TRC_EMPTY_STORE
+ "PSN chat restriction.", // TRC_PSN_CHAT_RESTRICTION
+ "User-generated Media restriction", // TRC_PSN_UGC_RESTRICTION
+ nullptr, // !!NOP
+ "Camera not connected.", // CAMERA_NOT_CONNECTED
+ "Warning: profile picture and name are not set", // WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED
+};
+static_assert(std::size(system_message_texts) ==
+ static_cast(SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED) + 1);
+
+void MsgDialogUi::DrawSystemMessage() {
+ // TODO: Implement go to settings & user profile
+ const auto& [msg_type] = state->GetState();
+ ASSERT(msg_type <= SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED);
+ auto [msg] = system_message_texts[static_cast(msg_type)];
+ DrawCenteredText(msg);
+ const auto ws = GetWindowSize();
+ SetCursorPos({
+ ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
+ ws.y - 10.0f - BUTTON_SIZE.y,
+ });
+ if (Button("OK", BUTTON_SIZE)) {
+ Finish(ButtonId::OK);
+ }
+ if (first_render) {
+ SetItemCurrentNavFocus();
+ }
+}
+
+MsgDialogUi::MsgDialogUi(MsgDialogState* state, Status* status, DialogResult* result)
+ : state(state), status(status), result(result) {
+ if (status && *status == Status::RUNNING) {
+ first_render = true;
+ AddLayer(this);
+ }
+}
+MsgDialogUi::~MsgDialogUi() {
+ Finish(ButtonId::INVALID);
+}
+MsgDialogUi::MsgDialogUi(MsgDialogUi&& other) noexcept
+ : Layer(other), state(other.state), status(other.status), result(other.result) {
+ other.state = nullptr;
+ other.status = nullptr;
+ other.result = nullptr;
+}
+MsgDialogUi& MsgDialogUi::operator=(MsgDialogUi other) {
+ using std::swap;
+ swap(state, other.state);
+ swap(status, other.status);
+ swap(result, other.result);
+ if (status && *status == Status::RUNNING) {
+ first_render = true;
+ AddLayer(this);
+ }
+ return *this;
+}
+
+void MsgDialogUi::Finish(ButtonId buttonId, Result r) {
+ if (result) {
+ result->result = r;
+ result->buttonId = buttonId;
+ }
+ if (status) {
+ *status = Status::FINISHED;
+ }
+ state = nullptr;
+ status = nullptr;
+ result = nullptr;
+ RemoveLayer(this);
+}
+
+void MsgDialogUi::Draw() {
+ if (status == nullptr || *status != Status::RUNNING) {
+ return;
+ }
+ const auto& io = GetIO();
+
+ const ImVec2 window_size{
+ std::min(io.DisplaySize.x, 500.0f),
+ std::min(io.DisplaySize.y, 300.0f),
+ };
+
+ CentralizeWindow();
+ SetNextWindowSize(window_size);
+ SetNextWindowFocus();
+ SetNextWindowCollapsed(false);
+ KeepNavHighlight();
+ // Hack to allow every dialog to have a unique window
+ if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) {
+ switch (state->GetMode()) {
+ case MsgDialogMode::USER_MSG:
+ DrawUser();
+ break;
+ case MsgDialogMode::PROGRESS_BAR:
+ DrawProgressBar();
+ break;
+ case MsgDialogMode::SYSTEM_MSG:
+ DrawSystemMessage();
+ break;
+ }
+ }
+ End();
+
+ first_render = false;
+}
\ No newline at end of file
diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h
new file mode 100644
index 000000000..845abdc43
--- /dev/null
+++ b/src/core/libraries/system/msgdialog_ui.h
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+#include "common/fixed_value.h"
+#include "common/types.h"
+#include "core/libraries/system/commondialog.h"
+#include "imgui/imgui_layer.h"
+
+namespace Libraries::MsgDialog {
+
+using OrbisUserServiceUserId = s32;
+
+enum class MsgDialogMode : u32 {
+ USER_MSG = 1,
+ PROGRESS_BAR = 2,
+ SYSTEM_MSG = 3,
+};
+
+enum class ButtonId : u32 {
+ INVALID = 0,
+ OK = 1,
+ YES = 1,
+ NO = 2,
+ BUTTON1 = 1,
+ BUTTON2 = 2,
+};
+
+enum class ButtonType : u32 {
+ OK = 0,
+ YESNO = 1,
+ NONE = 2,
+ OK_CANCEL = 3,
+ WAIT = 5,
+ WAIT_CANCEL = 6,
+ YESNO_FOCUS_NO = 7,
+ OK_CANCEL_FOCUS_CANCEL = 8,
+ TWO_BUTTONS = 9,
+};
+
+enum class ProgressBarType : u32 {
+ PERCENTAGE = 0,
+ PERCENTAGE_CANCEL = 1,
+};
+
+enum class SystemMessageType : u32 {
+ TRC_EMPTY_STORE = 0,
+ TRC_PSN_CHAT_RESTRICTION = 1,
+ TRC_PSN_UGC_RESTRICTION = 2,
+ CAMERA_NOT_CONNECTED = 4,
+ WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5,
+};
+
+enum class OrbisMsgDialogProgressBarTarget : u32 {
+ DEFAULT = 0,
+};
+
+struct ButtonsParam {
+ const char* msg1{};
+ const char* msg2{};
+ std::array reserved{};
+};
+
+struct UserMessageParam {
+ ButtonType buttonType{};
+ s32 : 32;
+ const char* msg{};
+ ButtonsParam* buttonsParam{};
+ std::array reserved{};
+};
+
+struct ProgressBarParam {
+ ProgressBarType barType{};
+ s32 : 32;
+ const char* msg{};
+ std::array reserved{};
+};
+
+struct SystemMessageParam {
+ SystemMessageType sysMsgType{};
+ std::array reserved{};
+};
+
+struct OrbisParam {
+ CommonDialog::BaseParam baseParam;
+ std::size_t size;
+ MsgDialogMode mode;
+ s32 : 32;
+ UserMessageParam* userMsgParam;
+ ProgressBarParam* progBarParam;
+ SystemMessageParam* sysMsgParam;
+ OrbisUserServiceUserId userId;
+ std::array reserved;
+ s32 : 32;
+};
+
+struct DialogResult {
+ FixedValue mode{};
+ CommonDialog::Result result{CommonDialog::Result::OK};
+ ButtonId buttonId{ButtonId::INVALID};
+ std::array reserved{};
+};
+
+// State is used to copy all the data from the param struct
+class MsgDialogState {
+public:
+ struct UserState {
+ ButtonType type{};
+ std::string msg{};
+ std::string btn_param1{};
+ std::string btn_param2{};
+ };
+ struct ProgressState {
+ ProgressBarType type{};
+ std::string msg{};
+ u32 progress{};
+ };
+ struct SystemState {
+ SystemMessageType type{};
+ };
+
+private:
+ OrbisUserServiceUserId user_id{};
+ MsgDialogMode mode{};
+ std::variant state{std::monostate{}};
+
+public:
+ explicit MsgDialogState(const OrbisParam& param);
+ MsgDialogState() = default;
+
+ [[nodiscard]] OrbisUserServiceUserId GetUserId() const {
+ return user_id;
+ }
+
+ [[nodiscard]] MsgDialogMode GetMode() const {
+ return mode;
+ }
+
+ template
+ [[nodiscard]] T& GetState() {
+ return std::get(state);
+ }
+};
+
+class MsgDialogUi final : public ImGui::Layer {
+ bool first_render{false};
+ MsgDialogState* state{};
+ CommonDialog::Status* status{};
+ DialogResult* result{};
+
+ void DrawUser();
+ void DrawProgressBar();
+ void DrawSystemMessage();
+
+public:
+ explicit MsgDialogUi(MsgDialogState* state = nullptr, CommonDialog::Status* status = nullptr,
+ DialogResult* result = nullptr);
+ ~MsgDialogUi() override;
+ MsgDialogUi(const MsgDialogUi& other) = delete;
+ MsgDialogUi(MsgDialogUi&& other) noexcept;
+ MsgDialogUi& operator=(MsgDialogUi other);
+
+ void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
+
+ void SetProgressBarValue(u32 value, bool increment);
+
+ void Draw() override;
+
+ bool ShouldGrabGamepad() override {
+ return true;
+ }
+};
+
+}; // namespace Libraries::MsgDialog
\ No newline at end of file
diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp
index 91694cfaf..27fe773b6 100644
--- a/src/core/libraries/videoout/driver.cpp
+++ b/src/core/libraries/videoout/driver.cpp
@@ -161,10 +161,6 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
}
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
- if (!req) {
- return std::chrono::microseconds{0};
- }
-
const auto start = std::chrono::high_resolution_clock::now();
// Whatever the game is rendering show splash if it is active
@@ -207,6 +203,11 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
return std::chrono::duration_cast(end - start);
}
+void VideoOutDriver::DrawBlankFrame() {
+ const auto empty_frame = renderer->PrepareBlankFrame(false);
+ renderer->Present(empty_frame);
+}
+
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
bool is_eop /*= false*/) {
{
@@ -283,7 +284,14 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
auto& vblank_status = main_port.vblank_status;
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
const auto request = receive_request();
- delay = Flip(request);
+ if (!request) {
+ delay = std::chrono::microseconds{0};
+ if (!main_port.is_open) {
+ DrawBlankFrame();
+ }
+ } else {
+ delay = Flip(request);
+ }
FRAME_END;
}
diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h
index 6fc74e012..141294bfd 100644
--- a/src/core/libraries/videoout/driver.h
+++ b/src/core/libraries/videoout/driver.h
@@ -102,6 +102,7 @@ private:
};
std::chrono::microseconds Flip(const Request& req);
+ void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
void PresentThread(std::stop_token token);
diff --git a/src/core/linker.cpp b/src/core/linker.cpp
index 0d76f4b9e..e8aab673d 100644
--- a/src/core/linker.cpp
+++ b/src/core/linker.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
+#include "common/arch.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
@@ -27,6 +28,7 @@ static PS4_SYSV_ABI void ProgramExitFunc() {
}
static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
+#ifdef ARCH_X86_64
// reinterpret_cast(addr)(params, exit_func); // can't be used, stack has to have
// a specific layout
asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes
@@ -46,6 +48,9 @@ static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
:
: "r"(addr), "r"(params), "r"(exit_func)
: "rax", "rsi", "rdi");
+#else
+ UNIMPLEMENTED_MSG("Missing RunMainEntry() implementation for target CPU architecture.");
+#endif
}
Linker::Linker() : memory{Memory::Instance()} {}
@@ -85,7 +90,9 @@ void Linker::Execute() {
// Init primary thread.
Common::SetCurrentThreadName("GAME_MainThread");
+#ifdef ARCH_X86_64
InitializeThreadPatchStack();
+#endif
Libraries::Kernel::pthreadInitSelfMainThread();
InitTlsForThread(true);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 640751477..44f96a001 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -7,6 +7,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/memory_management.h"
#include "core/memory.h"
+#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
namespace Core {
@@ -292,6 +293,118 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
return ORBIS_OK;
}
+int MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) {
+ std::scoped_lock lk{mutex};
+
+ // Find the virtual memory area that contains the specified address range.
+ auto it = FindVMA(addr);
+ if (it == vma_map.end() || !it->second.Contains(addr, size)) {
+ LOG_ERROR(Core, "Address range not mapped");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ VirtualMemoryArea& vma = it->second;
+ if (vma.type == VMAType::Free) {
+ LOG_ERROR(Core, "Cannot change protection on free memory region");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ // Validate protection flags
+ constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
+ MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
+ MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
+
+ MemoryProt invalid_flags = prot & ~valid_flags;
+ if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) {
+ LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot),
+ u32(invalid_flags));
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ // Change protection
+ vma.prot = prot;
+
+ // Set permissions
+ Core::MemoryPermission perms{};
+
+ if (True(prot & MemoryProt::CpuRead)) {
+ perms |= Core::MemoryPermission::Read;
+ }
+ if (True(prot & MemoryProt::CpuReadWrite)) {
+ perms |= Core::MemoryPermission::ReadWrite;
+ }
+ if (True(prot & MemoryProt::GpuRead)) {
+ perms |= Core::MemoryPermission::Read;
+ }
+ if (True(prot & MemoryProt::GpuWrite)) {
+ perms |= Core::MemoryPermission::Write;
+ }
+ if (True(prot & MemoryProt::GpuReadWrite)) {
+ perms |= Core::MemoryPermission::ReadWrite;
+ }
+
+ impl.Protect(addr, size, perms);
+
+ return ORBIS_OK;
+}
+
+int MemoryManager::MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot) {
+ std::scoped_lock lk{mutex};
+
+ // Find the virtual memory area that contains the specified address range.
+ auto it = FindVMA(addr);
+ if (it == vma_map.end() || !it->second.Contains(addr, size)) {
+ LOG_ERROR(Core, "Address range not mapped");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ VirtualMemoryArea& vma = it->second;
+
+ if (vma.type == VMAType::Free) {
+ LOG_ERROR(Core, "Cannot change protection on free memory region");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ // Validate protection flags
+ constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
+ MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
+ MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
+
+ MemoryProt invalid_flags = prot & ~valid_flags;
+ if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) {
+ LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot),
+ u32(invalid_flags));
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ // Change type and protection
+ vma.type = mtype;
+ vma.prot = prot;
+
+ // Set permissions
+ Core::MemoryPermission perms{};
+
+ if (True(prot & MemoryProt::CpuRead)) {
+ perms |= Core::MemoryPermission::Read;
+ }
+ if (True(prot & MemoryProt::CpuReadWrite)) {
+ perms |= Core::MemoryPermission::ReadWrite;
+ }
+ if (True(prot & MemoryProt::GpuRead)) {
+ perms |= Core::MemoryPermission::Read;
+ }
+ if (True(prot & MemoryProt::GpuWrite)) {
+ perms |= Core::MemoryPermission::Write;
+ }
+ if (True(prot & MemoryProt::GpuReadWrite)) {
+ perms |= Core::MemoryPermission::ReadWrite;
+ }
+
+ impl.Protect(addr, size, perms);
+
+ return ORBIS_OK;
+}
+
int MemoryManager::VirtualQuery(VAddr addr, int flags,
::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
std::scoped_lock lk{mutex};
diff --git a/src/core/memory.h b/src/core/memory.h
index 919995b0c..d0935ffb7 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -30,6 +30,7 @@ enum class MemoryProt : u32 {
GpuWrite = 32,
GpuReadWrite = 38,
};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryProt)
enum class MemoryMapFlags : u32 {
NoFlags = 0,
@@ -163,6 +164,10 @@ public:
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
+ int Protect(VAddr addr, size_t size, MemoryProt prot);
+
+ int MTypeProtect(VAddr addr, size_t size, VMAType mtype, MemoryProt prot);
+
int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info);
int DirectMemoryQuery(PAddr addr, bool find_next,
diff --git a/src/core/module.cpp b/src/core/module.cpp
index f48848bbd..c28ac1061 100644
--- a/src/core/module.cpp
+++ b/src/core/module.cpp
@@ -3,6 +3,7 @@
#include
#include "common/alignment.h"
+#include "common/arch.h"
#include "common/assert.h"
#include "common/logging/log.h"
#ifdef ENABLE_QT_GUI
@@ -134,9 +135,11 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode);
add_segment(elf_pheader[i]);
+#ifdef ARCH_X86_64
if (elf_pheader[i].p_flags & PF_EXEC) {
PatchInstructions(segment_addr, segment_file_size, c);
}
+#endif
break;
}
case PT_DYNAMIC:
diff --git a/src/core/tls.cpp b/src/core/tls.cpp
index 4a0cdb0dc..eb07e7a72 100644
--- a/src/core/tls.cpp
+++ b/src/core/tls.cpp
@@ -2,23 +2,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include
+#include "common/arch.h"
#include "common/assert.h"
#include "common/types.h"
#include "core/tls.h"
#ifdef _WIN32
#include
-#elif defined(__APPLE__)
+#elif defined(__APPLE__) && defined(ARCH_X86_64)
#include
#include
#include
#include
+#elif !defined(ARCH_X86_64)
+#include
#endif
namespace Core {
#ifdef _WIN32
+// Windows
+
static DWORD slot = 0;
static std::once_flag slot_alloc_flag;
@@ -40,7 +45,9 @@ Tcb* GetTcbBase() {
return reinterpret_cast(TlsGetValue(GetTcbKey()));
}
-#elif defined(__APPLE__)
+#elif defined(__APPLE__) && defined(ARCH_X86_64)
+
+// Apple x86_64
// Reserve space in the 32-bit address range for allocating TCB pages.
asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000");
@@ -132,7 +139,9 @@ Tcb* GetTcbBase() {
return tcb;
}
-#else
+#elif defined(ARCH_X86_64)
+
+// Other POSIX x86_64
void SetTcbBase(void* image_address) {
asm volatile("wrgsbase %0" ::"r"(image_address) : "memory");
@@ -144,6 +153,32 @@ Tcb* GetTcbBase() {
return tcb;
}
+#else
+
+// POSIX non-x86_64
+// Just sets up a simple thread-local variable to store it, then instruction translation can point
+// code to it.
+
+static pthread_key_t slot = 0;
+static std::once_flag slot_alloc_flag;
+
+static void AllocTcbKey() {
+ ASSERT(pthread_key_create(&slot, nullptr) == 0);
+}
+
+pthread_key_t GetTcbKey() {
+ std::call_once(slot_alloc_flag, &AllocTcbKey);
+ return slot;
+}
+
+void SetTcbBase(void* image_address) {
+ ASSERT(pthread_setspecific(GetTcbKey(), image_address) == 0);
+}
+
+Tcb* GetTcbBase() {
+ return static_cast(pthread_getspecific(GetTcbKey()));
+}
+
#endif
} // namespace Core
diff --git a/src/emulator.cpp b/src/emulator.cpp
index eb5414f3f..3006de1e8 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -19,14 +19,15 @@
#include "core/file_format/playgo_chunk.h"
#include "core/file_format/psf.h"
#include "core/file_format/splash.h"
+#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/kernel/thread_management.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libs.h"
#include "core/libraries/ngs2/ngs2.h"
+#include "core/libraries/np_trophy/np_trophy.h"
#include "core/libraries/rtc/rtc.h"
-#include "core/libraries/videoout/video_out.h"
#include "core/linker.h"
#include "core/memory.h"
#include "emulator.h"
@@ -99,6 +100,15 @@ void Emulator::Run(const std::filesystem::path& file) {
auto* param_sfo = Common::Singleton::Instance();
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
+ Libraries::NpTrophy::game_serial = id;
+ const auto trophyDir =
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
+ if (!std::filesystem::exists(trophyDir)) {
+ TRP trp;
+ if (!trp.Extract(file.parent_path())) {
+ LOG_ERROR(Loader, "Couldn't extract trophies");
+ }
+ }
#ifdef ENABLE_QT_GUI
MemoryPatcher::g_game_serial = id;
#endif
@@ -195,7 +205,7 @@ void Emulator::Run(const std::filesystem::path& file) {
}
void Emulator::LoadSystemModules(const std::filesystem::path& file) {
- constexpr std::array ModulesToLoad{
+ constexpr std::array ModulesToLoad{
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2},
{"libSceFiber.sprx", nullptr},
{"libSceUlt.sprx", nullptr},
@@ -204,7 +214,8 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) {
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal},
{"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap},
{"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc},
- {"libSceJpegEnc.sprx", nullptr}},
+ {"libSceJpegEnc.sprx", nullptr},
+ {"libSceFont.sprx", nullptr}},
};
std::vector found_modules;
diff --git a/src/images/play_icon.png b/src/images/play_icon.png
index c67831a1e..2815be39d 100644
Binary files a/src/images/play_icon.png and b/src/images/play_icon.png differ
diff --git a/src/images/themes_icon.png b/src/images/themes_icon.png
index 822ef3af0..cc711011e 100644
Binary files a/src/images/themes_icon.png and b/src/images/themes_icon.png differ
diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h
new file mode 100644
index 000000000..4602382ed
--- /dev/null
+++ b/src/imgui/imgui_config.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+// WARNING: All includes from this file must be relative to allow Dear_ImGui project to compile
+// without having this project include paths.
+
+#include
+
+extern void assert_fail_debug_msg(const char* msg);
+
+#define ImDrawIdx std::uint32_t
+
+#define IM_STRINGIZE(x) IM_STRINGIZE2(x)
+#define IM_STRINGIZE2(x) #x
+#define IM_ASSERT(_EXPR) \
+ ([&]() { \
+ if (!(_EXPR)) [[unlikely]] { \
+ assert_fail_debug_msg(#_EXPR " at " __FILE__ ":" IM_STRINGIZE(__LINE__)); \
+ } \
+ }())
+
+#define IMGUI_USE_WCHAR32
+#define IMGUI_ENABLE_STB_TRUETYPE
+#define IMGUI_DEFINE_MATH_OPERATORS
+
+#define IM_VEC2_CLASS_EXTRA \
+ constexpr ImVec2(float _v) : x(_v), y(_v) {}
\ No newline at end of file
diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h
new file mode 100644
index 000000000..a2ec7fd24
--- /dev/null
+++ b/src/imgui/imgui_layer.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace ImGui {
+
+class Layer {
+public:
+ virtual ~Layer() = default;
+ static void AddLayer(Layer* layer);
+ static void RemoveLayer(Layer* layer);
+
+ virtual void Draw() = 0;
+
+ virtual bool ShouldGrabGamepad() {
+ return false;
+ }
+};
+
+} // namespace ImGui
\ No newline at end of file
diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h
new file mode 100644
index 000000000..6d97cc11b
--- /dev/null
+++ b/src/imgui/imgui_std.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+#include "imgui_internal.h"
+
+namespace ImGui {
+
+inline void CentralizeWindow() {
+ const auto display_size = GetIO().DisplaySize;
+ SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
+}
+
+inline void KeepNavHighlight() {
+ GetCurrentContext()->NavDisableHighlight = false;
+}
+
+inline void SetItemCurrentNavFocus() {
+ const auto ctx = GetCurrentContext();
+ SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
+ ctx->NavInitResult.Clear();
+}
+
+} // namespace ImGui
diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp
new file mode 100644
index 000000000..2a60926fa
--- /dev/null
+++ b/src/imgui/layer/video_info.cpp
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include "video_info.h"
+
+void ImGui::Layers::VideoInfo::Draw() {
+ const ImGuiIO& io = GetIO();
+
+ m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show;
+
+ if (m_show) {
+ if (Begin("Video Info")) {
+ Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
+ }
+ End();
+ }
+}
diff --git a/src/imgui/layer/video_info.h b/src/imgui/layer/video_info.h
new file mode 100644
index 000000000..8eec972a8
--- /dev/null
+++ b/src/imgui/layer/video_info.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "imgui/imgui_layer.h"
+
+namespace Vulkan {
+class RendererVulkan;
+}
+namespace ImGui::Layers {
+
+class VideoInfo : public Layer {
+ bool m_show = false;
+ ::Vulkan::RendererVulkan* renderer{};
+
+public:
+ explicit VideoInfo(::Vulkan::RendererVulkan* renderer) : renderer(renderer) {}
+
+ void Draw() override;
+};
+
+} // namespace ImGui::Layers
diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp
new file mode 100644
index 000000000..ee77499ee
--- /dev/null
+++ b/src/imgui/renderer/imgui_core.cpp
@@ -0,0 +1,195 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include
+#include "common/config.h"
+#include "common/path_util.h"
+#include "imgui/imgui_layer.h"
+#include "imgui_core.h"
+#include "imgui_impl_sdl3.h"
+#include "imgui_impl_vulkan.h"
+#include "sdl_window.h"
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+
+static void CheckVkResult(const vk::Result err) {
+ LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err));
+}
+
+static std::vector layers;
+
+// Update layers before rendering to allow layer changes to be applied during rendering.
+// Using deque to keep the order of changes in case a Layer is removed then added again between
+// frames.
+static std::deque> change_layers;
+static std::mutex change_layers_mutex{};
+
+namespace ImGui {
+
+namespace Core {
+
+void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& window,
+ const u32 image_count, vk::Format surface_format,
+ const vk::AllocationCallbacks* allocator) {
+
+ const auto config_path = GetUserPath(Common::FS::PathType::UserDir) / "imgui.ini";
+ const auto log_path = GetUserPath(Common::FS::PathType::LogDir) / "imgui_log.txt";
+
+ CreateContext();
+ ImGuiIO& io = GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
+ io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight());
+ io.IniFilename = SDL_strdup(config_path.string().c_str());
+ io.LogFilename = SDL_strdup(log_path.string().c_str());
+ StyleColorsDark();
+
+ Sdl::Init(window.GetSdlWindow());
+
+ const Vulkan::InitInfo vk_info{
+ .instance = instance.GetInstance(),
+ .physical_device = instance.GetPhysicalDevice(),
+ .device = instance.GetDevice(),
+ .queue_family = instance.GetPresentQueueFamilyIndex(),
+ .queue = instance.GetPresentQueue(),
+ .image_count = image_count,
+ .min_allocation_size = 1024 * 1024,
+ .pipeline_rendering_create_info{
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &surface_format,
+ },
+ .allocator = allocator,
+ .check_vk_result_fn = &CheckVkResult,
+ };
+ Vulkan::Init(vk_info);
+}
+
+void OnResize() {
+ Sdl::OnResize();
+}
+
+void Shutdown(const vk::Device& device) {
+ device.waitIdle();
+
+ const ImGuiIO& io = GetIO();
+ const auto ini_filename = (void*)io.IniFilename;
+ const auto log_filename = (void*)io.LogFilename;
+
+ Vulkan::Shutdown();
+ Sdl::Shutdown();
+ DestroyContext();
+
+ SDL_free(ini_filename);
+ SDL_free(log_filename);
+}
+
+bool ProcessEvent(SDL_Event* event) {
+ Sdl::ProcessEvent(event);
+ switch (event->type) {
+ case SDL_EVENT_MOUSE_MOTION:
+ case SDL_EVENT_MOUSE_WHEEL:
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ return GetIO().WantCaptureMouse;
+ case SDL_EVENT_TEXT_INPUT:
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP:
+ return GetIO().WantCaptureKeyboard;
+ case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+ case SDL_EVENT_GAMEPAD_BUTTON_UP:
+ case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+ case SDL_EVENT_GAMEPAD_ADDED:
+ case SDL_EVENT_GAMEPAD_REMOVED:
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
+ case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
+ return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
+ default:
+ return false;
+ }
+}
+
+void NewFrame() {
+ {
+ std::scoped_lock lock{change_layers_mutex};
+ while (!change_layers.empty()) {
+ const auto [to_be_added, layer] = change_layers.front();
+ if (to_be_added) {
+ layers.push_back(layer);
+ } else {
+ const auto [begin, end] = std::ranges::remove(layers, layer);
+ layers.erase(begin, end);
+ }
+ change_layers.pop_front();
+ }
+ }
+
+ Vulkan::NewFrame();
+ Sdl::NewFrame();
+ ImGui::NewFrame();
+
+ bool capture_gamepad = false;
+ for (auto* layer : layers) {
+ layer->Draw();
+ if (layer->ShouldGrabGamepad()) {
+ capture_gamepad = true;
+ }
+ }
+ if (capture_gamepad) {
+ GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
+ } else {
+ GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+ }
+}
+
+void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) {
+ ImGui::Render();
+ ImDrawData* draw_data = GetDrawData();
+ if (draw_data->CmdListsCount == 0) {
+ return;
+ }
+
+ if (Config::vkMarkersEnabled()) {
+ cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
+ .pLabelName = "ImGui Render",
+ });
+ }
+
+ vk::RenderingAttachmentInfo color_attachments[1]{
+ {
+ .imageView = frame->image_view,
+ .imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
+ .loadOp = vk::AttachmentLoadOp::eLoad,
+ .storeOp = vk::AttachmentStoreOp::eStore,
+ },
+ };
+ vk::RenderingInfo render_info = {};
+ render_info.renderArea = vk::Rect2D{
+ .offset = {0, 0},
+ .extent = {frame->width, frame->height},
+ };
+ render_info.layerCount = 1;
+ render_info.colorAttachmentCount = 1;
+ render_info.pColorAttachments = color_attachments;
+ cmdbuf.beginRendering(render_info);
+ Vulkan::RenderDrawData(*draw_data, cmdbuf);
+ cmdbuf.endRendering();
+ if (Config::vkMarkersEnabled()) {
+ cmdbuf.endDebugUtilsLabelEXT();
+ }
+}
+
+} // namespace Core
+
+void Layer::AddLayer(Layer* layer) {
+ std::scoped_lock lock{change_layers_mutex};
+ change_layers.emplace_back(true, layer);
+}
+
+void Layer::RemoveLayer(Layer* layer) {
+ std::scoped_lock lock{change_layers_mutex};
+ change_layers.emplace_back(false, layer);
+}
+
+} // namespace ImGui
diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h
new file mode 100644
index 000000000..9ad708f81
--- /dev/null
+++ b/src/imgui/renderer/imgui_core.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "video_core/renderer_vulkan/vk_instance.h"
+#include "vulkan/vulkan_handles.hpp"
+
+union SDL_Event;
+
+namespace Vulkan {
+struct Frame;
+}
+
+namespace ImGui::Core {
+
+void Initialize(const Vulkan::Instance& instance, const Frontend::WindowSDL& window,
+ u32 image_count, vk::Format surface_format,
+ const vk::AllocationCallbacks* allocator = nullptr);
+
+void OnResize();
+
+void Shutdown(const vk::Device& device);
+
+bool ProcessEvent(SDL_Event* event);
+
+void NewFrame();
+
+void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame);
+
+} // namespace ImGui::Core
diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp
new file mode 100644
index 000000000..2a7d801e4
--- /dev/null
+++ b/src/imgui/renderer/imgui_impl_sdl3.cpp
@@ -0,0 +1,789 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on imgui_impl_sdl3.cpp from Dear ImGui repository
+
+#include
+#include "imgui_impl_sdl3.h"
+
+// SDL
+#include
+#if defined(__APPLE__)
+#include
+#endif
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include
+#endif
+
+namespace ImGui::Sdl {
+
+// SDL Data
+struct SdlData {
+ SDL_Window* window{};
+ SDL_WindowID window_id{};
+ Uint64 time{};
+ const char* clipboard_text_data{};
+
+ // IME handling
+ SDL_Window* ime_window{};
+
+ // Mouse handling
+ Uint32 mouse_window_id{};
+ int mouse_buttons_down{};
+ SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{};
+ SDL_Cursor* mouse_last_cursor{};
+ int mouse_pending_leave_frame{};
+
+ // Gamepad handling
+ ImVector gamepads{};
+ GamepadMode gamepad_mode{};
+ bool want_update_gamepads_list{};
+};
+
+// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui
+// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single
+// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static SdlData* GetBackendData() {
+ return ImGui::GetCurrentContext() ? (SdlData*)ImGui::GetIO().BackendPlatformUserData : nullptr;
+}
+
+static const char* GetClipboardText(ImGuiContext*) {
+ SdlData* bd = GetBackendData();
+ if (bd->clipboard_text_data)
+ SDL_free((void*)bd->clipboard_text_data);
+ const char* sdl_clipboard_text = SDL_GetClipboardText();
+ bd->clipboard_text_data = sdl_clipboard_text;
+ return bd->clipboard_text_data;
+}
+
+static void SetClipboardText(ImGuiContext*, const char* text) {
+ SDL_SetClipboardText(text);
+}
+
+static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) {
+ SdlData* bd = GetBackendData();
+ auto window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
+ SDL_Window* window = SDL_GetWindowFromID(window_id);
+ if ((!data->WantVisible || bd->ime_window != window) && bd->ime_window != nullptr) {
+ SDL_StopTextInput(bd->ime_window);
+ bd->ime_window = nullptr;
+ }
+ if (data->WantVisible) {
+ SDL_Rect r;
+ r.x = (int)data->InputPos.x;
+ r.y = (int)data->InputPos.y;
+ r.w = 1;
+ r.h = (int)data->InputLineHeight;
+ SDL_SetTextInputArea(window, &r, 0);
+ SDL_StartTextInput(window);
+ bd->ime_window = window;
+ }
+}
+
+static ImGuiKey KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode) {
+ // Keypad doesn't have individual key values in SDL3
+ switch (scancode) {
+ case SDL_SCANCODE_KP_0:
+ return ImGuiKey_Keypad0;
+ case SDL_SCANCODE_KP_1:
+ return ImGuiKey_Keypad1;
+ case SDL_SCANCODE_KP_2:
+ return ImGuiKey_Keypad2;
+ case SDL_SCANCODE_KP_3:
+ return ImGuiKey_Keypad3;
+ case SDL_SCANCODE_KP_4:
+ return ImGuiKey_Keypad4;
+ case SDL_SCANCODE_KP_5:
+ return ImGuiKey_Keypad5;
+ case SDL_SCANCODE_KP_6:
+ return ImGuiKey_Keypad6;
+ case SDL_SCANCODE_KP_7:
+ return ImGuiKey_Keypad7;
+ case SDL_SCANCODE_KP_8:
+ return ImGuiKey_Keypad8;
+ case SDL_SCANCODE_KP_9:
+ return ImGuiKey_Keypad9;
+ case SDL_SCANCODE_KP_PERIOD:
+ return ImGuiKey_KeypadDecimal;
+ case SDL_SCANCODE_KP_DIVIDE:
+ return ImGuiKey_KeypadDivide;
+ case SDL_SCANCODE_KP_MULTIPLY:
+ return ImGuiKey_KeypadMultiply;
+ case SDL_SCANCODE_KP_MINUS:
+ return ImGuiKey_KeypadSubtract;
+ case SDL_SCANCODE_KP_PLUS:
+ return ImGuiKey_KeypadAdd;
+ case SDL_SCANCODE_KP_ENTER:
+ return ImGuiKey_KeypadEnter;
+ case SDL_SCANCODE_KP_EQUALS:
+ return ImGuiKey_KeypadEqual;
+ default:
+ break;
+ }
+ switch (keycode) {
+ case SDLK_TAB:
+ return ImGuiKey_Tab;
+ case SDLK_LEFT:
+ return ImGuiKey_LeftArrow;
+ case SDLK_RIGHT:
+ return ImGuiKey_RightArrow;
+ case SDLK_UP:
+ return ImGuiKey_UpArrow;
+ case SDLK_DOWN:
+ return ImGuiKey_DownArrow;
+ case SDLK_PAGEUP:
+ return ImGuiKey_PageUp;
+ case SDLK_PAGEDOWN:
+ return ImGuiKey_PageDown;
+ case SDLK_HOME:
+ return ImGuiKey_Home;
+ case SDLK_END:
+ return ImGuiKey_End;
+ case SDLK_INSERT:
+ return ImGuiKey_Insert;
+ case SDLK_DELETE:
+ return ImGuiKey_Delete;
+ case SDLK_BACKSPACE:
+ return ImGuiKey_Backspace;
+ case SDLK_SPACE:
+ return ImGuiKey_Space;
+ case SDLK_RETURN:
+ return ImGuiKey_Enter;
+ case SDLK_ESCAPE:
+ return ImGuiKey_Escape;
+ case SDLK_APOSTROPHE:
+ return ImGuiKey_Apostrophe;
+ case SDLK_COMMA:
+ return ImGuiKey_Comma;
+ case SDLK_MINUS:
+ return ImGuiKey_Minus;
+ case SDLK_PERIOD:
+ return ImGuiKey_Period;
+ case SDLK_SLASH:
+ return ImGuiKey_Slash;
+ case SDLK_SEMICOLON:
+ return ImGuiKey_Semicolon;
+ case SDLK_EQUALS:
+ return ImGuiKey_Equal;
+ case SDLK_LEFTBRACKET:
+ return ImGuiKey_LeftBracket;
+ case SDLK_BACKSLASH:
+ return ImGuiKey_Backslash;
+ case SDLK_RIGHTBRACKET:
+ return ImGuiKey_RightBracket;
+ case SDLK_GRAVE:
+ return ImGuiKey_GraveAccent;
+ case SDLK_CAPSLOCK:
+ return ImGuiKey_CapsLock;
+ case SDLK_SCROLLLOCK:
+ return ImGuiKey_ScrollLock;
+ case SDLK_NUMLOCKCLEAR:
+ return ImGuiKey_NumLock;
+ case SDLK_PRINTSCREEN:
+ return ImGuiKey_PrintScreen;
+ case SDLK_PAUSE:
+ return ImGuiKey_Pause;
+ case SDLK_LCTRL:
+ return ImGuiKey_LeftCtrl;
+ case SDLK_LSHIFT:
+ return ImGuiKey_LeftShift;
+ case SDLK_LALT:
+ return ImGuiKey_LeftAlt;
+ case SDLK_LGUI:
+ return ImGuiKey_LeftSuper;
+ case SDLK_RCTRL:
+ return ImGuiKey_RightCtrl;
+ case SDLK_RSHIFT:
+ return ImGuiKey_RightShift;
+ case SDLK_RALT:
+ return ImGuiKey_RightAlt;
+ case SDLK_RGUI:
+ return ImGuiKey_RightSuper;
+ case SDLK_APPLICATION:
+ return ImGuiKey_Menu;
+ case SDLK_0:
+ return ImGuiKey_0;
+ case SDLK_1:
+ return ImGuiKey_1;
+ case SDLK_2:
+ return ImGuiKey_2;
+ case SDLK_3:
+ return ImGuiKey_3;
+ case SDLK_4:
+ return ImGuiKey_4;
+ case SDLK_5:
+ return ImGuiKey_5;
+ case SDLK_6:
+ return ImGuiKey_6;
+ case SDLK_7:
+ return ImGuiKey_7;
+ case SDLK_8:
+ return ImGuiKey_8;
+ case SDLK_9:
+ return ImGuiKey_9;
+ case SDLK_A:
+ return ImGuiKey_A;
+ case SDLK_B:
+ return ImGuiKey_B;
+ case SDLK_C:
+ return ImGuiKey_C;
+ case SDLK_D:
+ return ImGuiKey_D;
+ case SDLK_E:
+ return ImGuiKey_E;
+ case SDLK_F:
+ return ImGuiKey_F;
+ case SDLK_G:
+ return ImGuiKey_G;
+ case SDLK_H:
+ return ImGuiKey_H;
+ case SDLK_I:
+ return ImGuiKey_I;
+ case SDLK_J:
+ return ImGuiKey_J;
+ case SDLK_K:
+ return ImGuiKey_K;
+ case SDLK_L:
+ return ImGuiKey_L;
+ case SDLK_M:
+ return ImGuiKey_M;
+ case SDLK_N:
+ return ImGuiKey_N;
+ case SDLK_O:
+ return ImGuiKey_O;
+ case SDLK_P:
+ return ImGuiKey_P;
+ case SDLK_Q:
+ return ImGuiKey_Q;
+ case SDLK_R:
+ return ImGuiKey_R;
+ case SDLK_S:
+ return ImGuiKey_S;
+ case SDLK_T:
+ return ImGuiKey_T;
+ case SDLK_U:
+ return ImGuiKey_U;
+ case SDLK_V:
+ return ImGuiKey_V;
+ case SDLK_W:
+ return ImGuiKey_W;
+ case SDLK_X:
+ return ImGuiKey_X;
+ case SDLK_Y:
+ return ImGuiKey_Y;
+ case SDLK_Z:
+ return ImGuiKey_Z;
+ case SDLK_F1:
+ return ImGuiKey_F1;
+ case SDLK_F2:
+ return ImGuiKey_F2;
+ case SDLK_F3:
+ return ImGuiKey_F3;
+ case SDLK_F4:
+ return ImGuiKey_F4;
+ case SDLK_F5:
+ return ImGuiKey_F5;
+ case SDLK_F6:
+ return ImGuiKey_F6;
+ case SDLK_F7:
+ return ImGuiKey_F7;
+ case SDLK_F8:
+ return ImGuiKey_F8;
+ case SDLK_F9:
+ return ImGuiKey_F9;
+ case SDLK_F10:
+ return ImGuiKey_F10;
+ case SDLK_F11:
+ return ImGuiKey_F11;
+ case SDLK_F12:
+ return ImGuiKey_F12;
+ case SDLK_F13:
+ return ImGuiKey_F13;
+ case SDLK_F14:
+ return ImGuiKey_F14;
+ case SDLK_F15:
+ return ImGuiKey_F15;
+ case SDLK_F16:
+ return ImGuiKey_F16;
+ case SDLK_F17:
+ return ImGuiKey_F17;
+ case SDLK_F18:
+ return ImGuiKey_F18;
+ case SDLK_F19:
+ return ImGuiKey_F19;
+ case SDLK_F20:
+ return ImGuiKey_F20;
+ case SDLK_F21:
+ return ImGuiKey_F21;
+ case SDLK_F22:
+ return ImGuiKey_F22;
+ case SDLK_F23:
+ return ImGuiKey_F23;
+ case SDLK_F24:
+ return ImGuiKey_F24;
+ case SDLK_AC_BACK:
+ return ImGuiKey_AppBack;
+ case SDLK_AC_FORWARD:
+ return ImGuiKey_AppForward;
+ default:
+ break;
+ }
+ return ImGuiKey_None;
+}
+
+static void UpdateKeyModifiers(SDL_Keymod sdl_key_mods) {
+ ImGuiIO& io = ImGui::GetIO();
+ io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
+ io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
+ io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
+ io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
+}
+
+static ImGuiViewport* GetViewportForWindowId(SDL_WindowID window_id) {
+ SdlData* bd = GetBackendData();
+ return (window_id == bd->window_id) ? ImGui::GetMainViewport() : nullptr;
+}
+
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to
+// use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or
+// clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main
+// application, or clear/overwrite your copy of the keyboard data. Generally you may always pass all
+// inputs to dear imgui, and hide them from your application based on those two flags. If you have
+// multiple SDL events and some of them are not meant to be used by dear imgui, you may need to
+// filter events based on their windowID field.
+bool ProcessEvent(const SDL_Event* event) {
+ SdlData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr &&
+ "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
+ ImGuiIO& io = ImGui::GetIO();
+
+ switch (event->type) {
+ case SDL_EVENT_MOUSE_MOTION: {
+ if (GetViewportForWindowId(event->motion.windowID) == NULL)
+ return false;
+ ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
+ io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID
+ ? ImGuiMouseSource_TouchScreen
+ : ImGuiMouseSource_Mouse);
+ io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
+ return true;
+ }
+ case SDL_EVENT_MOUSE_WHEEL: {
+ if (GetViewportForWindowId(event->wheel.windowID) == NULL)
+ return false;
+ // IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x,
+ // (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
+ float wheel_x = -event->wheel.x;
+ float wheel_y = event->wheel.y;
+#ifdef __EMSCRIPTEN__
+ wheel_x /= 100.0f;
+#endif
+ io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID
+ ? ImGuiMouseSource_TouchScreen
+ : ImGuiMouseSource_Mouse);
+ io.AddMouseWheelEvent(wheel_x, wheel_y);
+ return true;
+ }
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP: {
+ if (GetViewportForWindowId(event->button.windowID) == NULL)
+ return false;
+ int mouse_button = -1;
+ if (event->button.button == SDL_BUTTON_LEFT) {
+ mouse_button = 0;
+ }
+ if (event->button.button == SDL_BUTTON_RIGHT) {
+ mouse_button = 1;
+ }
+ if (event->button.button == SDL_BUTTON_MIDDLE) {
+ mouse_button = 2;
+ }
+ if (event->button.button == SDL_BUTTON_X1) {
+ mouse_button = 3;
+ }
+ if (event->button.button == SDL_BUTTON_X2) {
+ mouse_button = 4;
+ }
+ if (mouse_button == -1)
+ break;
+ io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID
+ ? ImGuiMouseSource_TouchScreen
+ : ImGuiMouseSource_Mouse);
+ io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN));
+ bd->mouse_buttons_down = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)
+ ? (bd->mouse_buttons_down | (1 << mouse_button))
+ : (bd->mouse_buttons_down & ~(1 << mouse_button));
+ return true;
+ }
+ case SDL_EVENT_TEXT_INPUT: {
+ if (GetViewportForWindowId(event->text.windowID) == NULL)
+ return false;
+ io.AddInputCharactersUTF8(event->text.text);
+ return true;
+ }
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP: {
+ if (GetViewportForWindowId(event->key.windowID) == NULL)
+ return false;
+ // IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event->type ==
+ // SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event->key.key, event->key.scancode,
+ // event->key.mod);
+ UpdateKeyModifiers((SDL_Keymod)event->key.mod);
+ ImGuiKey key = KeyEventToImGuiKey(event->key.key, event->key.scancode);
+ io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
+ io.SetKeyEventNativeData(
+ key, event->key.key, event->key.scancode,
+ event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend
+ // uses SDLK_*** as indices to IsKeyXXX() functions.
+ return true;
+ }
+ case SDL_EVENT_WINDOW_MOUSE_ENTER: {
+ if (GetViewportForWindowId(event->window.windowID) == NULL)
+ return false;
+ bd->mouse_window_id = event->window.windowID;
+ bd->mouse_pending_leave_frame = 0;
+ return true;
+ }
+ // - In some cases, when detaching a window from main viewport SDL may send
+ // SDL_WINDOWEVENT_ENTER one frame too late,
+ // causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse
+ // position. This is why we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See
+ // issue #5012 for details.
+ // FIXME: Unconfirmed whether this is still needed with SDL3.
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE: {
+ if (GetViewportForWindowId(event->window.windowID) == NULL)
+ return false;
+ bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1;
+ return true;
+ }
+ case SDL_EVENT_WINDOW_FOCUS_GAINED:
+ case SDL_EVENT_WINDOW_FOCUS_LOST: {
+ if (GetViewportForWindowId(event->window.windowID) == NULL)
+ return false;
+ io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED);
+ return true;
+ }
+ case SDL_EVENT_GAMEPAD_ADDED:
+ case SDL_EVENT_GAMEPAD_REMOVED: {
+ bd->want_update_gamepads_list = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window) {
+ viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window);
+ viewport->PlatformHandleRaw = nullptr;
+#if defined(_WIN32) && !defined(__WINRT__)
+ viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(
+ SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
+#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
+ viewport->PlatformHandleRaw = SDL_GetPointerProperty(
+ SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
+#endif
+}
+
+bool Init(SDL_Window* window) {
+ ImGuiIO& io = ImGui::GetIO();
+ IMGUI_CHECKVERSION();
+ IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+
+ // Setup backend capabilities flags
+ SdlData* bd = IM_NEW(SdlData)();
+ io.BackendPlatformUserData = (void*)bd;
+ io.BackendPlatformName = "imgui_impl_sdl3_shadps4";
+ io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values
+ io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests
+ // (optional, rarely used)
+
+ bd->window = window;
+ bd->window_id = SDL_GetWindowID(window);
+
+ ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+ platform_io.Platform_SetClipboardTextFn = SetClipboardText;
+ platform_io.Platform_GetClipboardTextFn = GetClipboardText;
+ platform_io.Platform_SetImeDataFn = PlatformSetImeData;
+
+ // Gamepad handling
+ bd->gamepad_mode = ImGui_ImplSDL3_GamepadMode_AutoFirst;
+ bd->want_update_gamepads_list = true;
+
+ // Load mouse cursors
+#define CURSOR(left, right) \
+ bd->mouse_cursors[ImGuiMouseCursor_##left] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_##right)
+ CURSOR(Arrow, DEFAULT);
+ CURSOR(TextInput, TEXT);
+ CURSOR(ResizeAll, MOVE);
+ CURSOR(ResizeNS, NS_RESIZE);
+ CURSOR(ResizeEW, EW_RESIZE);
+ CURSOR(ResizeNESW, NESW_RESIZE);
+ CURSOR(ResizeNWSE, NWSE_RESIZE);
+ CURSOR(Hand, POINTER);
+ CURSOR(NotAllowed, NOT_ALLOWED);
+#undef CURSOR
+
+ // Set platform dependent data in viewport
+ // Our mouse update function expect PlatformHandle to be filled for the main viewport
+ ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+ SetupPlatformHandles(main_viewport, window);
+
+ // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't
+ // emit the event. Without this, when clicking to gain focus, our widgets wouldn't activate even
+ // though they showed as hovered. (This is unfortunately a global SDL setting, so enabling it
+ // might have a side-effect on your application. It is unlikely to make a difference, but if
+ // your app absolutely needs to ignore the initial on-focus click: you can ignore
+ // SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
+#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
+ SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+#endif
+
+ // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows
+ // (see #5710)
+#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
+ SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
+#endif
+
+ return true;
+}
+
+static void CloseGamepads();
+
+void Shutdown() {
+ SdlData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+ ImGuiIO& io = ImGui::GetIO();
+
+ if (bd->clipboard_text_data) {
+ SDL_free((void*)bd->clipboard_text_data);
+ }
+ for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
+ SDL_DestroyCursor(bd->mouse_cursors[cursor_n]);
+ CloseGamepads();
+
+ io.BackendPlatformName = nullptr;
+ io.BackendPlatformUserData = nullptr;
+ io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos |
+ ImGuiBackendFlags_HasGamepad);
+ IM_DELETE(bd);
+}
+
+static void UpdateMouseData() {
+ SdlData* bd = GetBackendData();
+ ImGuiIO& io = ImGui::GetIO();
+
+ // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused
+ // (below)
+ // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries
+ // shouldn't e.g. trigger other operations outside
+ SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE);
+ SDL_Window* focused_window = SDL_GetKeyboardFocus();
+ const bool is_app_focused = (bd->window == focused_window);
+
+ if (is_app_focused) {
+ // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when
+ // ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
+ if (io.WantSetMousePos)
+ SDL_WarpMouseInWindow(bd->window, io.MousePos.x, io.MousePos.y);
+
+ // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION
+ // already provides this when hovered or captured)
+ if (bd->mouse_buttons_down == 0) {
+ // Single-viewport mode: mouse position in client window coordinates (io.MousePos is
+ // (0,0) when the mouse is on the upper-left corner of the app window)
+ float mouse_x_global, mouse_y_global;
+ int window_x, window_y;
+ SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
+ SDL_GetWindowPosition(focused_window, &window_x, &window_y);
+ io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y);
+ }
+ }
+}
+
+static void UpdateMouseCursor() {
+ ImGuiIO& io = ImGui::GetIO();
+ if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+ return;
+ SdlData* bd = GetBackendData();
+
+ ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+ if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) {
+ // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+ SDL_HideCursor();
+ } else {
+ // Show OS mouse cursor
+ SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor]
+ ? bd->mouse_cursors[imgui_cursor]
+ : bd->mouse_cursors[ImGuiMouseCursor_Arrow];
+ if (bd->mouse_last_cursor != expected_cursor) {
+ SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
+ bd->mouse_last_cursor = expected_cursor;
+ }
+ SDL_ShowCursor();
+ }
+}
+
+static void CloseGamepads() {
+ SdlData* bd = GetBackendData();
+ if (bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual)
+ for (SDL_Gamepad* gamepad : bd->gamepads)
+ SDL_CloseGamepad(gamepad);
+ bd->gamepads.resize(0);
+}
+
+void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array,
+ int manual_gamepads_count) {
+ SdlData* bd = GetBackendData();
+ CloseGamepads();
+ if (mode == ImGui_ImplSDL3_GamepadMode_Manual) {
+ IM_ASSERT(manual_gamepads_array != nullptr && manual_gamepads_count > 0);
+ for (int n = 0; n < manual_gamepads_count; n++)
+ bd->gamepads.push_back(manual_gamepads_array[n]);
+ } else {
+ IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0);
+ bd->want_update_gamepads_list = true;
+ }
+ bd->gamepad_mode = mode;
+}
+
+static void UpdateGamepadButton(SdlData* bd, ImGuiIO& io, ImGuiKey key,
+ SDL_GamepadButton button_no) {
+ bool merged_value = false;
+ for (SDL_Gamepad* gamepad : bd->gamepads)
+ merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
+ io.AddKeyEvent(key, merged_value);
+}
+
+static inline float Saturate(float v) {
+ return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v;
+}
+static void UpdateGamepadAnalog(SdlData* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no,
+ float v0, float v1) {
+ float merged_value = 0.0f;
+ for (SDL_Gamepad* gamepad : bd->gamepads) {
+ float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0));
+ if (merged_value < vn)
+ merged_value = vn;
+ }
+ io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value);
+}
+
+static void UpdateGamepads() {
+ ImGuiIO& io = ImGui::GetIO();
+ SdlData* bd = GetBackendData();
+
+ // Update list of gamepads to use
+ if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
+ CloseGamepads();
+ int sdl_gamepads_count = 0;
+ const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count);
+ for (int n = 0; n < sdl_gamepads_count; n++)
+ if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) {
+ bd->gamepads.push_back(gamepad);
+ if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst)
+ break;
+ }
+ bd->want_update_gamepads_list = false;
+ }
+
+ // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
+ if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
+ return;
+ io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+ if (bd->gamepads.Size == 0)
+ return;
+ io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+
+ // Update gamepad inputs
+ const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value.
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft,
+ SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight,
+ SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp,
+ SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown,
+ SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK);
+ UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX,
+ -thumb_dead_zone, -32768);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX,
+ +thumb_dead_zone, +32767);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone,
+ -32768);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY,
+ +thumb_dead_zone, +32767);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX,
+ -thumb_dead_zone, -32768);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX,
+ +thumb_dead_zone, +32767);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone,
+ -32768);
+ UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY,
+ +thumb_dead_zone, +32767);
+}
+
+void NewFrame() {
+ SdlData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+ ImGuiIO& io = ImGui::GetIO();
+
+ // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
+ // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens
+ // in VMs and Emscripten, see #6189, #6114, #3644)
+ static Uint64 frequency = SDL_GetPerformanceFrequency();
+ Uint64 current_time = SDL_GetPerformanceCounter();
+ if (current_time <= bd->time)
+ current_time = bd->time + 1;
+ io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency)
+ : (float)(1.0f / 60.0f);
+ bd->time = current_time;
+
+ if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() &&
+ bd->mouse_buttons_down == 0) {
+ bd->mouse_window_id = 0;
+ bd->mouse_pending_leave_frame = 0;
+ io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+ }
+
+ UpdateMouseData();
+ UpdateMouseCursor();
+
+ // Update game controllers (if enabled and available)
+ UpdateGamepads();
+}
+
+void OnResize() {
+ SdlData* bd = GetBackendData();
+ ImGuiIO& io = ImGui::GetIO();
+
+ int w, h;
+ int display_w, display_h;
+ SDL_GetWindowSize(bd->window, &w, &h);
+ if (SDL_GetWindowFlags(bd->window) & SDL_WINDOW_MINIMIZED) {
+ w = h = 0;
+ }
+ SDL_GetWindowSizeInPixels(bd->window, &display_w, &display_h);
+ io.DisplaySize = ImVec2((float)w, (float)h);
+ if (w > 0 && h > 0) {
+ io.DisplayFramebufferScale = {(float)display_w / (float)w, (float)display_h / (float)h};
+ }
+}
+
+} // namespace ImGui::Sdl
diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h
new file mode 100644
index 000000000..59b1a6856
--- /dev/null
+++ b/src/imgui/renderer/imgui_impl_sdl3.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on imgui_impl_sdl3.h from Dear ImGui repository
+
+#pragma once
+
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Gamepad;
+typedef union SDL_Event SDL_Event;
+
+namespace ImGui::Sdl {
+
+bool Init(SDL_Window* window);
+void Shutdown();
+void NewFrame();
+bool ProcessEvent(const SDL_Event* event);
+void OnResize();
+
+// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad.
+// You may override this. When using manual mode, caller is responsible for opening/closing gamepad.
+enum GamepadMode {
+ ImGui_ImplSDL3_GamepadMode_AutoFirst,
+ ImGui_ImplSDL3_GamepadMode_AutoAll,
+ ImGui_ImplSDL3_GamepadMode_Manual
+};
+void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array = NULL,
+ int manual_gamepads_count = -1);
+
+}; // namespace ImGui::Sdl
diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp
new file mode 100644
index 000000000..2c1c135f7
--- /dev/null
+++ b/src/imgui/renderer/imgui_impl_vulkan.cpp
@@ -0,0 +1,1107 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
+
+#include
+#include
+
+#include "imgui_impl_vulkan.h"
+
+#ifndef IM_MAX
+#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B))
+#endif
+
+#define IDX_SIZE sizeof(ImDrawIdx)
+
+namespace ImGui::Vulkan {
+
+struct RenderBuffer {
+ vk::DeviceMemory buffer_memory{};
+ vk::DeviceSize buffer_size{};
+ vk::Buffer buffer{};
+};
+
+// Reusable buffers used for rendering 1 current in-flight frame, for RenderDrawData()
+struct FrameRenderBuffers {
+ RenderBuffer vertex;
+ RenderBuffer index;
+};
+
+// Each viewport will hold 1 WindowRenderBuffers
+struct WindowRenderBuffers {
+ uint32_t index{};
+ uint32_t count{};
+ std::vector frame_render_buffers{};
+};
+
+// Vulkan data
+struct VkData {
+ const InitInfo init_info;
+ vk::DeviceSize buffer_memory_alignment = 256;
+ vk::PipelineCreateFlags pipeline_create_flags{};
+ vk::DescriptorPool descriptor_pool{};
+ vk::DescriptorSetLayout descriptor_set_layout{};
+ vk::PipelineLayout pipeline_layout{};
+ vk::Pipeline pipeline{};
+ vk::ShaderModule shader_module_vert{};
+ vk::ShaderModule shader_module_frag{};
+
+ // Font data
+ vk::Sampler font_sampler{};
+ vk::DeviceMemory font_memory{};
+ vk::Image font_image{};
+ vk::ImageView font_view{};
+ vk::DescriptorSet font_descriptor_set{};
+ vk::CommandPool font_command_pool{};
+ vk::CommandBuffer font_command_buffer{};
+
+ // Render buffers
+ WindowRenderBuffers render_buffers{};
+
+ VkData(const InitInfo init_info) : init_info(init_info) {
+ render_buffers.count = init_info.image_count;
+ render_buffers.frame_render_buffers.resize(render_buffers.count);
+ }
+};
+
+//-----------------------------------------------------------------------------
+// SHADERS
+//-----------------------------------------------------------------------------
+
+// backends/vulkan/glsl_shader.vert, compiled with:
+// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert
+/*
+#version 450 core
+layout(location = 0) in vec2 aPos;
+layout(location = 1) in vec2 aUV;
+layout(location = 2) in vec4 aColor;
+layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc;
+
+out gl_PerVertex { vec4 gl_Position; };
+layout(location = 0) out struct { vec4 Color; vec2 UV; } Out;
+
+void main()
+{
+ Out.Color = aColor;
+ Out.UV = aUV;
+ gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1);
+}
+*/
+static uint32_t glsl_shader_vert_spv[] = {
+ 0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
+ 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
+ 0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000f, 0x00000015,
+ 0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d,
+ 0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43,
+ 0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f,
+ 0x00040005, 0x0000000f, 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005,
+ 0x00000019, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000019, 0x00000000,
+ 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, 0x00040005, 0x0000001c,
+ 0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074,
+ 0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001,
+ 0x61725475, 0x616c736e, 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b,
+ 0x0000001e, 0x00000000, 0x00040047, 0x0000000f, 0x0000001e, 0x00000002, 0x00040047, 0x00000015,
+ 0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, 0x00000000, 0x00030047,
+ 0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e,
+ 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008,
+ 0x00030047, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
+ 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040017,
+ 0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, 0x00000008, 0x00040020,
+ 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015,
+ 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020,
+ 0x0000000e, 0x00000001, 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020,
+ 0x00000011, 0x00000003, 0x00000007, 0x0004002b, 0x0000000c, 0x00000013, 0x00000001, 0x00040020,
+ 0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040020,
+ 0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a,
+ 0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014,
+ 0x0000001c, 0x00000001, 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f,
+ 0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, 0x00000020, 0x00000009, 0x00040020, 0x00000021,
+ 0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006,
+ 0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8,
+ 0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012,
+ 0x0000000b, 0x0000000d, 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016,
+ 0x00000015, 0x00050041, 0x00000017, 0x00000018, 0x0000000b, 0x00000013, 0x0003003e, 0x00000018,
+ 0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, 0x00000021, 0x00000022,
+ 0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008,
+ 0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013,
+ 0x0004003d, 0x00000008, 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024,
+ 0x00000026, 0x00050051, 0x00000006, 0x0000002a, 0x00000027, 0x00000000, 0x00050051, 0x00000006,
+ 0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, 0x0000002a, 0x0000002b,
+ 0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e,
+ 0x0000002d, 0x0000002c, 0x000100fd, 0x00010038};
+
+// backends/vulkan/glsl_shader.frag, compiled with:
+// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag
+/*
+#version 450 core
+layout(location = 0) out vec4 fColor;
+layout(set=0, binding=0) uniform sampler2D sTexture;
+layout(location = 0) in struct { vec4 Color; vec2 UV; } In;
+void main()
+{
+ fColor = In.Color * texture(sTexture, In.UV.st);
+}
+*/
+static uint32_t glsl_shader_frag_spv[] = {
+ 0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
+ 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
+ 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000d, 0x00030010,
+ 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d,
+ 0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000,
+ 0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001,
+ 0x00005655, 0x00030005, 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574,
+ 0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d, 0x0000001e,
+ 0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x00000021,
+ 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006,
+ 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003,
+ 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006,
+ 0x00000002, 0x0004001e, 0x0000000b, 0x00000007, 0x0000000a, 0x00040020, 0x0000000c, 0x00000001,
+ 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020,
+ 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001,
+ 0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000000, 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000,
+ 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x0000000e, 0x00000018,
+ 0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, 0x00000002, 0x00000004,
+ 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d,
+ 0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017,
+ 0x00000016, 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a,
+ 0x0000001b, 0x0000001a, 0x00050057, 0x00000007, 0x0000001c, 0x00000017, 0x0000001b, 0x00050085,
+ 0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x000100fd,
+ 0x00010038};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui
+// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single
+// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static VkData* GetBackendData() {
+ return ImGui::GetCurrentContext() ? (VkData*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+static uint32_t FindMemoryType(vk::MemoryPropertyFlags properties, uint32_t type_bits) {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+ const auto prop = v.physical_device.getMemoryProperties();
+ for (uint32_t i = 0; i < prop.memoryTypeCount; i++)
+ if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i))
+ return i;
+ return 0xFFFFFFFF; // Unable to find memoryType
+}
+
+template
+static T CheckVkResult(vk::ResultValue res) {
+ if (res.result == vk::Result::eSuccess) {
+ return res.value;
+ }
+ const VkData* bd = GetBackendData();
+ if (!bd) {
+ return res.value;
+ }
+ const InitInfo& v = bd->init_info;
+ if (v.check_vk_result_fn) {
+ v.check_vk_result_fn(res.result);
+ }
+ return res.value;
+}
+
+static void CheckVkErr(vk::Result res) {
+ if (res == vk::Result::eSuccess) {
+ return;
+ }
+ const VkData* bd = GetBackendData();
+ if (!bd) {
+ return;
+ }
+ const InitInfo& v = bd->init_info;
+ if (v.check_vk_result_fn) {
+ v.check_vk_result_fn(res);
+ }
+}
+
+// Same as IM_MEMALIGN(). 'alignment' must be a power of two.
+static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize alignment) {
+ return (size + alignment - 1) & ~(alignment - 1);
+}
+
+// Register a texture
+vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
+ vk::ImageLayout image_layout) {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+
+ // Create Descriptor Set:
+ vk::DescriptorSet descriptor_set;
+ {
+ vk::DescriptorSetAllocateInfo alloc_info{
+ .sType = vk::StructureType::eDescriptorSetAllocateInfo,
+ .descriptorPool = bd->descriptor_pool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &bd->descriptor_set_layout,
+ };
+ descriptor_set = CheckVkResult(v.device.allocateDescriptorSets(alloc_info)).front();
+ }
+
+ // Update the Descriptor Set:
+ {
+ vk::DescriptorImageInfo desc_image[1]{
+ {
+ .sampler = sampler,
+ .imageView = image_view,
+ .imageLayout = image_layout,
+ },
+ };
+ vk::WriteDescriptorSet write_desc[1]{
+ {
+ .sType = vk::StructureType::eWriteDescriptorSet,
+ .dstSet = descriptor_set,
+ .descriptorCount = 1,
+ .descriptorType = vk::DescriptorType::eCombinedImageSampler,
+ .pImageInfo = desc_image,
+ },
+ };
+ v.device.updateDescriptorSets({write_desc}, {});
+ }
+ return descriptor_set;
+}
+
+void RemoveTexture(vk::DescriptorSet descriptor_set) {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+ v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set});
+}
+
+static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) {
+ VkData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr);
+ const InitInfo& v = bd->init_info;
+ if (rb.buffer != VK_NULL_HANDLE) {
+ v.device.destroyBuffer(rb.buffer, v.allocator);
+ }
+ if (rb.buffer_memory != VK_NULL_HANDLE) {
+ v.device.freeMemory(rb.buffer_memory, v.allocator);
+ }
+
+ const vk::DeviceSize buffer_size_aligned =
+ AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment);
+ vk::BufferCreateInfo buffer_info{
+ .sType = vk::StructureType::eBufferCreateInfo,
+ .size = buffer_size_aligned,
+ .usage = usage,
+ .sharingMode = vk::SharingMode::eExclusive,
+ };
+ rb.buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
+
+ const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer);
+ bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
+ vk::MemoryAllocateInfo alloc_info{
+ .sType = vk::StructureType::eMemoryAllocateInfo,
+ .allocationSize = req.size,
+ .memoryTypeIndex =
+ FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
+ };
+ rb.buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
+
+ CheckVkErr(v.device.bindBufferMemory(rb.buffer, rb.buffer_memory, 0));
+ rb.buffer_size = buffer_size_aligned;
+}
+
+static void SetupRenderState(ImDrawData& draw_data, vk::Pipeline pipeline, vk::CommandBuffer cmdbuf,
+ FrameRenderBuffers& frb, int fb_width, int fb_height) {
+ VkData* bd = GetBackendData();
+
+ // Bind pipeline:
+ cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
+
+ // Bind Vertex And Index Buffer:
+ if (draw_data.TotalVtxCount > 0) {
+ vk::Buffer vertex_buffers[1] = {frb.vertex.buffer};
+ vk::DeviceSize vertex_offset[1] = {0};
+ cmdbuf.bindVertexBuffers(0, {vertex_buffers}, vertex_offset);
+ cmdbuf.bindIndexBuffer(frb.index.buffer, 0,
+ IDX_SIZE == 2 ? vk::IndexType::eUint16 : vk::IndexType::eUint32);
+ }
+
+ // Setup viewport:
+ {
+ vk::Viewport viewport{
+ .x = 0,
+ .y = 0,
+ .width = (float)fb_width,
+ .height = (float)fb_height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ cmdbuf.setViewport(0, {viewport});
+ }
+
+ // Setup scale and translation:
+ // Our visible imgui space lies from draw_data->DisplayPps (top left) to
+ // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single
+ // viewport apps.
+ {
+ float scale[2];
+ scale[0] = 2.0f / draw_data.DisplaySize.x;
+ scale[1] = 2.0f / draw_data.DisplaySize.y;
+ float translate[2];
+ translate[0] = -1.0f - draw_data.DisplayPos.x * scale[0];
+ translate[1] = -1.0f - draw_data.DisplayPos.y * scale[1];
+ cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex,
+ sizeof(float) * 0, sizeof(float) * 2, scale);
+ cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex,
+ sizeof(float) * 2, sizeof(float) * 2, translate);
+ }
+}
+
+// Render function
+void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
+ vk::Pipeline pipeline) {
+ // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates !=
+ // framebuffer coordinates)
+ int fb_width = (int)(draw_data.DisplaySize.x * draw_data.FramebufferScale.x);
+ int fb_height = (int)(draw_data.DisplaySize.y * draw_data.FramebufferScale.y);
+ if (fb_width <= 0 || fb_height <= 0) {
+ return;
+ }
+
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+ if (pipeline == VK_NULL_HANDLE) {
+ pipeline = bd->pipeline;
+ }
+
+ // Allocate array to store enough vertex/index buffers
+ WindowRenderBuffers& wrb = bd->render_buffers;
+ wrb.index = (wrb.index + 1) % wrb.count;
+ FrameRenderBuffers& frb = wrb.frame_render_buffers[wrb.index];
+
+ if (draw_data.TotalVtxCount > 0) {
+ // Create or resize the vertex/index buffers
+ size_t vertex_size = AlignBufferSize(draw_data.TotalVtxCount * sizeof(ImDrawVert),
+ bd->buffer_memory_alignment);
+ size_t index_size =
+ AlignBufferSize(draw_data.TotalIdxCount * IDX_SIZE, bd->buffer_memory_alignment);
+ if (frb.vertex.buffer == VK_NULL_HANDLE || frb.vertex.buffer_size < vertex_size) {
+ CreateOrResizeBuffer(frb.vertex, vertex_size, vk::BufferUsageFlagBits::eVertexBuffer);
+ }
+ if (frb.index.buffer == VK_NULL_HANDLE || frb.index.buffer_size < index_size) {
+ CreateOrResizeBuffer(frb.index, index_size, vk::BufferUsageFlagBits::eIndexBuffer);
+ }
+
+ // Upload vertex/index data into a single contiguous GPU buffer
+ ImDrawVert* vtx_dst = nullptr;
+ ImDrawIdx* idx_dst = nullptr;
+ vtx_dst = (ImDrawVert*)CheckVkResult(
+ v.device.mapMemory(frb.vertex.buffer_memory, 0, vertex_size, vk::MemoryMapFlags{}));
+ idx_dst = (ImDrawIdx*)CheckVkResult(
+ v.device.mapMemory(frb.index.buffer_memory, 0, index_size, vk::MemoryMapFlags{}));
+ for (int n = 0; n < draw_data.CmdListsCount; n++) {
+ const ImDrawList* cmd_list = draw_data.CmdLists[n];
+ memcpy(vtx_dst, cmd_list->VtxBuffer.Data,
+ cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+ memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * IDX_SIZE);
+ vtx_dst += cmd_list->VtxBuffer.Size;
+ idx_dst += cmd_list->IdxBuffer.Size;
+ }
+ vk::MappedMemoryRange range[2]{
+ {
+ .sType = vk::StructureType::eMappedMemoryRange,
+ .memory = frb.vertex.buffer_memory,
+ .size = VK_WHOLE_SIZE,
+ },
+ {
+ .sType = vk::StructureType::eMappedMemoryRange,
+ .memory = frb.index.buffer_memory,
+ .size = VK_WHOLE_SIZE,
+ },
+ };
+ CheckVkErr(v.device.flushMappedMemoryRanges({range}));
+ v.device.unmapMemory(frb.vertex.buffer_memory);
+ v.device.unmapMemory(frb.index.buffer_memory);
+ }
+
+ // Setup desired Vulkan state
+ SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height);
+
+ // Will project scissor/clipping rectangles into framebuffer space
+
+ // (0,0) unless using multi-viewports
+ ImVec2 clip_off = draw_data.DisplayPos;
+ // (1,1) unless using retina display which are often (2,2)
+ ImVec2 clip_scale = draw_data.FramebufferScale;
+
+ // Render command lists
+ // (Because we merged all buffers into a single one, we maintain our own offset into them)
+ int global_vtx_offset = 0;
+ int global_idx_offset = 0;
+ for (int n = 0; n < draw_data.CmdListsCount; n++) {
+ const ImDrawList* cmd_list = draw_data.CmdLists[n];
+ for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
+ const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+ if (pcmd->UserCallback != nullptr) {
+ // User callback, registered via ImDrawList::AddCallback()
+ // (ImDrawCallback_ResetRenderState is a special callback value used by the user to
+ // request the renderer to reset render state.)
+ if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) {
+ SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height);
+ } else {
+ pcmd->UserCallback(cmd_list, pcmd);
+ }
+ } else {
+ // Project scissor/clipping rectangles into framebuffer space
+ ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x,
+ (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+ ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
+ (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+
+ // Clamp to viewport as vk::CmdSetScissor() won't accept values that are off bounds
+ if (clip_min.x < 0.0f) {
+ clip_min.x = 0.0f;
+ }
+ if (clip_min.y < 0.0f) {
+ clip_min.y = 0.0f;
+ }
+ if (clip_max.x > fb_width) {
+ clip_max.x = (float)fb_width;
+ }
+ if (clip_max.y > fb_height) {
+ clip_max.y = (float)fb_height;
+ }
+ if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+ continue;
+
+ // Apply scissor/clipping rectangle
+ vk::Rect2D scissor{
+ .offset{
+ .x = (int32_t)(clip_min.x),
+ .y = (int32_t)(clip_min.y),
+ },
+ .extent{
+ .width = (uint32_t)(clip_max.x - clip_min.x),
+ .height = (uint32_t)(clip_max.y - clip_min.y),
+ },
+ };
+ command_buffer.setScissor(0, 1, &scissor);
+
+ // Bind DescriptorSet with font or user texture
+ vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId};
+ if (sizeof(ImTextureID) < sizeof(ImU64)) {
+ // We don't support texture switches if ImTextureID hasn't been redefined to be
+ // 64-bit. Do a flaky check that other textures haven't been used.
+ IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set);
+ desc_set[0] = bd->font_descriptor_set;
+ }
+ command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
+ bd->pipeline_layout, 0, {desc_set}, {});
+
+ // Draw
+ command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset,
+ pcmd->VtxOffset + global_vtx_offset, 0);
+ }
+ }
+ global_idx_offset += cmd_list->IdxBuffer.Size;
+ global_vtx_offset += cmd_list->VtxBuffer.Size;
+ }
+ // vk::Rect2D scissor = {{0, 0}, {(uint32_t)fb_width, (uint32_t)fb_height}};
+ // command_buffer.setScissor(0, 1, &scissor);
+}
+
+static void DestroyFontsTexture();
+
+static bool CreateFontsTexture() {
+ ImGuiIO& io = ImGui::GetIO();
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+
+ // Destroy existing texture (if any)
+ if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) {
+ CheckVkErr(v.queue.waitIdle());
+ DestroyFontsTexture();
+ }
+
+ // Create command pool/buffer
+ if (bd->font_command_pool == VK_NULL_HANDLE) {
+ vk::CommandPoolCreateInfo info{
+ .sType = vk::StructureType::eCommandPoolCreateInfo,
+ .flags = vk::CommandPoolCreateFlags{},
+ .queueFamilyIndex = v.queue_family,
+ };
+ bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
+ }
+ if (bd->font_command_buffer == VK_NULL_HANDLE) {
+ vk::CommandBufferAllocateInfo info{
+ .sType = vk::StructureType::eCommandBufferAllocateInfo,
+ .commandPool = bd->font_command_pool,
+ .commandBufferCount = 1,
+ };
+ bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
+ }
+
+ // Start command buffer
+ {
+ CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{}));
+ vk::CommandBufferBeginInfo begin_info{};
+ begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
+ begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
+ CheckVkErr(bd->font_command_buffer.begin(&begin_info));
+ }
+
+ unsigned char* pixels;
+ int width, height;
+ io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+ size_t upload_size = width * height * 4 * sizeof(char);
+
+ // Create the Image:
+ {
+ vk::ImageCreateInfo info{
+ .sType = vk::StructureType::eImageCreateInfo,
+ .imageType = vk::ImageType::e2D,
+ .format = vk::Format::eR8G8B8A8Unorm,
+ .extent{
+ .width = static_cast(width),
+ .height = static_cast(height),
+ .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = vk::SampleCountFlagBits::e1,
+ .tiling = vk::ImageTiling::eOptimal,
+ .usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
+ .sharingMode = vk::SharingMode::eExclusive,
+ .initialLayout = vk::ImageLayout::eUndefined,
+ };
+ bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator));
+ vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image);
+ vk::MemoryAllocateInfo alloc_info{
+ .sType = vk::StructureType::eMemoryAllocateInfo,
+ .allocationSize = IM_MAX(v.min_allocation_size, req.size),
+ .memoryTypeIndex =
+ FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
+ };
+ bd->font_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
+ CheckVkErr(v.device.bindImageMemory(bd->font_image, bd->font_memory, 0));
+ }
+
+ // Create the Image View:
+ {
+ vk::ImageViewCreateInfo info{
+ .sType = vk::StructureType::eImageViewCreateInfo,
+ .image = bd->font_image,
+ .viewType = vk::ImageViewType::e2D,
+ .format = vk::Format::eR8G8B8A8Unorm,
+ .subresourceRange{
+ .aspectMask = vk::ImageAspectFlagBits::eColor,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ bd->font_view = CheckVkResult(v.device.createImageView(info, v.allocator));
+ }
+
+ // Create the Descriptor Set:
+ bd->font_descriptor_set =
+ AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
+
+ // Create the Upload Buffer:
+ vk::DeviceMemory upload_buffer_memory{};
+ vk::Buffer upload_buffer{};
+ {
+ vk::BufferCreateInfo buffer_info{
+ .sType = vk::StructureType::eBufferCreateInfo,
+ .size = upload_size,
+ .usage = vk::BufferUsageFlagBits::eTransferSrc,
+ .sharingMode = vk::SharingMode::eExclusive,
+ };
+ upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
+ vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer);
+ bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
+ vk::MemoryAllocateInfo alloc_info{
+ .sType = vk::StructureType::eMemoryAllocateInfo,
+ .allocationSize = IM_MAX(v.min_allocation_size, req.size),
+ .memoryTypeIndex =
+ FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
+ };
+ upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
+ CheckVkErr(v.device.bindBufferMemory(upload_buffer, upload_buffer_memory, 0));
+ }
+
+ // Upload to Buffer:
+ {
+ char* map = (char*)CheckVkResult(v.device.mapMemory(upload_buffer_memory, 0, upload_size));
+ memcpy(map, pixels, upload_size);
+ vk::MappedMemoryRange range[1]{
+ {
+ .sType = vk::StructureType::eMappedMemoryRange,
+ .memory = upload_buffer_memory,
+ .size = upload_size,
+ },
+ };
+ CheckVkErr(v.device.flushMappedMemoryRanges({range}));
+ v.device.unmapMemory(upload_buffer_memory);
+ }
+
+ // Copy to Image:
+ {
+ vk::ImageMemoryBarrier copy_barrier[1]{
+ {
+ .sType = vk::StructureType::eImageMemoryBarrier,
+ .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
+ .oldLayout = vk::ImageLayout::eUndefined,
+ .newLayout = vk::ImageLayout::eTransferDstOptimal,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = bd->font_image,
+ .subresourceRange{
+ .aspectMask = vk::ImageAspectFlagBits::eColor,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ },
+ };
+ bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost,
+ vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
+ {copy_barrier});
+
+ vk::BufferImageCopy region{
+ .imageSubresource{
+ .aspectMask = vk::ImageAspectFlagBits::eColor,
+ .layerCount = 1,
+ },
+ .imageExtent{
+ .width = static_cast(width),
+ .height = static_cast(height),
+ .depth = 1,
+ },
+ };
+ bd->font_command_buffer.copyBufferToImage(upload_buffer, bd->font_image,
+ vk::ImageLayout::eTransferDstOptimal, {region});
+
+ vk::ImageMemoryBarrier use_barrier[1]{{
+ .sType = vk::StructureType::eImageMemoryBarrier,
+ .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
+ .dstAccessMask = vk::AccessFlagBits::eShaderRead,
+ .oldLayout = vk::ImageLayout::eTransferDstOptimal,
+ .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = bd->font_image,
+ .subresourceRange{
+ .aspectMask = vk::ImageAspectFlagBits::eColor,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ }};
+ bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
+ vk::PipelineStageFlagBits::eFragmentShader, {}, {},
+ {}, {use_barrier});
+ }
+
+ // Store our identifier
+ io.Fonts->SetTexID((ImTextureID)bd->font_descriptor_set);
+
+ // End command buffer
+ vk::SubmitInfo end_info = {};
+ end_info.sType = vk::StructureType::eSubmitInfo;
+ end_info.commandBufferCount = 1;
+ end_info.pCommandBuffers = &bd->font_command_buffer;
+ CheckVkErr(bd->font_command_buffer.end());
+ CheckVkErr(v.queue.submit({end_info}));
+
+ CheckVkErr(v.queue.waitIdle());
+
+ v.device.destroyBuffer(upload_buffer, v.allocator);
+ v.device.freeMemory(upload_buffer_memory, v.allocator);
+
+ return true;
+}
+
+// You probably never need to call this, as it is called by CreateFontsTexture()
+// and Shutdown().
+static void DestroyFontsTexture() {
+ ImGuiIO& io = ImGui::GetIO();
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+
+ if (bd->font_descriptor_set) {
+ RemoveTexture(bd->font_descriptor_set);
+ bd->font_descriptor_set = VK_NULL_HANDLE;
+ io.Fonts->SetTexID(nullptr);
+ }
+
+ if (bd->font_view) {
+ v.device.destroyImageView(bd->font_view, v.allocator);
+ bd->font_view = VK_NULL_HANDLE;
+ }
+ if (bd->font_image) {
+ v.device.destroyImage(bd->font_image, v.allocator);
+ bd->font_image = VK_NULL_HANDLE;
+ }
+ if (bd->font_memory) {
+ v.device.freeMemory(bd->font_memory, v.allocator);
+ bd->font_memory = VK_NULL_HANDLE;
+ }
+}
+
+static void DestroyFrameRenderBuffers(vk::Device device, RenderBuffer& rb,
+ const vk::AllocationCallbacks* allocator) {
+ if (rb.buffer) {
+ device.destroyBuffer(rb.buffer, allocator);
+ rb.buffer = VK_NULL_HANDLE;
+ }
+ if (rb.buffer_memory) {
+ device.freeMemory(rb.buffer_memory, allocator);
+ rb.buffer_memory = VK_NULL_HANDLE;
+ }
+ rb.buffer_size = 0;
+}
+
+static void DestroyWindowRenderBuffers(vk::Device device, WindowRenderBuffers& buffers,
+ const vk::AllocationCallbacks* allocator) {
+ for (uint32_t n = 0; n < buffers.count; n++) {
+ auto& frb = buffers.frame_render_buffers[n];
+ DestroyFrameRenderBuffers(device, frb.index, allocator);
+ DestroyFrameRenderBuffers(device, frb.vertex, allocator);
+ }
+ buffers = {};
+}
+
+static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks* allocator) {
+ // Create the shader modules
+ VkData* bd = GetBackendData();
+ if (bd->shader_module_vert == VK_NULL_HANDLE) {
+ vk::ShaderModuleCreateInfo vert_info{
+ .sType = vk::StructureType::eShaderModuleCreateInfo,
+ .codeSize = sizeof(glsl_shader_vert_spv),
+ .pCode = (uint32_t*)glsl_shader_vert_spv,
+ };
+ bd->shader_module_vert = CheckVkResult(device.createShaderModule(vert_info, allocator));
+ }
+ if (bd->shader_module_frag == VK_NULL_HANDLE) {
+ vk::ShaderModuleCreateInfo frag_info{
+ .sType = vk::StructureType::eShaderModuleCreateInfo,
+ .codeSize = sizeof(glsl_shader_frag_spv),
+ .pCode = (uint32_t*)glsl_shader_frag_spv,
+ };
+ bd->shader_module_frag = CheckVkResult(device.createShaderModule(frag_info, allocator));
+ }
+}
+
+static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* allocator,
+ vk::PipelineCache pipeline_cache, vk::RenderPass render_pass,
+ vk::Pipeline* pipeline, uint32_t subpass) {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+
+ CreateShaderModules(device, allocator);
+
+ vk::PipelineShaderStageCreateInfo stage[2]{
+ {
+ .sType = vk::StructureType::ePipelineShaderStageCreateInfo,
+ .stage = vk::ShaderStageFlagBits::eVertex,
+ .module = bd->shader_module_vert,
+ .pName = "main",
+ },
+ {
+ .sType = vk::StructureType::ePipelineShaderStageCreateInfo,
+ .stage = vk::ShaderStageFlagBits::eFragment,
+ .module = bd->shader_module_frag,
+ .pName = "main",
+ },
+ };
+
+ vk::VertexInputBindingDescription binding_desc[1]{
+ {
+ .stride = sizeof(ImDrawVert),
+ .inputRate = vk::VertexInputRate::eVertex,
+ },
+ };
+
+ vk::VertexInputAttributeDescription attribute_desc[3]{
+ {
+ .location = 0,
+ .binding = binding_desc[0].binding,
+ .format = vk::Format::eR32G32Sfloat,
+ .offset = offsetof(ImDrawVert, pos),
+ },
+ {
+ .location = 1,
+ .binding = binding_desc[0].binding,
+ .format = vk::Format::eR32G32Sfloat,
+ .offset = offsetof(ImDrawVert, uv),
+ },
+ {
+ .location = 2,
+ .binding = binding_desc[0].binding,
+ .format = vk::Format::eR8G8B8A8Unorm,
+ .offset = offsetof(ImDrawVert, col),
+ },
+ };
+
+ vk::PipelineVertexInputStateCreateInfo vertex_info{
+ .sType = vk::StructureType::ePipelineVertexInputStateCreateInfo,
+ .vertexBindingDescriptionCount = 1,
+ .pVertexBindingDescriptions = binding_desc,
+ .vertexAttributeDescriptionCount = 3,
+ .pVertexAttributeDescriptions = attribute_desc,
+ };
+
+ vk::PipelineInputAssemblyStateCreateInfo ia_info{
+ .sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo,
+ .topology = vk::PrimitiveTopology::eTriangleList,
+ };
+
+ vk::PipelineViewportStateCreateInfo viewport_info{
+ .sType = vk::StructureType::ePipelineViewportStateCreateInfo,
+ .viewportCount = 1,
+ .scissorCount = 1,
+ };
+
+ vk::PipelineRasterizationStateCreateInfo raster_info{
+ .sType = vk::StructureType::ePipelineRasterizationStateCreateInfo,
+ .polygonMode = vk::PolygonMode::eFill,
+ .cullMode = vk::CullModeFlagBits::eNone,
+ .frontFace = vk::FrontFace::eCounterClockwise,
+ .lineWidth = 1.0f,
+ };
+
+ vk::PipelineMultisampleStateCreateInfo ms_info{
+ .sType = vk::StructureType::ePipelineMultisampleStateCreateInfo,
+ .rasterizationSamples = vk::SampleCountFlagBits::e1,
+ };
+
+ vk::PipelineColorBlendAttachmentState color_attachment[1]{
+ {
+ .blendEnable = VK_TRUE,
+ .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
+ .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
+ .colorBlendOp = vk::BlendOp::eAdd,
+ .srcAlphaBlendFactor = vk::BlendFactor::eOne,
+ .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
+ .alphaBlendOp = vk::BlendOp::eAdd,
+ .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
+ vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
+ },
+ };
+
+ vk::PipelineDepthStencilStateCreateInfo depth_info{
+ .sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo,
+ };
+
+ vk::PipelineColorBlendStateCreateInfo blend_info{
+ .sType = vk::StructureType::ePipelineColorBlendStateCreateInfo,
+ .attachmentCount = 1,
+ .pAttachments = color_attachment,
+ };
+
+ vk::DynamicState dynamic_states[2]{
+ vk::DynamicState::eViewport,
+ vk::DynamicState::eScissor,
+ };
+ vk::PipelineDynamicStateCreateInfo dynamic_state{
+ .sType = vk::StructureType::ePipelineDynamicStateCreateInfo,
+ .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states),
+ .pDynamicStates = dynamic_states,
+ };
+
+ vk::GraphicsPipelineCreateInfo info{
+ .sType = vk::StructureType::eGraphicsPipelineCreateInfo,
+ .pNext = &v.pipeline_rendering_create_info,
+ .flags = bd->pipeline_create_flags,
+ .stageCount = 2,
+ .pStages = stage,
+ .pVertexInputState = &vertex_info,
+ .pInputAssemblyState = &ia_info,
+ .pViewportState = &viewport_info,
+ .pRasterizationState = &raster_info,
+ .pMultisampleState = &ms_info,
+ .pDepthStencilState = &depth_info,
+ .pColorBlendState = &blend_info,
+ .pDynamicState = &dynamic_state,
+ .layout = bd->pipeline_layout,
+ .renderPass = render_pass,
+ .subpass = subpass,
+ };
+
+ *pipeline =
+ CheckVkResult(device.createGraphicsPipelines(pipeline_cache, {info}, allocator)).front();
+}
+
+bool CreateDeviceObjects() {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+ vk::Result err;
+
+ if (!bd->descriptor_pool) {
+ // large enough descriptor pool
+ vk::DescriptorPoolSize pool_sizes[]{
+ {vk::DescriptorType::eSampler, 1000},
+ {vk::DescriptorType::eCombinedImageSampler, 1000},
+ {vk::DescriptorType::eSampledImage, 1000},
+ {vk::DescriptorType::eStorageImage, 1000},
+ {vk::DescriptorType::eUniformTexelBuffer, 1000},
+ {vk::DescriptorType::eStorageTexelBuffer, 1000},
+ {vk::DescriptorType::eUniformBuffer, 1000},
+ {vk::DescriptorType::eStorageBuffer, 1000},
+ {vk::DescriptorType::eUniformBufferDynamic, 1000},
+ {vk::DescriptorType::eStorageBufferDynamic, 1000},
+ {vk::DescriptorType::eInputAttachment, 1000},
+ };
+
+ vk::DescriptorPoolCreateInfo pool_info{
+ .sType = vk::StructureType::eDescriptorPoolCreateInfo,
+ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
+ .maxSets = 1000,
+ .poolSizeCount = std::size(pool_sizes),
+ .pPoolSizes = pool_sizes,
+ };
+
+ bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info));
+ }
+
+ if (!bd->font_sampler) {
+ // Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
+ // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
+ // point/nearest sampling.
+ vk::SamplerCreateInfo info{
+ .sType = vk::StructureType::eSamplerCreateInfo,
+ .magFilter = vk::Filter::eLinear,
+ .minFilter = vk::Filter::eLinear,
+ .mipmapMode = vk::SamplerMipmapMode::eLinear,
+ .addressModeU = vk::SamplerAddressMode::eRepeat,
+ .addressModeV = vk::SamplerAddressMode::eRepeat,
+ .addressModeW = vk::SamplerAddressMode::eRepeat,
+ .maxAnisotropy = 1.0f,
+ .minLod = -1000,
+ .maxLod = 1000,
+ };
+ bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
+ }
+
+ if (!bd->descriptor_set_layout) {
+ vk::DescriptorSetLayoutBinding binding[1]{
+ {
+ .descriptorType = vk::DescriptorType::eCombinedImageSampler,
+ .descriptorCount = 1,
+ .stageFlags = vk::ShaderStageFlagBits::eFragment,
+ },
+ };
+ vk::DescriptorSetLayoutCreateInfo info{
+ .sType = vk::StructureType::eDescriptorSetLayoutCreateInfo,
+ .bindingCount = 1,
+ .pBindings = binding,
+ };
+ bd->descriptor_set_layout =
+ CheckVkResult(v.device.createDescriptorSetLayout(info, v.allocator));
+ }
+
+ if (!bd->pipeline_layout) {
+ // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection
+ // matrix
+ vk::PushConstantRange push_constants[1]{
+ {
+ .stageFlags = vk::ShaderStageFlagBits::eVertex,
+ .offset = sizeof(float) * 0,
+ .size = sizeof(float) * 4,
+ },
+ };
+ vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout};
+ vk::PipelineLayoutCreateInfo layout_info{
+ .sType = vk::StructureType::ePipelineLayoutCreateInfo,
+ .setLayoutCount = 1,
+ .pSetLayouts = set_layout,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = push_constants,
+ };
+ bd->pipeline_layout =
+ CheckVkResult(v.device.createPipelineLayout(layout_info, v.allocator));
+ }
+
+ CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass);
+
+ return true;
+}
+
+void ImGuiImplVulkanDestroyDeviceObjects() {
+ VkData* bd = GetBackendData();
+ const InitInfo& v = bd->init_info;
+ DestroyWindowRenderBuffers(v.device, bd->render_buffers, v.allocator);
+ DestroyFontsTexture();
+
+ if (bd->font_command_buffer) {
+ v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer});
+ bd->font_command_buffer = VK_NULL_HANDLE;
+ }
+ if (bd->font_command_pool) {
+ v.device.destroyCommandPool(bd->font_command_pool, v.allocator);
+ bd->font_command_pool = VK_NULL_HANDLE;
+ }
+ if (bd->shader_module_vert) {
+ v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
+ bd->shader_module_vert = VK_NULL_HANDLE;
+ }
+ if (bd->shader_module_frag) {
+ v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
+ bd->shader_module_frag = VK_NULL_HANDLE;
+ }
+ if (bd->font_sampler) {
+ v.device.destroySampler(bd->font_sampler, v.allocator);
+ bd->font_sampler = VK_NULL_HANDLE;
+ }
+ if (bd->descriptor_set_layout) {
+ v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
+ bd->descriptor_set_layout = VK_NULL_HANDLE;
+ }
+ if (bd->pipeline_layout) {
+ v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator);
+ bd->pipeline_layout = VK_NULL_HANDLE;
+ }
+ if (bd->pipeline) {
+ v.device.destroyPipeline(bd->pipeline, v.allocator);
+ bd->pipeline = VK_NULL_HANDLE;
+ }
+}
+
+bool Init(InitInfo info) {
+
+ IM_ASSERT(info.instance != VK_NULL_HANDLE);
+ IM_ASSERT(info.physical_device != VK_NULL_HANDLE);
+ IM_ASSERT(info.device != VK_NULL_HANDLE);
+ IM_ASSERT(info.image_count >= 2);
+
+ ImGuiIO& io = ImGui::GetIO();
+ IMGUI_CHECKVERSION();
+ IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+ // Setup backend capabilities flags
+ auto* bd = IM_NEW(VkData)(info);
+ io.BackendRendererUserData = (void*)bd;
+ io.BackendRendererName = "imgui_impl_vulkan_shadps4";
+ // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+ io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
+
+ CreateDeviceObjects();
+ CreateFontsTexture();
+
+ return true;
+}
+
+void Shutdown() {
+ VkData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+ ImGuiIO& io = ImGui::GetIO();
+
+ ImGuiImplVulkanDestroyDeviceObjects();
+ io.BackendRendererName = nullptr;
+ io.BackendRendererUserData = nullptr;
+ io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+ IM_DELETE(bd);
+}
+
+void NewFrame() {
+ VkData* bd = GetBackendData();
+ IM_ASSERT(bd != nullptr &&
+ "Context or backend not initialized! Did you call ImGuiImplVulkanInit()?");
+
+ if (!bd->font_descriptor_set)
+ CreateFontsTexture();
+}
+
+} // namespace ImGui::Vulkan
diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h
new file mode 100644
index 000000000..e68b8723f
--- /dev/null
+++ b/src/imgui/renderer/imgui_impl_vulkan.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on imgui_impl_vulkan.h from Dear ImGui repository
+
+#pragma once
+
+#define VULKAN_HPP_NO_EXCEPTIONS
+#include "video_core/renderer_vulkan/vk_common.h"
+
+struct ImDrawData;
+
+namespace ImGui::Vulkan {
+
+struct InitInfo {
+ vk::Instance instance;
+ vk::PhysicalDevice physical_device;
+ vk::Device device;
+ uint32_t queue_family;
+ vk::Queue queue;
+ uint32_t image_count; // >= 2
+ vk::DeviceSize min_allocation_size; // Minimum allocation size
+ vk::PipelineCache pipeline_cache;
+ uint32_t subpass;
+ vk::PipelineRenderingCreateInfoKHR pipeline_rendering_create_info;
+
+ // (Optional) Allocation, Logging
+ const vk::AllocationCallbacks* allocator{};
+ void (*check_vk_result_fn)(vk::Result err);
+};
+
+vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
+ vk::ImageLayout image_layout);
+
+void RemoveTexture(vk::DescriptorSet descriptor_set);
+
+bool Init(InitInfo info);
+void Shutdown();
+void NewFrame();
+void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
+ vk::Pipeline pipeline = VK_NULL_HANDLE);
+
+} // namespace ImGui::Vulkan
\ No newline at end of file
diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp
index 1c30f7e92..0b3dc3ce9 100644
--- a/src/qt_gui/cheats_patches.cpp
+++ b/src/qt_gui/cheats_patches.cpp
@@ -669,105 +669,86 @@ void CheatsPatches::populateFileListPatches() {
void CheatsPatches::downloadPatches(const QString repository, const bool showMessageBox) {
QString url;
if (repository == "GoldHEN") {
- url = "https://github.com/illusion0001/PS4-PS5-Game-Patch/tree/main/"
- "patches/xml";
+ url = "https://api.github.com/repos/illusion0001/PS4-PS5-Game-Patch/contents/patches/xml";
}
if (repository == "shadPS4") {
- url = "https://github.com/shadps4-emu/ps4_cheats/tree/main/"
- "PATCHES";
+ url = "https://api.github.com/repos/shadps4-emu/ps4_cheats/contents/PATCHES";
}
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
QNetworkRequest request(url);
+ request.setRawHeader("Accept", "application/vnd.github.v3+json");
QNetworkReply* reply = manager->get(request);
- connect(reply, &QNetworkReply::finished, [=, this]() {
+ connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
- QByteArray htmlData = reply->readAll();
+ QByteArray jsonData = reply->readAll();
reply->deleteLater();
- // Parsear HTML e extrair JSON usando QRegularExpression
- QString htmlString = QString::fromUtf8(htmlData);
- QRegularExpression jsonRegex(
- R"()");
- QRegularExpressionMatch match = jsonRegex.match(htmlString);
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
+ QJsonArray itemsArray = jsonDoc.array();
- if (match.hasMatch()) {
- QByteArray jsonData = match.captured(1).toUtf8();
- QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
- QJsonObject jsonObj = jsonDoc.object();
- QJsonArray itemsArray =
- jsonObj["payload"].toObject()["tree"].toObject()["items"].toArray();
-
- QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
- QString fullPath = dir.filePath(repository);
- if (!dir.exists(fullPath)) {
- dir.mkpath(fullPath);
- }
- dir.setPath(fullPath);
-
- foreach (const QJsonValue& value, itemsArray) {
- QJsonObject fileObj = value.toObject();
- QString fileName = fileObj["name"].toString();
- QString filePath = fileObj["path"].toString();
-
- if (fileName.endsWith(".xml")) {
- QString fileUrl;
- if (repository == "GoldHEN") {
- fileUrl = QString("https://raw.githubusercontent.com/illusion0001/"
- "PS4-PS5-Game-Patch/main/%1")
- .arg(filePath);
- }
- if (repository == "shadPS4") {
- fileUrl = QString("https://raw.githubusercontent.com/shadps4-emu/"
- "ps4_cheats/main/%1")
- .arg(filePath);
- }
- QNetworkRequest fileRequest(fileUrl);
- QNetworkReply* fileReply = manager->get(fileRequest);
-
- connect(fileReply, &QNetworkReply::finished, [=, this]() {
- if (fileReply->error() == QNetworkReply::NoError) {
- QByteArray fileData = fileReply->readAll();
- QFile localFile(dir.filePath(fileName));
- if (localFile.open(QIODevice::WriteOnly)) {
- localFile.write(fileData);
- localFile.close();
- } else {
- if (showMessageBox) {
- QMessageBox::warning(
- this, tr("Error"),
- QString(tr("Failed to save:") + "\n%1").arg(fileName));
- }
- }
- } else {
- if (showMessageBox) {
- QMessageBox::warning(
- this, tr("Error"),
- QString(tr("Failed to download:") + "\n%1").arg(fileUrl));
- }
- }
- fileReply->deleteLater();
- });
- }
- }
- if (showMessageBox) {
- QMessageBox::information(this, tr("Download Complete"),
- QString(tr("DownloadComplete_MSG")));
- }
-
- // Create the files.json file with the identification of which file to open
- createFilesJson(repository);
- populateFileListPatches();
-
- } else {
+ if (itemsArray.isEmpty()) {
if (showMessageBox) {
QMessageBox::warning(this, tr("Error"),
tr("Failed to parse JSON data from HTML."));
}
+ return;
}
+
+ QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir));
+ QString fullPath = dir.filePath(repository);
+ if (!dir.exists(fullPath)) {
+ dir.mkpath(fullPath);
+ }
+ dir.setPath(fullPath);
+
+ foreach (const QJsonValue& value, itemsArray) {
+ QJsonObject fileObj = value.toObject();
+ QString fileName = fileObj["name"].toString();
+ QString filePath = fileObj["path"].toString();
+ QString downloadUrl = fileObj["download_url"].toString();
+
+ if (fileName.endsWith(".xml")) {
+ QNetworkRequest fileRequest(downloadUrl);
+ QNetworkReply* fileReply = manager->get(fileRequest);
+
+ connect(fileReply, &QNetworkReply::finished, [=]() {
+ if (fileReply->error() == QNetworkReply::NoError) {
+ QByteArray fileData = fileReply->readAll();
+ QFile localFile(dir.filePath(fileName));
+ if (localFile.open(QIODevice::WriteOnly)) {
+ localFile.write(fileData);
+ localFile.close();
+ } else {
+ if (showMessageBox) {
+ QMessageBox::warning(
+ this, tr("Error"),
+ QString(tr("Failed to save:") + "\n%1").arg(fileName));
+ }
+ }
+ } else {
+ if (showMessageBox) {
+ QMessageBox::warning(
+ this, tr("Error"),
+ QString(tr("Failed to download:") + "\n%1").arg(downloadUrl));
+ }
+ }
+ fileReply->deleteLater();
+ });
+ }
+ }
+ if (showMessageBox) {
+ QMessageBox::information(this, tr("Download Complete"),
+ QString(tr("DownloadComplete_MSG")));
+ }
+ // Create the files.json file with the identification of which file to open
+ createFilesJson(repository);
+ populateFileListPatches();
} else {
if (showMessageBox) {
- QMessageBox::warning(this, tr("Error"), tr("Failed to retrieve HTML page."));
+ QMessageBox::warning(this, tr("Error"),
+ QString(tr("Failed to retrieve HTML page.") + "\n%1")
+ .arg(reply->errorString()));
}
}
emit downloadFinished();
diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp
index 3d5ab14ec..9fba0c47c 100644
--- a/src/qt_gui/game_grid_frame.cpp
+++ b/src/qt_gui/game_grid_frame.cpp
@@ -114,8 +114,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
QWidget* item = this->cellWidget(row, column);
if (item) {
QString pic1Path = QString::fromStdString((*m_games_shared)[itemID].pic_path);
- const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) /
- "game_data" / (*m_games_shared)[itemID].serial / "pic1.png";
+ const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
+ (*m_games_shared)[itemID].serial / "pic1.png";
#ifdef _WIN32
const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring());
#else
@@ -128,7 +128,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16);
std::filesystem::path img_path =
- std::filesystem::path("user/game_data/") / (*m_games_shared)[itemID].serial;
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
+ (*m_games_shared)[itemID].serial;
std::filesystem::create_directories(img_path);
if (!backgroundImage.save(blurredPic1PathQt, "PNG")) {
// qDebug() << "Error: Unable to save image.";
diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp
index f5c2facbe..b17da127e 100644
--- a/src/qt_gui/game_list_frame.cpp
+++ b/src/qt_gui/game_list_frame.cpp
@@ -31,14 +31,8 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg
this->setColumnWidth(5, 90); // Size
this->setColumnWidth(6, 90); // Version
QStringList headers;
- headers << "Icon"
- << "Name"
- << "Serial"
- << "Region"
- << "Firmware"
- << "Size"
- << "Version"
- << "Path";
+ headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware")
+ << tr("Size") << tr("Version") << tr("Path");
this->setHorizontalHeaderLabels(headers);
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
@@ -96,9 +90,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
}
QString pic1Path = QString::fromStdString(m_game_info->m_games[item->row()].pic_path);
- const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) /
- "game_data" / m_game_info->m_games[item->row()].serial /
- "pic1.png";
+ const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
+ m_game_info->m_games[item->row()].serial / "pic1.png";
#ifdef _WIN32
const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring());
#else
@@ -111,7 +104,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16);
std::filesystem::path img_path =
- std::filesystem::path("user/game_data/") / m_game_info->m_games[item->row()].serial;
+ Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
+ m_game_info->m_games[item->row()].serial;
std::filesystem::create_directories(img_path);
if (!backgroundImage.save(blurredPic1PathQt, "PNG")) {
// qDebug() << "Error: Unable to save image.";
diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp
index 02957c6d5..abbf6dcb6 100644
--- a/src/qt_gui/main.cpp
+++ b/src/qt_gui/main.cpp
@@ -16,7 +16,6 @@ int main(int argc, char* argv[]) {
// Load configurations and initialize Qt application
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");
- std::filesystem::create_directory(user_dir / "game_data");
// Check if elf or eboot.bin path was passed as a command line argument
bool has_command_line_argument = argc > 1;
diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp
index c3e644cfb..59387a1a9 100644
--- a/src/qt_gui/main_window.cpp
+++ b/src/qt_gui/main_window.cpp
@@ -34,12 +34,12 @@ bool MainWindow::Init() {
CreateActions();
CreateRecentGameActions();
ConfigureGuiFromSettings();
+ LoadTranslation();
CreateDockWindows();
CreateConnects();
SetLastUsedTheme();
SetLastIconSizeBullet();
GetPhysicalDevices();
- LoadTranslation();
// show ui
setMinimumSize(350, minimumSizeHint().height());
setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION)));
@@ -89,6 +89,7 @@ void MainWindow::AddUiWidgets() {
ui->toolBar->addWidget(ui->playButton);
ui->toolBar->addWidget(ui->pauseButton);
ui->toolBar->addWidget(ui->stopButton);
+ ui->toolBar->addWidget(ui->refreshButton);
ui->toolBar->addWidget(ui->settingsButton);
auto connection = QObject::connect(ui->controllerButton, &QPushButton::clicked, this,
&MainWindow::ControllerConfigurationButtonPressed);
@@ -111,7 +112,7 @@ void MainWindow::CreateDockWindows() {
QWidget* phCentralWidget = new QWidget(this);
setCentralWidget(phCentralWidget);
- m_dock_widget.reset(new QDockWidget("Game List", this));
+ m_dock_widget.reset(new QDockWidget(tr("Game List"), this));
m_game_list_frame.reset(new GameListFrame(m_game_info, this));
m_game_list_frame->setObjectName("gamelist");
m_game_grid_frame.reset(new GameGridFrame(m_game_info, this));
@@ -185,6 +186,7 @@ void MainWindow::CreateConnects() {
connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable);
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable);
+ connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable);
connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList);
connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable);
@@ -583,7 +585,7 @@ void MainWindow::SaveWindowState() const {
void MainWindow::InstallPkg() {
QFileDialog dialog;
dialog.setFileMode(QFileDialog::ExistingFiles);
- dialog.setNameFilter(tr("PKG File (*.PKG)"));
+ dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)"));
if (dialog.exec()) {
QStringList fileNames = dialog.selectedFiles();
int nPkg = fileNames.size();
@@ -864,6 +866,7 @@ void MainWindow::SetUiIcons(bool isWhite) {
ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite));
ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite));
ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite));
+ ui->refreshButton->setIcon(RecolorIcon(ui->refreshButton->icon(), isWhite));
ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite));
ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite));
ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite));
@@ -958,4 +961,4 @@ void MainWindow::OnLanguageChanged(const std::string& locale) {
Config::setEmulatorLanguage(locale);
LoadTranslation();
-}
\ No newline at end of file
+}
diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp
index c89fa5a00..35e64ef74 100644
--- a/src/qt_gui/main_window_themes.cpp
+++ b/src/qt_gui/main_window_themes.cpp
@@ -8,13 +8,13 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
switch (theme) {
case Theme::Dark:
- mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */"
- "color: #ffffff; /* White text */"
- "border: 1px solid #ffffff; /* White border */"
+ mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background
+ "color: #ffffff;" // White text
+ "border: 2px solid #ffffff;" // White border
"padding: 5px;");
- themePalette.setColor(QPalette::Window, QColor(53, 53, 53));
+ themePalette.setColor(QPalette::Window, QColor(50, 50, 50));
themePalette.setColor(QPalette::WindowText, Qt::white);
- themePalette.setColor(QPalette::Base, QColor(25, 25, 25));
+ themePalette.setColor(QPalette::Base, QColor(20, 20, 20));
themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25));
themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
themePalette.setColor(QPalette::ToolTipBase, Qt::white);
@@ -30,8 +30,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
break;
case Theme::Light:
- mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */"
- "color: #000000; /* Black text */"
+ mw_searchbar->setStyleSheet("background-color: #ffffff;" // Light gray background
+ "color: #000000;" // Black text
+ "border: 2px solid #000000;" // Black border
"padding: 5px;");
themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray
themePalette.setColor(QPalette::WindowText, Qt::black); // Black
@@ -49,9 +50,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
break;
case Theme::Green:
- mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */"
- "color: #ffffff; /* White text */"
- "border: 1px solid #ffffff; /* White border */"
+ mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background
+ "color: #ffffff;" // White text
+ "border: 2px solid #ffffff;" // White border
"padding: 5px;");
themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
@@ -72,9 +73,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
break;
case Theme::Blue:
- mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */"
- "color: #ffffff; /* White text */"
- "border: 1px solid #ffffff; /* White border */"
+ mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background
+ "color: #ffffff;" // White text
+ "border: 2px solid #ffffff;" // White border
"padding: 5px;");
themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
@@ -95,9 +96,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
break;
case Theme::Violet:
- mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */"
- "color: #ffffff; /* White text */"
- "border: 1px solid #ffffff; /* White border */"
+ mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background
+ "color: #ffffff;" // White text
+ "border: 2px solid #ffffff;" // White border
"padding: 5px;");
themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h
index 0acfade0e..6ddc4155e 100644
--- a/src/qt_gui/main_window_ui.h
+++ b/src/qt_gui/main_window_ui.h
@@ -38,6 +38,7 @@ public:
QPushButton* playButton;
QPushButton* pauseButton;
QPushButton* stopButton;
+ QPushButton* refreshButton;
QPushButton* settingsButton;
QPushButton* controllerButton;
@@ -176,6 +177,10 @@ public:
stopButton->setFlat(true);
stopButton->setIcon(QIcon(":images/stop_icon.png"));
stopButton->setIconSize(QSize(40, 40));
+ refreshButton = new QPushButton(centralWidget);
+ refreshButton->setFlat(true);
+ refreshButton->setIcon(QIcon(":images/refresh_icon.png"));
+ refreshButton->setIconSize(QSize(32, 32));
settingsButton = new QPushButton(centralWidget);
settingsButton->setFlat(true);
settingsButton->setIcon(QIcon(":images/settings_icon.png"));
@@ -262,8 +267,8 @@ public:
menuView->addAction(menuGame_List_Mode->menuAction());
menuView->addAction(menuGame_List_Icons->menuAction());
menuView->addAction(menuThemes->menuAction());
- menuThemes->addAction(setThemeLight);
menuThemes->addAction(setThemeDark);
+ menuThemes->addAction(setThemeLight);
menuThemes->addAction(setThemeGreen);
menuThemes->addAction(setThemeBlue);
menuThemes->addAction(setThemeViolet);
diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp
index d572915c6..4206c4463 100644
--- a/src/qt_gui/settings_dialog.cpp
+++ b/src/qt_gui/settings_dialog.cpp
@@ -80,9 +80,13 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge
}
});
- connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, [this]() {
- ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus();
- });
+ ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save"));
+ ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply"));
+ ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults"));
+ ui->buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
+
+ connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this,
+ [this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); });
// GENERAL TAB
{
diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts
index d35bbbf86..12b1e7ba6 100644
--- a/src/qt_gui/translations/ar.ts
+++ b/src/qt_gui/translations/ar.ts
@@ -502,6 +502,11 @@
MainWindow
+
+
+ Game List
+ ققائمة الألعاب
+
* Unsupported Vulkan Version
@@ -864,7 +869,7 @@
DownloadComplete_MSG
- تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش.
+ تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. قد تحتاج إلى تحديث اللعبة.
@@ -911,5 +916,76 @@
Name:
:الاسم
+
+
+ Can't apply cheats before the game is started
+ لا يمكن تطبيق الغش قبل بدء اللعبة.
+
+
+ SettingsDialog
+
+
+ Save
+ حفظ
+
+
+
+ Apply
+ تطبيق
+
+
+
+ Restore Defaults
+ استعادة الإعدادات الافتراضية
+
+
+
+ Close
+ إغلاق
+
+
+
+ GameListFrame
+
+
+ Icon
+ أيقونة
+
+
+
+ Name
+ اسم
+
+
+
+ Serial
+ سيريال
+
+
+
+ Region
+ منطقة
+
+
+
+ Firmware
+ البرمجيات الثابتة
+
+
+
+ Size
+ حجم
+
+
+
+ Version
+ إصدار
+
+
+
+ Path
+ مسار
+
+
diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts
index c67d29b1d..bb405ec0a 100644
--- a/src/qt_gui/translations/da_DK.ts
+++ b/src/qt_gui/translations/da_DK.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Spiloversigt
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd.
+ Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. Hvis opdateringen ikke vises, kan det være, at den ikke findes for den specifikke serie og version af spillet. Det kan være nødvendigt at opdatere spillet.
@@ -898,5 +903,76 @@
Name:
Navn:
+
+
+ Can't apply cheats before the game is started
+ Kan ikke anvende snyd før spillet er startet.
+
+
+ SettingsDialog
+
+
+ Save
+ Gem
+
+
+
+ Apply
+ Anvend
+
+
+
+ Restore Defaults
+ Gendan standardindstillinger
+
+
+
+ Close
+ Luk
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikon
+
+
+
+ Name
+ Navn
+
+
+
+ Serial
+ Seriel
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Størrelse
+
+
+
+ Version
+ Version
+
+
+
+ Path
+ Sti
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts
index c208ad441..1482686ce 100644
--- a/src/qt_gui/translations/de.ts
+++ b/src/qt_gui/translations/de.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Spieleliste
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist.
+ Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. Wenn der Patch nicht angezeigt wird, könnte es sein, dass er für die spezifische Seriennummer und Version des Spiels nicht existiert. Möglicherweise müssen Sie das Spiel aktualisieren.
@@ -898,5 +903,76 @@
Name:
Name:
+
+
+ Can't apply cheats before the game is started
+ Kann keine Cheats anwenden, bevor das Spiel gestartet ist.
+
+
+ SettingsDialog
+
+
+ Save
+ Speichern
+
+
+
+ Apply
+ Übernehmen
+
+
+
+ Restore Defaults
+ Werkseinstellungen wiederherstellen
+
+
+
+ Close
+ Schließen
+
+
+
+ GameListFrame
+
+
+ Icon
+ Symbol
+
+
+
+ Name
+ Name
+
+
+
+ Serial
+ Seriennummer
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Größe
+
+
+
+ Version
+ Version
+
+
+
+ Path
+ Pfad
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts
index ef831fb09..4a3aa54ff 100644
--- a/src/qt_gui/translations/el.ts
+++ b/src/qt_gui/translations/el.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Λίστα παιχνιδιών
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats.
+ Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. Εάν η ενημέρωση δεν εμφανίζεται, μπορεί να μην υπάρχει για τον συγκεκριμένο σειριακό αριθμό και έκδοση του παιχνιδιού. Μπορεί να χρειαστεί να ενημερώσετε το παιχνίδι.
@@ -898,5 +903,76 @@
Name:
Όνομα:
+
+
+ Can't apply cheats before the game is started
+ Δεν μπορείτε να εφαρμόσετε cheats πριν ξεκινήσει το παιχνίδι.
+
+
+ SettingsDialog
+
+
+ Save
+ Αποθήκευση
+
+
+
+ Apply
+ Εφαρμογή
+
+
+
+ Restore Defaults
+ Επαναφορά Προεπιλογών
+
+
+
+ Close
+ Κλείσιμο
+
+
+
+ GameListFrame
+
+
+ Icon
+ Εικονίδιο
+
+
+
+ Name
+ Όνομα
+
+
+
+ Serial
+ Σειριακός αριθμός
+
+
+
+ Region
+ Περιοχή
+
+
+
+ Firmware
+ Λογισμικό
+
+
+
+ Size
+ Μέγεθος
+
+
+
+ Version
+ Έκδοση
+
+
+
+ Path
+ Διαδρομή
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts
index b3c3b699b..9696610bc 100644
--- a/src/qt_gui/translations/en.ts
+++ b/src/qt_gui/translations/en.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Game List
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats.
+ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game.
@@ -898,5 +903,76 @@
Name:
Name:
-
+
+
+ Can't apply cheats before the game is started
+ Can't apply cheats before the game is started.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Save
+
+
+
+ Apply
+ Apply
+
+
+
+ Restore Defaults
+ Restore Defaults
+
+
+
+ Close
+ Close
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icon
+
+
+
+ Name
+ Name
+
+
+
+ Serial
+ Serial
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Size
+
+
+
+ Version
+ Version
+
+
+
+ Path
+ Path
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts
index c34dc3d44..e1bc91809 100644
--- a/src/qt_gui/translations/es_ES.ts
+++ b/src/qt_gui/translations/es_ES.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Lista de juegos
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos.
+ ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. Si el parche no aparece, puede ser que no exista para el número de serie y versión específicos del juego. Puede ser necesario actualizar el juego.
@@ -898,5 +903,76 @@
Name:
Nombre:
+
+
+ Can't apply cheats before the game is started
+ No se pueden aplicar trucos antes de que se inicie el juego.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Guardar
+
+
+
+ Apply
+ Aplicar
+
+
+
+ Restore Defaults
+ Restaurar Valores Predeterminados
+
+
+
+ Close
+ Cerrar
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ícono
+
+
+
+ Name
+ Nombre
+
+
+
+ Serial
+ Serie
+
+
+
+ Region
+ Región
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Tamaño
+
+
+
+ Version
+ Versión
+
+
+
+ Path
+ Ruta
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts
index 129d54792..089653069 100644
--- a/src/qt_gui/translations/fa_IR.ts
+++ b/src/qt_gui/translations/fa_IR.ts
@@ -1,899 +1,978 @@
-
- AboutDialog
-
-
- About shadPS4
- درباره ShadPS4
-
-
-
- shadPS4
- ShadPS4
-
-
-
- shadPS4 is an experimental open-source emulator for the PlayStation 4.
- یک شبیه ساز متن باز برای پلی استیشن 4 است.
-
-
-
- This software should not be used to play games you have not legally obtained.
- این برنامه نباید برای بازی هایی که شما به صورت غیرقانونی به دست آوردید استفاده شود.
-
-
-
- ElfViewer
-
-
- Open Folder
- فولدر را بازکن
-
-
-
- GameInfoClass
-
-
- Loading game list, please wait :3
- درحال بارگیری لیست بازی ها,لطفا کمی صبرکنید :3
-
-
-
- Cancel
- لغو
-
-
-
- Loading...
- ...درحال بارگیری
-
-
-
- GameInstallDialog
-
-
- shadPS4 - Choose directory
- ShadPS4 - انتخاب محل نصب بازی
-
-
-
- Directory to install games
- محل نصب بازی ها
-
-
-
- Browse
- انتخاب دستی
-
-
-
- Error
- ارور
-
-
-
- The value for location to install games is not valid.
- .مکان داده شده برای نصب بازی درست نمی باشد
-
-
-
- GuiContextMenus
-
-
- Create Shortcut
- ساخت شورتکات
-
-
-
- Open Game Folder
- بازکردن محل نصب بازی
-
-
-
- Cheats / Patches
- چیت/پچ ها
-
-
-
- SFO Viewer
- SFO مشاهده
-
-
-
- Trophy Viewer
- مشاهده تروفی ها
-
-
-
- Copy info
- کپی کردن اطلاعات
-
-
-
- Copy Name
- کپی کردن نام
-
-
-
- Copy Serial
- کپی کردن سریال
-
-
-
- Copy All
- کپی کردن تمامی مقادیر
-
-
-
- Shortcut creation
- سازنده شورتکات
-
-
-
- Shortcut created successfully!\n %1
- شورتکات با موفقیت ساخته شد! \n %1
-
-
-
- Error
- ارور
-
-
-
- Error creating shortcut!\n %1
- مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1
-
-
-
- Install PKG
- نصب PKG
-
-
-
- MainWindow
-
-
- Open/Add Elf Folder
- ELF بازکردن/ساختن پوشه
-
-
-
- Install Packages (PKG)
- نصب بسته (PKG)
-
-
-
- Boot Game
- اجرای بازی
-
-
-
- About shadPS4
- ShadPS4 درباره
-
-
-
- Configure...
- ...تنظیمات
-
-
-
- Install application from a .pkg file
- .PKG نصب بازی از فایل
-
-
-
- Recent Games
- بازی های اخیر
-
-
-
- Exit
- خروج
-
-
-
- Exit shadPS4
- ShadPS4 بستن
-
-
-
- Exit the application.
- بستن برنامه
-
-
-
- Show Game List
- نشان دادن بازی ها
-
-
-
- Game List Refresh
- رفرش لیست بازی ها
-
-
-
- Tiny
- کوچک ترین
-
-
-
- Small
- کوچک
-
-
-
- Medium
- متوسط
-
-
-
- Large
- بزرگ
-
-
-
- List View
- لیستی
-
-
-
- Grid View
- شبکه ای (چهارخونه)
-
-
-
- Elf Viewer
- Elf Viewer
-
-
-
- Game Install Directory
- محل نصب بازی
-
-
-
- Download Cheats/Patches
- دانلود چیت/پچ
-
-
-
- Dump Game List
- استخراج لیست بازی ها
-
-
-
- PKG Viewer
- PKG مشاهده گر
-
-
-
- Search...
- جست و جو...
-
-
-
- File
- فایل
-
-
-
- View
- شخصی سازی
-
-
-
- Game List Icons
- آیکون ها
-
-
-
- Game List Mode
- حالت نمایش لیست بازی ها
-
-
-
- Settings
- تنظیمات
-
-
-
- Utils
- ابزارها
-
-
-
- Themes
- تم ها
-
-
-
- About
- درباره ما
-
-
-
- Dark
- تیره
-
-
-
- Light
- روشن
-
-
-
- Green
- سبز
-
-
-
- Blue
- آبی
-
-
-
- Violet
- بنفش
-
-
-
- toolBar
- نوار ابزار
-
-
-
- * Unsupported Vulkan Version
- شما پشتیبانی نمیشود Vulkan ورژن*
-
-
-
- Download Cheats For All Installed Games
- دانلود چیت برای همه بازی ها
-
-
-
- Download Patches For All Games
- دانلود پچ برای همه بازی ها
-
-
-
- Download Complete
- دانلود کامل شد✅
-
-
-
- You have downloaded cheats for all the games you have installed.
- چیت برای همه بازی های شما دانلودشد✅
-
-
-
- Patches Downloaded Successfully!
- پچ ها با موفقیت دانلود شد✅
-
-
-
- All Patches available for all games have been downloaded.
- ✅تمام پچ های موجود برای همه بازی های شما دانلود شد
-
-
-
- Games:
- بازی ها:
-
-
-
- PKG File (*.PKG)
- PKG فایل (*.PKG)
-
-
-
- ELF files (*.bin *.elf *.oelf)
- ELF فایل های (*.bin *.elf *.oelf)
-
-
-
- Game Boot
- اجرای بازی
-
-
-
- Only one file can be selected!
- فقط یک فایل انتخاب کنید!
-
-
-
- PKG Extraction
- PKG استخراج فایل
-
-
-
- Patch detected!
- پچ شناسایی شد!
-
-
-
- PKG and Game versions match:
- و نسخه بازی همخوانی دارد PKG فایل:
-
-
-
- Would you like to overwrite?
- آیا مایل به جایگزینی فایل هستید؟
-
-
-
- PKG Version %1 is older than installed version:
- نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است:
-
-
-
- Game is installed:
- بازی نصب شد:
-
-
-
- Would you like to install Patch:
- آیا مایل به نصب پچ هستید:
-
-
-
- DLC Installation
- نصب DLC
-
-
-
- Would you like to install DLC: %1?
- آیا مایل به نصب DLC هستید: %1
-
-
-
- DLC already installed:
- قبلا نصب شده DLC این:
-
-
-
- Game already installed
- این بازی قبلا نصب شده
-
-
-
- PKG is a patch, please install the game first!
- فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید
-
-
-
- PKG ERROR
- PKG ارور فایل
-
-
-
- Extracting PKG %1/%2
- درحال استخراج PKG %1/%2
-
-
-
- Extraction Finished
- استخراج به پایان رسید
-
-
-
- Game successfully installed at %1
- بازی با موفقیت در %1 نصب شد
-
-
-
- File doesn't appear to be a valid PKG file
- این فایل یک PKG درست به نظر نمی آید
-
-
-
- PKGViewer
-
-
- Open Folder
- بازکردن پوشه
-
-
-
- TrophyViewer
-
-
- Trophy Viewer
- تروفی ها
-
-
-
- SettingsDialog
-
-
- Settings
- تنظیمات
-
-
-
- General
- عمومی
-
-
-
- System
- سیستم
-
-
-
- Console Language
- زبان کنسول
-
-
-
- Emulator Language
- زبان شبیه ساز
-
-
-
- Emulator
- شبیه ساز
-
-
-
- Enable Fullscreen
- تمام صفحه
-
-
-
- Show Splash
- Splash نمایش
-
-
-
- Is PS4 Pro
- PS4 Pro حالت
-
-
-
- Username
- نام کاربری
-
-
-
- Logger
- Logger
-
-
-
- Log Type
- Log نوع
-
-
-
- Log Filter
- Log فیلتر
-
-
-
- Graphics
- گرافیک
-
-
-
- Graphics Device
- کارت گرافیک مورداستفاده
-
-
-
- Width
- عرض
-
-
-
- Height
- طول
-
-
-
- Vblank Divider
- Vblank Divider
-
-
-
- Advanced
- ...بیشتر
-
-
-
- Enable Shaders Dumping
- Shaders Dumping فعال کردن
-
-
-
- Enable NULL GPU
- NULL GPU فعال کردن
-
-
-
- Enable PM4 Dumping
- PM4 Dumping فعال کردن
-
-
-
- Debug
- Debug
-
-
-
- Enable Debug Dumping
- Debug Dumping
-
-
-
- Enable Vulkan Validation Layers
- Vulkan Validation Layers
-
-
-
- Enable Vulkan Synchronization Validation
- Vulkan Synchronization Validation
-
-
-
- Enable RenderDoc Debugging
- RenderDoc Debugging
-
-
-
- CheatsPatches
-
-
- Cheats / Patches
- چیت / پچ ها
-
-
-
- defaultTextEdit_MSG
- defaultTextEdit_MSG
-
-
-
- No Image Available
- تصویری موجود نمی باشد
-
-
-
- Serial:
- سریال:
-
-
-
- Version:
- ورژن:
-
-
-
- Size:
- حجم:
-
-
-
- Select Cheat File:
- فایل چیت را انتخاب کنید:
-
-
-
- Repository:
- :منبع
-
-
-
- Download Cheats
- دانلود چیت ها
-
-
-
- Delete File
- پاک کردن فایل
-
-
-
- No files selected.
- فایلی انتخاب نشده.
-
-
-
- You can delete the cheats you don't want after downloading them.
- شما میتوانید بعد از دانلود چیت هایی که نمیخواهید را پاک کنید
-
-
-
- Do you want to delete the selected file?\n%1
- آیا میخواهید فایل های انتخاب شده را پاک کنید؟ \n%1
-
-
-
- Select Patch File:
- فایل پچ را انتخاب کنید
-
-
-
- Download Patches
- دانلود کردن پچ ها
-
-
-
- Save
- ذخیره
-
-
-
- Cheats
- چیت ها
-
-
-
- Patches
- پچ ها
-
-
-
- Error
- ارور
-
-
-
- No patch selected.
- هیچ پچ انتخاب نشده
-
-
-
- Unable to open files.json for reading.
- .json مشکل در خواندن فایل
-
-
-
- No patch file found for the current serial.
- هیچ فایل پچ برای سریال بازی شما پیدا نشد.
-
-
-
- Unable to open the file for reading.
- خطا در خواندن فایل
-
-
-
- Unable to open the file for writing.
- خطا در نوشتن فایل
-
-
-
- Failed to parse XML:
- انجام نشد XML تجزیه فایل:
-
-
-
- Success
- عملیات موفق بود
-
-
-
- Options saved successfully.
- تغییرات با موفقیت ذخیره شد✅
-
-
-
- Invalid Source
- منبع نامعتبر❌
-
-
-
- The selected source is invalid.
- منبع انتخاب شده نامعتبر است
-
-
-
- File Exists
- فایل وجود دارد
-
-
-
- File already exists. Do you want to replace it?
- فایل از قبل وجود دارد. آیا می خواهید آن را جایگزین کنید؟
-
-
-
- Failed to save file:
- ذخیره فایل موفقیت آمیز نبود:
-
-
-
- Failed to download file:
- خطا در دانلود فایل:
-
-
-
- Cheats Not Found
- چیت یافت نشد
-
-
-
- CheatsNotFound_MSG
- متاسفانه هیچ چیتی از منبع انتخاب شده پیدا نشد! شما میتوانید منابع دیگری را برای دانلود انتخاب و یا چیت های خود را به صورت دستی واردکنید.
-
-
-
- Cheats Downloaded Successfully
- دانلود چیت ها موفقیت آمیز بود✅
-
-
-
- CheatsDownloadedSuccessfully_MSG
- تمامی چیت های موجود برای این بازی از منبع انتخاب شده دانلود شد! شما همچنان میتوانید چیت های دیگری را ازمنابع مختلف دانلود کنید و درصورت موجود بودن از آنها استفاده کنید.
-
-
-
- Failed to save:
- خطا در ذخیره اطلاعات:
-
-
-
- Failed to download:
- خطا در دانلود❌
-
-
-
- Download Complete
- دانلود موفقیت آمیز بود✅
-
-
-
- DownloadComplete_MSG
- دانلود با موفقیت به اتمام رسید✅
-
-
-
- Failed to parse JSON data from HTML.
- HTML از JSON خطا در تجزیه اطلاعات.
-
-
-
- Failed to retrieve HTML page.
- HTML خطا دربازیابی صفحه
-
-
-
- Failed to open file:
- خطا در اجرای فایل:
-
-
-
- XML ERROR:
- XML ERROR:
-
-
-
- Failed to open files.json for writing
- .json خطا در نوشتن فایل
-
-
-
- Author:
- تولید کننده:
-
-
-
- Directory does not exist:
- پوشه وجود ندارد:
-
-
-
- Failed to open files.json for reading.
- .json خطا در خواندن فایل
-
-
-
- Name:
- نام:
-
-
-
+
+ AboutDialog
+
+
+ About shadPS4
+ درباره ShadPS4
+
+
+
+ shadPS4
+ ShadPS4
+
+
+
+ shadPS4 is an experimental open-source emulator for the PlayStation 4.
+ یک شبیه ساز متن باز برای پلی استیشن 4 است.
+
+
+
+ This software should not be used to play games you have not legally obtained.
+ این برنامه نباید برای بازی هایی که شما به صورت غیرقانونی به دست آوردید استفاده شود.
+
+
+
+ ElfViewer
+
+
+ Open Folder
+ فولدر را بازکن
+
+
+
+ GameInfoClass
+
+
+ Loading game list, please wait :3
+ درحال بارگیری لیست بازی ها,لطفا کمی صبرکنید :3
+
+
+
+ Cancel
+ لغو
+
+
+
+ Loading...
+ ...درحال بارگیری
+
+
+
+ GameInstallDialog
+
+
+ shadPS4 - Choose directory
+ ShadPS4 - انتخاب محل نصب بازی
+
+
+
+ Directory to install games
+ محل نصب بازی ها
+
+
+
+ Browse
+ انتخاب دستی
+
+
+
+ Error
+ ارور
+
+
+
+ The value for location to install games is not valid.
+ .مکان داده شده برای نصب بازی درست نمی باشد
+
+
+
+ GuiContextMenus
+
+
+ Create Shortcut
+ ساخت شورتکات
+
+
+
+ Open Game Folder
+ بازکردن محل نصب بازی
+
+
+
+ Cheats / Patches
+ چیت/پچ ها
+
+
+
+ SFO Viewer
+ SFO مشاهده
+
+
+
+ Trophy Viewer
+ مشاهده تروفی ها
+
+
+
+ Copy info
+ کپی کردن اطلاعات
+
+
+
+ Copy Name
+ کپی کردن نام
+
+
+
+ Copy Serial
+ کپی کردن سریال
+
+
+
+ Copy All
+ کپی کردن تمامی مقادیر
+
+
+
+ Shortcut creation
+ سازنده شورتکات
+
+
+
+ Shortcut created successfully!\n %1
+ شورتکات با موفقیت ساخته شد! \n %1
+
+
+
+ Error
+ ارور
+
+
+
+ Error creating shortcut!\n %1
+ مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1
+
+
+
+ Install PKG
+ نصب PKG
+
+
+
+ MainWindow
+
+
+ Open/Add Elf Folder
+ ELF بازکردن/ساختن پوشه
+
+
+
+ Install Packages (PKG)
+ نصب بسته (PKG)
+
+
+
+ Boot Game
+ اجرای بازی
+
+
+
+ About shadPS4
+ ShadPS4 درباره
+
+
+
+ Configure...
+ ...تنظیمات
+
+
+
+ Install application from a .pkg file
+ .PKG نصب بازی از فایل
+
+
+
+ Recent Games
+ بازی های اخیر
+
+
+
+ Exit
+ خروج
+
+
+
+ Exit shadPS4
+ ShadPS4 بستن
+
+
+
+ Exit the application.
+ بستن برنامه
+
+
+
+ Show Game List
+ نشان دادن بازی ها
+
+
+
+ Game List Refresh
+ رفرش لیست بازی ها
+
+
+
+ Tiny
+ کوچک ترین
+
+
+
+ Small
+ کوچک
+
+
+
+ Medium
+ متوسط
+
+
+
+ Large
+ بزرگ
+
+
+
+ List View
+ لیستی
+
+
+
+ Grid View
+ شبکه ای (چهارخونه)
+
+
+
+ Elf Viewer
+ Elf Viewer
+
+
+
+ Game Install Directory
+ محل نصب بازی
+
+
+
+ Download Cheats/Patches
+ دانلود چیت/پچ
+
+
+
+ Dump Game List
+ استخراج لیست بازی ها
+
+
+
+ PKG Viewer
+ PKG مشاهده گر
+
+
+
+ Search...
+ جست و جو...
+
+
+
+ File
+ فایل
+
+
+
+ View
+ شخصی سازی
+
+
+
+ Game List Icons
+ آیکون ها
+
+
+
+ Game List Mode
+ حالت نمایش لیست بازی ها
+
+
+
+ Settings
+ تنظیمات
+
+
+
+ Utils
+ ابزارها
+
+
+
+ Themes
+ تم ها
+
+
+
+ About
+ درباره ما
+
+
+
+ Dark
+ تیره
+
+
+
+ Light
+ روشن
+
+
+
+ Green
+ سبز
+
+
+
+ Blue
+ آبی
+
+
+
+ Violet
+ بنفش
+
+
+
+ toolBar
+ نوار ابزار
+
+
+
+ PKGViewer
+
+
+ Open Folder
+ بازکردن پوشه
+
+
+
+ TrophyViewer
+
+
+ Trophy Viewer
+ تروفی ها
+
+
+
+ SettingsDialog
+
+
+ Settings
+ تنظیمات
+
+
+
+ General
+ عمومی
+
+
+
+ System
+ سیستم
+
+
+
+ Console Language
+ زبان کنسول
+
+
+
+ Emulator Language
+ زبان شبیه ساز
+
+
+
+ Emulator
+ شبیه ساز
+
+
+
+ Enable Fullscreen
+ تمام صفحه
+
+
+
+ Show Splash
+ Splash نمایش
+
+
+
+ Is PS4 Pro
+ PS4 Pro حالت
+
+
+
+ Username
+ نام کاربری
+
+
+
+ Logger
+ Logger
+
+
+
+ Log Type
+ Log نوع
+
+
+
+ Log Filter
+ Log فیلتر
+
+
+
+ Graphics
+ گرافیک
+
+
+
+ Graphics Device
+ کارت گرافیک مورداستفاده
+
+
+
+ Width
+ عرض
+
+
+
+ Height
+ طول
+
+
+
+ Vblank Divider
+ Vblank Divider
+
+
+
+ Advanced
+ ...بیشتر
+
+
+
+ Enable Shaders Dumping
+ Shaders Dumping فعال کردن
+
+
+
+ Enable NULL GPU
+ NULL GPU فعال کردن
+
+
+
+ Enable PM4 Dumping
+ PM4 Dumping فعال کردن
+
+
+
+ Debug
+ Debug
+
+
+
+ Enable Debug Dumping
+ Debug Dumping
+
+
+
+ Enable Vulkan Validation Layers
+ Vulkan Validation Layers
+
+
+
+ Enable Vulkan Synchronization Validation
+ Vulkan Synchronization Validation
+
+
+
+ Enable RenderDoc Debugging
+ RenderDoc Debugging
+
+
+
+ MainWindow
+
+
+ Game List
+ لیست بازی
+
+
+
+ * Unsupported Vulkan Version
+ شما پشتیبانی نمیشود Vulkan ورژن*
+
+
+
+ Download Cheats For All Installed Games
+ دانلود چیت برای همه بازی ها
+
+
+
+ Download Patches For All Games
+ دانلود پچ برای همه بازی ها
+
+
+
+ Download Complete
+ دانلود کامل شد✅
+
+
+
+ You have downloaded cheats for all the games you have installed.
+ چیت برای همه بازی های شما دانلودشد✅
+
+
+
+ Patches Downloaded Successfully!
+ پچ ها با موفقیت دانلود شد✅
+
+
+
+ All Patches available for all games have been downloaded.
+ ✅تمام پچ های موجود برای همه بازی های شما دانلود شد
+
+
+
+ Games:
+ بازی ها:
+
+
+
+ PKG File (*.PKG)
+ PKG فایل (*.PKG)
+
+
+
+ ELF files (*.bin *.elf *.oelf)
+ ELF فایل های (*.bin *.elf *.oelf)
+
+
+
+ Game Boot
+ اجرای بازی
+
+
+
+ Only one file can be selected!
+ فقط یک فایل انتخاب کنید!
+
+
+
+ PKG Extraction
+ PKG استخراج فایل
+
+
+
+ Patch detected!
+ پچ شناسایی شد!
+
+
+
+ PKG and Game versions match:
+ و نسخه بازی همخوانی دارد PKG فایل:
+
+
+
+ Would you like to overwrite?
+ آیا مایل به جایگزینی فایل هستید؟
+
+
+
+ PKG Version %1 is older than installed version:
+ نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است:
+
+
+
+ Game is installed:
+ بازی نصب شد:
+
+
+
+ Would you like to install Patch:
+ آیا مایل به نصب پچ هستید:
+
+
+
+ DLC Installation
+ نصب DLC
+
+
+
+ Would you like to install DLC: %1?
+ آیا مایل به نصب DLC هستید: %1
+
+
+
+ DLC already installed:
+ قبلا نصب شده DLC این:
+
+
+
+ Game already installed
+ این بازی قبلا نصب شده
+
+
+
+ PKG is a patch, please install the game first!
+ فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید
+
+
+
+ PKG ERROR
+ PKG ارور فایل
+
+
+
+ Extracting PKG %1/%2
+ درحال استخراج PKG %1/%2
+
+
+
+ Extraction Finished
+ استخراج به پایان رسید
+
+
+
+ Game successfully installed at %1
+ بازی با موفقیت در %1 نصب شد
+
+
+
+ File doesn't appear to be a valid PKG file
+ این فایل یک PKG درست به نظر نمی آید
+
+
+
+ CheatsPatches
+
+
+ Cheats / Patches
+ چیت / پچ ها
+
+
+
+ defaultTextEdit_MSG
+ defaultTextEdit_MSG
+
+
+
+ No Image Available
+ تصویری موجود نمی باشد
+
+
+
+ Serial:
+ سریال:
+
+
+
+ Version:
+ ورژن:
+
+
+
+ Size:
+ حجم:
+
+
+
+ Select Cheat File:
+ فایل چیت را انتخاب کنید:
+
+
+
+ Repository:
+ :منبع
+
+
+
+ Download Cheats
+ دانلود چیت ها
+
+
+
+ Delete File
+ پاک کردن فایل
+
+
+
+ No files selected.
+ فایلی انتخاب نشده.
+
+
+
+ You can delete the cheats you don't want after downloading them.
+ شما میتوانید بعد از دانلود چیت هایی که نمیخواهید را پاک کنید
+
+
+
+ Do you want to delete the selected file?\n%1
+ آیا میخواهید فایل های انتخاب شده را پاک کنید؟ \n%1
+
+
+
+ Select Patch File:
+ فایل پچ را انتخاب کنید
+
+
+
+ Download Patches
+ دانلود کردن پچ ها
+
+
+
+ Save
+ ذخیره
+
+
+
+ Cheats
+ چیت ها
+
+
+
+ Patches
+ پچ ها
+
+
+
+ Error
+ ارور
+
+
+
+ No patch selected.
+ هیچ پچ انتخاب نشده
+
+
+
+ Unable to open files.json for reading.
+ .json مشکل در خواندن فایل
+
+
+
+ No patch file found for the current serial.
+ هیچ فایل پچ برای سریال بازی شما پیدا نشد.
+
+
+
+ Unable to open the file for reading.
+ خطا در خواندن فایل
+
+
+
+ Unable to open the file for writing.
+ خطا در نوشتن فایل
+
+
+
+ Failed to parse XML:
+ انجام نشد XML تجزیه فایل:
+
+
+
+ Success
+ عملیات موفق بود
+
+
+
+ Options saved successfully.
+ تغییرات با موفقیت ذخیره شد✅
+
+
+
+ Invalid Source
+ منبع نامعتبر❌
+
+
+
+ The selected source is invalid.
+ منبع انتخاب شده نامعتبر است
+
+
+
+ File Exists
+ فایل وجود دارد
+
+
+
+ File already exists. Do you want to replace it?
+ فایل از قبل وجود دارد. آیا می خواهید آن را جایگزین کنید؟
+
+
+
+ Failed to save file:
+ ذخیره فایل موفقیت آمیز نبود:
+
+
+
+ Failed to download file:
+ خطا در دانلود فایل:
+
+
+
+ Cheats Not Found
+ چیت یافت نشد
+
+
+
+ CheatsNotFound_MSG
+ متاسفانه هیچ چیتی از منبع انتخاب شده پیدا نشد! شما میتوانید منابع دیگری را برای دانلود انتخاب و یا چیت های خود را به صورت دستی واردکنید.
+
+
+
+ Cheats Downloaded Successfully
+ دانلود چیت ها موفقیت آمیز بود✅
+
+
+
+ CheatsDownloadedSuccessfully_MSG
+ تمامی چیت های موجود برای این بازی از منبع انتخاب شده دانلود شد! شما همچنان میتوانید چیت های دیگری را ازمنابع مختلف دانلود کنید و درصورت موجود بودن از آنها استفاده کنید.
+
+
+
+ Failed to save:
+ خطا در ذخیره اطلاعات:
+
+
+
+ Failed to download:
+ خطا در دانلود❌
+
+
+
+ Download Complete
+ پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅
+
+
+
+ DownloadComplete_MSG
+ دانلود با موفقیت به اتمام رسید✅
+
+
+
+ Failed to parse JSON data from HTML.
+ HTML از JSON خطا در تجزیه اطلاعات.
+
+
+
+ Failed to retrieve HTML page.
+ HTML خطا دربازیابی صفحه
+
+
+
+ Failed to open file:
+ خطا در اجرای فایل:
+
+
+
+ XML ERROR:
+ XML ERROR:
+
+
+
+ Failed to open files.json for writing
+ .json خطا در نوشتن فایل
+
+
+
+ Author:
+ تولید کننده:
+
+
+
+ Directory does not exist:
+ پوشه وجود ندارد:
+
+
+
+ Failed to open files.json for reading.
+ .json خطا در خواندن فایل
+
+
+
+ Name:
+ نام:
+
+
+
+ Can't apply cheats before the game is started
+ قبل از شروع بازی نمی توانید تقلب ها را اعمال کنید.
+
+
+
+ SettingsDialog
+
+
+ Save
+ ذخیره
+
+
+
+ Apply
+ اعمال
+
+
+
+ Restore Defaults
+ بازیابی پیش فرض ها
+
+
+
+ Close
+ بستن
+
+
+
+ GameListFrame
+
+
+ Icon
+ آیکون
+
+
+
+ Name
+ نام
+
+
+
+ Serial
+ سریال
+
+
+
+ Region
+ منطقه
+
+
+
+ Firmware
+ فریمور
+
+
+
+ Size
+ اندازه
+
+
+
+ Version
+ نسخه
+
+
+
+ Path
+ مسیر
+
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts
index d667dd379..50e24fce6 100644
--- a/src/qt_gui/translations/fi.ts
+++ b/src/qt_gui/translations/fi.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Pelilista
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa.
+ Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. Jos päivitystä ei näy, se saattaa olla, että sitä ei ole saatavilla tietylle sarjanumerolle ja peliversiolle. Saattaa olla tarpeen päivittää peli.
@@ -898,5 +903,76 @@
Name:
Nimi:
+
+
+ Can't apply cheats before the game is started
+ Ei voi käyttää huijauksia ennen kuin peli on aloitettu.
+
+
+ SettingsDialog
+
+
+ Save
+ Tallenna
+
+
+
+ Apply
+ Ota käyttöön
+
+
+
+ Restore Defaults
+ Palauta oletukset
+
+
+
+ Close
+ Sulje
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikoni
+
+
+
+ Name
+ Nimi
+
+
+
+ Serial
+ Sarjanumero
+
+
+
+ Region
+ Alue
+
+
+
+ Firmware
+ Ohjelmisto
+
+
+
+ Size
+ Koko
+
+
+
+ Version
+ Versio
+
+
+
+ Path
+ Polku
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts
index 388912d23..5ba5e7e2e 100644
--- a/src/qt_gui/translations/fr.ts
+++ b/src/qt_gui/translations/fr.ts
@@ -21,7 +21,7 @@
This software should not be used to play games you have not legally obtained.
- Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement.
+ Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement.
@@ -60,7 +60,7 @@
Directory to install games
- Répertoire d'installation des jeux
+ Répertoire d'installation des jeux
@@ -75,7 +75,7 @@
The value for location to install games is not valid.
- Le répertoire d'installation des jeux n'est pas valide.
+ Le répertoire d'installation des jeux n'est pas valide.
@@ -118,7 +118,7 @@
Copy Serial
- Copier le numéro de série
+ Copier le N° de série
@@ -201,7 +201,7 @@
Exit the application.
- Fermer l'application.
+ Fermer l'application.
@@ -291,7 +291,7 @@
Game List Mode
- Mode d'affichage
+ Mode d'affichage
@@ -301,7 +301,7 @@
Utils
- Utilitaire
+ Utilitaires
@@ -316,12 +316,12 @@
Dark
- Noir
+ Sombre
Light
- Blanc
+ Clair
@@ -341,7 +341,7 @@
toolBar
- Bare d'outils
+ Bare d'outils
@@ -385,7 +385,7 @@
Emulator Language
- Langage de l'émulateur
+ Langage de l'émulateur
@@ -400,7 +400,7 @@
Show Splash
- Afficher l'image du jeu
+ Afficher l'image du jeu
@@ -410,7 +410,7 @@
Username
- Nom d'utilisateur
+ Nom d'utilisateur
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Liste de jeux
+
* Unsupported Vulkan Version
@@ -533,7 +538,7 @@
All Patches available for all games have been downloaded.
- Tous les patchs disponibles pour les jeux ont été téléchargés.
+ Tous les patchs disponibles ont été téléchargés.
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats.
+ Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu. Il peut être nécessaire de mettre à jour le jeu.
@@ -898,5 +903,76 @@
Name:
Nom :
+
+
+ Can't apply cheats before the game is started
+ Impossible d'appliquer les Cheats avant que le jeu ne commence.
+
-
+
+ SettingsDialog
+
+
+ Save
+ Enregistrer
+
+
+
+ Apply
+ Appliquer
+
+
+
+ Restore Defaults
+ Restaurer les paramètres par défaut
+
+
+
+ Close
+ Fermer
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icône
+
+
+
+ Name
+ Nom
+
+
+
+ Serial
+ Série
+
+
+
+ Region
+ Région
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Taille
+
+
+
+ Version
+ Version
+
+
+
+ Path
+ Répertoire
+
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts
index e5fb25a5d..0f69822e7 100644
--- a/src/qt_gui/translations/hu_HU.ts
+++ b/src/qt_gui/translations/hu_HU.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Játéklista
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében.
+ Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. Lehet, hogy frissítenie kell a játékot.
@@ -898,5 +903,76 @@
Name:
Név:
+
+
+ Can't apply cheats before the game is started
+ Nem lehet csalásokat alkalmazni, mielőtt a játék elindul.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Mentés
+
+
+
+ Apply
+ Alkalmaz
+
+
+
+ Restore Defaults
+ Alapértelmezett értékek visszaállítása
+
+
+
+ Close
+ Bezárás
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikon
+
+
+
+ Name
+ Név
+
+
+
+ Serial
+ Sorozatszám
+
+
+
+ Region
+ Régió
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Méret
+
+
+
+ Version
+ Verzió
+
+
+
+ Path
+ Útvonal
+
diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts
index b8ce27cde..6108ffa20 100644
--- a/src/qt_gui/translations/id.ts
+++ b/src/qt_gui/translations/id.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Daftar game
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat.
+ Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. Jika patch tidak muncul, mungkin patch tersebut tidak ada untuk nomor seri dan versi game yang spesifik. Mungkin perlu memperbarui game.
@@ -898,5 +903,76 @@
Name:
Nama:
+
+
+ Can't apply cheats before the game is started
+ Tidak bisa menerapkan cheat sebelum permainan dimulai.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Simpan
+
+
+
+ Apply
+ Terapkan
+
+
+
+ Restore Defaults
+ Kembalikan Pengaturan Default
+
+
+
+ Close
+ Tutup
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikon
+
+
+
+ Name
+ Nama
+
+
+
+ Serial
+ Serial
+
+
+
+ Region
+ Wilayah
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Ukuran
+
+
+
+ Version
+ Versi
+
+
+
+ Path
+ Jalur
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts
index 380a8e43b..39cb35dd3 100644
--- a/src/qt_gui/translations/it.ts
+++ b/src/qt_gui/translations/it.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Elenco giochi
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi.
+ Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. Se la patch non appare, potrebbe essere che non esista per il numero di serie e la versione specifica del gioco. Potrebbe essere necessario aggiornare il gioco.
@@ -898,5 +903,76 @@
Name:
Nome:
+
+
+ Can't apply cheats before the game is started
+ Non è possibile applicare i trucchi prima dell'inizio del gioco.
+
+
+ SettingsDialog
+
+
+ Save
+ Salva
+
+
+
+ Apply
+ Applica
+
+
+
+ Restore Defaults
+ Ripristina Impostazioni Predefinite
+
+
+
+ Close
+ Chiudi
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icona
+
+
+
+ Name
+ Nome
+
+
+
+ Serial
+ Seriale
+
+
+
+ Region
+ Regione
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Dimensione
+
+
+
+ Version
+ Versione
+
+
+
+ Path
+ Percorso
+
+
diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts
index 3d62de0de..680d4ebd5 100644
--- a/src/qt_gui/translations/ja_JP.ts
+++ b/src/qt_gui/translations/ja_JP.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ ゲームリスト
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。
+ パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 パッチが表示されない場合、特定のシリアル番号とバージョンのゲームには存在しない可能性があります。ゲームを更新する必要があるかもしれません。
@@ -898,5 +903,76 @@
Name:
名前:
-
+
+
+ Can't apply cheats before the game is started
+ ゲームが開始される前にチートを適用することはできません。
+
+
+
+ SettingsDialog
+
+
+ Save
+ 保存
+
+
+
+ Apply
+ 適用
+
+
+
+ Restore Defaults
+ デフォルトに戻す
+
+
+
+ Close
+ 閉じる
+
+
+
+ GameListFrame
+
+
+ Icon
+ アイコン
+
+
+
+ Name
+ 名前
+
+
+
+ Serial
+ シリアル
+
+
+
+ Region
+ 地域
+
+
+
+ Firmware
+ ファームウェア
+
+
+
+ Size
+ サイズ
+
+
+
+ Version
+ バージョン
+
+
+
+ Path
+ パス
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts
index f7f171dc6..a167311b9 100644
--- a/src/qt_gui/translations/ko_KR.ts
+++ b/src/qt_gui/translations/ko_KR.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Game List
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats.
+ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game.
@@ -898,5 +903,76 @@
Name:
Name:
-
+
+
+ Can't apply cheats before the game is started
+ Can't apply cheats before the game is started.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Save
+
+
+
+ Apply
+ Apply
+
+
+
+ Restore Defaults
+ Restore Defaults
+
+
+
+ Close
+ Close
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icon
+
+
+
+ Name
+ Name
+
+
+
+ Serial
+ Serial
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Size
+
+
+
+ Version
+ Version
+
+
+
+ Path
+ Path
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts
index 7aa4402e6..2c86ec0a0 100644
--- a/src/qt_gui/translations/lt_LT.ts
+++ b/src/qt_gui/translations/lt_LT.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Žaidimų sąrašas
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais.
+ Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. Jei pleistras nepasirodo, gali būti, kad jo nėra tam tikram žaidimo serijos numeriui ir versijai. Gali prireikti atnaujinti žaidimą.
@@ -898,5 +903,76 @@
Name:
Pavadinimas:
+
+
+ Can't apply cheats before the game is started
+ Negalima taikyti sukčiavimų prieš pradedant žaidimą.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Įrašyti
+
+
+
+ Apply
+ Taikyti
+
+
+
+ Restore Defaults
+ Atkurti numatytuosius nustatymus
+
+
+
+ Close
+ Uždaryti
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikona
+
+
+
+ Name
+ Vardas
+
+
+
+ Serial
+ Serijinis numeris
+
+
+
+ Region
+ Regionas
+
+
+
+ Firmware
+ Firmvare
+
+
+
+ Size
+ Dydis
+
+
+
+ Version
+ Versija
+
+
+
+ Path
+ Kelias
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts
index 76cad45b2..b62791e05 100644
--- a/src/qt_gui/translations/nb.ts
+++ b/src/qt_gui/translations/nb.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Spilliste
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser.
+ Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. Det kan være nødvendig å oppdatere spillet.
@@ -898,5 +903,76 @@
Name:
Navn:
-
+
+
+ Can't apply cheats before the game is started
+ Kan ikke bruke juksetriks før spillet er startet.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Lag
+
+
+
+ Apply
+ Bruk
+
+
+
+ Restore Defaults
+ Gjenopprett standardinnstillinger
+
+
+
+ Close
+ Lukk
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikon
+
+
+
+ Name
+ Navn
+
+
+
+ Serial
+ Serienummer
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Størrelse
+
+
+
+ Version
+ Versjon
+
+
+
+ Path
+ Sti
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts
index b00460479..3d5edfc5d 100644
--- a/src/qt_gui/translations/nl.ts
+++ b/src/qt_gui/translations/nl.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Lijst met spellen
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft.
+ Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. Als de patch niet verschijnt, kan het zijn dat deze niet bestaat voor het specifieke serienummer en de versie van het spel. Het kan nodig zijn om het spel bij te werken.
@@ -898,5 +903,76 @@
Name:
Naam:
+
+
+ Can't apply cheats before the game is started
+ Je kunt geen cheats toepassen voordat het spel is gestart.
+
+
+ SettingsDialog
+
+
+ Save
+ Opslaan
+
+
+
+ Apply
+ Toepassen
+
+
+
+ Restore Defaults
+ Standaardinstellingen herstellen
+
+
+
+ Close
+ Sluiten
+
+
+
+ GameListFrame
+
+
+ Icon
+ Pictogram
+
+
+
+ Name
+ Naam
+
+
+
+ Serial
+ Serienummer
+
+
+
+ Region
+ Regio
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Grootte
+
+
+
+ Version
+ Versie
+
+
+
+ Path
+ Pad
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts
index deaab42fb..af8330bbd 100644
--- a/src/qt_gui/translations/pl_PL.ts
+++ b/src/qt_gui/translations/pl_PL.ts
@@ -251,7 +251,7 @@
Game Install Directory
- Katalog zainstalowanych gry
+ Katalog zainstalowanych gier
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Lista gier
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody.
+ Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. Jeśli poprawka się nie pojawia, możliwe, że nie istnieje dla konkretnego numeru seryjnego i wersji gry. Może być konieczne zaktualizowanie gry.
@@ -898,5 +903,76 @@
Failed to parse JSON:
Nie udało się przeanlizować JSON:
+
+
+ Can't apply cheats before the game is started
+ Nie można zastosować kodów przed uruchomieniem gry.
+
+
+ SettingsDialog
+
+
+ Save
+ Zapisz
+
+
+
+ Apply
+ Zastosuj
+
+
+
+ Restore Defaults
+ Przywróć ustawienia domyślne
+
+
+
+ Close
+ Zamknij
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikona
+
+
+
+ Name
+ Nazwa
+
+
+
+ Serial
+ Numer seryjny
+
+
+
+ Region
+ Region
+
+
+
+ Firmware
+ Oprogramowanie
+
+
+
+ Size
+ Rozmiar
+
+
+
+ Version
+ Wersja
+
+
+
+ Path
+ Ścieżka
+
+
diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts
index 8b4538b9d..e774a30b4 100644
--- a/src/qt_gui/translations/pt_BR.ts
+++ b/src/qt_gui/translations/pt_BR.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Lista de Jogos
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats.
+ Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. Se o patch não aparecer, pode ser que ele não exista para o número de série e a versão específicos do jogo. Pode ser necessário atualizar o jogo.
@@ -897,6 +902,77 @@
Name:
Nome:
+
+
+
+ Can't apply cheats before the game is started
+ Não é possível aplicar cheats antes que o jogo comece.
+
+
+
+ SettingsDialog
+
+
+ Save
+ Salvar
+
+
+
+ Apply
+ Aplicar
+
+
+
+ Restore Defaults
+ Restaurar Padrões
+
+
+
+ Close
+ Fechar
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icone
+
+
+
+ Name
+ Nome
+
+
+
+ Serial
+ Serial
+
+
+
+ Region
+ Região
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Tamanho
+
+
+
+ Version
+ Versão
+
+
+
+ Path
+ Diretório
\ No newline at end of file
diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts
index 8b2fda0c1..56df113f5 100644
--- a/src/qt_gui/translations/ro_RO.ts
+++ b/src/qt_gui/translations/ro_RO.ts
@@ -499,6 +499,11 @@
+
+
+ Game List
+ Lista jocurilor
+
MainWindow
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats.
+ Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. Dacă patch-ul nu apare, este posibil să nu existe pentru seria și versiunea specifică a jocului. Poate fi necesar să actualizați jocul.
@@ -898,5 +903,76 @@
Name:
Nume:
+
+
+ Can't apply cheats before the game is started
+ Nu poți aplica cheats înainte ca jocul să înceapă.
+
+
+ SettingsDialog
+
+
+ Save
+ Salvează
+
+
+
+ Apply
+ Aplică
+
+
+
+ Restore Defaults
+ Restabilește Impozitivele
+
+
+
+ Close
+ Închide
+
+
+
+ GameListFrame
+
+
+ Icon
+ Icon
+
+
+
+ Name
+ Nume
+
+
+
+ Serial
+ Serie
+
+
+
+ Region
+ Regiune
+
+
+
+ Firmware
+ Firmware
+
+
+
+ Size
+ Dimensiune
+
+
+
+ Version
+ Versiune
+
+
+
+ Path
+ Drum
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts
index 9e3446ad4..1eac3e515 100644
--- a/src/qt_gui/translations/ru_RU.ts
+++ b/src/qt_gui/translations/ru_RU.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Список игр
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами.
+ Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. Возможно, потребуется обновить игру.
@@ -898,5 +903,76 @@
Name:
Имя:
+
+
+ Can't apply cheats before the game is started
+ Невозможно применить читы до начала игрыs
+
+
+
+ SettingsDialog
+
+
+ Save
+ Сохранить
+
+
+
+ Apply
+ Применить
+
+
+
+ Restore Defaults
+ Восстановить умолчания
+
+
+
+ Close
+ Закрыть
+
+
+
+ GameListFrame
+
+
+ Icon
+ Иконка
+
+
+
+ Name
+ Название
+
+
+
+ Serial
+ Серийный номер
+
+
+
+ Region
+ Регион
+
+
+
+ Firmware
+ Прошивка
+
+
+
+ Size
+ Размер
+
+
+
+ Version
+ Версия
+
+
+
+ Path
+ Путь
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts
index cd1ee74fb..85110c6ae 100644
--- a/src/qt_gui/translations/sq.ts
+++ b/src/qt_gui/translations/sq.ts
@@ -37,7 +37,7 @@
Loading game list, please wait :3
- Duke ngarkuar listën e lojërave, të lutem prit :3
+ Po ngarkohet lista e lojërave, të lutem prit :3
@@ -181,7 +181,7 @@
Install application from a .pkg file
- Instalo aplikacionin nga skedari .pkg
+ Instalo aplikacionin nga një skedar .pkg
@@ -256,12 +256,12 @@
Download Cheats/Patches
- Shkarko Mashtrimet / Arnat
+ Shkarko Mashtrime/Arna
Dump Game List
- Zbraz Listën e lojërave
+ Zbraz Listën e Lojërave
@@ -276,12 +276,12 @@
File
- Skedar
+ Skedari
View
- Pamje
+ Pamja
@@ -301,7 +301,7 @@
Utils
- Shërbime
+ Shërbimet
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Lista e lojërave
+
* Unsupported Vulkan Version
@@ -642,7 +647,7 @@
- File doesn't appear to be a valid PKG file
+ File doesn't appear to be a valid PKG file
Skedari nuk duket si skedar PKG i vlefshëm
@@ -656,7 +661,7 @@
defaultTextEdit_MSG
- Mashtrimet/Arnat janë eksperimentale.\nPërdori me kujdes.\n\nShkarko mashtrimet individualisht duke zgjedhur depon dhe duke klikuar butonin e shkarkimit.\nNë skedën Arna, mund t'i shkarkosh të gjitha arnat menjëherë, të zgjidhësh cilat dëshiron të përdorësh dhe të ruash zgjedhjen tënde.\n\nMeqenëse ne nuk zhvillojmë Mashtrimet/Arnat,\ntë lutem raporto problemet te autori i mashtrimit.\n\nKe krijuar një mashtrim të ri? Vizito:\nhttps://github.com/shadps4-emu/ps4_cheats
+ Mashtrimet/Arnat janë eksperimentale.\nPërdori me kujdes.\n\nShkarko mashtrimet individualisht duke zgjedhur depon dhe duke klikuar butonin e shkarkimit.\nNë skedën Arna, mund t'i shkarkosh të gjitha arnat menjëherë, të zgjidhësh cilat dëshiron të përdorësh dhe të ruash zgjedhjen tënde.\n\nMeqenëse ne nuk zhvillojmë Mashtrimet/Arnat,\ntë lutem raporto problemet te autori i mashtrimit.\n\nKe krijuar një mashtrim të ri? Vizito:\nhttps://github.com/shadps4-emu/ps4_cheats
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet.
+ Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësosh lojën.
@@ -898,5 +903,76 @@
Name:
Emri:
+
+
+ Can't apply cheats before the game is started
+ Nuk mund të zbatohen mashtrime para se të fillojë loja.
+
+
+ SettingsDialog
+
+
+ Save
+ Ruaj
+
+
+
+ Apply
+ Zbato
+
+
+
+ Restore Defaults
+ Rikthe paracaktimet
+
+
+
+ Close
+ Mbyll
+
+
+
+ GameListFrame
+
+
+ Icon
+ Ikona
+
+
+
+ Name
+ Emri
+
+
+
+ Serial
+ Seriku
+
+
+
+ Region
+ Rajoni
+
+
+
+ Firmware
+ Firmueri
+
+
+
+ Size
+ Madhësia
+
+
+
+ Version
+ Versioni
+
+
+
+ Path
+ Shtegu
+
+
diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts
index e11a2d960..83f62c85e 100644
--- a/src/qt_gui/translations/tr_TR.ts
+++ b/src/qt_gui/translations/tr_TR.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Oyun Listesi
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi.
+ Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. Yamanın görünmemesi durumunda, belirli seri numarası ve oyun sürümü için mevcut olmayabilir. Oyunu güncellemeniz gerekebilir.
@@ -969,5 +974,76 @@
Apply Changes
Değişiklikleri Uygula
+
+
+ Can't apply cheats before the game is started
+ Hileleri oyuna başlamadan önce uygulayamazsınız.
+
+
+ SettingsDialog
+
+
+ Save
+ Kaydet
+
+
+
+ Apply
+ Uygula
+
+
+
+ Restore Defaults
+ Varsayılanları Geri Yükle
+
+
+
+ Close
+ Kapat
+
+
+
+ GameListFrame
+
+
+ Icon
+ Simge
+
+
+
+ Name
+ Ad
+
+
+
+ Serial
+ Seri Numarası
+
+
+
+ Region
+ Bölge
+
+
+
+ Firmware
+ Yazılım
+
+
+
+ Size
+ Boyut
+
+
+
+ Version
+ Sürüm
+
+
+
+ Path
+ Yol
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts
index aead45a63..017e61af5 100644
--- a/src/qt_gui/translations/vi_VN.ts
+++ b/src/qt_gui/translations/vi_VN.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ Danh sách trò chơi
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat.
+ Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. Nếu bản vá không xuất hiện, có thể là nó không tồn tại cho số seri và phiên bản cụ thể của trò chơi. Có thể bạn cần phải cập nhật trò chơi.
@@ -898,5 +903,76 @@
Name:
Tên:
+
+
+ Can't apply cheats before the game is started
+ Không thể áp dụng cheat trước khi trò chơi bắt đầu.
+
+
+ SettingsDialog
+
+
+ Save
+ Lưu
+
+
+
+ Apply
+ Áp dụng
+
+
+
+ Restore Defaults
+ Khôi phục cài đặt mặc định
+
+
+
+ Close
+ Đóng
+
+
+
+ GameListFrame
+
+
+ Icon
+ Biểu tượng
+
+
+
+ Name
+ Tên
+
+
+
+ Serial
+ Số seri
+
+
+
+ Region
+ Khu vực
+
+
+
+ Firmware
+ Phần mềm
+
+
+
+ Size
+ Kích thước
+
+
+
+ Version
+ Phiên bản
+
+
+
+ Path
+ Đường dẫn
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts
index 7584fd5ea..f8675ed01 100644
--- a/src/qt_gui/translations/zh_CN.ts
+++ b/src/qt_gui/translations/zh_CN.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ 游戏列表
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。
+ 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。可能需要更新游戏。
@@ -898,5 +903,76 @@
Name:
名称:
-
+
+
+ Can't apply cheats before the game is started
+ 在游戏开始之前无法应用作弊。
+
+
+
+ SettingsDialog
+
+
+ Save
+ 保存
+
+
+
+ Apply
+ 应用
+
+
+
+ Restore Defaults
+ 恢复默认
+
+
+
+ Close
+ 关闭
+
+
+
+ GameListFrame
+
+
+ Icon
+ 图标
+
+
+
+ Name
+ 名称
+
+
+
+ Serial
+ 序列号
+
+
+
+ Region
+ 区域
+
+
+
+ Firmware
+ 固件
+
+
+
+ Size
+ 大小
+
+
+
+ Version
+ 版本
+
+
+
+ Path
+ 路径
+
+
\ No newline at end of file
diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts
index 3836ed18a..77fe691fe 100644
--- a/src/qt_gui/translations/zh_TW.ts
+++ b/src/qt_gui/translations/zh_TW.ts
@@ -500,6 +500,11 @@
MainWindow
+
+
+ Game List
+ 遊戲列表
+
* Unsupported Vulkan Version
@@ -851,7 +856,7 @@
DownloadComplete_MSG
- 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。
+ 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。可能需要更新遊戲。
@@ -898,5 +903,76 @@
Name:
名稱:
+
+
+ Can't apply cheats before the game is started
+ 在遊戲開始之前無法應用作弊。
+
+
+ SettingsDialog
+
+
+ Save
+ 儲存
+
+
+
+ Apply
+ 應用
+
+
+
+ Restore Defaults
+ 還原預設值
+
+
+
+ Close
+ 關閉
+
+
+
+ GameListFrame
+
+
+ Icon
+ 圖示
+
+
+
+ Name
+ 名稱
+
+
+
+ Serial
+ 序號
+
+
+
+ Region
+ 區域
+
+
+
+ Firmware
+ 固件
+
+
+
+ Size
+ 大小
+
+
+
+ Version
+ 版本
+
+
+
+ Path
+ 路徑
+
+
\ No newline at end of file
diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp
index 57dce6b4e..8b96948cb 100644
--- a/src/qt_gui/trophy_viewer.cpp
+++ b/src/qt_gui/trophy_viewer.cpp
@@ -9,7 +9,8 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo
this->setAttribute(Qt::WA_DeleteOnClose);
tabWidget = new QTabWidget(this);
gameTrpPath_ = gameTrpPath;
- headers << "Trophy"
+ headers << "Unlocked"
+ << "Trophy"
<< "Name"
<< "Description"
<< "ID"
@@ -21,11 +22,11 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo
void TrophyViewer::PopulateTrophyWidget(QString title) {
#ifdef _WIN32
- const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" /
+ const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
title.toStdWString() / "TrophyFiles";
const auto trophyDirQt = QString::fromStdWString(trophyDir.wstring());
#else
- const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" /
+ const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
title.toStdString() / "TrophyFiles";
const auto trophyDirQt = QString::fromStdString(trophyDir.string());
#endif
@@ -61,6 +62,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
QStringList trpId;
QStringList trpHidden;
+ QStringList trpUnlocked;
QStringList trpType;
QStringList trpPid;
QStringList trophyNames;
@@ -81,6 +83,15 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
trpHidden.append(reader.attributes().value("hidden").toString());
trpType.append(reader.attributes().value("ttype").toString());
trpPid.append(reader.attributes().value("pid").toString());
+ if (reader.attributes().hasAttribute("unlockstate")) {
+ if (reader.attributes().value("unlockstate").toString() == "unlocked") {
+ trpUnlocked.append("unlocked");
+ } else {
+ trpUnlocked.append("locked");
+ }
+ } else {
+ trpUnlocked.append("locked");
+ }
}
if (reader.name().toString() == "name" && !trpId.isEmpty()) {
@@ -93,7 +104,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
}
QTableWidget* tableWidget = new QTableWidget(this);
tableWidget->setShowGrid(false);
- tableWidget->setColumnCount(7);
+ tableWidget->setColumnCount(8);
tableWidget->setHorizontalHeaderLabels(headers);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
@@ -105,21 +116,22 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
QTableWidgetItem* item = new QTableWidgetItem();
item->setData(Qt::DecorationRole, icon);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
- tableWidget->setItem(row, 0, item);
+ tableWidget->setItem(row, 1, item);
if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) {
- SetTableItem(tableWidget, row, 1, trophyNames[row]);
- SetTableItem(tableWidget, row, 2, trophyDetails[row]);
- SetTableItem(tableWidget, row, 3, trpId[row]);
- SetTableItem(tableWidget, row, 4, trpHidden[row]);
- SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0)));
- SetTableItem(tableWidget, row, 6, trpPid[row]);
+ SetTableItem(tableWidget, row, 0, trpUnlocked[row]);
+ SetTableItem(tableWidget, row, 2, trophyNames[row]);
+ SetTableItem(tableWidget, row, 3, trophyDetails[row]);
+ SetTableItem(tableWidget, row, 4, trpId[row]);
+ SetTableItem(tableWidget, row, 5, trpHidden[row]);
+ SetTableItem(tableWidget, row, 6, GetTrpType(trpType[row].at(0)));
+ SetTableItem(tableWidget, row, 7, trpPid[row]);
}
tableWidget->verticalHeader()->resizeSection(row, icon.height());
row++;
}
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
int width = 16;
- for (int i = 0; i < 7; i++) {
+ for (int i = 0; i < 8; i++) {
width += tableWidget->horizontalHeader()->sectionSize(i);
}
tableWidget->resize(width, 720);
diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp
index eb66724e9..a26d84308 100644
--- a/src/sdl_window.cpp
+++ b/src/sdl_window.cpp
@@ -9,6 +9,7 @@
#include "common/config.h"
#include "common/version.h"
#include "core/libraries/pad/pad.h"
+#include "imgui/renderer/imgui_core.h"
#include "input/controller.h"
#include "sdl_window.h"
#include "video_core/renderdoc.h"
@@ -80,6 +81,10 @@ void WindowSDL::waitEvent() {
return;
}
+ if (ImGui::Core::ProcessEvent(&event)) {
+ return;
+ }
+
switch (event.type) {
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
@@ -119,6 +124,7 @@ void WindowSDL::setKeysBindingsMap(const std::map& bindingsMap
void WindowSDL::onResize() {
SDL_GetWindowSizeInPixels(window, &width, &height);
+ ImGui::Core::OnResize();
}
using Libraries::Pad::OrbisPadButtonDataOffset;
@@ -279,6 +285,12 @@ void WindowSDL::onKeyPress(const SDL_Event* event) {
SDL_SetWindowFullscreen(window, !is_fullscreen);
}
break;
+ case SDLK_F12:
+ if (event->type == SDL_EVENT_KEY_DOWN) {
+ // Trigger rdoc capture
+ VideoCore::TriggerCapture();
+ }
+ break;
default:
break;
}
@@ -494,4 +506,4 @@ int WindowSDL::sdlGamepadToOrbisButton(u8 button) {
}
}
-} // namespace Frontend
+} // namespace Frontend
\ No newline at end of file
diff --git a/src/sdl_window.h b/src/sdl_window.h
index 550b80fa3..10ecd081c 100644
--- a/src/sdl_window.h
+++ b/src/sdl_window.h
@@ -61,6 +61,10 @@ public:
return is_open;
}
+ [[nodiscard]] SDL_Window* GetSdlWindow() const {
+ return window;
+ }
+
WindowSystemInfo getWindowInfo() const {
return window_info;
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
index c681be97c..11d2a1dde 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -208,6 +208,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
if (info.uses_group_quad) {
ctx.AddCapability(spv::Capability::GroupNonUniformQuad);
}
+ if (info.uses_group_ballot) {
+ ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
+ }
switch (program.info.stage) {
case Stage::Compute: {
const std::array workgroup_size{ctx.runtime_info.cs_info.workgroup_size};
@@ -327,6 +330,10 @@ void EmitGetVccHi(EmitContext& ctx) {
UNREACHABLE_MSG("Unreachable instruction");
}
+void EmitGetM0(EmitContext& ctx) {
+ UNREACHABLE_MSG("Unreachable instruction");
+}
+
void EmitSetScc(EmitContext& ctx) {
UNREACHABLE_MSG("Unreachable instruction");
}
@@ -351,4 +358,8 @@ void EmitSetVccHi(EmitContext& ctx) {
UNREACHABLE_MSG("Unreachable instruction");
}
+void EmitSetM0(EmitContext& ctx) {
+ UNREACHABLE_MSG("Unreachable instruction");
+}
+
} // namespace Shader::Backend::SPIRV
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
index 1d553dc56..a58b2778f 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
@@ -152,4 +152,20 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicExchange);
}
+Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) {
+ auto& buffer = ctx.buffers[binding];
+ const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value,
+ ctx.ConstU32(gds_addr));
+ const auto [scope, semantics]{AtomicArgs(ctx)};
+ return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics);
+}
+
+Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) {
+ auto& buffer = ctx.buffers[binding];
+ const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value,
+ ctx.ConstU32(gds_addr));
+ const auto [scope, semantics]{AtomicArgs(ctx)};
+ return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics);
+}
+
} // namespace Shader::Backend::SPIRV
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 39a214fa0..7df62a910 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -133,10 +133,6 @@ Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) {
return ctx.OpLoad(buffer.data_types->Get(1), ptr);
}
-Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index) {
- return ctx.OpBitcast(ctx.U32[1], EmitReadConstBuffer(ctx, handle, index));
-}
-
Id EmitReadStepRate(EmitContext& ctx, int rate_idx) {
return ctx.OpLoad(
ctx.U32[1], ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]),
@@ -222,12 +218,8 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen
ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value));
}
-Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
- return EmitLoadBufferF32(ctx, inst, handle, address);
-}
-
template
-static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) {
+static Id EmitLoadBufferU32xN(EmitContext& ctx, u32 handle, Id address) {
auto& buffer = ctx.buffers[handle];
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
@@ -246,20 +238,20 @@ static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) {
}
}
-Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
- return EmitLoadBufferF32xN<1>(ctx, handle, address);
+Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
+ return EmitLoadBufferU32xN<1>(ctx, handle, address);
}
-Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
- return EmitLoadBufferF32xN<2>(ctx, handle, address);
+Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
+ return EmitLoadBufferU32xN<2>(ctx, handle, address);
}
-Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
- return EmitLoadBufferF32xN<3>(ctx, handle, address);
+Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
+ return EmitLoadBufferU32xN<3>(ctx, handle, address);
}
-Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
- return EmitLoadBufferF32xN<4>(ctx, handle, address);
+Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) {
+ return EmitLoadBufferU32xN<4>(ctx, handle, address);
}
Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
@@ -275,7 +267,7 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr
}
template
-static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) {
+static void EmitStoreBufferU32xN(EmitContext& ctx, u32 handle, Id address, Id value) {
auto& buffer = ctx.buffers[handle];
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
@@ -287,29 +279,25 @@ static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id va
const Id index_i = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i));
const Id ptr =
ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index_i);
- ctx.OpStore(ptr, ctx.OpCompositeExtract(ctx.F32[1], value, i));
+ ctx.OpStore(ptr, ctx.OpCompositeExtract(buffer.data_types->Get(1), value, i));
}
}
}
-void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
- EmitStoreBufferF32xN<1>(ctx, handle, address, value);
-}
-
-void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
- EmitStoreBufferF32xN<2>(ctx, handle, address, value);
-}
-
-void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
- EmitStoreBufferF32xN<3>(ctx, handle, address, value);
-}
-
-void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
- EmitStoreBufferF32xN<4>(ctx, handle, address, value);
-}
-
void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
- EmitStoreBufferF32xN<1>(ctx, handle, address, value);
+ EmitStoreBufferU32xN<1>(ctx, handle, address, value);
+}
+
+void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
+ EmitStoreBufferU32xN<2>(ctx, handle, address, value);
+}
+
+void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
+ EmitStoreBufferU32xN<3>(ctx, handle, address, value);
+}
+
+void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
+ EmitStoreBufferU32xN<4>(ctx, handle, address, value);
}
void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
@@ -317,7 +305,7 @@ void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a
const Id tex_buffer = ctx.OpLoad(buffer.image_type, buffer.id);
const Id coord = ctx.OpIAdd(ctx.U32[1], address, buffer.coord_offset);
if (buffer.is_integer) {
- value = ctx.OpBitcast(ctx.U32[4], value);
+ value = ctx.OpBitcast(ctx.S32[4], value);
}
ctx.OpImageWrite(tex_buffer, coord, value);
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index ce4d3f137..e506ced3a 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -36,12 +36,14 @@ void EmitGetVcc(EmitContext& ctx);
void EmitGetSccLo(EmitContext& ctx);
void EmitGetVccLo(EmitContext& ctx);
void EmitGetVccHi(EmitContext& ctx);
+void EmitGetM0(EmitContext& ctx);
void EmitSetScc(EmitContext& ctx);
void EmitSetExec(EmitContext& ctx);
void EmitSetVcc(EmitContext& ctx);
void EmitSetSccLo(EmitContext& ctx);
void EmitSetVccLo(EmitContext& ctx);
void EmitSetVccHi(EmitContext& ctx);
+void EmitSetM0(EmitContext& ctx);
void EmitFPCmpClass32(EmitContext& ctx);
void EmitPrologue(EmitContext& ctx);
void EmitEpilogue(EmitContext& ctx);
@@ -62,25 +64,16 @@ void EmitGetGotoVariable(EmitContext& ctx);
void EmitSetScc(EmitContext& ctx);
Id EmitReadConst(EmitContext& ctx);
Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index);
-Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index);
-Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-Id EmitLoadBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
-void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
-void EmitStoreBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
+Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
+Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
+Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
+Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
+void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
+void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
+void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
+void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
@@ -404,12 +397,13 @@ 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 EmitLaneId(EmitContext& ctx);
Id EmitWarpId(EmitContext& ctx);
Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index);
Id EmitReadFirstLane(EmitContext& ctx, Id value);
Id EmitReadLane(EmitContext& ctx, Id value, u32 lane);
Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane);
+Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding);
+Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding);
} // namespace Shader::Backend::SPIRV
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
index 898de8b57..2d13d09f0 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp
@@ -27,7 +27,8 @@ Id EmitReadFirstLane(EmitContext& ctx, Id value) {
}
Id EmitReadLane(EmitContext& ctx, Id value, u32 lane) {
- UNREACHABLE();
+ return ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value,
+ ctx.ConstU32(lane));
}
Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index b65cbdf46..8554f8615 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -324,16 +324,18 @@ void EmitContext::DefineOutputs() {
void EmitContext::DefinePushDataBlock() {
// Create push constants block for instance steps rates
- const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4]), "AuxData")};
+ const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4]), "AuxData")};
Decorate(struct_type, spv::Decoration::Block);
MemberName(struct_type, 0, "sr0");
MemberName(struct_type, 1, "sr1");
MemberName(struct_type, 2, "buf_offsets0");
MemberName(struct_type, 3, "buf_offsets1");
+ MemberName(struct_type, 4, "buf_offsets2");
MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U);
MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U);
MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U);
MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U);
+ MemberDecorate(struct_type, 4, spv::Decoration::Offset, 40U);
push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant);
Name(push_data_block, "push_data");
interfaces.push_back(push_data_block);
diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp
index b5c02d747..4452b4fda 100644
--- a/src/shader_recompiler/frontend/decode.cpp
+++ b/src/shader_recompiler/frontend/decode.cpp
@@ -5,6 +5,8 @@
#include "common/assert.h"
#include "shader_recompiler/frontend/decode.h"
+#include "magic_enum.hpp"
+
namespace Shader::Gcn {
namespace bit {
@@ -253,7 +255,9 @@ void GcnDecodeContext::updateInstructionMeta(InstEncoding encoding) {
ASSERT_MSG(instFormat.src_type != ScalarType::Undefined &&
instFormat.dst_type != ScalarType::Undefined,
- "TODO: Instruction format table not complete, please fix it manually.");
+ "Instruction format table incomplete for opcode {} ({}, encoding = {})",
+ magic_enum::enum_name(m_instruction.opcode), u32(m_instruction.opcode),
+ magic_enum::enum_name(encoding));
m_instruction.inst_class = instFormat.inst_class;
m_instruction.category = instFormat.inst_category;
diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp
index 8df3ac364..34bd618cc 100644
--- a/src/shader_recompiler/frontend/format.cpp
+++ b/src/shader_recompiler/frontend/format.cpp
@@ -1786,8 +1786,7 @@ constexpr std::array InstructionFormatVOP3 = {{
constexpr std::array InstructionFormatVOP1 = {{
// 0 = V_NOP
- {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Undefined,
- ScalarType::Undefined},
+ {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Any, ScalarType::Any},
// 1 = V_MOV_B32
{InstClass::VectorRegMov, InstCategory::VectorALU, 1, 1, ScalarType::Uint32,
ScalarType::Uint32},
@@ -3603,8 +3602,8 @@ constexpr std::array InstructionFormatMIMG = {{
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
// 79 = IMAGE_GATHER4_C_LZ
- {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
- ScalarType::Undefined},
+ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
+ ScalarType::Uint32},
// 80 = IMAGE_GATHER4_O
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
@@ -3656,8 +3655,8 @@ constexpr std::array InstructionFormatMIMG = {{
{},
{},
// 104 = IMAGE_SAMPLE_CD
- {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
- ScalarType::Undefined},
+ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Float32,
+ ScalarType::Float32},
// 105 = IMAGE_SAMPLE_CD_CL
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp
index 7c23c7284..d01c1977a 100644
--- a/src/shader_recompiler/frontend/translate/data_share.cpp
+++ b/src/shader_recompiler/frontend/translate/data_share.cpp
@@ -43,6 +43,10 @@ void Translator::EmitDataShare(const GcnInst& inst) {
return DS_MIN_U32(inst, false, true);
case Opcode::DS_MAX_RTN_U32:
return DS_MAX_U32(inst, false, true);
+ case Opcode::DS_APPEND:
+ return DS_APPEND(inst);
+ case Opcode::DS_CONSUME:
+ return DS_CONSUME(inst);
default:
LogMissingOpcode(inst);
}
@@ -171,9 +175,9 @@ void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) {
const IR::U32 value{GetSrc(inst.src[0])};
if (info.stage != Stage::Compute) {
- ir.SetScalarReg(dst, value);
+ SetDst(inst.dst[0], value);
} else {
- ir.SetScalarReg(dst, ir.ReadFirstLane(value));
+ SetDst(inst.dst[0], ir.ReadFirstLane(value));
}
}
@@ -192,4 +196,18 @@ void Translator::V_WRITELANE_B32(const GcnInst& inst) {
ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane));
}
+void Translator::DS_APPEND(const GcnInst& inst) {
+ const u32 inst_offset = inst.control.ds.offset0;
+ const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset));
+ const IR::U32 prev = ir.DataAppend(gds_offset);
+ SetDst(inst.dst[0], prev);
+}
+
+void Translator::DS_CONSUME(const GcnInst& inst) {
+ const u32 inst_offset = inst.control.ds.offset0;
+ const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset));
+ const IR::U32 prev = ir.DataConsume(gds_offset);
+ SetDst(inst.dst[0], prev);
+}
+
} // namespace Shader::Gcn
diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp
index d4db09a64..18e830f7b 100644
--- a/src/shader_recompiler/frontend/translate/export.cpp
+++ b/src/shader_recompiler/frontend/translate/export.cpp
@@ -31,6 +31,12 @@ void Translator::EmitExport(const GcnInst& inst) {
case MrtSwizzle::Alt:
static constexpr std::array AltSwizzle = {2, 1, 0, 3};
return AltSwizzle[comp];
+ case MrtSwizzle::Reverse:
+ static constexpr std::array RevSwizzle = {3, 2, 1, 0};
+ return RevSwizzle[comp];
+ case MrtSwizzle::ReverseAlt:
+ static constexpr std::array AltRevSwizzle = {3, 0, 1, 2};
+ return AltRevSwizzle[comp];
default:
UNREACHABLE();
}
diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp
index af258cd19..5b194db88 100644
--- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp
@@ -46,7 +46,11 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
case Opcode::S_ADD_I32:
return S_ADD_I32(inst);
case Opcode::S_AND_B32:
- return S_AND_B32(inst);
+ return S_AND_B32(NegateMode::None, inst);
+ case Opcode::S_NAND_B32:
+ return S_AND_B32(NegateMode::Result, inst);
+ case Opcode::S_ANDN2_B32:
+ return S_AND_B32(NegateMode::Src1, inst);
case Opcode::S_ASHR_I32:
return S_ASHR_I32(inst);
case Opcode::S_OR_B32:
@@ -73,9 +77,13 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
case Opcode::S_SUB_I32:
return S_SUB_U32(inst);
case Opcode::S_MIN_U32:
- return S_MIN_U32(inst);
+ return S_MIN_U32(false, inst);
+ case Opcode::S_MIN_I32:
+ return S_MIN_U32(true, inst);
case Opcode::S_MAX_U32:
- return S_MAX_U32(inst);
+ return S_MAX_U32(false, inst);
+ case Opcode::S_MAX_I32:
+ return S_MAX_U32(true, inst);
case Opcode::S_WQM_B64:
break;
default:
@@ -377,10 +385,16 @@ void Translator::S_ADD_I32(const GcnInst& inst) {
// TODO: Overflow flag
}
-void Translator::S_AND_B32(const GcnInst& inst) {
+void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
- const IR::U32 src1{GetSrc(inst.src[1])};
- const IR::U32 result{ir.BitwiseAnd(src0, src1)};
+ IR::U32 src1{GetSrc(inst.src[1])};
+ if (negate == NegateMode::Src1) {
+ src1 = ir.BitwiseNot(src1);
+ }
+ IR::U32 result{ir.BitwiseAnd(src0, src1)};
+ if (negate == NegateMode::Result) {
+ result = ir.BitwiseNot(result);
+ }
SetDst(inst.dst[0], result);
ir.SetScc(ir.INotEqual(result, ir.Imm32(0)));
}
@@ -533,18 +547,18 @@ void Translator::S_ADDC_U32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry));
}
-void Translator::S_MAX_U32(const GcnInst& inst) {
+void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
- const IR::U32 result = ir.UMax(src0, src1);
+ const IR::U32 result = ir.IMax(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
-void Translator::S_MIN_U32(const GcnInst& inst) {
+void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
- const IR::U32 result = ir.UMin(src0, src1);
+ const IR::U32 result = ir.IMin(src0, src1, is_signed);
SetDst(inst.dst[0], result);
ir.SetScc(ir.IEqual(result, src0));
}
diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp
index b33746c7b..c9144fac1 100644
--- a/src/shader_recompiler/frontend/translate/translate.cpp
+++ b/src/shader_recompiler/frontend/translate/translate.cpp
@@ -153,10 +153,11 @@ T Translator::GetSrc(const InstOperand& operand) {
break;
case OperandField::M0:
if constexpr (is_float) {
- UNREACHABLE();
+ value = ir.BitCast(ir.GetM0());
} else {
- return m0_value;
+ value = ir.GetM0();
}
+ break;
default:
UNREACHABLE();
}
@@ -170,7 +171,7 @@ T Translator::GetSrc(const InstOperand& operand) {
}
} else {
if (operand.input_modifier.abs) {
- LOG_WARNING(Render_Vulkan, "Input abs modifier on integer instruction");
+ value = ir.IAbs(value);
}
if (operand.input_modifier.neg) {
UNREACHABLE();
@@ -296,8 +297,7 @@ void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) {
case OperandField::VccHi:
return ir.SetVccHi(result);
case OperandField::M0:
- m0_value = result;
- break;
+ return ir.SetM0(result);
default:
UNREACHABLE();
}
diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h
index 0c1f3a587..888d3451b 100644
--- a/src/shader_recompiler/frontend/translate/translate.h
+++ b/src/shader_recompiler/frontend/translate/translate.h
@@ -84,7 +84,7 @@ public:
void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst);
void S_AND_B64(NegateMode negate, const GcnInst& inst);
void S_ADD_I32(const GcnInst& inst);
- void S_AND_B32(const GcnInst& inst);
+ void S_AND_B32(NegateMode negate, const GcnInst& inst);
void S_ASHR_I32(const GcnInst& inst);
void S_OR_B32(const GcnInst& inst);
void S_LSHR_B32(const GcnInst& inst);
@@ -101,8 +101,8 @@ public:
void S_ADDC_U32(const GcnInst& inst);
void S_MULK_I32(const GcnInst& inst);
void S_ADDK_I32(const GcnInst& inst);
- void S_MAX_U32(const GcnInst& inst);
- void S_MIN_U32(const GcnInst& inst);
+ void S_MAX_U32(bool is_signed, const GcnInst& inst);
+ void S_MIN_U32(bool is_signed, const GcnInst& inst);
void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst);
// Scalar Memory
@@ -173,7 +173,7 @@ public:
void V_BCNT_U32_B32(const GcnInst& inst);
void V_COS_F32(const GcnInst& inst);
void V_MAX3_F32(const GcnInst& inst);
- void V_MAX3_U32(const GcnInst& inst);
+ void V_MAX3_U32(bool is_signed, const GcnInst& inst);
void V_CVT_I32_F32(const GcnInst& inst);
void V_MIN_I32(const GcnInst& inst);
void V_MUL_LO_U32(const GcnInst& inst);
@@ -192,6 +192,9 @@ public:
void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst);
void V_BFM_B32(const GcnInst& inst);
void V_FFBH_U32(const GcnInst& inst);
+ void V_MOVRELS_B32(const GcnInst& inst);
+ void V_MOVRELD_B32(const GcnInst& inst);
+ void V_MOVRELSD_B32(const GcnInst& inst);
// Vector Memory
void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst);
@@ -214,6 +217,8 @@ public:
void V_READFIRSTLANE_B32(const GcnInst& inst);
void V_READLANE_B32(const GcnInst& inst);
void V_WRITELANE_B32(const GcnInst& inst);
+ void DS_APPEND(const GcnInst& inst);
+ void DS_CONSUME(const GcnInst& inst);
void S_BARRIER();
// MIMG
@@ -233,6 +238,9 @@ private:
void SetDst(const InstOperand& operand, const IR::U32F32& value);
void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw);
+ IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0);
+ void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0);
+
void LogMissingOpcode(const GcnInst& inst);
private:
@@ -240,7 +248,6 @@ private:
Info& info;
const RuntimeInfo& runtime_info;
const Profile& profile;
- IR::U32 m0_value;
bool opcode_missing = false;
};
diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp
index a07e70785..b4470ee39 100644
--- a/src/shader_recompiler/frontend/translate/vector_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "shader_recompiler/frontend/opcodes.h"
#include "shader_recompiler/frontend/translate/translate.h"
namespace Shader::Gcn {
@@ -226,7 +227,9 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
case Opcode::V_MAX3_F32:
return V_MAX3_F32(inst);
case Opcode::V_MAX3_U32:
- return V_MAX3_U32(inst);
+ return V_MAX3_U32(false, inst);
+ case Opcode::V_MAX3_I32:
+ return V_MAX_U32(true, inst);
case Opcode::V_TRUNC_F32:
return V_TRUNC_F32(inst);
case Opcode::V_CEIL_F32:
@@ -309,6 +312,12 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_MBCNT_U32_B32(true, inst);
case Opcode::V_MBCNT_HI_U32_B32:
return V_MBCNT_U32_B32(false, inst);
+ case Opcode::V_MOVRELS_B32:
+ return V_MOVRELS_B32(inst);
+ case Opcode::V_MOVRELD_B32:
+ return V_MOVRELD_B32(inst);
+ case Opcode::V_MOVRELSD_B32:
+ return V_MOVRELSD_B32(inst);
case Opcode::V_NOP:
return;
@@ -824,11 +833,11 @@ void Translator::V_MAX3_F32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2)));
}
-void Translator::V_MAX3_U32(const GcnInst& inst) {
+void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 src2{GetSrc(inst.src[2])};
- SetDst(inst.dst[0], ir.UMax(src0, ir.UMax(src1, src2)));
+ SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed));
}
void Translator::V_CVT_I32_F32(const GcnInst& inst) {
@@ -960,14 +969,29 @@ void Translator::V_FFBL_B32(const GcnInst& inst) {
}
void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) {
- const IR::U32 src0{GetSrc(inst.src[0])};
- const IR::U32 src1{GetSrc(inst.src[1])};
if (!is_low) {
- ASSERT(src0.IsImmediate() && src0.U32() == ~0U && src1.IsImmediate() && src1.U32() == 0U);
- return;
+ // v_mbcnt_hi_u32_b32 v2, -1, 0
+ if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 &&
+ inst.src[1].field == OperandField::ConstZero) {
+ return;
+ }
+ // v_mbcnt_hi_u32_b32 vX, exec_hi, 0
+ if (inst.src[0].field == OperandField::ExecHi &&
+ inst.src[1].field == OperandField::ConstZero) {
+ return;
+ }
+ } else {
+ // v_mbcnt_lo_u32_b32 v2, -1, vX
+ // used combined with above to fetch lane id in non-compute stages
+ if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) {
+ SetDst(inst.dst[0], ir.LaneId());
+ }
+ // v_mbcnt_lo_u32_b32 v20, exec_lo, vX
+ // used combined in above for append buffer indexing.
+ if (inst.src[0].field == OperandField::ExecLo) {
+ SetDst(inst.dst[0], ir.Imm32(0));
+ }
}
- ASSERT(src0.IsImmediate() && src0.U32() == ~0U);
- SetDst(inst.dst[0], ir.LaneId());
}
void Translator::V_BFM_B32(const GcnInst& inst) {
@@ -990,4 +1014,52 @@ void Translator::V_FFBH_U32(const GcnInst& inst) {
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))});
}
+// TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of
+// [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to
+
+IR::U32 Translator::VMovRelSHelper(u32 src_vgprno, const IR::U32 m0) {
+ // Read from VGPR0 by default when src_vgprno + m0 > num_allocated_vgprs
+ IR::U32 src_val = ir.GetVectorReg(IR::VectorReg::V0);
+ for (u32 i = src_vgprno; i < runtime_info.num_allocated_vgprs; i++) {
+ const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - src_vgprno));
+ src_val =
+ IR::U32{ir.Select(cond, ir.GetVectorReg(IR::VectorReg::V0 + i), src_val)};
+ }
+ return src_val;
+}
+
+void Translator::VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0) {
+ for (u32 i = dst_vgprno; i < runtime_info.num_allocated_vgprs; i++) {
+ const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - dst_vgprno));
+ const IR::U32 dst_val =
+ IR::U32{ir.Select(cond, src_val, ir.GetVectorReg(IR::VectorReg::V0 + i))};
+ ir.SetVectorReg(IR::VectorReg::V0 + i, dst_val);
+ }
+}
+
+void Translator::V_MOVRELS_B32(const GcnInst& inst) {
+ u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0);
+ const IR::U32 m0 = ir.GetM0();
+
+ const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0);
+ SetDst(inst.dst[0], src_val);
+}
+
+void Translator::V_MOVRELD_B32(const GcnInst& inst) {
+ const IR::U32 src_val{GetSrc(inst.src[0])};
+ u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0);
+ IR::U32 m0 = ir.GetM0();
+
+ VMovRelDHelper(dst_vgprno, src_val, m0);
+}
+
+void Translator::V_MOVRELSD_B32(const GcnInst& inst) {
+ u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0);
+ u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0);
+ IR::U32 m0 = ir.GetM0();
+
+ const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0);
+ VMovRelDHelper(dst_vgprno, src_val, m0);
+}
+
} // namespace Shader::Gcn
diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp
index 73530dade..f602e762e 100644
--- a/src/shader_recompiler/frontend/translate/vector_memory.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp
@@ -18,9 +18,11 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
case Opcode::IMAGE_SAMPLE_B:
case Opcode::IMAGE_SAMPLE_C_LZ_O:
case Opcode::IMAGE_SAMPLE_D:
+ case Opcode::IMAGE_SAMPLE_CD:
return IMAGE_SAMPLE(inst);
- case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_LZ:
+ case Opcode::IMAGE_GATHER4_C:
+ case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
return IMAGE_GATHER(inst);
case Opcode::IMAGE_ATOMIC_ADD:
@@ -98,6 +100,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
return BUFFER_STORE(2, true, inst);
case Opcode::TBUFFER_STORE_FORMAT_XYZ:
return BUFFER_STORE(3, true, inst);
+ case Opcode::TBUFFER_STORE_FORMAT_XYZW:
+ return BUFFER_STORE(4, true, inst);
case Opcode::BUFFER_STORE_DWORD:
return BUFFER_STORE(1, false, inst);
@@ -113,6 +117,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
return BUFFER_ATOMIC(AtomicOp::Add, inst);
case Opcode::BUFFER_ATOMIC_SWAP:
return BUFFER_ATOMIC(AtomicOp::Swap, inst);
+ case Opcode::BUFFER_ATOMIC_UMIN:
+ return BUFFER_ATOMIC(AtomicOp::Umin, inst);
+ case Opcode::BUFFER_ATOMIC_UMAX:
+ return BUFFER_ATOMIC(AtomicOp::Umax, inst);
default:
LogMissingOpcode(inst);
}
@@ -143,10 +151,6 @@ void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) {
void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg;
- if (mimg.da) {
- LOG_WARNING(Render_Vulkan, "Image instruction declares an array");
- }
-
IR::VectorReg addr_reg{inst.src[0].code};
IR::VectorReg dest_reg{inst.dst[0].code};
const IR::ScalarReg tsharp_reg{inst.src[2].code * 4};
@@ -280,6 +284,7 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) {
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
+ info.has_offset.Assign(flags.test(MimgModifier::Offset));
// info.explicit_lod.Assign(explicit_lod);
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
@@ -384,11 +389,11 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst)
const IR::Value value = ir.LoadBuffer(num_dwords, handle, address, info);
const IR::VectorReg dst_reg{inst.src[1].code};
if (num_dwords == 1) {
- ir.SetVectorReg(dst_reg, IR::F32{value});
+ ir.SetVectorReg(dst_reg, IR::U32{value});
return;
}
for (u32 i = 0; i < num_dwords; i++) {
- ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)});
+ ir.SetVectorReg(dst_reg + i, IR::U32{ir.CompositeExtract(value, i)});
}
}
@@ -452,21 +457,18 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst
const IR::VectorReg src_reg{inst.src[1].code};
switch (num_dwords) {
case 1:
- value = ir.GetVectorReg(src_reg);
+ value = ir.GetVectorReg(src_reg);
break;
case 2:
- value = ir.CompositeConstruct(ir.GetVectorReg(src_reg),
- ir.GetVectorReg(src_reg + 1));
+ value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1));
break;
case 3:
- value = ir.CompositeConstruct(ir.GetVectorReg(src_reg),
- ir.GetVectorReg(src_reg + 1),
- ir.GetVectorReg(src_reg + 2));
+ value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1),
+ ir.GetVectorReg(src_reg + 2));
break;
case 4:
- value = ir.CompositeConstruct(
- ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1),
- ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3));
+ value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1),
+ ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3));
break;
}
const IR::Value handle =
@@ -514,6 +516,15 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
const IR::VectorReg vaddr{inst.src[0].code};
const IR::VectorReg vdata{inst.src[1].code};
const IR::ScalarReg srsrc{inst.src[2].code * 4};
+ const IR::Value address = [&] -> IR::Value {
+ if (mubuf.idxen && mubuf.offen) {
+ return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1));
+ }
+ if (mubuf.idxen || mubuf.offen) {
+ return ir.GetVectorReg(vaddr);
+ }
+ return {};
+ }();
const IR::U32 soffset{GetSrc(inst.src[3])};
ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported");
@@ -523,7 +534,6 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
info.offset_enable.Assign(mubuf.offen);
IR::Value vdata_val = ir.GetVectorReg(vdata);
- const IR::U32 address = ir.GetVectorReg(vaddr);
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1),
ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3));
diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h
index cdc17304c..c4e16b7a4 100644
--- a/src/shader_recompiler/info.h
+++ b/src/shader_recompiler/info.h
@@ -1,6 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-
#pragma once
#include
@@ -37,12 +36,13 @@ struct BufferResource {
u32 dword_offset;
IR::Type used_types;
AmdGpu::Buffer inline_cbuf;
+ bool is_gds_buffer{};
bool is_instance_data{};
bool is_written{};
bool IsStorage(AmdGpu::Buffer buffer) const noexcept {
static constexpr size_t MaxUboSize = 65536;
- return buffer.GetSize() > MaxUboSize || is_written;
+ return buffer.GetSize() > MaxUboSize || is_written || is_gds_buffer;
}
constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept;
@@ -88,7 +88,7 @@ struct PushData {
u32 step0;
u32 step1;
- std::array buf_offsets;
+ std::array buf_offsets;
void AddOffset(u32 binding, u32 offset) {
ASSERT(offset < 256 && binding < buf_offsets.size());
@@ -165,6 +165,7 @@ struct Info {
bool has_image_query{};
bool uses_lane_id{};
bool uses_group_quad{};
+ bool uses_group_ballot{};
bool uses_shared{};
bool uses_fp16{};
bool uses_step_rates{};
@@ -180,6 +181,7 @@ struct Info {
const u32* base = user_data.data();
if (ptr_index != IR::NumScalarRegs) {
std::memcpy(&base, &user_data[ptr_index], sizeof(base));
+ base = reinterpret_cast(VAddr(base) & 0xFFFFFFFFFFFFULL);
}
std::memcpy(&data, base + dword_offset, sizeof(T));
return data;
diff --git a/src/shader_recompiler/ir/basic_block.h b/src/shader_recompiler/ir/basic_block.h
index 1eb11469c..11ae969bc 100644
--- a/src/shader_recompiler/ir/basic_block.h
+++ b/src/shader_recompiler/ir/basic_block.h
@@ -147,6 +147,7 @@ public:
/// Intrusively store the value of a register in the block.
std::array ssa_sreg_values;
+ std::array ssa_sbit_values;
std::array ssa_vreg_values;
bool has_multiple_predecessors{false};
diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp
index 473ae4f66..7e52cfb5f 100644
--- a/src/shader_recompiler/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/ir/ir_emitter.cpp
@@ -217,6 +217,10 @@ U32 IREmitter::GetVccHi() {
return Inst(Opcode::GetVccHi);
}
+U32 IREmitter::GetM0() {
+ return Inst(Opcode::GetM0);
+}
+
void IREmitter::SetScc(const U1& value) {
Inst(Opcode::SetScc, value);
}
@@ -241,6 +245,10 @@ void IREmitter::SetVccHi(const U32& value) {
Inst(Opcode::SetVccHi, value);
}
+void IREmitter::SetM0(const U32& value) {
+ Inst(Opcode::SetM0, value);
+}
+
F32 IREmitter::GetAttribute(IR::Attribute attribute, u32 comp) {
return Inst(Opcode::GetAttribute, attribute, Imm32(comp));
}
@@ -305,21 +313,21 @@ U32 IREmitter::ReadConst(const Value& base, const U32& offset) {
return Inst(Opcode::ReadConst, base, offset);
}
-F32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) {
- return Inst(Opcode::ReadConstBuffer, handle, index);
+U32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) {
+ return Inst(Opcode::ReadConstBuffer, handle, index);
}
Value IREmitter::LoadBuffer(int num_dwords, const Value& handle, const Value& address,
BufferInstInfo info) {
switch (num_dwords) {
case 1:
- return Inst(Opcode::LoadBufferF32, Flags{info}, handle, address);
+ return Inst(Opcode::LoadBufferU32, Flags{info}, handle, address);
case 2:
- return Inst(Opcode::LoadBufferF32x2, Flags{info}, handle, address);
+ return Inst(Opcode::LoadBufferU32x2, Flags{info}, handle, address);
case 3:
- return Inst(Opcode::LoadBufferF32x3, Flags{info}, handle, address);
+ return Inst(Opcode::LoadBufferU32x3, Flags{info}, handle, address);
case 4:
- return Inst(Opcode::LoadBufferF32x4, Flags{info}, handle, address);
+ return Inst(Opcode::LoadBufferU32x4, Flags{info}, handle, address);
default:
UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords);
}
@@ -333,17 +341,16 @@ void IREmitter::StoreBuffer(int num_dwords, const Value& handle, const Value& ad
const Value& data, BufferInstInfo info) {
switch (num_dwords) {
case 1:
- Inst(data.Type() == Type::F32 ? Opcode::StoreBufferF32 : Opcode::StoreBufferU32,
- Flags{info}, handle, address, data);
+ Inst(Opcode::StoreBufferU32, Flags{info}, handle, address, data);
break;
case 2:
- Inst(Opcode::StoreBufferF32x2, Flags{info}, handle, address, data);
+ Inst(Opcode::StoreBufferU32x2, Flags{info}, handle, address, data);
break;
case 3:
- Inst(Opcode::StoreBufferF32x3, Flags{info}, handle, address, data);
+ Inst(Opcode::StoreBufferU32x3, Flags{info}, handle, address, data);
break;
case 4:
- Inst(Opcode::StoreBufferF32x4, Flags{info}, handle, address, data);
+ Inst(Opcode::StoreBufferU32x4, Flags{info}, handle, address, data);
break;
default:
UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords);
@@ -402,6 +409,14 @@ void IREmitter::StoreBufferFormat(const Value& handle, const Value& address, con
Inst(Opcode::StoreBufferFormatF32, Flags{info}, handle, address, data);
}
+U32 IREmitter::DataAppend(const U32& counter) {
+ return Inst(Opcode::DataAppend, counter, Imm32(0));
+}
+
+U32 IREmitter::DataConsume(const U32& counter) {
+ return Inst(Opcode::DataConsume, counter, Imm32(0));
+}
+
U32 IREmitter::LaneId() {
return Inst(Opcode::LaneId);
}
diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h
index de8fe450d..01e71893c 100644
--- a/src/shader_recompiler/ir/ir_emitter.h
+++ b/src/shader_recompiler/ir/ir_emitter.h
@@ -67,12 +67,14 @@ public:
[[nodiscard]] U1 GetVcc();
[[nodiscard]] U32 GetVccLo();
[[nodiscard]] U32 GetVccHi();
+ [[nodiscard]] U32 GetM0();
void SetScc(const U1& value);
void SetExec(const U1& value);
void SetVcc(const U1& value);
void SetSccLo(const U32& value);
void SetVccLo(const U32& value);
void SetVccHi(const U32& value);
+ void SetM0(const U32& value);
[[nodiscard]] U1 Condition(IR::Condition cond);
@@ -88,7 +90,7 @@ public:
[[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed);
[[nodiscard]] U32 ReadConst(const Value& base, const U32& offset);
- [[nodiscard]] F32 ReadConstBuffer(const Value& handle, const U32& index);
+ [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index);
[[nodiscard]] Value LoadBuffer(int num_dwords, const Value& handle, const Value& address,
BufferInstInfo info);
@@ -118,6 +120,8 @@ public:
[[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address,
const Value& value, BufferInstInfo info);
+ [[nodiscard]] U32 DataAppend(const U32& counter);
+ [[nodiscard]] U32 DataConsume(const U32& counter);
[[nodiscard]] U32 LaneId();
[[nodiscard]] U32 WarpId();
[[nodiscard]] U32 QuadShuffle(const U32& value, const U32& index);
diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp
index d6ef49cf7..601c453d9 100644
--- a/src/shader_recompiler/ir/microinstruction.cpp
+++ b/src/shader_recompiler/ir/microinstruction.cpp
@@ -51,12 +51,11 @@ bool Inst::MayHaveSideEffects() const noexcept {
case Opcode::Discard:
case Opcode::DiscardCond:
case Opcode::SetAttribute:
- case Opcode::StoreBufferF32:
- case Opcode::StoreBufferF32x2:
- case Opcode::StoreBufferF32x3:
- case Opcode::StoreBufferF32x4:
- case Opcode::StoreBufferFormatF32:
case Opcode::StoreBufferU32:
+ case Opcode::StoreBufferU32x2:
+ case Opcode::StoreBufferU32x3:
+ case Opcode::StoreBufferU32x4:
+ case Opcode::StoreBufferFormatF32:
case Opcode::BufferAtomicIAdd32:
case Opcode::BufferAtomicSMin32:
case Opcode::BufferAtomicUMin32:
@@ -68,6 +67,8 @@ bool Inst::MayHaveSideEffects() const noexcept {
case Opcode::BufferAtomicOr32:
case Opcode::BufferAtomicXor32:
case Opcode::BufferAtomicSwap32:
+ case Opcode::DataAppend:
+ case Opcode::DataConsume:
case Opcode::WriteSharedU128:
case Opcode::WriteSharedU64:
case Opcode::WriteSharedU32:
diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc
index 40dcfa441..4b922d55b 100644
--- a/src/shader_recompiler/ir/opcodes.inc
+++ b/src/shader_recompiler/ir/opcodes.inc
@@ -17,8 +17,7 @@ OPCODE(DiscardCond, Void, U1,
// Constant memory operations
OPCODE(ReadConst, U32, U32x2, U32, )
-OPCODE(ReadConstBuffer, F32, Opaque, U32, )
-OPCODE(ReadConstBufferU32, U32, Opaque, U32, )
+OPCODE(ReadConstBuffer, U32, Opaque, U32, )
// Barriers
OPCODE(Barrier, Void, )
@@ -60,12 +59,14 @@ OPCODE(GetExec, U1, Void,
OPCODE(GetVcc, U1, Void, )
OPCODE(GetVccLo, U32, Void, )
OPCODE(GetVccHi, U32, Void, )
+OPCODE(GetM0, U32, Void, )
OPCODE(SetScc, Void, U1, )
OPCODE(SetExec, Void, U1, )
OPCODE(SetVcc, Void, U1, )
OPCODE(SetSccLo, Void, U32, )
OPCODE(SetVccLo, Void, U32, )
OPCODE(SetVccHi, Void, U32, )
+OPCODE(SetM0, Void, U32, )
// Undefined
OPCODE(UndefU1, U1, )
@@ -75,21 +76,19 @@ OPCODE(UndefU32, U32,
OPCODE(UndefU64, U64, )
// Buffer operations
-OPCODE(LoadBufferF32, F32, Opaque, Opaque, )
-OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, )
-OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, )
-OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, )
-OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, )
OPCODE(LoadBufferU32, U32, Opaque, Opaque, )
-OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, )
-OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, )
-OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, )
-OPCODE(StoreBufferF32x4, Void, Opaque, Opaque, F32x4, )
-OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, F32x4, )
+OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, )
+OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, )
+OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, )
+OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, )
OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, )
+OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, )
+OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, )
+OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, )
+OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, U32x4, )
// Buffer atomic operations
-OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 )
+OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
@@ -99,7 +98,7 @@ OPCODE(BufferAtomicDec32, U32, Opaq
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, )
OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, )
-OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
+OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
// Vector utility
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
@@ -343,3 +342,5 @@ OPCODE(QuadShuffle, U32, U32,
OPCODE(ReadFirstLane, U32, U32, )
OPCODE(ReadLane, U32, U32, U32 )
OPCODE(WriteLane, U32, U32, U32, U32 )
+OPCODE(DataAppend, U32, U32, U32 )
+OPCODE(DataConsume, U32, U32, U32 )
diff --git a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp
index a87cf31b1..76bfcf911 100644
--- a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp
+++ b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp
@@ -21,8 +21,7 @@ void LowerSharedMemToRegisters(IR::Program& program) {
const IR::Inst* prod = inst.Arg(0).InstRecursive();
const auto it = std::ranges::find_if(ds_writes, [&](const IR::Inst* write) {
const IR::Inst* write_prod = write->Arg(0).InstRecursive();
- return write_prod->Arg(1).U32() == prod->Arg(1).U32() &&
- write_prod->Arg(0) == prod->Arg(0);
+ return write_prod->Arg(1).U32() == prod->Arg(1).U32();
});
ASSERT(it != ds_writes.end());
// Replace data read with value written.
diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
index 025bb98c8..6b2aa8bbf 100644
--- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
+++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
@@ -3,7 +3,6 @@
#include
#include
-#include "common/alignment.h"
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/basic_block.h"
#include "shader_recompiler/ir/breadth_first_search.h"
@@ -42,11 +41,10 @@ bool IsBufferAtomic(const IR::Inst& inst) {
bool IsBufferStore(const IR::Inst& inst) {
switch (inst.GetOpcode()) {
- case IR::Opcode::StoreBufferF32:
- case IR::Opcode::StoreBufferF32x2:
- case IR::Opcode::StoreBufferF32x3:
- case IR::Opcode::StoreBufferF32x4:
case IR::Opcode::StoreBufferU32:
+ case IR::Opcode::StoreBufferU32x2:
+ case IR::Opcode::StoreBufferU32x3:
+ case IR::Opcode::StoreBufferU32x4:
return true;
default:
return IsBufferAtomic(inst);
@@ -55,25 +53,28 @@ bool IsBufferStore(const IR::Inst& inst) {
bool IsBufferInstruction(const IR::Inst& inst) {
switch (inst.GetOpcode()) {
- case IR::Opcode::LoadBufferF32:
- case IR::Opcode::LoadBufferF32x2:
- case IR::Opcode::LoadBufferF32x3:
- case IR::Opcode::LoadBufferF32x4:
case IR::Opcode::LoadBufferU32:
+ case IR::Opcode::LoadBufferU32x2:
+ case IR::Opcode::LoadBufferU32x3:
+ case IR::Opcode::LoadBufferU32x4:
case IR::Opcode::ReadConstBuffer:
- case IR::Opcode::ReadConstBufferU32:
return true;
default:
return IsBufferStore(inst);
}
}
+bool IsDataRingInstruction(const IR::Inst& inst) {
+ return inst.GetOpcode() == IR::Opcode::DataAppend ||
+ inst.GetOpcode() == IR::Opcode::DataConsume;
+}
+
bool IsTextureBufferInstruction(const IR::Inst& inst) {
return inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32 ||
inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32;
}
-static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) {
+bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) {
switch (num_format) {
case AmdGpu::NumberFormat::Float:
switch (data_format) {
@@ -97,26 +98,7 @@ static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_for
}
IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) {
- switch (inst.GetOpcode()) {
- case IR::Opcode::LoadBufferF32:
- case IR::Opcode::LoadBufferF32x2:
- case IR::Opcode::LoadBufferF32x3:
- case IR::Opcode::LoadBufferF32x4:
- case IR::Opcode::ReadConstBuffer:
- case IR::Opcode::StoreBufferF32:
- case IR::Opcode::StoreBufferF32x2:
- case IR::Opcode::StoreBufferF32x3:
- case IR::Opcode::StoreBufferF32x4:
- return IR::Type::F32;
- case IR::Opcode::LoadBufferU32:
- case IR::Opcode::ReadConstBufferU32:
- case IR::Opcode::StoreBufferU32:
- case IR::Opcode::BufferAtomicIAdd32:
- case IR::Opcode::BufferAtomicSwap32:
- return IR::Type::U32;
- default:
- UNREACHABLE();
- }
+ return IR::Type::U32;
}
bool IsImageAtomicInstruction(const IR::Inst& inst) {
@@ -191,6 +173,10 @@ public:
u32 Add(const BufferResource& desc) {
const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) {
+ // Only one GDS binding can exist.
+ if (desc.is_gds_buffer && existing.is_gds_buffer) {
+ return true;
+ }
return desc.sgpr_base == existing.sgpr_base &&
desc.dword_offset == existing.dword_offset &&
desc.inline_cbuf == existing.inline_cbuf;
@@ -222,12 +208,8 @@ public:
u32 Add(const SamplerResource& desc) {
const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) {
- if (desc.sgpr_base == existing.sgpr_base &&
- desc.dword_offset == existing.dword_offset) {
- return true;
- }
- // Samplers with different bindings might still be the same.
- return existing.GetSharp(info) == desc.GetSharp(info);
+ return desc.sgpr_base == existing.sgpr_base &&
+ desc.dword_offset == existing.dword_offset;
})};
return index;
}
@@ -399,8 +381,7 @@ void PatchBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info,
ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable);
// Address of constant buffer reads can be calculated at IR emittion time.
- if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer ||
- inst.GetOpcode() == IR::Opcode::ReadConstBufferU32) {
+ if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer) {
return;
}
@@ -609,6 +590,51 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
}
}
+void PatchDataRingInstruction(IR::Block& block, IR::Inst& inst, Info& info,
+ Descriptors& descriptors) {
+ // Insert gds binding in the shader if it doesn't exist already.
+ // The buffer is used for append/consume counters.
+ constexpr static AmdGpu::Buffer GdsSharp{.base_address = 1};
+ const u32 binding = descriptors.Add(BufferResource{
+ .used_types = IR::Type::U32,
+ .inline_cbuf = GdsSharp,
+ .is_gds_buffer = true,
+ .is_written = true,
+ });
+
+ const auto pred = [](const IR::Inst* inst) -> std::optional {
+ if (inst->GetOpcode() == IR::Opcode::GetUserData) {
+ return inst;
+ }
+ return std::nullopt;
+ };
+
+ // Attempt to deduce the GDS address of counter at compile time.
+ const u32 gds_addr = [&] {
+ const IR::Value& gds_offset = inst.Arg(0);
+ if (gds_offset.IsImmediate()) {
+ // Nothing to do, offset is known.
+ return gds_offset.U32() & 0xFFFF;
+ }
+ const auto result = IR::BreadthFirstSearch(&inst, pred);
+ ASSERT_MSG(result, "Unable to track M0 source");
+
+ // M0 must be set by some user data register.
+ const IR::Inst* prod = gds_offset.InstRecursive();
+ const u32 ud_reg = u32(result.value()->Arg(0).ScalarReg());
+ u32 m0_val = info.user_data[ud_reg] >> 16;
+ if (prod->GetOpcode() == IR::Opcode::IAdd32) {
+ m0_val += prod->Arg(1).U32();
+ }
+ return m0_val & 0xFFFF;
+ }();
+
+ // Patch instruction.
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ inst.SetArg(0, ir.Imm32(gds_addr >> 2));
+ inst.SetArg(1, ir.Imm32(binding));
+}
+
void ResourceTrackingPass(IR::Program& program) {
// Iterate resource instructions and patch them after finding the sharp.
auto& info = program.info;
@@ -625,6 +651,10 @@ void ResourceTrackingPass(IR::Program& program) {
}
if (IsImageInstruction(inst)) {
PatchImageInstruction(*block, inst, info, descriptors);
+ continue;
+ }
+ if (IsDataRingInstruction(inst)) {
+ PatchDataRingInstruction(*block, inst, info, descriptors);
}
}
}
diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
index 7105f01f1..63fe8a571 100644
--- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
+++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
@@ -22,6 +22,7 @@ void Visit(Info& info, IR::Inst& inst) {
case IR::Opcode::WriteSharedU64:
info.uses_shared = true;
break;
+ case IR::Opcode::ConvertF16F32:
case IR::Opcode::ConvertF32F16:
case IR::Opcode::BitCastF16U16:
info.uses_fp16 = true;
@@ -38,6 +39,11 @@ void Visit(Info& info, IR::Inst& inst) {
case IR::Opcode::QuadShuffle:
info.uses_group_quad = true;
break;
+ case IR::Opcode::ReadLane:
+ case IR::Opcode::ReadFirstLane:
+ case IR::Opcode::WriteLane:
+ info.uses_group_ballot = true;
+ break;
case IR::Opcode::Discard:
case IR::Opcode::DiscardCond:
info.has_discard = true;
diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
index 9edb157db..54dce0355 100644
--- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
+++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
@@ -33,6 +33,7 @@ struct ExecFlagTag : FlagTag {};
struct VccFlagTag : FlagTag {};
struct VccLoTag : FlagTag {};
struct VccHiTag : FlagTag {};
+struct M0Tag : FlagTag {};
struct GotoVariable : FlagTag {
GotoVariable() = default;
@@ -43,8 +44,17 @@ struct GotoVariable : FlagTag {
u32 index;
};
-using Variant = std::variant;
+struct ThreadBitScalar : FlagTag {
+ ThreadBitScalar() = default;
+ explicit ThreadBitScalar(IR::ScalarReg sgpr_) : sgpr{sgpr_} {}
+
+ auto operator<=>(const ThreadBitScalar&) const noexcept = default;
+
+ IR::ScalarReg sgpr;
+};
+
+using Variant = std::variant;
using ValueMap = std::unordered_map;
struct DefTable {
@@ -69,6 +79,13 @@ struct DefTable {
goto_vars[variable.index].insert_or_assign(block, value);
}
+ const IR::Value& Def(IR::Block* block, ThreadBitScalar variable) {
+ return block->ssa_sreg_values[RegIndex(variable.sgpr)];
+ }
+ void SetDef(IR::Block* block, ThreadBitScalar variable, const IR::Value& value) {
+ block->ssa_sreg_values[RegIndex(variable.sgpr)] = value;
+ }
+
const IR::Value& Def(IR::Block* block, SccFlagTag) {
return scc_flag[block];
}
@@ -103,6 +120,12 @@ struct DefTable {
void SetDef(IR::Block* block, VccFlagTag, const IR::Value& value) {
vcc_flag.insert_or_assign(block, value);
}
+ const IR::Value& Def(IR::Block* block, M0Tag) {
+ return m0_flag[block];
+ }
+ void SetDef(IR::Block* block, M0Tag, const IR::Value& value) {
+ m0_flag.insert_or_assign(block, value);
+ }
std::unordered_map goto_vars;
ValueMap scc_flag;
@@ -111,6 +134,7 @@ struct DefTable {
ValueMap scc_lo_flag;
ValueMap vcc_lo_flag;
ValueMap vcc_hi_flag;
+ ValueMap m0_flag;
};
IR::Opcode UndefOpcode(IR::ScalarReg) noexcept {
@@ -129,6 +153,10 @@ IR::Opcode UndefOpcode(const VccHiTag) noexcept {
return IR::Opcode::UndefU32;
}
+IR::Opcode UndefOpcode(const M0Tag) noexcept {
+ return IR::Opcode::UndefU32;
+}
+
IR::Opcode UndefOpcode(const FlagTag) noexcept {
return IR::Opcode::UndefU1;
}
@@ -161,7 +189,7 @@ public:
}
template
- IR::Value ReadVariable(Type variable, IR::Block* root_block, bool is_thread_bit = false) {
+ IR::Value ReadVariable(Type variable, IR::Block* root_block) {
boost::container::small_vector, 64> stack{
ReadState(nullptr),
ReadState(root_block),
@@ -189,7 +217,7 @@ public:
} else if (!block->IsSsaSealed()) {
// Incomplete CFG
IR::Inst* phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
- phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable)));
+ phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
incomplete_phis[block].insert_or_assign(variable, phi);
stack.back().result = IR::Value{&*phi};
@@ -202,7 +230,7 @@ public:
} else {
// Break potential cycles with operandless phi
IR::Inst* const phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
- phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable)));
+ phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
WriteVariable(variable, block, IR::Value{phi});
@@ -251,9 +279,7 @@ private:
template
IR::Value AddPhiOperands(Type variable, IR::Inst& phi, IR::Block* block) {
for (IR::Block* const imm_pred : block->ImmPredecessors()) {
- const bool is_thread_bit =
- std::is_same_v && phi.Flags() == IR::Type::U1;
- phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred, is_thread_bit));
+ phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred));
}
return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable));
}
@@ -301,7 +327,11 @@ private:
void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) {
const IR::Opcode opcode{inst.GetOpcode()};
switch (opcode) {
- case IR::Opcode::SetThreadBitScalarReg:
+ case IR::Opcode::SetThreadBitScalarReg: {
+ const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
+ pass.WriteVariable(ThreadBitScalar{reg}, block, inst.Arg(1));
+ break;
+ }
case IR::Opcode::SetScalarRegister: {
const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
pass.WriteVariable(reg, block, inst.Arg(1));
@@ -330,11 +360,18 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) {
case IR::Opcode::SetVccHi:
pass.WriteVariable(VccHiTag{}, block, inst.Arg(0));
break;
- case IR::Opcode::GetThreadBitScalarReg:
+ case IR::Opcode::SetM0:
+ pass.WriteVariable(M0Tag{}, block, inst.Arg(0));
+ break;
+ case IR::Opcode::GetThreadBitScalarReg: {
+ const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
+ const IR::Value value = pass.ReadVariable(ThreadBitScalar{reg}, block);
+ inst.ReplaceUsesWith(value);
+ break;
+ }
case IR::Opcode::GetScalarRegister: {
const IR::ScalarReg reg{inst.Arg(0).ScalarReg()};
- const bool thread_bit = opcode == IR::Opcode::GetThreadBitScalarReg;
- const IR::Value value = pass.ReadVariable(reg, block, thread_bit);
+ const IR::Value value = pass.ReadVariable(reg, block);
inst.ReplaceUsesWith(value);
break;
}
@@ -362,6 +399,9 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) {
case IR::Opcode::GetVccHi:
inst.ReplaceUsesWith(pass.ReadVariable(VccHiTag{}, block));
break;
+ case IR::Opcode::GetM0:
+ inst.ReplaceUsesWith(pass.ReadVariable(M0Tag{}, block));
+ break;
default:
break;
}
diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h
index 776fd90a6..1bb065544 100644
--- a/src/shader_recompiler/runtime_info.h
+++ b/src/shader_recompiler/runtime_info.h
@@ -107,6 +107,7 @@ struct RuntimeInfo {
Stage stage;
u32 num_user_data;
u32 num_input_vgprs;
+ u32 num_allocated_vgprs;
VertexRuntimeInfo vs_info;
FragmentRuntimeInfo fs_info;
ComputeRuntimeInfo cs_info;
diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h
index 3dd75dbd7..bbcafdb86 100644
--- a/src/shader_recompiler/specialization.h
+++ b/src/shader_recompiler/specialization.h
@@ -37,14 +37,14 @@ struct ImageSpecialization {
* after the first compilation of a module.
*/
struct StageSpecialization {
- static constexpr size_t MaxStageResources = 32;
+ static constexpr size_t MaxStageResources = 64;
const Shader::Info* info;
RuntimeInfo runtime_info;
std::bitset bitset{};
boost::container::small_vector buffers;
boost::container::small_vector tex_buffers;
- boost::container::small_vector images;
+ boost::container::small_vector images;
u32 start_binding{};
explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_,
diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp
index cee30f755..a2bd60f2e 100644
--- a/src/video_core/amdgpu/liverpool.cpp
+++ b/src/video_core/amdgpu/liverpool.cpp
@@ -221,11 +221,15 @@ 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};
- rasterizer->ScopeMarkerBegin(label);
+ if (rasterizer) {
+ rasterizer->ScopeMarkerBegin(label);
+ }
break;
}
case PM4CmdNop::PayloadType::DebugMarkerPop: {
- rasterizer->ScopeMarkerEnd();
+ if (rasterizer) {
+ rasterizer->ScopeMarkerEnd();
+ }
break;
}
default:
@@ -465,6 +469,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
event_eos->SignalFence();
+ if (event_eos->command == PM4CmdEventWriteEos::Command::GdsStore) {
+ ASSERT(event_eos->size == 1);
+ if (rasterizer) {
+ rasterizer->Finish();
+ const u32 value = rasterizer->ReadDataFromGds(event_eos->gds_index);
+ *event_eos->Address() = value;
+ }
+ }
break;
}
case PM4ItOpcode::EventWriteEop: {
@@ -474,6 +486,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header);
+ if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) {
+ rasterizer->InlineDataToGds(dma_data->dst_addr_lo, dma_data->data);
+ }
break;
}
case PM4ItOpcode::WriteData: {
@@ -525,7 +540,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanCpSync();
+ if (rasterizer) {
+ rasterizer->CpSync();
+ }
break;
}
default:
diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h
index 58ade221b..064b89951 100644
--- a/src/video_core/amdgpu/pm4_cmds.h
+++ b/src/video_core/amdgpu/pm4_cmds.h
@@ -187,6 +187,11 @@ struct PM4CmdSetData {
BitField<28, 4, u32> index; ///< Index for UCONFIG/CONTEXT on CI+
///< Program to zero for other opcodes and on SI
};
+ u32 data[0];
+
+ [[nodiscard]] u32 Size() const {
+ return header.count << 2u;
+ }
template
static constexpr u32* SetContextReg(u32* cmdbuf, Args... data) {
@@ -350,6 +355,27 @@ struct PM4CmdEventWriteEop {
}
};
+struct PM4CmdAcquireMem {
+ PM4Type3Header header;
+ u32 cp_coher_cntl;
+ u32 cp_coher_size_lo;
+ u32 cp_coher_size_hi;
+ u32 cp_coher_base_lo;
+ u32 cp_coher_base_hi;
+ u32 poll_interval;
+};
+
+enum class DmaDataDst : u32 {
+ Memory = 0,
+ Gds = 1,
+};
+
+enum class DmaDataSrc : u32 {
+ Memory = 0,
+ Gds = 1,
+ Data = 2,
+};
+
struct PM4DmaData {
PM4Type3Header header;
union {
@@ -357,11 +383,11 @@ struct PM4DmaData {
BitField<12, 1, u32> src_atc;
BitField<13, 2, u32> src_cache_policy;
BitField<15, 1, u32> src_volatile;
- BitField<20, 2, u32> dst_sel;
+ BitField<20, 2, DmaDataDst> dst_sel;
BitField<24, 1, u32> dst_atc;
BitField<25, 2, u32> dst_cache_policy;
BitField<27, 1, u32> dst_volatile;
- BitField<29, 2, u32> src_sel;
+ BitField<29, 2, DmaDataSrc> src_sel;
BitField<31, 1, u32> cp_sync;
};
union {
@@ -456,6 +482,10 @@ struct PM4CmdWriteData {
};
u32 data[0];
+ u32 Size() const {
+ return (header.count.Value() - 2) * 4;
+ }
+
template
void Address(T addr) {
addr64 = static_cast(addr);
@@ -502,13 +532,17 @@ struct PM4CmdEventWriteEos {
}
void SignalFence() const {
- switch (command.Value()) {
+ const auto cmd = command.Value();
+ switch (cmd) {
case Command::SingalFence: {
*Address() = DataDWord();
break;
}
+ case Command::GdsStore: {
+ break;
+ }
default: {
- UNREACHABLE();
+ UNREACHABLE_MSG("Unknown command {}", u32(cmd));
}
}
}
diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp
index adcea000b..702958034 100644
--- a/src/video_core/buffer_cache/buffer.cpp
+++ b/src/video_core/buffer_cache/buffer.cpp
@@ -91,10 +91,10 @@ void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usa
buffer = vk::Buffer{unsafe_buffer};
}
-Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_addr_,
- vk::BufferUsageFlags flags, u64 size_bytes_)
- : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, usage{usage_},
- buffer{instance->GetDevice(), instance->GetAllocator()} {
+Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, MemoryUsage usage_,
+ VAddr cpu_addr_, vk::BufferUsageFlags flags, u64 size_bytes_)
+ : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, scheduler{&scheduler_},
+ usage{usage_}, buffer{instance->GetDevice(), instance->GetAllocator()} {
// Create buffer object.
const vk::BufferCreateInfo buffer_ci = {
.size = size_bytes,
@@ -117,13 +117,6 @@ Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_
vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataFormat dfmt,
AmdGpu::NumberFormat nfmt) {
- const auto it{std::ranges::find_if(views, [=](const BufferView& view) {
- return offset == view.offset && size == view.size && is_written == view.is_written &&
- dfmt == view.dfmt && nfmt == view.nfmt;
- })};
- if (it != views.end()) {
- return *it->handle;
- }
const vk::BufferUsageFlags2CreateInfoKHR usage_flags = {
.usage = is_written ? vk::BufferUsageFlagBits2KHR::eStorageTexelBuffer
: vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer,
@@ -135,23 +128,18 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF
.offset = offset,
.range = size,
};
- views.push_back({
- .offset = offset,
- .size = size,
- .is_written = is_written,
- .dfmt = dfmt,
- .nfmt = nfmt,
- .handle = instance->GetDevice().createBufferViewUnique(view_ci),
- });
- return *views.back().handle;
+ const auto view = instance->GetDevice().createBufferView(view_ci);
+ scheduler->DeferOperation(
+ [view, device = instance->GetDevice()] { device.destroyBufferView(view); });
+ return view;
}
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
-StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_,
+StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
MemoryUsage usage, u64 size_bytes)
- : Buffer{instance, usage, 0, AllFlags, size_bytes}, scheduler{scheduler_} {
+ : Buffer{instance, scheduler, usage, 0, AllFlags, size_bytes} {
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
const auto device = instance.GetDevice();
@@ -206,7 +194,7 @@ void StreamBuffer::Commit() {
auto& watch = current_watches[current_watch_cursor++];
watch.upper_bound = offset;
- watch.tick = scheduler.CurrentTick();
+ watch.tick = scheduler->CurrentTick();
}
void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) {
@@ -220,7 +208,7 @@ void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
auto& watch = previous_watches[wait_cursor];
wait_bound = watch.upper_bound;
- scheduler.Wait(watch.tick);
+ scheduler->Wait(watch.tick);
++wait_cursor;
}
}
diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h
index 26d48eaef..403d4ed85 100644
--- a/src/video_core/buffer_cache/buffer.h
+++ b/src/video_core/buffer_cache/buffer.h
@@ -73,8 +73,9 @@ struct UniqueBuffer {
class Buffer {
public:
- explicit Buffer(const Vulkan::Instance& instance, MemoryUsage usage, VAddr cpu_addr_,
- vk::BufferUsageFlags flags, u64 size_bytes_);
+ explicit Buffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
+ MemoryUsage usage, VAddr cpu_addr_, vk::BufferUsageFlags flags,
+ u64 size_bytes_);
Buffer& operator=(const Buffer&) = delete;
Buffer(const Buffer&) = delete;
@@ -118,6 +119,25 @@ public:
return buffer;
}
+ std::optional GetBarrier(vk::AccessFlagBits2 dst_acess_mask,
+ vk::PipelineStageFlagBits2 dst_stage) {
+ if (dst_acess_mask == access_mask && stage == dst_stage) {
+ return {};
+ }
+
+ auto barrier = vk::BufferMemoryBarrier2{
+ .srcStageMask = stage,
+ .srcAccessMask = access_mask,
+ .dstStageMask = dst_stage,
+ .dstAccessMask = dst_acess_mask,
+ .buffer = buffer.buffer,
+ .size = size_bytes,
+ };
+ access_mask = dst_acess_mask;
+ stage = dst_stage;
+ return barrier;
+ }
+
public:
VAddr cpu_addr = 0;
bool is_picked{};
@@ -125,18 +145,12 @@ public:
int stream_score = 0;
size_t size_bytes = 0;
std::span mapped_data;
- const Vulkan::Instance* instance{};
+ const Vulkan::Instance* instance;
+ Vulkan::Scheduler* scheduler;
MemoryUsage usage;
UniqueBuffer buffer;
- struct BufferView {
- u32 offset;
- u32 size;
- bool is_written;
- AmdGpu::DataFormat dfmt;
- AmdGpu::NumberFormat nfmt;
- vk::UniqueBufferView handle;
- };
- std::vector views;
+ vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone};
+ vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone};
};
class StreamBuffer : public Buffer {
@@ -175,7 +189,6 @@ private:
void WaitPendingOperations(u64 requested_upper_bound);
private:
- Vulkan::Scheduler& scheduler;
u64 offset{};
u64 mapped_size{};
std::vector current_watches;
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
index 93e05085d..2ed0ddc87 100644
--- a/src/video_core/buffer_cache/buffer_cache.cpp
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -10,20 +10,28 @@
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/texture_cache/texture_cache.h"
namespace VideoCore {
-static constexpr size_t StagingBufferSize = 512_MB;
-static constexpr size_t UboStreamBufferSize = 64_MB;
+static constexpr size_t NumVertexBuffers = 32;
+static constexpr size_t GdsBufferSize = 64_KB;
+static constexpr size_t StagingBufferSize = 1_GB;
+static constexpr size_t UboStreamBufferSize = 128_MB;
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
- const AmdGpu::Liverpool* liverpool_, PageManager& tracker_)
- : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, tracker{tracker_},
+ const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_,
+ PageManager& tracker_)
+ : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_},
+ texture_cache{texture_cache_}, tracker{tracker_},
staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize},
stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize},
+ gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize},
memory_tracker{&tracker} {
+ Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer");
+
// Ensure the first slot is used for the null buffer
- void(slot_buffers.insert(instance, MemoryUsage::DeviceLocal, 0, ReadFlags, 1));
+ void(slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1));
}
BufferCache::~BufferCache() = default;
@@ -100,9 +108,9 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
return false;
}
- std::array host_buffers;
- std::array host_offsets;
- boost::container::static_vector guest_buffers;
+ std::array host_buffers;
+ std::array host_offsets;
+ boost::container::static_vector guest_buffers;
struct BufferRange {
VAddr base_address;
@@ -117,7 +125,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
// Calculate buffers memory overlaps
bool has_step_rate = false;
- boost::container::static_vector ranges{};
+ boost::container::static_vector ranges{};
for (const auto& input : vs_info.vs_inputs) {
if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 ||
input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) {
@@ -152,7 +160,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
return lhv.base_address < rhv.base_address;
});
- boost::container::static_vector ranges_merged{ranges[0]};
+ boost::container::static_vector ranges_merged{ranges[0]};
for (auto range : ranges) {
auto& prev_range = ranges_merged.back();
if (prev_range.end_address < range.base_address) {
@@ -228,11 +236,32 @@ u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) {
return regs.num_indices;
}
+void BufferCache::InlineDataToGds(u32 gds_offset, u32 value) {
+ ASSERT_MSG(gds_offset % 4 == 0, "GDS offset must be dword aligned");
+ scheduler.EndRendering();
+ const auto cmdbuf = scheduler.CommandBuffer();
+ const vk::BufferMemoryBarrier2 buf_barrier = {
+ .srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
+ .srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
+ .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
+ .dstAccessMask = vk::AccessFlagBits2::eMemoryRead,
+ .buffer = gds_buffer.Handle(),
+ .offset = gds_offset,
+ .size = sizeof(u32),
+ };
+ cmdbuf.pipelineBarrier2(vk::DependencyInfo{
+ .dependencyFlags = vk::DependencyFlagBits::eByRegion,
+ .bufferMemoryBarrierCount = 1,
+ .pBufferMemoryBarriers = &buf_barrier,
+ });
+ cmdbuf.updateBuffer(gds_buffer.Handle(), gds_offset, sizeof(u32), &value);
+}
+
std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written,
bool is_texel_buffer) {
static constexpr u64 StreamThreshold = CACHING_PAGESIZE;
const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size);
- if (!is_written && !is_texel_buffer && size <= StreamThreshold && !is_gpu_dirty) {
+ if (!is_written && size <= StreamThreshold && !is_gpu_dirty) {
// For small uniform buffers that have not been modified by gpu
// use device local stream buffer to reduce renderpass breaks.
const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment());
@@ -241,19 +270,20 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b
const BufferId buffer_id = FindBuffer(device_addr, size);
Buffer& buffer = slot_buffers[buffer_id];
- SynchronizeBuffer(buffer, device_addr, size);
+ SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer);
if (is_written) {
memory_tracker.MarkRegionAsGpuModified(device_addr, size);
}
return {&buffer, buffer.Offset(device_addr)};
}
-std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) {
+std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) {
const u64 page = gpu_addr >> CACHING_PAGEBITS;
const BufferId buffer_id = page_table[page];
if (buffer_id) {
- const Buffer& buffer = slot_buffers[buffer_id];
+ Buffer& buffer = slot_buffers[buffer_id];
if (buffer.IsInBounds(gpu_addr, size)) {
+ SynchronizeBuffer(buffer, gpu_addr, size, false);
return {&buffer, buffer.Offset(gpu_addr)};
}
}
@@ -420,8 +450,8 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) {
wanted_size = static_cast(device_addr_end - device_addr);
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast(overlap.end - overlap.begin);
- const BufferId new_buffer_id =
- slot_buffers.insert(instance, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size);
+ const BufferId new_buffer_id = slot_buffers.insert(
+ instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size);
auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes();
const auto cmdbuf = scheduler.CommandBuffer();
@@ -459,7 +489,8 @@ void BufferCache::ChangeRegister(BufferId buffer_id) {
}
}
-bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) {
+void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
+ bool is_texel_buffer) {
std::scoped_lock lk{mutex};
boost::container::small_vector copies;
u64 total_size_bytes = 0;
@@ -479,8 +510,13 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size)
// Prevent uploading to gpu modified regions.
// gpu_modified_ranges.ForEachNotInRange(device_addr_out, range_size, add_copy);
});
+ SCOPE_EXIT {
+ if (is_texel_buffer) {
+ SynchronizeBufferFromImage(buffer, device_addr, size);
+ }
+ };
if (total_size_bytes == 0) {
- return true;
+ return;
}
vk::Buffer src_buffer = staging_buffer.Handle();
if (total_size_bytes < StagingBufferSize) {
@@ -496,7 +532,11 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size)
} else {
// For large one time transfers use a temporary host buffer.
// RenderDoc can lag quite a bit if the stream buffer is too large.
- Buffer temp_buffer{instance, MemoryUsage::Upload, 0, vk::BufferUsageFlagBits::eTransferSrc,
+ Buffer temp_buffer{instance,
+ scheduler,
+ MemoryUsage::Upload,
+ 0,
+ vk::BufferUsageFlagBits::eTransferSrc,
total_size_bytes};
src_buffer = temp_buffer.Handle();
u8* const staging = temp_buffer.mapped_data.data();
@@ -524,7 +564,51 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size)
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {});
- return false;
+}
+
+bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) {
+ static constexpr FindFlags find_flags =
+ FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize;
+ ImageInfo info{};
+ info.guest_address = device_addr;
+ info.guest_size_bytes = size;
+ const ImageId image_id = texture_cache.FindImage(info, find_flags);
+ if (!image_id) {
+ return false;
+ }
+ Image& image = texture_cache.GetImage(image_id);
+ boost::container::small_vector copies;
+ u32 offset = buffer.Offset(image.cpu_addr);
+ const u32 num_layers = image.info.resources.layers;
+ for (u32 m = 0; m < image.info.resources.levels; m++) {
+ const u32 width = std::max(image.info.size.width >> m, 1u);
+ const u32 height = std::max(image.info.size.height >> m, 1u);
+ const u32 depth =
+ image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u;
+ const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m];
+ copies.push_back({
+ .bufferOffset = offset,
+ .bufferRowLength = static_cast(mip_pitch),
+ .bufferImageHeight = static_cast(mip_height),
+ .imageSubresource{
+ .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
+ .mipLevel = m,
+ .baseArrayLayer = 0,
+ .layerCount = num_layers,
+ },
+ .imageOffset = {0, 0, 0},
+ .imageExtent = {width, height, depth},
+ });
+ offset += mip_ofs * num_layers;
+ }
+ if (!copies.empty()) {
+ scheduler.EndRendering();
+ image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead);
+ const auto cmdbuf = scheduler.CommandBuffer();
+ cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer,
+ copies);
+ }
+ return true;
}
void BufferCache::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index b9002cea2..cd6ea28fc 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -28,7 +28,7 @@ using BufferId = Common::SlotId;
static constexpr BufferId NULL_BUFFER_ID{0};
-static constexpr u32 NUM_VERTEX_BUFFERS = 32;
+class TextureCache;
class BufferCache {
public:
@@ -53,9 +53,15 @@ public:
public:
explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
- const AmdGpu::Liverpool* liverpool, PageManager& tracker);
+ const AmdGpu::Liverpool* liverpool, TextureCache& texture_cache,
+ PageManager& tracker);
~BufferCache();
+ /// Returns a pointer to GDS device local buffer.
+ [[nodiscard]] const Buffer* GetGdsBuffer() const noexcept {
+ return &gds_buffer;
+ }
+
/// Invalidates any buffer in the logical page range.
void InvalidateMemory(VAddr device_addr, u64 size);
@@ -65,12 +71,15 @@ public:
/// Bind host index buffer for the current draw.
u32 BindIndexBuffer(bool& is_indexed, u32 index_offset);
+ /// Writes a value to GDS buffer.
+ void InlineDataToGds(u32 gds_offset, u32 value);
+
/// Obtains a buffer for the specified region.
[[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written,
bool is_texel_buffer = false);
/// Obtains a temporary buffer for usage in texture cache.
- [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size);
+ [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size);
/// Return true when a region is registered on the cache
[[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
@@ -116,16 +125,20 @@ private:
template
void ChangeRegister(BufferId buffer_id);
- bool SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size);
+ void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer);
+
+ bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size);
void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
const Vulkan::Instance& instance;
Vulkan::Scheduler& scheduler;
const AmdGpu::Liverpool* liverpool;
+ TextureCache& texture_cache;
PageManager& tracker;
StreamBuffer staging_buffer;
StreamBuffer stream_buffer;
+ Buffer gds_buffer;
std::mutex mutex;
Common::SlotVector slot_buffers;
MemoryTracker memory_tracker;
diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp
index 18b8aee21..d62077b04 100644
--- a/src/video_core/page_manager.cpp
+++ b/src/video_core/page_manager.cpp
@@ -3,6 +3,7 @@
#include
#include "common/alignment.h"
+#include "common/arch.h"
#include "common/assert.h"
#include "common/error.h"
#include "video_core/page_manager.h"
@@ -159,6 +160,27 @@ struct PageManager::Impl {
int uffd;
};
#else
+
+#if defined(__APPLE__)
+
+#if defined(ARCH_X86_64)
+#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__err & 0x2)
+#elif defined(ARCH_ARM64)
+#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__esr & 0x40)
+#endif
+
+#else
+
+#if defined(ARCH_X86_64)
+#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext.gregs[REG_ERR] & 0x2)
+#endif
+
+#endif
+
+#ifndef IS_WRITE_ERROR
+#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture.
+#endif
+
struct PageManager::Impl {
Impl(Vulkan::Rasterizer* rasterizer_) {
rasterizer = rasterizer_;
@@ -194,12 +216,7 @@ struct PageManager::Impl {
static void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) {
ucontext_t* ctx = reinterpret_cast(raw_context);
const VAddr address = reinterpret_cast(info->si_addr);
-#ifdef __APPLE__
- const u32 err = ctx->uc_mcontext->__es.__err;
-#else
- const greg_t err = ctx->uc_mcontext.gregs[REG_ERR];
-#endif
- if (err & 0x2) {
+ if (IS_WRITE_ERROR(ctx)) {
const VAddr addr_aligned = Common::AlignDown(address, PAGESIZE);
rasterizer->InvalidateMemory(addr_aligned, PAGESIZE);
} else {
diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp
index 40a1124a6..a97c3dee9 100644
--- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp
@@ -585,11 +585,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu
vk::Format AdjustColorBufferFormat(vk::Format base_format,
Liverpool::ColorBuffer::SwapMode comp_swap, bool is_vo_surface) {
- ASSERT_MSG(comp_swap == Liverpool::ColorBuffer::SwapMode::Standard ||
- comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate,
- "Unsupported component swap mode {}", static_cast(comp_swap));
-
const bool comp_swap_alt = comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate;
+ const bool comp_swap_reverse = comp_swap == Liverpool::ColorBuffer::SwapMode::StandardReverse;
+ const bool comp_swap_alt_reverse =
+ comp_swap == Liverpool::ColorBuffer::SwapMode::AlternateReverse;
if (comp_swap_alt) {
switch (base_format) {
case vk::Format::eR8G8B8A8Unorm:
@@ -605,6 +604,18 @@ vk::Format AdjustColorBufferFormat(vk::Format base_format,
default:
break;
}
+ } else if (comp_swap_reverse) {
+ switch (base_format) {
+ case vk::Format::eR8G8B8A8Unorm:
+ return vk::Format::eA8B8G8R8UnormPack32;
+ case vk::Format::eR8G8B8A8Srgb:
+ return is_vo_surface ? vk::Format::eA8B8G8R8UnormPack32
+ : vk::Format::eA8B8G8R8SrgbPack32;
+ default:
+ break;
+ }
+ } else if (comp_swap_alt_reverse) {
+ return base_format;
} else {
if (is_vo_surface && base_format == vk::Format::eR8G8B8A8Srgb) {
return vk::Format::eR8G8B8A8Unorm;
@@ -649,8 +660,8 @@ void EmitQuadToTriangleListIndices(u8* out_ptr, u32 num_vertices) {
*out_data++ = i;
*out_data++ = i + 1;
*out_data++ = i + 2;
- *out_data++ = i + 2;
*out_data++ = i;
+ *out_data++ = i + 2;
*out_data++ = i + 3;
}
}
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index f1c81b6e2..d019ff034 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -6,6 +6,7 @@
#include "common/singleton.h"
#include "core/file_format/splash.h"
#include "core/libraries/system/systemservice.h"
+#include "imgui/renderer/imgui_core.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
@@ -73,7 +74,7 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool*
draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance},
swapchain{instance, window},
rasterizer{std::make_unique(instance, draw_scheduler, liverpool)},
- texture_cache{rasterizer->GetTextureCache()} {
+ texture_cache{rasterizer->GetTextureCache()}, video_info_ui{this} {
const u32 num_images = swapchain.GetImageCount();
const vk::Device device = instance.GetDevice();
@@ -84,9 +85,14 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool*
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
free_queue.push(&frame);
}
+
+ // Setup ImGui
+ ImGui::Core::Initialize(instance, window, num_images, swapchain.GetSurfaceFormat().format);
+ ImGui::Layer::AddLayer(&video_info_ui);
}
RendererVulkan::~RendererVulkan() {
+ ImGui::Layer::RemoveLayer(&video_info_ui);
draw_scheduler.Finish();
const vk::Device device = instance.GetDevice();
for (auto& frame : present_frames) {
@@ -94,6 +100,7 @@ RendererVulkan::~RendererVulkan() {
device.destroyImageView(frame.image_view);
device.destroyFence(frame.present_done);
}
+ ImGui::Core::Shutdown(device);
}
void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) {
@@ -254,6 +261,8 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop
}
void RendererVulkan::Present(Frame* frame) {
+ ImGui::Core::NewFrame();
+
swapchain.AcquireNextImage();
const vk::Image swapchain_image = swapchain.Image();
@@ -317,6 +326,8 @@ void RendererVulkan::Present(Frame* frame) {
},
};
+ ImGui::Core::Render(cmdbuf, frame);
+
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index eab9d527c..c8e566418 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -4,6 +4,8 @@
#pragma once
#include
+
+#include "imgui/layer/video_info.h"
#include "video_core/amdgpu/liverpool.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -103,6 +105,8 @@ private:
std::condition_variable_any frame_cv;
std::optional splash_img;
std::vector vo_buffers_addr;
+
+ ImGui::Layers::VideoInfo video_info_ui;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h
index 3e048749f..a2f9cbcaf 100644
--- a/src/video_core/renderer_vulkan/vk_common.h
+++ b/src/video_core/renderer_vulkan/vk_common.h
@@ -13,6 +13,7 @@
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#define VULKAN_HPP_NO_CONSTRUCTORS
#define VULKAN_HPP_NO_STRUCT_SETTERS
+#define VULKAN_HPP_HAS_SPACESHIP_OPERATOR
#include
#define VMA_STATIC_VULKAN_FUNCTIONS 0
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 1d9001238..96358bf67 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -12,9 +12,11 @@
namespace Vulkan {
ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler_,
- vk::PipelineCache pipeline_cache, u64 compute_key_,
- const Shader::Info& info_, vk::ShaderModule module)
- : instance{instance_}, scheduler{scheduler_}, compute_key{compute_key_}, info{&info_} {
+ DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache,
+ u64 compute_key_, const Shader::Info& info_,
+ vk::ShaderModule module)
+ : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, compute_key{compute_key_},
+ info{&info_} {
const vk::PipelineShaderStageCreateInfo shader_ci = {
.stage = vk::ShaderStageFlagBits::eCompute,
.module = module,
@@ -66,8 +68,12 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler
.size = sizeof(Shader::PushData),
};
+ uses_push_descriptors = binding < instance.MaxPushDescriptors();
+ const auto flags = uses_push_descriptors
+ ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
+ : vk::DescriptorSetLayoutCreateFlagBits{};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
- .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
+ .flags = flags,
.bindingCount = static_cast(bindings.size()),
.pBindings = bindings.data(),
};
@@ -101,44 +107,50 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
VideoCore::TextureCache& texture_cache) const {
// Bind resource buffers and textures.
boost::container::static_vector buffer_views;
- boost::container::static_vector buffer_infos;
- boost::container::static_vector image_infos;
+ boost::container::static_vector buffer_infos;
+ boost::container::static_vector image_infos;
boost::container::small_vector set_writes;
+ boost::container::small_vector buffer_barriers;
Shader::PushData push_data{};
u32 binding{};
for (const auto& desc : info->buffers) {
- const auto vsharp = desc.GetSharp(*info);
- const bool is_storage = desc.IsStorage(vsharp);
- const VAddr address = vsharp.base_address;
- // Most of the time when a metadata is updated with a shader it gets cleared. It means we
- // can skip the whole dispatch and update the tracked state instead. Also, it is not
- // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we will
- // need its full emulation anyways. For cases of metadata read a warning will be logged.
- if (desc.is_written) {
- if (texture_cache.TouchMeta(address, true)) {
- LOG_TRACE(Render_Vulkan, "Metadata update skipped");
- return false;
- }
+ bool is_storage = true;
+ if (desc.is_gds_buffer) {
+ auto* vk_buffer = buffer_cache.GetGdsBuffer();
+ buffer_infos.emplace_back(vk_buffer->Handle(), 0, vk_buffer->SizeBytes());
} else {
- if (texture_cache.IsMeta(address)) {
- LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)");
+ const auto vsharp = desc.GetSharp(*info);
+ is_storage = desc.IsStorage(vsharp);
+ const VAddr address = vsharp.base_address;
+ // Most of the time when a metadata is updated with a shader it gets cleared. It means
+ // we can skip the whole dispatch and update the tracked state instead. Also, it is not
+ // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we
+ // will need its full emulation anyways. For cases of metadata read a warning will be
+ // logged.
+ if (desc.is_written) {
+ if (texture_cache.TouchMeta(address, true)) {
+ LOG_TRACE(Render_Vulkan, "Metadata update skipped");
+ return false;
+ }
+ } else {
+ if (texture_cache.IsMeta(address)) {
+ LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)");
+ }
}
+ const u32 size = vsharp.GetSize();
+ const u32 alignment =
+ is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment();
+ const auto [vk_buffer, offset] =
+ buffer_cache.ObtainBuffer(address, size, desc.is_written);
+ const u32 offset_aligned = Common::AlignDown(offset, alignment);
+ const u32 adjust = offset - offset_aligned;
+ if (adjust != 0) {
+ ASSERT(adjust % 4 == 0);
+ push_data.AddOffset(binding, adjust);
+ }
+ buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust);
}
- const u32 size = vsharp.GetSize();
- if (desc.is_written) {
- texture_cache.InvalidateMemory(address, size);
- }
- const u32 alignment =
- is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment();
- const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, desc.is_written);
- const u32 offset_aligned = Common::AlignDown(offset, alignment);
- const u32 adjust = offset - offset_aligned;
- if (adjust != 0) {
- ASSERT(adjust % 4 == 0);
- push_data.AddOffset(binding, adjust);
- }
- buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust);
set_writes.push_back({
.dstSet = VK_NULL_HANDLE,
.dstBinding = binding++,
@@ -153,9 +165,9 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
for (const auto& desc : info->texture_buffers) {
const auto vsharp = desc.GetSharp(*info);
vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE);
- if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) {
+ const u32 size = vsharp.GetSize();
+ if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) {
const VAddr address = vsharp.base_address;
- const u32 size = vsharp.GetSize();
if (desc.is_written) {
if (texture_cache.TouchMeta(address, true)) {
LOG_TRACE(Render_Vulkan, "Metadata update skipped");
@@ -166,9 +178,6 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)");
}
}
- if (desc.is_written) {
- texture_cache.InvalidateMemory(address, size);
- }
const u32 alignment = instance.TexelBufferMinAlignment();
const auto [vk_buffer, offset] =
buffer_cache.ObtainBuffer(address, size, desc.is_written, true);
@@ -183,6 +192,15 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
}
buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written,
vsharp.GetDataFmt(), vsharp.GetNumberFmt());
+ if (auto barrier =
+ vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite
+ : vk::AccessFlagBits2::eShaderRead,
+ vk::PipelineStageFlagBits2::eComputeShader)) {
+ buffer_barriers.emplace_back(*barrier);
+ }
+ if (desc.is_written) {
+ texture_cache.MarkWritten(address, size);
+ }
}
set_writes.push_back({
.dstSet = VK_NULL_HANDLE,
@@ -198,7 +216,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
for (const auto& image_desc : info->images) {
const auto tsharp = image_desc.GetSharp(*info);
if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) {
- VideoCore::ImageInfo image_info{tsharp};
+ VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth};
VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage};
const auto& image_view = texture_cache.FindTexture(image_info, view_info);
const auto& image = texture_cache.GetImage(image_view.image_id);
@@ -222,6 +240,9 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
}
for (const auto& sampler : info->samplers) {
const auto ssharp = sampler.GetSharp(*info);
+ if (ssharp.force_degamma) {
+ LOG_WARNING(Render_Vulkan, "Texture requires gamma correction");
+ }
const auto vk_sampler = texture_cache.GetSampler(ssharp);
image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral);
set_writes.push_back({
@@ -239,9 +260,32 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache,
}
const auto cmdbuf = scheduler.CommandBuffer();
+
+ if (!buffer_barriers.empty()) {
+ const auto dependencies = vk::DependencyInfo{
+ .dependencyFlags = vk::DependencyFlagBits::eByRegion,
+ .bufferMemoryBarrierCount = u32(buffer_barriers.size()),
+ .pBufferMemoryBarriers = buffer_barriers.data(),
+ };
+ scheduler.EndRendering();
+ cmdbuf.pipelineBarrier2(dependencies);
+ }
+
+ if (uses_push_descriptors) {
+ cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0,
+ set_writes);
+ } else {
+ const auto desc_set = desc_heap.Commit(*desc_layout);
+ for (auto& set_write : set_writes) {
+ set_write.dstSet = desc_set;
+ }
+ instance.GetDevice().updateDescriptorSets(set_writes, {});
+ cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, desc_set,
+ {});
+ }
+
cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data),
&push_data);
- cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes);
return true;
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
index 54eaf6532..8a6213a29 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
@@ -16,12 +16,13 @@ namespace Vulkan {
class Instance;
class Scheduler;
+class DescriptorHeap;
class ComputePipeline {
public:
explicit ComputePipeline(const Instance& instance, Scheduler& scheduler,
- vk::PipelineCache pipeline_cache, u64 compute_key,
- const Shader::Info& info, vk::ShaderModule module);
+ DescriptorHeap& desc_heap, vk::PipelineCache pipeline_cache,
+ u64 compute_key, const Shader::Info& info, vk::ShaderModule module);
~ComputePipeline();
[[nodiscard]] vk::Pipeline Handle() const noexcept {
@@ -34,11 +35,13 @@ public:
private:
const Instance& instance;
Scheduler& scheduler;
+ DescriptorHeap& desc_heap;
vk::UniquePipeline pipeline;
vk::UniquePipelineLayout pipeline_layout;
vk::UniqueDescriptorSetLayout desc_layout;
u64 compute_key;
const Shader::Info* info;
+ bool uses_push_descriptors{};
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index f03e5d5de..2f5209eb2 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -17,11 +17,11 @@
namespace Vulkan {
GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& scheduler_,
- const GraphicsPipelineKey& key_,
+ DescriptorHeap& desc_heap_, const GraphicsPipelineKey& key_,
vk::PipelineCache pipeline_cache,
std::span infos,
std::span modules)
- : instance{instance_}, scheduler{scheduler_}, key{key_} {
+ : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, key{key_} {
const vk::Device device = instance.GetDevice();
std::ranges::copy(infos, stages.begin());
BuildDescSetLayout();
@@ -301,7 +301,6 @@ GraphicsPipeline::~GraphicsPipeline() = default;
void GraphicsPipeline::BuildDescSetLayout() {
u32 binding{};
- boost::container::small_vector bindings;
for (const auto* stage : stages) {
if (!stage) {
continue;
@@ -343,8 +342,12 @@ void GraphicsPipeline::BuildDescSetLayout() {
});
}
}
+ uses_push_descriptors = binding < instance.MaxPushDescriptors();
+ const auto flags = uses_push_descriptors
+ ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
+ : vk::DescriptorSetLayoutCreateFlagBits{};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
- .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
+ .flags = flags,
.bindingCount = static_cast(bindings.size()),
.pBindings = bindings.data(),
};
@@ -359,6 +362,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs,
boost::container::static_vector buffer_infos;
boost::container::static_vector image_infos;
boost::container::small_vector set_writes;
+ boost::container::small_vector buffer_barriers;
Shader::PushData push_data{};
u32 binding{};
@@ -404,15 +408,15 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs,
});
}
- for (const auto& tex_buffer : stage->texture_buffers) {
- const auto vsharp = tex_buffer.GetSharp(*stage);
+ for (const auto& desc : stage->texture_buffers) {
+ const auto vsharp = desc.GetSharp(*stage);
vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE);
- if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) {
+ const u32 size = vsharp.GetSize();
+ if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) {
const VAddr address = vsharp.base_address;
- const u32 size = vsharp.GetSize();
const u32 alignment = instance.TexelBufferMinAlignment();
const auto [vk_buffer, offset] =
- buffer_cache.ObtainBuffer(address, size, tex_buffer.is_written, true);
+ buffer_cache.ObtainBuffer(address, size, desc.is_written, true);
const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3;
ASSERT_MSG(fmt_stride == vsharp.GetStride(),
"Texel buffer stride must match format stride");
@@ -422,26 +426,35 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs,
ASSERT(adjust % fmt_stride == 0);
push_data.AddOffset(binding, adjust / fmt_stride);
}
- buffer_view = vk_buffer->View(offset_aligned, size + adjust, tex_buffer.is_written,
+ buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written,
vsharp.GetDataFmt(), vsharp.GetNumberFmt());
+ const auto dst_access = desc.is_written ? vk::AccessFlagBits2::eShaderWrite
+ : vk::AccessFlagBits2::eShaderRead;
+ if (auto barrier = vk_buffer->GetBarrier(
+ dst_access, vk::PipelineStageFlagBits2::eVertexShader)) {
+ buffer_barriers.emplace_back(*barrier);
+ }
+ if (desc.is_written) {
+ texture_cache.MarkWritten(address, size);
+ }
}
set_writes.push_back({
.dstSet = VK_NULL_HANDLE,
.dstBinding = binding++,
.dstArrayElement = 0,
.descriptorCount = 1,
- .descriptorType = tex_buffer.is_written ? vk::DescriptorType::eStorageTexelBuffer
- : vk::DescriptorType::eUniformTexelBuffer,
+ .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer
+ : vk::DescriptorType::eUniformTexelBuffer,
.pTexelBufferView = &buffer_view,
});
}
- boost::container::static_vector