diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh
index 203d214e3..fe77c678c 100755
--- a/.github/linux-appimage-qt.sh
+++ b/.github/linux-appimage-qt.sh
@@ -19,12 +19,13 @@ chmod a+x linuxdeploy-x86_64.AppImage
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh
-
# Build AppImage
./linuxdeploy-x86_64.AppImage --appdir AppDir
./linuxdeploy-plugin-checkrt-x86_64.sh --appdir AppDir
cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin
-./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt --output appimage
+./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt
+rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so
+./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage
mv Shadps4-x86_64.AppImage Shadps4-qt.AppImage
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6f84ac330..419f2e12f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,6 +9,10 @@ on:
pull_request:
branches: [ "*" ]
+concurrency:
+ group: ci-${{ github.event_name }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'push' }}
+
env:
BUILD_TYPE: Release
@@ -287,7 +291,7 @@ jobs:
submodules: recursive
- name: Install dependencies
- run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
@@ -343,7 +347,7 @@ jobs:
submodules: recursive
- name: Install dependencies
- run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c9ea15918..598f59e11 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
if(APPLE)
enable_language(OBJC)
- set(CMAKE_OSX_DEPLOYMENT_TARGET 11)
+ set(CMAKE_OSX_DEPLOYMENT_TARGET 14)
endif()
if (NOT CMAKE_BUILD_TYPE)
diff --git a/README.md b/README.md
index 83f59a2c1..da01833e5 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
# General information
-shadPS4 is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
+**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).
@@ -44,7 +44,7 @@ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Di
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).
-For those who'd like to donate to the project, we now have a [Kofi page!](https://ko-fi.com/shadps4)
+For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)!
# Status
@@ -72,40 +72,11 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md).
> [!IMPORTANT]
-> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 11 on Intel-based Mac devices.
-
-## Building status
-
-
-Windows
-
-| Windows | Build status |
-|--------|--------|
-|Windows SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
-|Windows Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
-
-
-
-Linux
-
-| Linux | Build status |
-|--------|--------|
-|Linux SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
-|Linux Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
-
-
-
-macOS
-
-| macOS | Build status |
-|--------|--------|
-|macOS SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
-|macOS Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
-
+> macOS users need at least macOS 15 on Apple Silicon-based Mac devices and at least macOS 14 on Intel-based Mac devices.
# Debugging and reporting issues
-For more information on how to test, debug and report issues with the emulator or games, read the [Debugging documentation](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
+For more information on how to test, debug and report issues with the emulator or games, read the [**Debugging documentation**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md).
# Keyboard mapping
diff --git a/documents/building-linux.md b/documents/building-linux.md
index 622de543b..989669f4f 100644
--- a/documents/building-linux.md
+++ b/documents/building-linux.md
@@ -3,28 +3,28 @@ SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
-## Build shadPS4 for Linux
+## Build shadPS4 for Linux
### Install the necessary tools to build shadPS4:
#### 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 qt6-base-dev qt6-tools-dev
+sudo apt install build-essential clang git cmake libasound2-dev libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev vulkan-validationlayers
```
#### Fedora
```
-sudo dnf install alsa-lib-devel cmake libatomic libevdev-devel libudev-devel openal-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel
+sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-connection-kit-devel openal-devel openssl-devel libevdev-devel libudev-devel libXext-devel qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel vulkan-devel vulkan-validation-layers
```
#### Arch Linux
```
-sudo pacman -S openal cmake vulkan-validation-layers qt6-base qt6-declarative qt6-multimedia sdl2 sndio jack2 base-devel
+sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers
```
#### OpenSUSE
```
-sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt6-qtbase-devel libqt6-qtmultimedia-devel libqt6-qtsvg-devel libQt6Gui-private-headers-devel libevdev-devel libsndio7_1 libjack-devel
+sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers
```
### Cloning and compiling:
@@ -34,9 +34,11 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git
cd shadPS4
```
-Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag:
+Generate the build directory in the shadPS4 directory. To disable the QT GUI, remove the ```-DENABLE_QT_GUI=ON``` flag:
+
+**Note**: Clang is the compiler used for official builds and CI. If you build with GCC, you might encounter issues—please report any you find. If you choose to use GCC, we recommend building with Clang at least once before submitting a pull request.
```
-cmake -S . -B build/ -DENABLE_QT_GUI=ON
+cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
Enter the directory:
diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp
index f544c52f9..bd27e4823 100644
--- a/src/audio_core/sdl_audio.cpp
+++ b/src/audio_core/sdl_audio.cpp
@@ -100,15 +100,16 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
if (ptr == nullptr) {
return 0;
}
+ lock.unlock();
// TODO mixing channels
- int result = SDL_PutAudioStreamData(port.stream, ptr,
- port.samples_num * port.sample_size * port.channels_num);
+ SDL_bool result = SDL_PutAudioStreamData(
+ port.stream, ptr, port.samples_num * port.sample_size * port.channels_num);
// TODO find a correct value 8192 is estimated
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) {
SDL_Delay(0);
}
- return result;
+ return result ? ORBIS_OK : -1;
}
bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 367efb6bb..8480fae26 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -28,4 +28,16 @@ template
return (value & 0x3FFF) == 0;
}
+template
+ requires std::is_integral_v
+[[nodiscard]] constexpr bool Is64KBAligned(T value) {
+ return (value & 0xFFFF) == 0;
+}
+
+template
+ requires std::is_integral_v
+[[nodiscard]] constexpr bool Is2MBAligned(T value) {
+ return (value & 0x1FFFFF) == 0;
+}
+
} // namespace Common
diff --git a/src/common/config.cpp b/src/common/config.cpp
index 4a8effbdf..f04678eb7 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -8,6 +8,7 @@
#include // for wstring support
#include
#include "common/logging/formatter.h"
+#include "common/path_util.h"
#include "config.h"
namespace toml {
@@ -32,6 +33,7 @@ namespace Config {
static bool isNeo = false;
static bool isFullscreen = false;
static bool playBGM = false;
+static int BGMvolume = 50;
static u32 screenWidth = 1280;
static u32 screenHeight = 720;
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
@@ -58,6 +60,7 @@ static bool vkCrashDiagnostic = false;
// Gui
std::filesystem::path settings_install_dir = {};
+std::filesystem::path settings_addon_install_dir = {};
u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280;
@@ -89,6 +92,10 @@ bool getPlayBGM() {
return playBGM;
}
+int getBGMvolume() {
+ return BGMvolume;
+}
+
u32 getScreenWidth() {
return screenWidth;
}
@@ -249,6 +256,10 @@ void setPlayBGM(bool enable) {
playBGM = enable;
}
+void setBGMvolume(int volume) {
+ BGMvolume = volume;
+}
+
void setLanguage(u32 language) {
m_language = language;
}
@@ -290,6 +301,9 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
void setGameInstallDir(const std::filesystem::path& dir) {
settings_install_dir = dir;
}
+void setAddonInstallDir(const std::filesystem::path& dir) {
+ settings_addon_install_dir = dir;
+}
void setMainWindowTheme(u32 theme) {
mw_themes = theme;
}
@@ -346,6 +360,13 @@ u32 getMainWindowGeometryH() {
std::filesystem::path getGameInstallDir() {
return settings_install_dir;
}
+std::filesystem::path getAddonInstallDir() {
+ if (settings_addon_install_dir.empty()) {
+ // Default for users without a config file or a config file from before this option existed
+ return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont";
+ }
+ return settings_addon_install_dir;
+}
u32 getMainWindowTheme() {
return mw_themes;
}
@@ -412,6 +433,7 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or(general, "isPS4Pro", false);
isFullscreen = toml::find_or(general, "Fullscreen", false);
playBGM = toml::find_or(general, "playBGM", false);
+ BGMvolume = toml::find_or(general, "BGMvolume", 50);
logFilter = toml::find_or(general, "logFilter", "");
logType = toml::find_or(general, "logType", "sync");
userName = toml::find_or(general, "userName", "shadPS4");
@@ -472,6 +494,7 @@ void load(const std::filesystem::path& path) {
m_window_size_W = toml::find_or(gui, "mw_width", 0);
m_window_size_H = toml::find_or(gui, "mw_height", 0);
settings_install_dir = toml::find_fs_path_or(gui, "installDir", {});
+ settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
main_window_geometry_x = toml::find_or(gui, "geometry_x", 0);
main_window_geometry_y = toml::find_or(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or(gui, "geometry_w", 0);
@@ -513,6 +536,7 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo;
data["General"]["Fullscreen"] = isFullscreen;
data["General"]["playBGM"] = playBGM;
+ data["General"]["BGMvolume"] = BGMvolume;
data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType;
data["General"]["userName"] = userName;
@@ -545,6 +569,8 @@ void save(const std::filesystem::path& path) {
data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data};
+ data["GUI"]["addonInstallDir"] =
+ std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["geometry_x"] = main_window_geometry_x;
data["GUI"]["geometry_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w;
@@ -565,6 +591,7 @@ void setDefaultValues() {
isNeo = false;
isFullscreen = false;
playBGM = false;
+ BGMvolume = 50;
screenWidth = 1280;
screenHeight = 720;
logFilter = "";
diff --git a/src/common/config.h b/src/common/config.h
index f2b5187f8..402b8660e 100644
--- a/src/common/config.h
+++ b/src/common/config.h
@@ -14,6 +14,8 @@ void save(const std::filesystem::path& path);
bool isNeoMode();
bool isFullscreenMode();
bool getPlayBGM();
+int getBGMvolume();
+
std::string getLogFilter();
std::string getLogType();
std::string getUserName();
@@ -49,6 +51,7 @@ void setScreenWidth(u32 width);
void setScreenHeight(u32 height);
void setFullscreenMode(bool enable);
void setPlayBGM(bool enable);
+void setBGMvolume(int volume);
void setLanguage(u32 language);
void setNeoMode(bool enable);
void setUserName(const std::string& type);
@@ -73,6 +76,7 @@ bool vkCrashDiagnosticEnabled();
// Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
void setGameInstallDir(const std::filesystem::path& dir);
+void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme);
void setIconSize(u32 size);
void setIconSizeGrid(u32 size);
@@ -91,6 +95,7 @@ u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
std::filesystem::path getGameInstallDir();
+std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();
diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp
index c1d9cc592..1b28d2bba 100644
--- a/src/common/io_file.cpp
+++ b/src/common/io_file.cpp
@@ -192,8 +192,9 @@ int IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileS
#endif
if (!IsOpen()) {
- LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}",
- PathToUTF8String(file_path));
+ const auto ec = std::error_code{result, std::generic_category()};
+ LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, error_message={}",
+ PathToUTF8String(file_path), ec.message());
}
return result;
@@ -372,6 +373,18 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
return false;
}
+ u64 size = GetSize();
+ if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) {
+ LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
+ return false;
+ } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) {
+ LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
+ return false;
+ } else if (origin == SeekOrigin::End && offset > 0) {
+ LOG_ERROR(Common_Filesystem, "Seeking past the end of the file");
+ return false;
+ }
+
errno = 0;
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp
index d7274fc74..f602f3513 100644
--- a/src/common/path_util.cpp
+++ b/src/common/path_util.cpp
@@ -119,7 +119,6 @@ static auto UserPaths = [] {
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
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 d40f4aab4..9bde2e287 100644
--- a/src/common/path_util.h
+++ b/src/common/path_util.h
@@ -26,7 +26,6 @@ enum class PathType {
CapturesDir, // Where rdoc captures are stored.
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.
};
@@ -45,7 +44,6 @@ constexpr auto DOWNLOAD_DIR = "download";
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
diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp
index 3404496ce..b812e5444 100644
--- a/src/core/cpu_patches.cpp
+++ b/src/core/cpu_patches.cpp
@@ -1057,7 +1057,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
// Undefined behavior if length + index is bigger than 64 according to the spec,
// we'll warn and continue execution.
LOG_WARNING(Core,
- "extrq at {:x} with length {} and index {} is bigger than 64, "
+ "extrq at {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}
@@ -1117,7 +1117,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
// Undefined behavior if length + index is bigger than 64 according to the spec,
// we'll warn and continue execution.
LOG_WARNING(Core,
- "insertq at {:x} with length {} and index {} is bigger than 64, "
+ "insertq at {} with length {} and index {} is bigger than 64, "
"undefined behavior",
fmt::ptr(code_address), length, index);
}
diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp
index f329e81a6..8d038b062 100644
--- a/src/core/file_format/pkg.cpp
+++ b/src/core/file_format/pkg.cpp
@@ -44,7 +44,7 @@ PKG::PKG() = default;
PKG::~PKG() = default;
-bool PKG::Open(const std::filesystem::path& filepath) {
+bool PKG::Open(const std::filesystem::path& filepath, std::string& failreason) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
@@ -70,7 +70,11 @@ bool PKG::Open(const std::filesystem::path& filepath) {
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
- file.Seek(offset);
+ if (!file.Seek(offset)) {
+ failreason = "Failed to seek to PKG table entry offset";
+ return false;
+ }
+
for (int i = 0; i < n_files; i++) {
PKGEntry entry{};
file.Read(entry.id);
@@ -85,7 +89,10 @@ bool PKG::Open(const std::filesystem::path& filepath) {
const auto name = GetEntryNameByType(entry.id);
if (name == "param.sfo") {
sfo.clear();
- file.Seek(entry.offset);
+ if (!file.Seek(entry.offset)) {
+ failreason = "Failed to seek to param.sfo offset";
+ return false;
+ }
sfo.resize(entry.size);
file.ReadRaw(sfo.data(), entry.size);
}
@@ -127,7 +134,11 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
std::array, 7> key1;
std::array imgkeydata;
- file.Seek(offset);
+ if (!file.Seek(offset)) {
+ failreason = "Failed to seek to PKG table entry offset";
+ return false;
+ }
+
for (int i = 0; i < n_files; i++) {
PKGEntry entry{};
file.Read(entry.id);
@@ -149,7 +160,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
// Just print with id
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
Common::FS::FileAccessMode::Write);
- file.Seek(entry.offset);
+ if (!file.Seek(entry.offset)) {
+ failreason = "Failed to seek to PKG entry offset";
+ return false;
+ }
std::vector data;
data.resize(entry.size);
@@ -195,7 +209,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
}
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
- file.Seek(entry.offset);
+ if (!file.Seek(entry.offset)) {
+ failreason = "Failed to seek to PKG entry offset";
+ return false;
+ }
std::vector data;
data.resize(entry.size);
@@ -207,7 +224,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
entry.id == 0x403) { // somehow 0x401 is not decrypting
decNp.resize(entry.size);
- file.Seek(entry.offset);
+ if (!file.Seek(entry.offset)) {
+ failreason = "Failed to seek to PKG entry offset";
+ return false;
+ }
std::vector data;
data.resize(entry.size);
@@ -237,7 +257,10 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
// Read the seed
std::array seed;
- file.Seek(pkgheader.pfs_image_offset + 0x370);
+ if (!file.Seek(pkgheader.pfs_image_offset + 0x370)) {
+ failreason = "Failed to seek to PFS image offset";
+ return false;
+ }
file.Read(seed);
// Get data and tweak keys.
diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h
index d30d50b44..a488a2df8 100644
--- a/src/core/file_format/pkg.h
+++ b/src/core/file_format/pkg.h
@@ -103,7 +103,7 @@ public:
PKG();
~PKG();
- bool Open(const std::filesystem::path& filepath);
+ bool Open(const std::filesystem::path& filepath, std::string& failreason);
void ExtractFiles(const int index);
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason);
diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp
index 724f90782..9c53c8588 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/logging/log.h"
#include "common/path_util.h"
#include "trp.h"
@@ -13,7 +14,10 @@ void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
if (!npbindFile.IsOpen()) {
return;
}
- npbindFile.Seek(0x84 + (index * 0x180));
+ if (!npbindFile.Seek(0x84 + (index * 0x180))) {
+ LOG_CRITICAL(Common_Filesystem, "Failed to seek to NPbind offset");
+ return;
+ }
npbindFile.ReadRaw(np_comm_id.data(), 12);
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
}
@@ -56,26 +60,38 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) {
std::filesystem::create_directory(trpFilesPath / "Xml");
for (int i = 0; i < header.entry_num; i++) {
- file.Seek(seekPos);
+ if (!file.Seek(seekPos)) {
+ LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
+ return false;
+ }
seekPos += (s64)header.entry_size;
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
- file.Seek(entry.entry_pos);
+ if (!file.Seek(entry.entry_pos)) {
+ LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
+ return false;
+ }
std::vector icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
- file.Seek(entry.entry_pos);
+ if (!file.Seek(entry.entry_pos)) {
+ LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry offset");
+ return false;
+ }
file.Read(esfmIv); // get iv key.
// Skip the first 16 bytes which are the iv key on every entry as we want a
// clean xml file.
std::vector ESFM(entry.entry_len - iv_len);
std::vector XML(entry.entry_len - iv_len);
- file.Seek(entry.entry_pos + iv_len);
+ if (!file.Seek(entry.entry_pos + iv_len)) {
+ LOG_CRITICAL(Common_Filesystem, "Failed to seek to TRP entry + iv offset");
+ return false;
+ }
file.Read(ESFM);
crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt
removePadding(XML);
diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp
index 754343eef..f912639eb 100644
--- a/src/core/libraries/app_content/app_content.cpp
+++ b/src/core/libraries/app_content/app_content.cpp
@@ -5,6 +5,7 @@
#include "app_content.h"
#include "common/assert.h"
+#include "common/config.h"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "common/path_util.h"
@@ -59,8 +60,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
OrbisAppContentMountPoint* mount_point) {
LOG_INFO(Lib_AppContent, "called");
- const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / title_id /
- entitlement_label->data;
+ const auto& mount_dir = Config::getAddonInstallDir() / title_id / entitlement_label->data;
auto* mnt = Common::Singleton::Instance();
for (int i = 0; i < addcont_count; i++) {
@@ -246,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
auto* param_sfo = Common::Singleton::Instance();
- const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
+ const auto addons_dir = Config::getAddonInstallDir();
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
title_id = *value;
} else {
diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp
index 45ebb4be8..e2093ce21 100644
--- a/src/core/libraries/kernel/file_system.cpp
+++ b/src/core/libraries/kernel/file_system.cpp
@@ -229,7 +229,10 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) {
}
std::scoped_lock lk{file->m_mutex};
- file->f.Seek(offset, origin);
+ if (!file->f.Seek(offset, origin)) {
+ LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
return file->f.Tell();
}
@@ -290,7 +293,8 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
- if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
+ std::error_code ec;
+ if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
return SCE_KERNEL_ERROR_EIO;
}
@@ -310,6 +314,58 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
return result;
}
+int PS4_SYSV_ABI sceKernelRmdir(const char* path) {
+ auto* mnt = Common::Singleton::Instance();
+ bool ro = false;
+
+ const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
+
+ if (dir_name.empty()) {
+ LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, permission denied",
+ fmt::UTF(dir_name.u8string()));
+ return SCE_KERNEL_ERROR_EACCES;
+ }
+
+ if (ro) {
+ LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, directory is read only",
+ fmt::UTF(dir_name.u8string()));
+ return SCE_KERNEL_ERROR_EROFS;
+ }
+
+ if (!std::filesystem::is_directory(dir_name)) {
+ LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, path is not a directory",
+ fmt::UTF(dir_name.u8string()));
+ return ORBIS_KERNEL_ERROR_ENOTDIR;
+ }
+
+ if (!std::filesystem::exists(dir_name)) {
+ LOG_INFO(Kernel_Fs, "Failed to remove directory: {}, no such file or directory",
+ fmt::UTF(dir_name.u8string()));
+ return ORBIS_KERNEL_ERROR_ENOENT;
+ }
+
+ std::error_code ec;
+ int result = std::filesystem::remove_all(dir_name, ec);
+
+ if (!ec) {
+ LOG_DEBUG(Kernel_Fs, "Removed directory: {}", fmt::UTF(dir_name.u8string()));
+ return ORBIS_OK;
+ }
+ LOG_ERROR(Kernel_Fs, "Failed to remove directory: {}, error_code={}",
+ fmt::UTF(dir_name.u8string()), ec.message());
+ return ErrnoToSceKernelError(ec.value());
+}
+
+int PS4_SYSV_ABI posix_rmdir(const char* path) {
+ int result = sceKernelRmdir(path);
+ if (result < 0) {
+ LOG_ERROR(Kernel_Pthread, "posix_rmdir: error = {}", result);
+ ErrSceToPosix(result);
+ return -1;
+ }
+ return result;
+}
+
int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
auto* mnt = Common::Singleton::Instance();
@@ -379,7 +435,10 @@ s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) {
SCOPE_EXIT {
file->f.Seek(pos);
};
- file->f.Seek(offset);
+ if (!file->f.Seek(offset)) {
+ LOG_CRITICAL(Kernel_Fs, "sceKernelPread: failed to seek");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
return file->f.ReadRaw(buf, nbytes);
}
@@ -513,7 +572,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
SCOPE_EXIT {
file->f.Seek(pos);
};
- file->f.Seek(offset);
+ if (!file->f.Seek(offset)) {
+ LOG_CRITICAL(Kernel_Fs, "sceKernelPwrite: failed to seek");
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
return file->f.WriteRaw(buf, nbytes);
}
@@ -564,13 +626,19 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("AqBioC2vF3I", "libScePosix", 1, "libkernel", 1, 1, posix_read);
LIB_FUNCTION("1-LFLmRFxxM", "libkernel", 1, "libkernel", 1, 1, sceKernelMkdir);
LIB_FUNCTION("JGMio+21L4c", "libScePosix", 1, "libkernel", 1, 1, posix_mkdir);
+ LIB_FUNCTION("JGMio+21L4c", "libkernel", 1, "libkernel", 1, 1, posix_mkdir);
+ LIB_FUNCTION("naInUjYt3so", "libkernel", 1, "libkernel", 1, 1, sceKernelRmdir);
+ LIB_FUNCTION("c7ZnT7V1B98", "libScePosix", 1, "libkernel", 1, 1, posix_rmdir);
+ LIB_FUNCTION("c7ZnT7V1B98", "libkernel", 1, "libkernel", 1, 1, posix_rmdir);
LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat);
LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat);
LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat);
+ LIB_FUNCTION("mqQMh1zPPT8", "libkernel", 1, "libkernel", 1, 1, posix_fstat);
LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate);
LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat);
+ LIB_FUNCTION("E6ao34wPw+U", "libkernel", 1, "libkernel", 1, 1, posix_stat);
LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread);
LIB_FUNCTION("uWyW3v98sU4", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckReachability);
LIB_FUNCTION("fTx66l5iWIA", "libkernel", 1, "libkernel", 1, 1, sceKernelFsync);
diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp
index 65d3dde14..72f8f1d1d 100644
--- a/src/core/libraries/kernel/libkernel.cpp
+++ b/src/core/libraries/kernel/libkernel.cpp
@@ -56,7 +56,7 @@ void KernelSignalRequest() {
}
static void KernelServiceThread(std::stop_token stoken) {
- Common::SetCurrentThreadName("Kernel_ServiceThread");
+ Common::SetCurrentThreadName("shadPS4:Kernel_ServiceThread");
while (!stoken.stop_requested()) {
HLE_TRACE;
@@ -186,6 +186,16 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd,
return ptr;
}
+s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
+ if (sizeOut == nullptr) {
+ return ORBIS_KERNEL_ERROR_EINVAL;
+ }
+
+ auto* memory = Core::Memory::Instance();
+ *sizeOut = memory->GetTotalFlexibleSize();
+ return ORBIS_OK;
+}
+
static uint64_t g_mspace_atomic_id_mask = 0;
static uint64_t g_mstate_table[64] = {0};
@@ -403,10 +413,12 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
// obj
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard);
+
// misc
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord);
LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect);
LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask);
+
// memory
LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException);
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory);
@@ -443,6 +455,14 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
+ LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
+ sceKernelConfiguredFlexibleMemorySize);
+
+ // Memory pool
+ LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);
+ LIB_FUNCTION("pU-QydtGcGY", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolReserve);
+ LIB_FUNCTION("Vzl66WmfLvk", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolCommit);
+ LIB_FUNCTION("LXo1tpFqJGs", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolDecommit);
// equeue
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);
diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp
index 7853a77a4..5331f47f2 100644
--- a/src/core/libraries/kernel/memory_management.cpp
+++ b/src/core/libraries/kernel/memory_management.cpp
@@ -347,4 +347,102 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
memory->NameVirtualRange(std::bit_cast(addr), len, name);
return ORBIS_OK;
}
+
+s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
+ size_t alignment, u64* physAddrOut) {
+ if (searchStart < 0 || searchEnd <= searchStart) {
+ LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ const bool is_in_range = searchEnd - searchStart >= len;
+ if (len <= 0 || !Common::Is64KBAligned(len) || !is_in_range) {
+ LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (alignment != 0 && !Common::Is64KBAligned(alignment)) {
+ LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (physAddrOut == nullptr) {
+ LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+
+ auto* memory = Core::Memory::Instance();
+ PAddr phys_addr = memory->PoolExpand(searchStart, searchEnd, len, alignment);
+ *physAddrOut = static_cast(phys_addr);
+
+ LOG_INFO(Kernel_Vmm,
+ "searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, physAddrOut "
+ "= {:#x}",
+ searchStart, searchEnd, len, alignment, phys_addr);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
+ void** addrOut) {
+ LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
+ fmt::ptr(addrIn), len, alignment, flags);
+
+ if (addrIn == nullptr) {
+ LOG_ERROR(Kernel_Vmm, "Address is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (len == 0 || !Common::Is2MBAligned(len)) {
+ LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (alignment != 0) {
+ if ((!std::has_single_bit(alignment) && !Common::Is2MBAligned(alignment))) {
+ LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ }
+
+ auto* memory = Core::Memory::Instance();
+ const VAddr in_addr = reinterpret_cast(addrIn);
+ const auto map_flags = static_cast(flags);
+ memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment);
+
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) {
+ if (addr == nullptr) {
+ LOG_ERROR(Kernel_Vmm, "Address is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (len == 0 || !Common::Is64KBAligned(len)) {
+ LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+
+ LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, type = {:#x}, prot = {:#x}, flags = {:#x}",
+ fmt::ptr(addr), len, type, prot, flags);
+
+ const VAddr in_addr = reinterpret_cast(addr);
+ const auto mem_prot = static_cast(prot);
+ auto* memory = Core::Memory::Instance();
+ return memory->PoolCommit(in_addr, len, mem_prot);
+}
+
+s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) {
+ if (addr == nullptr) {
+ LOG_ERROR(Kernel_Vmm, "Address is invalid!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+ if (len == 0 || !Common::Is64KBAligned(len)) {
+ LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 64KB aligned!");
+ return SCE_KERNEL_ERROR_EINVAL;
+ }
+
+ LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}", fmt::ptr(addr), len, flags);
+
+ const VAddr pool_addr = reinterpret_cast(addr);
+ auto* memory = Core::Memory::Instance();
+ memory->PoolDecommit(pool_addr, len);
+
+ return ORBIS_OK;
+}
+
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h
index 205b2274f..38898aa57 100644
--- a/src/core/libraries/kernel/memory_management.h
+++ b/src/core/libraries/kernel/memory_management.h
@@ -114,4 +114,11 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len,
+ size_t alignment, u64* physAddrOut);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags,
+ void** addrOut);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags);
+s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
+
} // namespace Libraries::Kernel
diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp
index 455ac24b1..90e03293b 100644
--- a/src/core/libraries/kernel/thread_management.cpp
+++ b/src/core/libraries/kernel/thread_management.cpp
@@ -414,6 +414,7 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
if (addr == nullptr || *addr != nullptr) {
return addr;
}
+
const VAddr vaddr = reinterpret_cast(addr);
std::string name = fmt::format("mutex{:#x}", vaddr);
scePthreadMutexInit(addr, nullptr, name.c_str());
@@ -515,9 +516,12 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
ptype = PTHREAD_MUTEX_RECURSIVE;
break;
case ORBIS_PTHREAD_MUTEX_NORMAL:
- case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
ptype = PTHREAD_MUTEX_NORMAL;
break;
+ case ORBIS_PTHREAD_MUTEX_ADAPTIVE:
+ LOG_ERROR(Kernel_Pthread, "Unimplemented adaptive mutex");
+ ptype = PTHREAD_MUTEX_ERRORCHECK;
+ break;
default:
return SCE_KERNEL_ERROR_EINVAL;
}
@@ -1620,6 +1624,10 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTrylock);
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexTimedlock);
+ // scePthreadMutexInitForInternalLibc, scePthreadMutexattrInitForInternalLibc
+ LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexInit);
+ LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1, scePthreadMutexattrInit);
+
// cond calls
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", 1, 1, scePthreadCondInit);
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", 1, 1, scePthreadCondattrInit);
diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp
index 91fdeb991..0641a2c06 100644
--- a/src/core/libraries/np_trophy/np_trophy.cpp
+++ b/src/core/libraries/np_trophy/np_trophy.cpp
@@ -14,8 +14,6 @@
namespace Libraries::NpTrophy {
-static TrophyUI g_trophy_ui;
-
std::string game_serial;
static constexpr auto MaxTrophyHandles = 4u;
@@ -223,6 +221,14 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
return ORBIS_OK;
}
+struct GameTrophyInfo {
+ uint32_t num_groups;
+ uint32_t num_trophies;
+ uint32_t num_trophies_by_rarity[5];
+ uint32_t unlocked_trophies;
+ uint32_t unlocked_trophies_by_rarity[5];
+};
+
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data) {
@@ -240,79 +246,66 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
if (details->size != 0x4A0 || data->size != 0x20)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
- const auto trophyDir =
+ const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+ auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
- pugi::xml_parse_result result =
- doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+ pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
- if (result) {
+ ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
- uint32_t numGroups = 0;
- uint32_t numTrophies = 0;
- uint32_t numTrophiesByRarity[5];
- numTrophiesByRarity[1] = 0;
- numTrophiesByRarity[2] = 0;
- numTrophiesByRarity[3] = 0;
- numTrophiesByRarity[4] = 0;
- uint32_t unlockedTrophies = 0;
- uint32_t unlockedTrophiesByRarity[5];
- unlockedTrophiesByRarity[1] = 0;
- unlockedTrophiesByRarity[2] = 0;
- unlockedTrophiesByRarity[3] = 0;
- unlockedTrophiesByRarity[4] = 0;
+ GameTrophyInfo game_info{};
- auto trophyconf = doc.child("trophyconf");
- for (pugi::xml_node_iterator it = trophyconf.children().begin();
- it != trophyconf.children().end(); ++it) {
+ auto trophyconf = doc.child("trophyconf");
+ for (const pugi::xml_node& node : trophyconf.children()) {
+ std::string_view node_name = node.name();
- if (std::string(it->name()) == "title-name") {
- strncpy(details->title, it->text().as_string(),
- ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
- }
-
- if (std::string(it->name()) == "title-detail") {
- strncpy(details->description, it->text().as_string(),
- ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
- }
-
- if (std::string(it->name()) == "group")
- numGroups++;
-
- if (std::string(it->name()) == "trophy") {
- std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
- std::string currentTrophyGrade = it->attribute("ttype").value();
-
- numTrophies++;
- if (!currentTrophyGrade.empty()) {
- int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
- numTrophiesByRarity[trophyGrade]++;
- if (currentTrophyUnlockState == "unlocked") {
- unlockedTrophies++;
- unlockedTrophiesByRarity[trophyGrade]++;
- }
- }
- }
+ if (node_name == "title-name") {
+ strncpy(details->title, node.text().as_string(), ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE);
}
- details->numGroups = numGroups;
- details->numTrophies = numTrophies;
- details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
- details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
- details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
- details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
- data->unlockedTrophies = unlockedTrophies;
- data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
- data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
- data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
- data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+ if (node_name == "title-detail") {
+ strncpy(details->description, node.text().as_string(),
+ ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
+ }
- // maybe this should be 1 instead of 100?
- data->progressPercentage = 100;
+ if (node_name == "group")
+ game_info.num_groups++;
- } else
- LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+ if (node_name == "trophy") {
+ bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
+ std::string_view current_trophy_grade = node.attribute("ttype").value();
+
+ if (current_trophy_grade.empty()) {
+ continue;
+ }
+
+ game_info.num_trophies++;
+ int trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
+ game_info.num_trophies_by_rarity[trophy_grade]++;
+
+ if (current_trophy_unlockstate) {
+ game_info.unlocked_trophies++;
+ game_info.unlocked_trophies_by_rarity[trophy_grade]++;
+ }
+ }
+ }
+
+ details->num_groups = game_info.num_groups;
+ details->num_trophies = game_info.num_trophies;
+ details->num_platinum = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
+ details->num_gold = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
+ details->num_silver = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
+ details->num_bronze = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+ data->unlocked_trophies = game_info.unlocked_trophies;
+ data->unlocked_platinum = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
+ data->unlocked_gold = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
+ data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
+ data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+
+ // maybe this should be 1 instead of 100?
+ data->progress_percentage = 100;
return ORBIS_OK;
}
@@ -323,6 +316,13 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTr
return ORBIS_OK;
}
+struct GroupTrophyInfo {
+ uint32_t num_trophies;
+ uint32_t num_trophies_by_rarity[5];
+ uint32_t unlocked_trophies;
+ uint32_t unlocked_trophies_by_rarity[5];
+};
+
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details,
@@ -341,89 +341,75 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
if (details->size != 0x4A0 || data->size != 0x28)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
- const auto trophyDir =
+ const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+ auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
- pugi::xml_parse_result result =
- doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+ pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
- if (result) {
+ ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
- uint32_t numGroups = 0;
- uint32_t numTrophies = 0;
- uint32_t numTrophiesByRarity[5];
- numTrophiesByRarity[1] = 0;
- numTrophiesByRarity[2] = 0;
- numTrophiesByRarity[3] = 0;
- numTrophiesByRarity[4] = 0;
- uint32_t unlockedTrophies = 0;
- uint32_t unlockedTrophiesByRarity[5];
- unlockedTrophiesByRarity[1] = 0;
- unlockedTrophiesByRarity[2] = 0;
- unlockedTrophiesByRarity[3] = 0;
- unlockedTrophiesByRarity[4] = 0;
+ GroupTrophyInfo group_info{};
- auto trophyconf = doc.child("trophyconf");
- for (pugi::xml_node_iterator it = trophyconf.children().begin();
- it != trophyconf.children().end(); ++it) {
+ auto trophyconf = doc.child("trophyconf");
+ for (const pugi::xml_node& node : trophyconf.children()) {
+ std::string_view node_name = node.name();
- if (std::string(it->name()) == "group") {
- numGroups++;
- std::string currentGroupId = it->attribute("id").value();
- if (!currentGroupId.empty()) {
- if (std::stoi(currentGroupId) == groupId) {
- std::string currentGroupName = it->child("name").text().as_string();
- std::string currentGroupDescription =
- it->child("detail").text().as_string();
+ if (node_name == "group") {
+ int current_group_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_GROUP_ID);
+ if (current_group_id != ORBIS_NP_TROPHY_INVALID_GROUP_ID) {
+ if (current_group_id == groupId) {
+ std::string_view current_group_name = node.child("name").text().as_string();
+ std::string_view current_group_description =
+ node.child("detail").text().as_string();
- strncpy(details->title, currentGroupName.c_str(),
- ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
- strncpy(details->description, currentGroupDescription.c_str(),
- ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
- }
- }
- }
-
- data->groupId = groupId;
-
- if (std::string(it->name()) == "trophy") {
- std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
- std::string currentTrophyGrade = it->attribute("ttype").value();
- std::string currentTrophyGroupID = it->attribute("gid").value();
-
- if (!currentTrophyGroupID.empty()) {
- if (std::stoi(currentTrophyGroupID) == groupId) {
- numTrophies++;
- if (!currentTrophyGrade.empty()) {
- int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
- numTrophiesByRarity[trophyGrade]++;
- if (currentTrophyUnlockState == "unlocked") {
- unlockedTrophies++;
- unlockedTrophiesByRarity[trophyGrade]++;
- }
- }
- }
+ strncpy(details->title, current_group_name.data(),
+ ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE);
+ strncpy(details->description, current_group_description.data(),
+ ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE);
}
}
}
- details->numTrophies = numTrophies;
- details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
- details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
- details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
- details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
- data->unlockedTrophies = unlockedTrophies;
- data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
- data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD];
- data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER];
- data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+ details->group_id = groupId;
+ data->group_id = groupId;
- // maybe this should be 1 instead of 100?
- data->progressPercentage = 100;
+ if (node_name == "trophy") {
+ bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
+ std::string_view current_trophy_grade = node.attribute("ttype").value();
+ int current_trophy_group_id = node.attribute("gid").as_int(-1);
- } else
- LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+ if (current_trophy_grade.empty()) {
+ continue;
+ }
+
+ if (current_trophy_group_id == groupId) {
+ group_info.num_trophies++;
+ int trophyGrade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
+ group_info.num_trophies_by_rarity[trophyGrade]++;
+ if (current_trophy_unlockstate) {
+ group_info.unlocked_trophies++;
+ group_info.unlocked_trophies_by_rarity[trophyGrade]++;
+ }
+ }
+ }
+ }
+
+ details->num_trophies = group_info.num_trophies;
+ details->num_platinum = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
+ details->num_gold = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
+ details->num_silver = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
+ details->num_bronze = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+ data->unlocked_trophies = group_info.unlocked_trophies;
+ data->unlocked_platinum =
+ group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM];
+ data->unlocked_gold = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD];
+ data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
+ data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
+
+ // maybe this should be 1 instead of 100?
+ data->progress_percentage = 100;
return ORBIS_OK;
}
@@ -454,87 +440,48 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
if (details->size != 0x498 || data->size != 0x18)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
- const auto trophyDir =
+ const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+ auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
- pugi::xml_parse_result result =
- doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+ pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
- if (result) {
- auto trophyconf = doc.child("trophyconf");
- for (pugi::xml_node_iterator it = trophyconf.children().begin();
- it != trophyconf.children().end(); ++it) {
+ ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
- if (std::string(it->name()) == "trophy") {
- std::string currentTrophyId = it->attribute("id").value();
- if (std::stoi(currentTrophyId) == trophyId) {
- std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
- std::string currentTrophyTimestamp = it->attribute("timestamp").value();
- std::string currentTrophyGrade = it->attribute("ttype").value();
- std::string currentTrophyGroupID = it->attribute("gid").value();
- std::string currentTrophyHidden = it->attribute("hidden").value();
- std::string currentTrophyName = it->child("name").text().as_string();
- std::string currentTrophyDescription = it->child("detail").text().as_string();
+ auto trophyconf = doc.child("trophyconf");
- if (currentTrophyUnlockState == "unlocked") {
- details->trophyId = trophyId;
- if (currentTrophyGrade.empty()) {
- details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
- } else {
- details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
- }
- if (currentTrophyGroupID.empty()) {
- details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
- } else {
- details->groupId = std::stoi(currentTrophyGroupID);
- }
- if (currentTrophyHidden == "yes") {
- details->hidden = true;
- } else {
- details->hidden = false;
- }
+ for (const pugi::xml_node& node : trophyconf.children()) {
+ std::string_view node_name = node.name();
- strncpy(details->name, currentTrophyName.c_str(),
- ORBIS_NP_TROPHY_NAME_MAX_SIZE);
- strncpy(details->description, currentTrophyDescription.c_str(),
- ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
+ if (node_name == "trophy") {
+ int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
+ if (current_trophy_id == trophyId) {
+ bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
+ std::string_view current_trophy_grade = node.attribute("ttype").value();
+ std::string_view current_trophy_name = node.child("name").text().as_string();
+ std::string_view current_trophy_description =
+ node.child("detail").text().as_string();
- data->trophyId = trophyId;
- data->unlocked = true;
- data->timestamp.tick = std::stoull(currentTrophyTimestamp);
- } else {
- details->trophyId = trophyId;
- if (currentTrophyGrade.empty()) {
- details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN;
- } else {
- details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0));
- }
- if (currentTrophyGroupID.empty()) {
- details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID;
- } else {
- details->groupId = std::stoi(currentTrophyGroupID);
- }
- if (currentTrophyHidden == "yes") {
- details->hidden = true;
- } else {
- details->hidden = false;
- }
+ uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
+ int current_trophy_groupid = node.attribute("gid").as_int(-1);
+ bool current_trophy_hidden = node.attribute("hidden").as_bool();
- strncpy(details->name, currentTrophyName.c_str(),
- ORBIS_NP_TROPHY_NAME_MAX_SIZE);
- strncpy(details->description, currentTrophyDescription.c_str(),
- ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
+ details->trophy_id = trophyId;
+ details->trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0));
+ details->group_id = current_trophy_groupid;
+ details->hidden = current_trophy_hidden;
- data->trophyId = trophyId;
- data->unlocked = false;
- data->timestamp.tick = 0;
- }
- }
+ strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
+ strncpy(details->description, current_trophy_description.data(),
+ ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
+
+ data->trophy_id = trophyId;
+ data->unlocked = current_trophy_unlockstate;
+ data->timestamp.tick = current_trophy_timestamp;
}
}
- } else
- LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+ }
return ORBIS_OK;
}
@@ -555,35 +502,33 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
- const auto trophyDir =
+ const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
- auto trophy_file = trophyDir / "trophy00" / "Xml" / "TROP.XML";
+ auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
- int numTrophies = 0;
+ ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
- if (result) {
- auto trophyconf = doc.child("trophyconf");
- for (pugi::xml_node_iterator it = trophyconf.children().begin();
- it != trophyconf.children().end(); ++it) {
+ int num_trophies = 0;
+ auto trophyconf = doc.child("trophyconf");
- std::string currentTrophyId = it->attribute("id").value();
- std::string currentTrophyUnlockState = it->attribute("unlockstate").value();
+ for (const pugi::xml_node& node : trophyconf.children()) {
+ std::string_view node_name = node.name();
+ int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
+ bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
- if (std::string(it->name()) == "trophy") {
- numTrophies++;
- }
-
- if (currentTrophyUnlockState == "unlocked") {
- ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags);
- }
+ if (node_name == "trophy") {
+ num_trophies++;
}
- } else
- LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
- *count = numTrophies;
+ if (current_trophy_unlockstate) {
+ ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
+ }
+ }
+
+ *count = num_trophies;
return ORBIS_OK;
}
@@ -912,148 +857,116 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr
if (platinumId == nullptr)
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
- const auto trophyDir =
+ const auto trophy_dir =
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
+ auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML";
pugi::xml_document doc;
- pugi::xml_parse_result result =
- doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
+ pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
+
+ ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description());
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
- int numTrophies = 0;
- int numTrophiesUnlocked = 0;
+ int num_trophies = 0;
+ int num_trophies_unlocked = 0;
+ pugi::xml_node platinum_node;
- pugi::xml_node_iterator platinumIt;
- int platinumTrophyGroup = -1;
+ auto trophyconf = doc.child("trophyconf");
- if (result) {
- auto trophyconf = doc.child("trophyconf");
- for (pugi::xml_node_iterator it = trophyconf.children().begin();
- it != trophyconf.children().end(); ++it) {
+ for (pugi::xml_node& node : trophyconf.children()) {
+ int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
+ bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
+ const char* current_trophy_name = node.child("name").text().as_string();
+ std::string_view current_trophy_description = node.child("detail").text().as_string();
+ std::string_view current_trophy_type = node.attribute("ttype").value();
- 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 (current_trophy_type == "P") {
+ platinum_node = node;
+ if (trophyId == current_trophy_id) {
+ return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
+ }
+ }
- 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_view(node.name()) == "trophy") {
+ if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
+ num_trophies++;
+ if (current_trophy_unlockstate) {
+ num_trophies_unlocked++;
}
}
- if (std::string(it->name()) == "trophy") {
- if (platinumTrophyGroup == -1) {
- if (std::string(it->attribute("gid").value()).empty()) {
- numTrophies++;
- if (currentTrophyUnlockState == "unlocked") {
- numTrophiesUnlocked++;
- }
- }
+ if (current_trophy_id == trophyId) {
+ if (current_trophy_unlockstate) {
+ LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
+ return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
} 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;
+ if (node.attribute("unlockstate").empty()) {
+ node.append_attribute("unlockstate") = "true";
} else {
- if (std::string(it->attribute("unlockstate").value()).empty()) {
- it->append_attribute("unlockstate") = "unlocked";
- } else {
- it->attribute("unlockstate").set_value("unlocked");
- }
-
- Rtc::OrbisRtcTick trophyTimestamp;
- Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
-
- if (std::string(it->attribute("timestamp").value()).empty()) {
- it->append_attribute("timestamp") =
- std::to_string(trophyTimestamp.tick).c_str();
- } else {
- it->attribute("timestamp")
- .set_value(std::to_string(trophyTimestamp.tick).c_str());
- }
-
- g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName);
+ node.attribute("unlockstate").set_value("true");
}
+
+ Rtc::OrbisRtcTick trophyTimestamp;
+ Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
+
+ if (node.attribute("timestamp").empty()) {
+ node.append_attribute("timestamp") =
+ std::to_string(trophyTimestamp.tick).c_str();
+ } else {
+ node.attribute("timestamp")
+ .set_value(std::to_string(trophyTimestamp.tick).c_str());
+ }
+
+ std::string trophy_icon_file = "TROP";
+ trophy_icon_file.append(node.attribute("id").value());
+ trophy_icon_file.append(".PNG");
+
+ std::filesystem::path current_icon_path =
+ trophy_dir / "trophy00" / "Icons" / trophy_icon_file;
+
+ AddTrophyToQueue(current_icon_path, current_trophy_name);
}
}
}
+ }
- if (std::string(platinumIt->attribute("unlockstate").value()).empty()) {
- if ((numTrophies - 2) == numTrophiesUnlocked) {
-
- platinumIt->append_attribute("unlockstate") = "unlocked";
-
- Rtc::OrbisRtcTick trophyTimestamp;
- Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
-
- if (std::string(platinumIt->attribute("timestamp").value()).empty()) {
- platinumIt->append_attribute("timestamp") =
- std::to_string(trophyTimestamp.tick).c_str();
- } else {
- platinumIt->attribute("timestamp")
- .set_value(std::to_string(trophyTimestamp.tick).c_str());
- }
-
- 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);
+ if (!platinum_node.attribute("unlockstate").as_bool()) {
+ if ((num_trophies - 1) == num_trophies_unlocked) {
+ if (platinum_node.attribute("unlockstate").empty()) {
+ platinum_node.append_attribute("unlockstate") = "true";
+ } else {
+ platinum_node.attribute("unlockstate").set_value("true");
}
- } else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") {
- if ((numTrophies - 2) == numTrophiesUnlocked) {
- platinumIt->attribute("unlockstate").set_value("unlocked");
+ Rtc::OrbisRtcTick trophyTimestamp;
+ Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
- Rtc::OrbisRtcTick trophyTimestamp;
- Rtc::sceRtcGetCurrentTick(&trophyTimestamp);
-
- if (std::string(platinumIt->attribute("timestamp").value()).empty()) {
- platinumIt->append_attribute("timestamp") =
- std::to_string(trophyTimestamp.tick).c_str();
- } else {
- platinumIt->attribute("timestamp")
- .set_value(std::to_string(trophyTimestamp.tick).c_str());
- }
-
- 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);
+ if (platinum_node.attribute("timestamp").empty()) {
+ platinum_node.append_attribute("timestamp") =
+ std::to_string(trophyTimestamp.tick).c_str();
+ } else {
+ platinum_node.attribute("timestamp")
+ .set_value(std::to_string(trophyTimestamp.tick).c_str());
}
+
+ int platinum_trophy_id =
+ platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
+ const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
+
+ std::string platinum_icon_file = "TROP";
+ platinum_icon_file.append(platinum_node.attribute("id").value());
+ platinum_icon_file.append(".PNG");
+
+ std::filesystem::path platinum_icon_path =
+ trophy_dir / "trophy00" / "Icons" / platinum_icon_file;
+
+ *platinumId = platinum_trophy_id;
+ AddTrophyToQueue(platinum_icon_path, platinum_trophy_name);
}
+ }
- doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str());
-
- } else
- LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description());
+ doc.save_file((trophy_dir.string() + "/trophy00/Xml/TROP.XML").c_str());
return ORBIS_OK;
}
diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h
index ae08b2969..ac13a9ab7 100644
--- a/src/core/libraries/np_trophy/np_trophy.h
+++ b/src/core/libraries/np_trophy/np_trophy.h
@@ -47,7 +47,7 @@ bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
struct OrbisNpTrophyData {
size_t size;
- OrbisNpTrophyId trophyId;
+ OrbisNpTrophyId trophy_id;
bool unlocked;
uint8_t reserved[3];
Rtc::OrbisRtcTick timestamp;
@@ -66,9 +66,9 @@ constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
struct OrbisNpTrophyDetails {
size_t size;
- OrbisNpTrophyId trophyId;
- OrbisNpTrophyGrade trophyGrade;
- OrbisNpTrophyGroupId groupId;
+ OrbisNpTrophyId trophy_id;
+ OrbisNpTrophyGrade trophy_grade;
+ OrbisNpTrophyGroupId group_id;
bool hidden;
uint8_t reserved[3];
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
@@ -77,46 +77,46 @@ struct OrbisNpTrophyDetails {
struct OrbisNpTrophyGameData {
size_t size;
- uint32_t unlockedTrophies;
- uint32_t unlockedPlatinum;
- uint32_t unlockedGold;
- uint32_t unlockedSilver;
- uint32_t unlockedBronze;
- uint32_t progressPercentage;
+ uint32_t unlocked_trophies;
+ uint32_t unlocked_platinum;
+ uint32_t unlocked_gold;
+ uint32_t unlocked_silver;
+ uint32_t unlocked_bronze;
+ uint32_t progress_percentage;
};
struct OrbisNpTrophyGameDetails {
size_t size;
- uint32_t numGroups;
- uint32_t numTrophies;
- uint32_t numPlatinum;
- uint32_t numGold;
- uint32_t numSilver;
- uint32_t numBronze;
+ uint32_t num_groups;
+ uint32_t num_trophies;
+ uint32_t num_platinum;
+ uint32_t num_gold;
+ uint32_t num_silver;
+ uint32_t num_bronze;
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;
+ OrbisNpTrophyGroupId group_id;
+ uint32_t unlocked_trophies;
+ uint32_t unlocked_platinum;
+ uint32_t unlocked_gold;
+ uint32_t unlocked_silver;
+ uint32_t unlocked_bronze;
+ uint32_t progress_percentage;
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;
+ OrbisNpTrophyGroupId group_id;
+ uint32_t num_trophies;
+ uint32_t num_platinum;
+ uint32_t num_gold;
+ uint32_t num_silver;
+ uint32_t num_bronze;
char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
};
diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp
index 8deaac25b..740bd3a10 100644
--- a/src/core/libraries/np_trophy/trophy_ui.cpp
+++ b/src/core/libraries/np_trophy/trophy_ui.cpp
@@ -2,15 +2,27 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include
+#include
#include
#include "common/assert.h"
+#include "common/singleton.h"
#include "imgui/imgui_std.h"
#include "trophy_ui.h"
using namespace ImGui;
-using namespace Libraries::NpTrophy;
+namespace Libraries::NpTrophy {
-TrophyUI::TrophyUI() {
+std::optional current_trophy_ui;
+std::queue trophy_queue;
+std::mutex queueMtx;
+
+TrophyUI::TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName)
+ : trophy_name(trophyName) {
+ if (std::filesystem::exists(trophyIconPath)) {
+ trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath);
+ } else {
+ LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", trophyIconPath.string());
+ }
AddLayer(this);
}
@@ -18,57 +30,65 @@ 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),
+ std::min(io.DisplaySize.x, 250.f),
+ std::min(io.DisplaySize.y, 70.f),
};
- if (trophyQueue.size() != 0) {
- if (!displayingTrophy) {
- displayingTrophy = true;
- trophyStartedTime = std::chrono::steady_clock::now();
+ SetNextWindowSize(window_size);
+ SetNextWindowCollapsed(false);
+ SetNextWindowPos(ImVec2(io.DisplaySize.x - 250, 50));
+ KeepNavHighlight();
+
+ if (Begin("Trophy Window", nullptr,
+ ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoInputs)) {
+ if (trophy_icon) {
+ Image(trophy_icon.GetTexture().im_id, ImVec2(50, 50));
+ ImGui::SameLine();
+ } else {
+ // placeholder
+ const auto pos = GetCursorScreenPos();
+ ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f},
+ GetColorU32(ImVec4{0.7f}));
+ ImGui::Indent(60);
}
+ TextWrapped("Trophy earned!\n%s", trophy_name.c_str());
+ }
+ End();
- 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("%s", currentTrophyInfo.trophyName.c_str());
- }
- End();
+ trophy_timer -= io.DeltaTime;
+ if (trophy_timer <= 0) {
+ queueMtx.lock();
+ if (!trophy_queue.empty()) {
+ TrophyInfo next_trophy = trophy_queue.front();
+ trophy_queue.pop();
+ current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name);
+ } else {
+ current_trophy_ui.reset();
}
+ queueMtx.unlock();
}
}
+
+void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName) {
+ queueMtx.lock();
+ if (current_trophy_ui.has_value()) {
+ TrophyInfo new_trophy;
+ new_trophy.trophy_icon_path = trophyIconPath;
+ new_trophy.trophy_name = trophyName;
+ trophy_queue.push(new_trophy);
+ } else {
+ current_trophy_ui.emplace(trophyIconPath, trophyName);
+ }
+ queueMtx.unlock();
+}
+
+} // namespace Libraries::NpTrophy
\ 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
index 060d80dec..4448c2281 100644
--- a/src/core/libraries/np_trophy/trophy_ui.h
+++ b/src/core/libraries/np_trophy/trophy_ui.h
@@ -5,32 +5,36 @@
#include
#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"
+#include "imgui/imgui_texture.h"
namespace Libraries::NpTrophy {
-struct TrophyInfo {
- int trophyId = -1;
- std::string trophyName;
-};
-
class TrophyUI final : public ImGui::Layer {
- std::vector trophyQueue;
-
public:
- TrophyUI();
+ TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName);
~TrophyUI() override;
- void AddTrophyToQueue(int trophyId, std::string trophyName);
-
void Finish();
void Draw() override;
+
+private:
+ std::string trophy_name;
+ float trophy_timer = 5.0f;
+ ImGui::RefCountedTexture trophy_icon;
};
+struct TrophyInfo {
+ std::filesystem::path trophy_icon_path;
+ std::string trophy_name;
+};
+
+void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName);
+
}; // namespace Libraries::NpTrophy
\ No newline at end of file
diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp
index 1d935aee1..da5172b15 100644
--- a/src/core/libraries/save_data/save_backup.cpp
+++ b/src/core/libraries/save_data/save_backup.cpp
@@ -79,7 +79,7 @@ static void backup(const std::filesystem::path& dir_name) {
}
static void BackupThreadBody() {
- Common::SetCurrentThreadName("SaveData_BackupThread");
+ Common::SetCurrentThreadName("shadPS4:SaveData_BackupThread");
while (g_backup_status != WorkerStatus::Stopping) {
g_backup_status = WorkerStatus::Waiting;
diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp
index 0a714a26f..c4d105612 100644
--- a/src/core/libraries/save_data/save_memory.cpp
+++ b/src/core/libraries/save_data/save_memory.cpp
@@ -66,7 +66,7 @@ static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& p
}
[[noreturn]] void SaveThreadLoop() {
- Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
+ Common::SetCurrentThreadName("shadPS4:SaveData_SaveDataMemoryThread");
std::mutex mtx;
while (true) {
{
diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp
index fa7577907..940a8c7d3 100644
--- a/src/core/libraries/videoout/driver.cpp
+++ b/src/core/libraries/videoout/driver.cpp
@@ -260,7 +260,7 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
static constexpr std::chrono::nanoseconds VblankPeriod{16666667};
const auto vblank_period = VblankPeriod / Config::vblankDiv();
- Common::SetCurrentThreadName("PresentThread");
+ Common::SetCurrentThreadName("shadPS4:PresentThread");
Common::SetCurrentThreadRealtime(vblank_period);
Common::AccurateTimer timer{vblank_period};
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 6d7c8773a..4de20436f 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -204,7 +204,10 @@ void Elf::Open(const std::filesystem::path& file_name) {
}
out.resize(num);
- m_f.Seek(offset, SeekOrigin::SetOrigin);
+ if (!m_f.Seek(offset, SeekOrigin::SetOrigin)) {
+ LOG_CRITICAL(Loader, "Failed to seek to header tables");
+ return;
+ }
m_f.Read(out);
};
@@ -465,7 +468,10 @@ std::string Elf::ElfPHeaderStr(u16 no) {
void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (!is_self) {
// It's elf file
- m_f.Seek(file_offset, SeekOrigin::SetOrigin);
+ if (!m_f.Seek(file_offset, SeekOrigin::SetOrigin)) {
+ LOG_CRITICAL(Loader, "Failed to seek to ELF header");
+ return;
+ }
m_f.ReadRaw(reinterpret_cast(virtual_addr), size);
return;
}
@@ -479,7 +485,10 @@ void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
auto offset = file_offset - phdr.p_offset;
- m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin);
+ if (!m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin)) {
+ LOG_CRITICAL(Loader, "Failed to seek to segment");
+ return;
+ }
m_f.ReadRaw(reinterpret_cast(virtual_addr), size);
return;
}
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index ebda00357..d21ebae83 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -51,6 +51,35 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
total_flexible_size, total_direct_size);
}
+PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) {
+ std::scoped_lock lk{mutex};
+
+ auto dmem_area = FindDmemArea(search_start);
+
+ const auto is_suitable = [&] {
+ const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment)
+ : dmem_area->second.base;
+ const auto alignment_size = aligned_base - dmem_area->second.base;
+ const auto remaining_size =
+ dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0;
+ return dmem_area->second.is_free && remaining_size >= size;
+ };
+ while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
+ dmem_area++;
+ }
+ ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size);
+
+ // Align free position
+ PAddr free_addr = dmem_area->second.base;
+ free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
+
+ // Add the allocated region to the list and commit its pages.
+ auto& area = CarveDmemArea(free_addr, size)->second;
+ area.is_free = false;
+ area.is_pooled = true;
+ return free_addr;
+}
+
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type) {
std::scoped_lock lk{mutex};
@@ -112,6 +141,43 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
MergeAdjacent(dmem_map, dmem_area);
}
+int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size,
+ MemoryMapFlags flags, u64 alignment) {
+ std::scoped_lock lk{mutex};
+
+ virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
+ alignment = alignment > 0 ? alignment : 2_MB;
+ VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
+
+ // Fixed mapping means the virtual address must exactly match the provided one.
+ if (True(flags & MemoryMapFlags::Fixed)) {
+ const auto& vma = FindVMA(mapped_addr)->second;
+ // If the VMA is mapped, unmap the region first.
+ if (vma.IsMapped()) {
+ UnmapMemoryImpl(mapped_addr, size);
+ }
+ const size_t remaining_size = vma.base + vma.size - mapped_addr;
+ ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
+ }
+
+ // Find the first free area starting with provided virtual address.
+ if (False(flags & MemoryMapFlags::Fixed)) {
+ mapped_addr = SearchFree(mapped_addr, size, alignment);
+ }
+
+ // Add virtual memory area
+ const auto new_vma_handle = CarveVMA(mapped_addr, size);
+ auto& new_vma = new_vma_handle->second;
+ new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
+ new_vma.prot = MemoryProt::NoAccess;
+ new_vma.name = "";
+ new_vma.type = VMAType::PoolReserved;
+ MergeAdjacent(vma_map, new_vma_handle);
+
+ *out_addr = std::bit_cast(mapped_addr);
+ return ORBIS_OK;
+}
+
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment) {
std::scoped_lock lk{mutex};
@@ -149,6 +215,36 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
+int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
+ std::scoped_lock lk{mutex};
+
+ const u64 alignment = 64_KB;
+
+ // When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
+ // flag so we will take the branch that searches for free (or reserved) mappings.
+ virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
+ VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
+
+ // This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
+ const auto& vma = FindVMA(mapped_addr)->second;
+ const size_t remaining_size = vma.base + vma.size - mapped_addr;
+ ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
+
+ // Perform the mapping.
+ void* out_addr = impl.Map(mapped_addr, size, alignment, -1, false);
+ TRACK_ALLOC(out_addr, size, "VMEM");
+
+ auto& new_vma = CarveVMA(mapped_addr, size)->second;
+ new_vma.disallow_merge = false;
+ new_vma.prot = prot;
+ new_vma.name = "";
+ new_vma.type = Core::VMAType::Pooled;
+ new_vma.is_exec = false;
+ new_vma.phys_base = 0;
+
+ return ORBIS_OK;
+}
+
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name,
bool is_exec, PAddr phys_addr, u64 alignment) {
@@ -232,6 +328,39 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK;
}
+void MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
+ std::scoped_lock lk{mutex};
+
+ const auto it = FindVMA(virtual_addr);
+ const auto& vma_base = it->second;
+ ASSERT_MSG(vma_base.Contains(virtual_addr, size),
+ "Existing mapping does not contain requested unmap range");
+
+ const auto vma_base_addr = vma_base.base;
+ const auto vma_base_size = vma_base.size;
+ const auto phys_base = vma_base.phys_base;
+ const bool is_exec = vma_base.is_exec;
+ const auto start_in_vma = virtual_addr - vma_base_addr;
+ const auto type = vma_base.type;
+
+ rasterizer->UnmapMemory(virtual_addr, size);
+
+ // Mark region as free and attempt to coalesce it with neighbours.
+ const auto new_it = CarveVMA(virtual_addr, size);
+ auto& vma = new_it->second;
+ vma.type = VMAType::PoolReserved;
+ vma.prot = MemoryProt::NoAccess;
+ vma.phys_base = 0;
+ vma.disallow_merge = false;
+ vma.name = "";
+ MergeAdjacent(vma_map, new_it);
+
+ // Unmap the memory region.
+ impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
+ false, false);
+ TRACK_FREE(virtual_addr, "VMEM");
+}
+
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
UnmapMemoryImpl(virtual_addr, size);
diff --git a/src/core/memory.h b/src/core/memory.h
index 73ffab503..752209cfc 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -50,15 +50,17 @@ enum class VMAType : u32 {
Direct = 2,
Flexible = 3,
Pooled = 4,
- Stack = 5,
- Code = 6,
- File = 7,
+ PoolReserved = 5,
+ Stack = 6,
+ Code = 7,
+ File = 8,
};
struct DirectMemoryArea {
PAddr base = 0;
size_t size = 0;
int memory_type = 0;
+ bool is_pooled = false;
bool is_free = true;
PAddr GetEnd() const {
@@ -96,7 +98,7 @@ struct VirtualMemoryArea {
}
bool IsMapped() const noexcept {
- return type != VMAType::Free && type != VMAType::Reserved;
+ return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
}
bool CanMergeWith(const VirtualMemoryArea& next) const {
@@ -135,6 +137,10 @@ public:
return total_direct_size;
}
+ u64 GetTotalFlexibleSize() const {
+ return total_flexible_size;
+ }
+
u64 GetAvailableFlexibleSize() const {
return total_flexible_size - flexible_usage;
}
@@ -145,14 +151,21 @@ public:
void SetupMemoryRegions(u64 flexible_size);
+ PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);
+
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type);
void Free(PAddr phys_addr, size_t size);
+ int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
+ u64 alignment = 0);
+
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment = 0);
+ int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
+
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "",
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
@@ -160,6 +173,8 @@ public:
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, uintptr_t fd, size_t offset);
+ void PoolDecommit(VAddr virtual_addr, size_t size);
+
void UnmapMemory(VAddr virtual_addr, size_t size);
int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
diff --git a/src/images/flag_china.png b/src/images/flag_china.png
index 13bf221e9..33fcdfd86 100644
Binary files a/src/images/flag_china.png and b/src/images/flag_china.png differ
diff --git a/src/images/flag_eu.png b/src/images/flag_eu.png
index 0922e11ec..461f814e1 100644
Binary files a/src/images/flag_eu.png and b/src/images/flag_eu.png differ
diff --git a/src/images/flag_jp.png b/src/images/flag_jp.png
index 6433eecfd..fd056962a 100644
Binary files a/src/images/flag_jp.png and b/src/images/flag_jp.png differ
diff --git a/src/images/flag_us.png b/src/images/flag_us.png
index f4bf3a300..7b0089af4 100644
Binary files a/src/images/flag_us.png and b/src/images/flag_us.png differ
diff --git a/src/images/flag_world.png b/src/images/flag_world.png
index 0dcccf800..7b96dff11 100644
Binary files a/src/images/flag_world.png and b/src/images/flag_world.png differ
diff --git a/src/input/controller.cpp b/src/input/controller.cpp
index 4de6d83b8..dcd8ed946 100644
--- a/src/input/controller.cpp
+++ b/src/input/controller.cpp
@@ -127,7 +127,7 @@ void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_sdl_gamepad != nullptr) {
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
- (largeMotor / 255.0f) * 0xFFFF, -1) == 0;
+ (largeMotor / 255.0f) * 0xFFFF, -1);
}
return true;
}
diff --git a/src/qt_gui/background_music_player.cpp b/src/qt_gui/background_music_player.cpp
index 37d877098..a40c5bfae 100644
--- a/src/qt_gui/background_music_player.cpp
+++ b/src/qt_gui/background_music_player.cpp
@@ -10,6 +10,12 @@ BackgroundMusicPlayer::BackgroundMusicPlayer(QObject* parent) : QObject(parent)
m_mediaPlayer->setLoops(QMediaPlayer::Infinite);
}
+void BackgroundMusicPlayer::setVolume(int volume) {
+ float linearVolume = QAudio::convertVolume(volume / 100.0f, QAudio::LogarithmicVolumeScale,
+ QAudio::LinearVolumeScale);
+ m_audioOutput->setVolume(linearVolume);
+}
+
void BackgroundMusicPlayer::playMusic(const QString& snd0path) {
if (snd0path.isEmpty()) {
stopMusic();
diff --git a/src/qt_gui/background_music_player.h b/src/qt_gui/background_music_player.h
index 52f44f431..6d70fe68c 100644
--- a/src/qt_gui/background_music_player.h
+++ b/src/qt_gui/background_music_player.h
@@ -16,6 +16,7 @@ public:
return instance;
}
+ void setVolume(int volume);
void playMusic(const QString& snd0path);
void stopMusic();
@@ -25,4 +26,4 @@ private:
QMediaPlayer* m_mediaPlayer;
QAudioOutput* m_audioOutput;
QUrl m_currentMusic;
-};
\ No newline at end of file
+};
diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp
index 655478d2b..c044c2c3c 100644
--- a/src/qt_gui/cheats_patches.cpp
+++ b/src/qt_gui/cheats_patches.cpp
@@ -456,10 +456,9 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (source == "GoldHEN") {
url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt";
} else if (source == "wolf2022") {
- url = "https://wolf2022.ir/trainer/" + gameSerial + "_" + gameVersion + ".json";
+ url = "https://wolf2022.ir/trainer/list.json";
} else if (source == "shadPS4") {
- url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/"
- "CHEATS_JSON.txt";
+ url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/CHEATS_JSON.txt";
} else {
QMessageBox::warning(this, tr("Invalid Source"),
QString(tr("The selected source is invalid.") + "\n%1").arg(source));
@@ -474,44 +473,32 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
QByteArray jsonData = reply->readAll();
bool foundFiles = false;
- if (source == "GoldHEN" || source == "shadPS4") {
- QString textContent(jsonData);
- QRegularExpression regex(
- QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
- QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
- QString baseUrl;
+ if (source == "wolf2022") {
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
+ QJsonArray gamesArray = jsonDoc.object().value("games").toArray();
- if (source == "GoldHEN") {
- baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/"
- "main/json/";
- } else {
- baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
- "main/CHEATS/";
- }
+ foreach (const QJsonValue& value, gamesArray) {
+ QJsonObject gameObject = value.toObject();
+ QString title = gameObject.value("title").toString();
+ QString version = gameObject.value("version").toString();
- while (matches.hasNext()) {
- QRegularExpressionMatch match = matches.next();
- QString fileName = match.captured(0);
+ if (title == gameSerial &&
+ (version == gameVersion || version == gameVersion.mid(1))) {
+ QString fileUrl =
+ "https://wolf2022.ir/trainer/" + gameObject.value("url").toString();
- if (!fileName.isEmpty()) {
- QString newFileName = fileName;
- int dotIndex = newFileName.lastIndexOf('.');
- if (dotIndex != -1) {
+ QString localFileName = gameObject.value("url").toString();
+ localFileName =
+ localFileName.left(localFileName.lastIndexOf('.')) + "_wolf2022.json";
- if (source == "GoldHEN") {
- newFileName.insert(dotIndex, "_GoldHEN");
- } else {
- newFileName.insert(dotIndex, "_shadPS4");
- }
- }
- QString fileUrl = baseUrl + fileName;
- QString localFilePath = dir.filePath(newFileName);
+ QString localFilePath = dir.filePath(localFileName);
if (QFile::exists(localFilePath) && showMessageBox) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
this, tr("File Exists"),
- tr("File already exists. Do you want to replace it?"),
+ tr("File already exists. Do you want to replace it?") + "\n" +
+ localFileName,
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::No) {
continue;
@@ -549,38 +536,81 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& gameSer
if (!foundFiles && showMessageBox) {
QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
}
- } else if (source == "wolf2022") {
- QString fileName = QFileInfo(QUrl(url).path()).fileName();
- QString baseFileName = fileName;
- int dotIndex = baseFileName.lastIndexOf('.');
- if (dotIndex != -1) {
- baseFileName.insert(dotIndex, "_wolf2022");
+ } else if (source == "GoldHEN" || source == "shadPS4") {
+ QString textContent(jsonData);
+ QRegularExpression regex(
+ QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion));
+ QRegularExpressionMatchIterator matches = regex.globalMatch(textContent);
+ QString baseUrl;
+
+ if (source == "GoldHEN") {
+ baseUrl = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/"
+ "main/json/";
+ } else {
+ baseUrl = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/"
+ "main/CHEATS/";
}
- QString filePath;
- Common::FS::PathToQString(filePath,
- Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
- filePath += "/" + baseFileName;
- if (QFile::exists(filePath) && showMessageBox) {
- QMessageBox::StandardButton reply2;
- reply2 =
- QMessageBox::question(this, tr("File Exists"),
- tr("File already exists. Do you want to replace it?"),
- QMessageBox::Yes | QMessageBox::No);
- if (reply2 == QMessageBox::No) {
- reply->deleteLater();
- return;
+
+ while (matches.hasNext()) {
+ QRegularExpressionMatch match = matches.next();
+ QString fileName = match.captured(0);
+
+ if (!fileName.isEmpty()) {
+ QString newFileName = fileName;
+ int dotIndex = newFileName.lastIndexOf('.');
+ if (dotIndex != -1) {
+
+ if (source == "GoldHEN") {
+ newFileName.insert(dotIndex, "_GoldHEN");
+ } else {
+ newFileName.insert(dotIndex, "_shadPS4");
+ }
+ }
+ QString fileUrl = baseUrl + fileName;
+ QString localFilePath = dir.filePath(newFileName);
+
+ if (QFile::exists(localFilePath) && showMessageBox) {
+ QMessageBox::StandardButton reply;
+ reply = QMessageBox::question(
+ this, tr("File Exists"),
+ tr("File already exists. Do you want to replace it?") + "\n" +
+ newFileName,
+ QMessageBox::Yes | QMessageBox::No);
+ if (reply == QMessageBox::No) {
+ continue;
+ }
+ }
+ QNetworkRequest fileRequest(fileUrl);
+ QNetworkReply* fileReply = manager->get(fileRequest);
+
+ connect(fileReply, &QNetworkReply::finished, [=, this]() {
+ if (fileReply->error() == QNetworkReply::NoError) {
+ QByteArray fileData = fileReply->readAll();
+ QFile localFile(localFilePath);
+ if (localFile.open(QIODevice::WriteOnly)) {
+ localFile.write(fileData);
+ localFile.close();
+ } else {
+ QMessageBox::warning(
+ this, tr("Error"),
+ QString(tr("Failed to save file:") + "\n%1")
+ .arg(localFilePath));
+ }
+ } else {
+ QMessageBox::warning(this, tr("Error"),
+ QString(tr("Failed to download file:") +
+ "%1\n\n" + tr("Error:") + "%2")
+ .arg(fileUrl)
+ .arg(fileReply->errorString()));
+ }
+ fileReply->deleteLater();
+ });
+
+ foundFiles = true;
}
}
- QFile cheatFile(filePath);
- if (cheatFile.open(QIODevice::WriteOnly)) {
- cheatFile.write(jsonData);
- cheatFile.close();
- foundFiles = true;
- populateFileListCheats();
- } else {
- QMessageBox::warning(
- this, tr("Error"),
- QString(tr("Failed to save file:") + "\n%1").arg(filePath));
+ if (!foundFiles && showMessageBox) {
+ QMessageBox::warning(this, tr("Cheats Not Found"), tr("CheatsNotFound_MSG"));
}
}
if (foundFiles && showMessageBox) {
@@ -910,11 +940,16 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr
void CheatsPatches::populateFileListCheats() {
QString cheatsDir;
Common::FS::PathToQString(cheatsDir, Common::FS::GetUserPath(Common::FS::PathType::CheatsDir));
- QString pattern = m_gameSerial + "_" + m_gameVersion + "*.json";
+
+ QString fullGameVersion = m_gameVersion;
+ QString modifiedGameVersion = m_gameVersion.mid(1);
+
+ QString patternWithFirstChar = m_gameSerial + "_" + fullGameVersion + "*.json";
+ QString patternWithoutFirstChar = m_gameSerial + "_" + modifiedGameVersion + "*.json";
QDir dir(cheatsDir);
QStringList filters;
- filters << pattern;
+ filters << patternWithFirstChar << patternWithoutFirstChar;
dir.setNameFilters(filters);
QFileInfoList fileList = dir.entryInfoList(QDir::Files);
@@ -1248,4 +1283,4 @@ void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) {
} else {
instructionsTextEdit->setText(defaultTextEdit);
}
-}
+}
\ No newline at end of file
diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp
index f5e284a26..b92974ba9 100644
--- a/src/qt_gui/check_update.cpp
+++ b/src/qt_gui/check_update.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include "check_update.h"
using namespace Common::FS;
@@ -313,15 +314,32 @@ void CheckUpdate::requestChangelog(const QString& currentRev, const QString& lat
}
void CheckUpdate::DownloadUpdate(const QString& url) {
+ QProgressBar* progressBar = new QProgressBar(this);
+ progressBar->setRange(0, 100);
+ progressBar->setTextVisible(true);
+ progressBar->setValue(0);
+
+ layout()->addWidget(progressBar);
+
QNetworkRequest request(url);
QNetworkReply* reply = networkManager->get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply, url]() {
+ connect(reply, &QNetworkReply::downloadProgress, this,
+ [progressBar](qint64 bytesReceived, qint64 bytesTotal) {
+ if (bytesTotal > 0) {
+ int percentage = static_cast((bytesReceived * 100) / bytesTotal);
+ progressBar->setValue(percentage);
+ }
+ });
+
+ connect(reply, &QNetworkReply::finished, this, [this, reply, progressBar, url]() {
+ progressBar->setValue(100);
if (reply->error() != QNetworkReply::NoError) {
QMessageBox::warning(this, tr("Error"),
tr("Network error occurred while trying to access the URL") +
":\n" + url + "\n" + reply->errorString());
reply->deleteLater();
+ progressBar->deleteLater();
return;
}
@@ -348,6 +366,7 @@ void CheckUpdate::DownloadUpdate(const QString& url) {
}
reply->deleteLater();
+ progressBar->deleteLater();
});
}
diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp
index 3113aecc0..29ea31ebc 100644
--- a/src/qt_gui/game_grid_frame.cpp
+++ b/src/qt_gui/game_grid_frame.cpp
@@ -22,7 +22,7 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, QWidg
this->setContextMenuPolicy(Qt::CustomContextMenu);
PopulateGameGrid(m_game_info->m_games, false);
- connect(this, &QTableWidget::cellClicked, this, &GameGridFrame::SetGridBackgroundImage);
+ connect(this, &QTableWidget::currentCellChanged, this, &GameGridFrame::onCurrentCellChanged);
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameGridFrame::RefreshGridBackgroundImage);
@@ -31,22 +31,33 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, QWidg
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false);
});
- connect(this, &QTableWidget::cellClicked, this, [&]() {
- cellClicked = true;
- crtRow = this->currentRow();
- crtColumn = this->currentColumn();
- columnCnt = this->columnCount();
- });
}
-void GameGridFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
- if (!item) {
+void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
+ int previousColumn) {
+ cellClicked = true;
+ crtRow = currentRow;
+ crtColumn = currentColumn;
+ columnCnt = this->columnCount();
+
+ auto itemID = (crtRow * columnCnt) + currentColumn;
+ if (itemID > m_game_info->m_games.count() - 1) {
+ validCellSelected = false;
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
- QString snd0path;
- Common::FS::PathToQString(snd0path, m_game_info->m_games[item->row()].snd0_path);
- BackgroundMusicPlayer::getInstance().playMusic(snd0path);
+ validCellSelected = true;
+ SetGridBackgroundImage(crtRow, crtColumn);
+ auto snd0Path = QString::fromStdString(m_game_info->m_games[itemID].snd0_path.string());
+ PlayBackgroundMusic(snd0Path);
+}
+
+void GameGridFrame::PlayBackgroundMusic(QString path) {
+ if (path.isEmpty()) {
+ BackgroundMusicPlayer::getInstance().stopMusic();
+ return;
+ }
+ BackgroundMusicPlayer::getInstance().playMusic(path);
}
void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool fromSearch) {
@@ -157,3 +168,7 @@ void GameGridFrame::RefreshGridBackgroundImage() {
this->setPalette(palette);
}
}
+
+bool GameGridFrame::IsValidCellSelected() {
+ return validCellSelected;
+}
diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h
index 0083fd688..c09767684 100644
--- a/src/qt_gui/game_grid_frame.h
+++ b/src/qt_gui/game_grid_frame.h
@@ -20,7 +20,9 @@ Q_SIGNALS:
public Q_SLOTS:
void SetGridBackgroundImage(int row, int column);
void RefreshGridBackgroundImage();
- void PlayBackgroundMusic(QTableWidgetItem* item);
+ void PlayBackgroundMusic(QString path);
+ void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
+ int previousColumn);
private:
QImage backgroundImage;
@@ -28,10 +30,12 @@ private:
GuiContextMenus m_gui_context_menus;
std::shared_ptr m_game_info;
std::shared_ptr> m_games_shared;
+ bool validCellSelected = false;
public:
explicit GameGridFrame(std::shared_ptr game_info_get, QWidget* parent = nullptr);
void PopulateGameGrid(QVector m_games, bool fromSearch);
+ bool IsValidCellSelected();
bool cellClicked = false;
int icon_size;
diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp
index d8cc7a837..11daf2de0 100644
--- a/src/qt_gui/game_install_dialog.cpp
+++ b/src/qt_gui/game_install_dialog.cpp
@@ -18,6 +18,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
auto layout = new QVBoxLayout(this);
layout->addWidget(SetupGamesDirectory());
+ layout->addWidget(SetupAddonsDirectory());
layout->addStretch();
layout->addWidget(SetupDialogActions());
@@ -27,7 +28,7 @@ GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
GameInstallDialog::~GameInstallDialog() {}
-void GameInstallDialog::Browse() {
+void GameInstallDialog::BrowseGamesDirectory() {
auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install games"));
if (!path.isEmpty()) {
@@ -35,6 +36,14 @@ void GameInstallDialog::Browse() {
}
}
+void GameInstallDialog::BrowseAddonsDirectory() {
+ auto path = QFileDialog::getExistingDirectory(this, tr("Directory to install DLC"));
+
+ if (!path.isEmpty()) {
+ m_addonsDirectory->setText(QDir::toNativeSeparators(path));
+ }
+}
+
QWidget* GameInstallDialog::SetupGamesDirectory() {
auto group = new QGroupBox(tr("Directory to install games"));
auto layout = new QHBoxLayout(group);
@@ -51,7 +60,30 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
// Browse button.
auto browse = new QPushButton(tr("Browse"));
- connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse);
+ connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseGamesDirectory);
+
+ layout->addWidget(browse);
+
+ return group;
+}
+
+QWidget* GameInstallDialog::SetupAddonsDirectory() {
+ auto group = new QGroupBox(tr("Directory to install DLC"));
+ auto layout = new QHBoxLayout(group);
+
+ // Input.
+ m_addonsDirectory = new QLineEdit();
+ QString install_dir;
+ Common::FS::PathToQString(install_dir, Config::getAddonInstallDir());
+ m_addonsDirectory->setText(install_dir);
+ m_addonsDirectory->setMinimumWidth(400);
+
+ layout->addWidget(m_addonsDirectory);
+
+ // Browse button.
+ auto browse = new QPushButton(tr("Browse"));
+
+ connect(browse, &QPushButton::clicked, this, &GameInstallDialog::BrowseAddonsDirectory);
layout->addWidget(browse);
@@ -70,6 +102,7 @@ QWidget* GameInstallDialog::SetupDialogActions() {
void GameInstallDialog::Save() {
// Check games directory.
auto gamesDirectory = m_gamesDirectory->text();
+ auto addonsDirectory = m_addonsDirectory->text();
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
!QDir::isAbsolutePath(gamesDirectory)) {
@@ -78,7 +111,22 @@ void GameInstallDialog::Save() {
return;
}
+ if (addonsDirectory.isEmpty() || !QDir::isAbsolutePath(addonsDirectory)) {
+ QMessageBox::critical(this, tr("Error"),
+ "The value for location to install DLC is not valid.");
+ return;
+ }
+ QDir addonsDir(addonsDirectory);
+ if (!addonsDir.exists()) {
+ if (!addonsDir.mkpath(".")) {
+ QMessageBox::critical(this, tr("Error"),
+ "The DLC install location could not be created.");
+ return;
+ }
+ }
+
Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory));
+ Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory));
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
accept();
diff --git a/src/qt_gui/game_install_dialog.h b/src/qt_gui/game_install_dialog.h
index 6f439e81d..0a4e29357 100644
--- a/src/qt_gui/game_install_dialog.h
+++ b/src/qt_gui/game_install_dialog.h
@@ -16,13 +16,16 @@ public:
~GameInstallDialog();
private slots:
- void Browse();
+ void BrowseGamesDirectory();
+ void BrowseAddonsDirectory();
private:
QWidget* SetupGamesDirectory();
+ QWidget* SetupAddonsDirectory();
QWidget* SetupDialogActions();
void Save();
private:
QLineEdit* m_gamesDirectory;
+ QLineEdit* m_addonsDirectory;
};
\ No newline at end of file
diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp
index 45e52f37d..818ee17ec 100644
--- a/src/qt_gui/game_list_frame.cpp
+++ b/src/qt_gui/game_list_frame.cpp
@@ -41,7 +41,7 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg
this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
PopulateGameList();
- connect(this, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage);
+ connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged);
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameListFrame::RefreshListBackgroundImage);
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
@@ -69,6 +69,16 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg
});
}
+void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
+ int previousColumn) {
+ QTableWidgetItem* item = this->item(currentRow, currentColumn);
+ if (!item) {
+ return;
+ }
+ SetListBackgroundImage(item);
+ PlayBackgroundMusic(item);
+}
+
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item) {
BackgroundMusicPlayer::getInstance().stopMusic();
diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h
index a1ec5c563..af9ce9280 100644
--- a/src/qt_gui/game_list_frame.h
+++ b/src/qt_gui/game_list_frame.h
@@ -23,6 +23,8 @@ public Q_SLOTS:
void SortNameAscending(int columnIndex);
void SortNameDescending(int columnIndex);
void PlayBackgroundMusic(QTableWidgetItem* item);
+ void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
+ int previousColumn);
private:
void SetTableItem(int row, int column, QString itemStr);
@@ -43,24 +45,44 @@ public:
int icon_size;
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
- if (columnIndex == 1) {
+ switch (columnIndex) {
+ case 1:
return a.name < b.name;
- } else if (columnIndex == 2) {
+ case 2:
return a.serial < b.serial;
- } else if (columnIndex == 3) {
+ case 3:
+ return a.region < b.region;
+ case 4:
return a.fw < b.fw;
+ case 5:
+ return a.size < b.size;
+ case 6:
+ return a.version < b.version;
+ case 7:
+ return a.path < b.path;
+ default:
+ return false;
}
- return false;
}
static bool CompareStringsDescending(GameInfo a, GameInfo b, int columnIndex) {
- if (columnIndex == 1) {
+ switch (columnIndex) {
+ case 1:
return a.name > b.name;
- } else if (columnIndex == 2) {
+ case 2:
return a.serial > b.serial;
- } else if (columnIndex == 3) {
+ case 3:
+ return a.region > b.region;
+ case 4:
return a.fw > b.fw;
+ case 5:
+ return a.size > b.size;
+ case 6:
+ return a.version > b.version;
+ case 7:
+ return a.path > b.path;
+ default:
+ return false;
}
- return false;
}
};
\ No newline at end of file
diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h
index 3218884d5..4eb657572 100644
--- a/src/qt_gui/gui_context_menus.h
+++ b/src/qt_gui/gui_context_menus.h
@@ -312,10 +312,7 @@ public:
if (selected == &installPackage) {
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
- std::filesystem::path path(pkg_app_[9].toStdString());
-#ifdef _WIN32
- path = std::filesystem::path(pkg_app_[9].toStdWString());
-#endif
+ std::filesystem::path path = Common::FS::PathFromQString(pkg_app_[9]);
InstallDragDropPkg(path, 1, 1);
}
}
diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp
index 759c6992e..8d8e17177 100644
--- a/src/qt_gui/main_window.cpp
+++ b/src/qt_gui/main_window.cpp
@@ -2,14 +2,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include
+#include
#include
-#include
#include "about_dialog.h"
#include "cheats_patches.h"
#include "check_update.h"
#include "common/io_file.h"
#include "common/path_util.h"
+#include "common/scm_rev.h"
#include "common/string_util.h"
#include "common/version.h"
#include "core/file_format/pkg.h"
@@ -21,6 +22,7 @@
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
+ installEventFilter(this);
setAttribute(Qt::WA_DeleteOnClose);
}
@@ -306,6 +308,7 @@ void MainWindow::CreateConnects() {
});
// List
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() {
+ BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_game_list_frame.data());
m_game_grid_frame->hide();
m_elf_viewer->hide();
@@ -322,6 +325,7 @@ void MainWindow::CreateConnects() {
});
// Grid
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() {
+ BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_game_grid_frame.data());
m_game_grid_frame->show();
m_game_list_frame->hide();
@@ -338,6 +342,7 @@ void MainWindow::CreateConnects() {
});
// Elf
connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() {
+ BackgroundMusicPlayer::getInstance().stopMusic();
m_dock_widget->setWidget(m_elf_viewer.data());
m_game_grid_frame->hide();
m_game_list_frame->hide();
@@ -512,25 +517,6 @@ void MainWindow::CreateConnects() {
isIconBlack = false;
}
});
-
- connect(m_game_grid_frame.get(), &QTableWidget::cellClicked, this,
- &MainWindow::PlayBackgroundMusic);
- connect(m_game_list_frame.get(), &QTableWidget::cellClicked, this,
- &MainWindow::PlayBackgroundMusic);
-}
-
-void MainWindow::PlayBackgroundMusic() {
- if (isGameRunning || !Config::getPlayBGM()) {
- BackgroundMusicPlayer::getInstance().stopMusic();
- return;
- }
- int itemID = isTableList ? m_game_list_frame->currentItem()->row()
- : m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt +
- m_game_grid_frame->crtColumn;
-
- QString snd0path;
- Common::FS::PathToQString(snd0path, m_game_info->m_games[itemID].snd0_path);
- BackgroundMusicPlayer::getInstance().playMusic(snd0path);
}
void MainWindow::StartGame() {
@@ -619,6 +605,7 @@ void MainWindow::ConfigureGuiFromSettings() {
} else {
ui->setlistModeGridAct->setChecked(true);
}
+ BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume());
}
void MainWindow::SaveWindowState() const {
@@ -638,10 +625,7 @@ void MainWindow::InstallPkg() {
int pkgNum = 0;
for (const QString& file : fileNames) {
++pkgNum;
- std::filesystem::path path(file.toStdString());
-#ifdef _WIN64
- path = std::filesystem::path(file.toStdWString());
-#endif
+ std::filesystem::path path = Common::FS::PathFromQString(file);
MainWindow::InstallDragDropPkg(path, pkgNum, nPkg);
}
}
@@ -659,10 +643,7 @@ void MainWindow::BootGame() {
QMessageBox::critical(nullptr, tr("Game Boot"),
QString(tr("Only one file can be selected!")));
} else {
- std::filesystem::path path(fileNames[0].toStdString());
-#ifdef _WIN64
- path = std::filesystem::path(fileNames[0].toStdWString());
-#endif
+ std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]);
Core::Emulator emulator;
if (!std::filesystem::exists(path)) {
QMessageBox::critical(nullptr, tr("Run Game"),
@@ -676,9 +657,12 @@ void MainWindow::BootGame() {
void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) {
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
- pkg = PKG();
- pkg.Open(file);
std::string failreason;
+ pkg = PKG();
+ if (!pkg.Open(file, failreason)) {
+ QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
+ return;
+ }
auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID();
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
QString gameDirPath;
@@ -703,8 +687,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
}
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
- auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
- pkg.GetTitleID() / entitlement_label;
+ auto addon_extract_path =
+ Config::getAddonInstallDir() / pkg.GetTitleID() / entitlement_label;
QString addonDirPath;
Common::FS::PathToQString(addonDirPath, addon_extract_path);
QDir addon_dir(addonDirPath);
@@ -784,7 +768,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
}
}
} else {
- msgBox.setText(QString(tr("Game already installed") + "\n" + addonDirPath + "\n" +
+ msgBox.setText(QString(tr("Game already installed") + "\n" + gameDirPath + "\n" +
tr("Would you like to overwrite?")));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
@@ -1042,3 +1026,17 @@ void MainWindow::OnLanguageChanged(const std::string& locale) {
LoadTranslation();
}
+
+bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
+ if (event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast(event);
+ if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
+ auto tblMode = Config::getTableMode();
+ if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) {
+ StartGame();
+ return true;
+ }
+ }
+ }
+ return QMainWindow::eventFilter(obj, event);
+}
diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h
index 9294ef8c9..a428f4317 100644
--- a/src/qt_gui/main_window.h
+++ b/src/qt_gui/main_window.h
@@ -94,6 +94,8 @@ private:
QTranslator* translator;
protected:
+ bool eventFilter(QObject* obj, QEvent* event) override;
+
void dragEnterEvent(QDragEnterEvent* event1) override {
if (event1->mimeData()->hasUrls()) {
event1->acceptProposedAction();
@@ -108,10 +110,7 @@ protected:
int nPkg = urlList.size();
for (const QUrl& url : urlList) {
pkgNum++;
- std::filesystem::path path(url.toLocalFile().toStdString());
-#ifdef _WIN64
- path = std::filesystem::path(url.toLocalFile().toStdWString());
-#endif
+ std::filesystem::path path = Common::FS::PathFromQString(url.toLocalFile());
InstallDragDropPkg(path, pkgNum, nPkg);
}
}
diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp
index 8f20f6929..0ffb9b579 100644
--- a/src/qt_gui/pkg_viewer.cpp
+++ b/src/qt_gui/pkg_viewer.cpp
@@ -104,11 +104,12 @@ void PKGViewer::ProcessPKGInfo() {
m_pkg_patch_list.clear();
m_full_pkg_list.clear();
for (int i = 0; i < m_pkg_list.size(); i++) {
- std::filesystem::path path(m_pkg_list[i].toStdString());
-#ifdef _WIN32
- path = std::filesystem::path(m_pkg_list[i].toStdWString());
-#endif
- package.Open(path);
+ std::filesystem::path path = Common::FS::PathFromQString(m_pkg_list[i]);
+ std::string failreason;
+ if (!package.Open(path, failreason)) {
+ QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
+ return;
+ }
psf.Open(package.sfo);
QString title_name =
QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")});
diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp
index e02aeed9e..954762049 100644
--- a/src/qt_gui/settings_dialog.cpp
+++ b/src/qt_gui/settings_dialog.cpp
@@ -70,7 +70,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge
InitializeEmulatorLanguages();
LoadValuesFromConfig();
- defaultTextEdit = tr("Point your mouse at an options to display a description in here");
+ defaultTextEdit = tr("Point your mouse at an option to display its description.");
ui->descriptionText->setText(defaultTextEdit);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
@@ -140,8 +140,17 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge
checkUpdate->exec();
});
- connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this,
- [](int val) { Config::setPlayBGM(val); });
+ connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this, [](int val) {
+ Config::setPlayBGM(val);
+ if (val == Qt::Unchecked) {
+ BackgroundMusicPlayer::getInstance().stopMusic();
+ }
+ });
+
+ connect(ui->BGMVolumeSlider, &QSlider::valueChanged, this, [](float val) {
+ Config::setBGMvolume(val);
+ BackgroundMusicPlayer::getInstance().setVolume(val);
+ });
}
// GPU TAB
@@ -231,6 +240,7 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->nullGpuCheckBox->setChecked(Config::nullGpu());
ui->dumpPM4CheckBox->setChecked(Config::dumpPM4());
ui->playBGMCheckBox->setChecked(Config::getPlayBGM());
+ ui->BGMVolumeSlider->setValue((Config::getBGMvolume()));
ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode());
ui->showSplashCheckBox->setChecked(Config::showSplash());
ui->ps4proCheckBox->setChecked(Config::isNeoMode());
@@ -371,4 +381,4 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
}
}
return QDialog::eventFilter(obj, event);
-}
\ No newline at end of file
+}
diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui
index ca71d5ca4..2ca22253d 100644
--- a/src/qt_gui/settings_dialog.ui
+++ b/src/qt_gui/settings_dialog.ui
@@ -52,7 +52,7 @@
0
0
836
- 442
+ 446
@@ -369,7 +369,7 @@
10
30
241
- 41
+ 71
@@ -386,6 +386,55 @@
+ -
+
+
-
+
+
-
+
+
+ Volume
+
+
+
+ -
+
+
+ Set the volume of the background music.
+
+
+ 100
+
+
+ 10
+
+
+ 20
+
+
+ 50
+
+
+ Qt::Orientation::Horizontal
+
+
+ false
+
+
+ false
+
+
+ QSlider::TickPosition::NoTicks
+
+
+ 10
+
+
+
+
+
+
+
diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts
index b62125a7a..f7e43877e 100644
--- a/src/qt_gui/translations/ar.ts
+++ b/src/qt_gui/translations/ar.ts
@@ -534,6 +534,11 @@
Play title music
تشغيل موسيقى العنوان
+
+
+ Volume
+ الصوت
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- وجه مؤشر الفأرة إلى خيار لعرض الوصف هنا
+ Point your mouse at an option to display its description.
+ وجّه الماوس نحو خيار لعرض وصفه.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- التحديث:\nمستقر: إصدارات رسمية يتم إصدارها شهريًا، قد تكون قديمة جدًا ولكنها أكثر استقرارًا وتم اختبارها.\nغير مستقر: إصدارات التطوير التي تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا.
+ تحديث: Release: إصدارات رسمية تصدر شهريًا، قد تكون قديمة بعض الشيء، لكنها أكثر استقرارًا واختبارًا. Nightly: إصدارات تطوير تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا.
diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts
index c41b34431..5e130212d 100644
--- a/src/qt_gui/translations/da_DK.ts
+++ b/src/qt_gui/translations/da_DK.ts
@@ -534,6 +534,11 @@
Play title music
Afspil titelsang
+
+
+ Volume
+ Lydstyrke
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Placer musen på en indstilling for at vise en beskrivelse her
+ Point your mouse at an option to display its description.
+ Peg musen over et valg for at vise dets beskrivelse.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Opdatering:\nStabil: Officielle builds, der frigives månedligt, som kan være meget ældre, men mere stabile og testet.\nUstabil: Udviklerbuilds med de nyeste funktioner og rettelser, men som kan indeholde fejl og være mindre stabile.
+ Opdatering:\nRelease: Officielle builds, der frigives månedligt, som kan være meget ældre, men mere stabile og testet.\nNightly: Udviklerbuilds med de nyeste funktioner og rettelser, men som kan indeholde fejl og være mindre stabile.
diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts
index 028f448bc..9c6906a11 100644
--- a/src/qt_gui/translations/de.ts
+++ b/src/qt_gui/translations/de.ts
@@ -534,6 +534,11 @@
Play title music
Titelmusik abspielen
+
+
+ Volume
+ Lautstärke
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Zeigen Sie mit der Maus auf eine Option, um hier eine Beschreibung anzuzeigen
+ Point your mouse at an option to display its description.
+ Bewege die Maus über eine Option, um deren Beschreibung anzuzeigen.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Update:\nStabil: Offizielle Builds, die monatlich veröffentlicht werden, können viel älter sein, aber stabiler und getestet.\nUnstabil: Entwickler-Builds, die die neuesten Funktionen und Fehlerbehebungen enthalten, aber Fehler enthalten und weniger stabil sein können.
+ Update:\nRelease: Offizielle Builds, die monatlich veröffentlicht werden, können viel älter sein, aber stabiler und getestet.\nNightly: Entwickler-Builds, die die neuesten Funktionen und Fehlerbehebungen enthalten, aber Fehler enthalten und weniger stabil sein können.
diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts
index ed3e0336d..20e0d1b00 100644
--- a/src/qt_gui/translations/el.ts
+++ b/src/qt_gui/translations/el.ts
@@ -534,6 +534,11 @@
Play title music
Αναπαραγωγή μουσικής τίτλου
+
+
+ Volume
+ ένταση
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Τοποθετήστε τον κέρσορα πάνω από μια επιλογή για να εμφανιστεί εδώ η περιγραφή
+ Point your mouse at an option to display its description.
+ Τοποθετήστε το ποντίκι σας πάνω σε μια επιλογή για να εμφανίσετε την περιγραφή της.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Ενημερώσεις:\nΣταθερές: Επίσημες εκδόσεις που κυκλοφορούν μηνιαίως, είναι παλαιότερες αλλά πιο σταθερές και δοκιμασμένες.\nΑσταθείς: Εκδόσεις προγραμματιστών με νέες δυνατότητες και διορθώσεις, αλλά μπορεί να περιέχουν σφάλματα και να είναι λιγότερο σταθερές.
+ Ενημερώσεις:\nRelease: Επίσημες εκδόσεις που κυκλοφορούν μηνιαίως, είναι παλαιότερες αλλά πιο σταθερές και δοκιμασμένες.\nNightly: Εκδόσεις προγραμματιστών με νέες δυνατότητες και διορθώσεις, αλλά μπορεί να περιέχουν σφάλματα και να είναι λιγότερο σταθερές.
diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts
index b0da5bde2..1dd11f69e 100644
--- a/src/qt_gui/translations/en.ts
+++ b/src/qt_gui/translations/en.ts
@@ -534,6 +534,11 @@
Play title music
Play title music
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Point your mouse at an options to display a description in here
+ Point your mouse at an option to display its description.
+ Point your mouse at an option to display its description.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
+ Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
@@ -1056,17 +1061,17 @@
debugDump
- Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory
+ Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.
vkValidationCheckBox
- Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.
+ Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.
vkSyncValidationCheckBox
- Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks. This will reduce performance and likely change the behavior of emulation.
+ Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.
diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts
index 730346d84..9af0e45e0 100644
--- a/src/qt_gui/translations/es_ES.ts
+++ b/src/qt_gui/translations/es_ES.ts
@@ -534,6 +534,11 @@
Play title music
Reproducir la música de apertura
+
+
+ Volume
+ Volumen
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Apunta con el ratón a una opción para mostrar una descripción aquí
+ Point your mouse at an option to display its description.
+ Coloque el mouse sobre una opción para mostrar su descripción.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Actualización:\nEstable: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nInestable: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables.
+ Actualización:\nRelease: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nNightly: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables.
diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts
index 217edb780..15e07c886 100644
--- a/src/qt_gui/translations/fa_IR.ts
+++ b/src/qt_gui/translations/fa_IR.ts
@@ -534,6 +534,11 @@
Play title music
پخش موسیقی عنوان
+
+
+ Volume
+ صدا
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Point your mouse at an options to display a description in here
+ Point your mouse at an option to display its description.
+ ماوس خود را بر روی یک گزینه قرار دهید تا توضیحات آن نمایش داده شود.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
+ Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
@@ -1056,12 +1061,12 @@
debugDump
- Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory
+ Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.
vkValidationCheckBox
- Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.
+ Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state. This will reduce performance and likely change the behavior of emulation.
diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts
index 68e47a6b6..8c1f4c090 100644
--- a/src/qt_gui/translations/fi.ts
+++ b/src/qt_gui/translations/fi.ts
@@ -534,6 +534,11 @@
Play title music
Soita otsikkomusiikkia
+
+
+ Volume
+ Äänenvoimakkuus
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Vie hiiri valinnan päälle näyttääksesi kuvauksen tähän
+ Point your mouse at an option to display its description.
+ Siirrä hiiri vaihtoehdon päälle näyttämään sen kuvaus.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Päivitys:\nVakaa: Viralliset versiot, jotka julkaistaan joka kuukausi ja voivat olla hyvin vanhoja, mutta ovat luotettavampia ja testatumpia.\nEpävakaa: Kehitysversiot, joissa on kaikki uusimmat ominaisuudet ja korjaukset, mutta ne voivat sisältää bugeja ja ovat vähemmän vakaita.
+ Päivitys:\nRelease: Viralliset versiot, jotka julkaistaan joka kuukausi ja voivat olla hyvin vanhoja, mutta ovat luotettavampia ja testatumpia.\nNightly: Kehitysversiot, joissa on kaikki uusimmat ominaisuudet ja korjaukset, mutta ne voivat sisältää bugeja ja ovat vähemmän vakaita.
diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts
index e47299964..f99ea2f5d 100644
--- a/src/qt_gui/translations/fr.ts
+++ b/src/qt_gui/translations/fr.ts
@@ -534,6 +534,11 @@
Play title music
Lire la musique du titre
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Placez votre souris sur une option pour afficher une description ici
+ Point your mouse at an option to display its description.
+ Pointez votre souris sur une option pour afficher sa description.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Mise à jour :\nStable : versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nInstable : versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables.
+ Mise à jour :\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables.
diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts
index 0d0d45262..7759f3fb6 100644
--- a/src/qt_gui/translations/hu_HU.ts
+++ b/src/qt_gui/translations/hu_HU.ts
@@ -534,6 +534,11 @@
Play title music
Címzene lejátszása
+
+
+ Volume
+ Hangerő
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Mutassa a kurzort a lehetőségeken, hogy itt leírást láthasson
+ Point your mouse at an option to display its description.
+ Helyezze az egérmutatót egy lehetőség fölé, hogy megjelenítse annak leírását.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Frissítés:\nStabil: Hivatalos verziók, amelyeket havonta adnak ki, és amelyek nagyon elavultak lehetnek, de megbízhatóbbak és teszteltek.\nInstabil: Fejlesztési verziók, amelyek az összes legújabb funkciót és javítást tartalmazzák, de hibákat tartalmazhatnak és kevésbé stabilak.
+ Frissítés:\nRelease: Hivatalos verziók, amelyeket havonta adnak ki, és amelyek nagyon elavultak lehetnek, de megbízhatóbbak és teszteltek.\nNightly: Fejlesztési verziók, amelyek az összes legújabb funkciót és javítást tartalmazzák, de hibákat tartalmazhatnak és kevésbé stabilak.
@@ -1056,7 +1061,7 @@
debugDump
- Debug dumpolás engedélyezése:\nElmenti a futó PS4 program import- és exportszimbólumait, valamint a fájl fejlécinformációit egy könyvtárba
+ Debug dumpolás engedélyezése:\nElmenti a futó PS4 program import- és exportszimbólumait, valamint a fájl fejlécinformációit egy könyvtárba.
diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts
index 9efa123fe..0842df897 100644
--- a/src/qt_gui/translations/id.ts
+++ b/src/qt_gui/translations/id.ts
@@ -534,6 +534,11 @@
Play title music
Putar musik judul
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Letakkan mouse Anda di atas opsi untuk menampilkan deskripsi di sini
+ Point your mouse at an option to display its description.
+ Arahkan mouse Anda pada opsi untuk menampilkan deskripsinya.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Pembaruan:\nStabil: Versi resmi yang dirilis setiap bulan yang mungkin sangat ketinggalan zaman, tetapi lebih dapat diandalkan dan teruji.\nTidak Stabil: Versi pengembangan yang memiliki semua fitur dan perbaikan terbaru, tetapi mungkin mengandung bug dan kurang stabil.
+ Pembaruan:\nRelease: Versi resmi yang dirilis setiap bulan yang mungkin sangat ketinggalan zaman, tetapi lebih dapat diandalkan dan teruji.\nNightly: Versi pengembangan yang memiliki semua fitur dan perbaikan terbaru, tetapi mungkin mengandung bug dan kurang stabil.
@@ -1056,7 +1061,7 @@
debugDump
- Aktifkan Pembuangan Debug:\nMenyimpan simbol impor dan ekspor serta informasi header file dari program PS4 yang sedang berjalan ke direktori
+ Aktifkan Pembuangan Debug:\nMenyimpan simbol impor dan ekspor serta informasi header file dari program PS4 yang sedang berjalan ke direktori.
diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts
index e3203862b..c42589d44 100644
--- a/src/qt_gui/translations/it.ts
+++ b/src/qt_gui/translations/it.ts
@@ -534,6 +534,11 @@
Play title music
Riproduci musica del titolo
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Posiziona il mouse su un'opzione per visualizzare una descrizione qui
+ Point your mouse at an option to display its description.
+ Sposta il mouse su un'opzione per visualizzarne la descrizione.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Aggiornamento:\nStabile: Versioni ufficiali rilasciate ogni mese che potrebbero essere molto datate, ma sono più affidabili e testate.\nInstabile: Versioni di sviluppo che hanno tutte le ultime funzionalità e correzioni, ma potrebbero contenere bug e sono meno stabili.
+ Aggiornamento:\nRelease: Versioni ufficiali rilasciate ogni mese che potrebbero essere molto datate, ma sono più affidabili e testate.\nNightly: Versioni di sviluppo che hanno tutte le ultime funzionalità e correzioni, ma potrebbero contenere bug e sono meno stabili.
@@ -1056,7 +1061,7 @@
debugDump
- Abilita Pompaggio di Debug:\nSalva i simboli di importazione ed esportazione e le informazioni sull'intestazione del file del programma PS4 attualmente in esecuzione in una directory
+ Abilita Pompaggio di Debug:\nSalva i simboli di importazione ed esportazione e le informazioni sull'intestazione del file del programma PS4 attualmente in esecuzione in una directory.
diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts
index 4e079f1d3..166100db6 100644
--- a/src/qt_gui/translations/ja_JP.ts
+++ b/src/qt_gui/translations/ja_JP.ts
@@ -534,6 +534,11 @@
Play title music
タイトル音楽を再生する
+
+
+ Volume
+ 音量
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- ここに説明を表示するには、オプションにマウスをポイントしてください
+ Point your mouse at an option to display its description.
+ オプションにマウスをポイントすると、その説明が表示されます。
@@ -1016,7 +1021,7 @@
updaterGroupBox
- 更新:\n安定版: 非常に古いかもしれないが、より信頼性が高くテスト済みの公式バージョンを毎月リリースします。\n不安定版: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。
+ 更新:\nRelease: 非常に古いかもしれないが、より信頼性が高くテスト済みの公式バージョンを毎月リリースします。\nNightly: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。
diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts
index 1476a893d..303e9fd52 100644
--- a/src/qt_gui/translations/ko_KR.ts
+++ b/src/qt_gui/translations/ko_KR.ts
@@ -534,6 +534,11 @@
Play title music
Play title music
+
+
+ Volume
+ 음량
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Point your mouse at an options to display a description in here
+ Point your mouse at an option to display its description.
+ Point your mouse at an option to display its description.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Update:\nStable: Official versions released every month that may be very outdated, but are more reliable and tested.\nUnstable: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
+ Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.
@@ -1056,12 +1061,12 @@
debugDump
- Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory
+ Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.
vkValidationCheckBox
- Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about it's internal state. This will reduce performance and likely change the behavior of emulation.
+ Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state. This will reduce performance and likely change the behavior of emulation.
diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts
index abf084e0b..bb5d185d6 100644
--- a/src/qt_gui/translations/lt_LT.ts
+++ b/src/qt_gui/translations/lt_LT.ts
@@ -534,6 +534,11 @@
Play title music
Groti antraštės muziką
+
+
+ Volume
+ Garsumas
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Rodykite pelę ant pasirinkimo, kad čia būtų rodoma aprašymas
+ Point your mouse at an option to display its description.
+ Žymeklį nukreipkite ant pasirinkimo, kad pamatytumėte jo aprašymą.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Atnaujinti:\nStabilus: Oficialios versijos, išleidžiamos kiekvieną mėnesį, kurios gali būti labai pasenusios, tačiau yra patikimos ir išbandytos.\nNestabilus: Vystymo versijos, kuriose yra visos naujausios funkcijos ir taisymai, tačiau gali turėti klaidų ir būti mažiau stabilios.
+ Atnaujinti:\nRelease: Oficialios versijos, išleidžiamos kiekvieną mėnesį, kurios gali būti labai pasenusios, tačiau yra patikimos ir išbandytos.\nNightly: Vystymo versijos, kuriose yra visos naujausios funkcijos ir taisymai, tačiau gali turėti klaidų ir būti mažiau stabilios.
diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts
index b3797ae05..b5dd59f60 100644
--- a/src/qt_gui/translations/nb.ts
+++ b/src/qt_gui/translations/nb.ts
@@ -534,6 +534,11 @@
Play title music
Spill tittelmusikk
+
+
+ Volume
+ Volum
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Hold musen over et alternativ for å vise en beskrivelse her
+ Point your mouse at an option to display its description.
+ Hold musen over et valg for at vise beskrivelsen.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Oppdatering:\nStabil: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nUstabil: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile.
+ Oppdatering:\nRelease: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nNightly: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile.
diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts
index cf659acbe..144eeeb5f 100644
--- a/src/qt_gui/translations/nl.ts
+++ b/src/qt_gui/translations/nl.ts
@@ -534,6 +534,11 @@
Play title music
Titelmuziek afspelen
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Beweeg je muis over een optie om hier een beschrijving weer te geven
+ Point your mouse at an option to display its description.
+ Wijzig de muisaanwijzer naar een optie om de beschrijving weer te geven.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Updateren:\nStabiel: Officiële versies die elke maand worden uitgebracht, die zeer verouderd kunnen zijn, maar betrouwbaar en getest zijn.\nOnstabiel: Ontwikkelingsversies die alle nieuwste functies en bugfixes bevatten, maar mogelijk bugs bevatten en minder stabiel zijn.
+ Updateren:\nRelease: Officiële versies die elke maand worden uitgebracht, die zeer verouderd kunnen zijn, maar betrouwbaar en getest zijn.\nNightly: Ontwikkelingsversies die alle nieuwste functies en bugfixes bevatten, maar mogelijk bugs bevatten en minder stabiel zijn.
diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts
index 810971804..880418715 100644
--- a/src/qt_gui/translations/pl_PL.ts
+++ b/src/qt_gui/translations/pl_PL.ts
@@ -534,6 +534,11 @@
Play title music
Odtwórz muzykę tytułową
+
+
+ Volume
+ Głośność
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Najedź myszą na opcję, aby wyświetlić opis tutaj
+ Point your mouse at an option to display its description.
+ Najedź kursorem na opcję, aby wyświetlić jej opis.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Aktualizator:\nStabilny: Oficjalne wersje wydawane co miesiąc, które mogą być bardzo przestarzałe, ale są niezawodne i przetestowane.\nNiestabilny: Wersje rozwojowe, które zawierają wszystkie najnowsze funkcje i poprawki błędów, ale mogą mieć błędy i być mniej stabilne.
+ Aktualizator:\nRelease: Oficjalne wersje wydawane co miesiąc, które mogą być bardzo przestarzałe, ale są niezawodne i przetestowane.\nNightly: Wersje rozwojowe, które zawierają wszystkie najnowsze funkcje i poprawki błędów, ale mogą mieć błędy i być mniej stabilne.
diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts
index 4676dfff6..7ee5b92f6 100644
--- a/src/qt_gui/translations/pt_BR.ts
+++ b/src/qt_gui/translations/pt_BR.ts
@@ -534,6 +534,11 @@
Play title music
Reproduzir música de abertura
+
+
+ Volume
+ Volume
+
MainWindow
@@ -970,13 +975,13 @@
- Point your mouse at an options to display a description in here
- Passe o mouse sobre uma opção para exibir a descrição aqui
+ Point your mouse at an option to display its description.
+ Passe o mouse sobre uma opção para exibir sua descrição.
consoleLanguageGroupBox
- Idioma do console:\nDefine o idioma usado pelo jogo PS4.\nRecomenda-se configurá-lo para um idioma que o jogo suporte, o que pode variar conforme a região.
+ Idioma do console:\nDefine o idioma usado pelo jogo no PS4.\nRecomenda-se configurá-lo para um idioma que o jogo suporte, o que pode variar conforme a região.
@@ -1016,12 +1021,12 @@
updaterGroupBox
- Atualizações:\nStable: versões oficiais que são lançadas todo mês e podem ser bastante antigas, mas são mais confiáveis e testadas.\nUnstable: versões de desenvolvimento que têm todos os novos recursos e correções, mas podem ter bugs e ser instáveis.
+ Atualizações:\nRelease: Versões oficiais que são lançadas todo mês e podem ser bastante antigas, mas são mais confiáveis e testadas.\nNightly: Versões de desenvolvimento que têm todos os novos recursos e correções, mas podem ter bugs e ser instáveis.
GUIgroupBox
- Reproduzir música de título:\nSe o jogo suportar, ativa a reprodução de música especial ao selecionar o jogo na interface.
+ Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu.
@@ -1061,12 +1066,12 @@
vkValidationCheckBox
- Ativar Camadas de Validação do Vulkan:\nAtiva um sistema que valida o estado do renderizador Vulkan e registra informações sobre seu estado interno. Isso diminui o desempenho e pode alterar o comportamento da emulação.
+ Ativar Camadas de Validação do Vulkan:\nAtiva um sistema que valida o estado do renderizador Vulkan e registra informações sobre seu estado interno.\nIsso diminui o desempenho e pode alterar o comportamento da emulação.
vkSyncValidationCheckBox
- Ativar Validação de Sincronização do Vulkan:\nAtiva um sistema que valida o agendamento de tarefas de renderização Vulkan. Isso diminui o desempenho e pode alterar o comportamento da emulação.
+ Ativar Validação de Sincronização do Vulkan:\nAtiva um sistema que valida o agendamento de tarefas de renderização Vulkan.\nIsso diminui o desempenho e pode alterar o comportamento da emulação.
diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts
index ac298469d..783cb7822 100644
--- a/src/qt_gui/translations/ro_RO.ts
+++ b/src/qt_gui/translations/ro_RO.ts
@@ -534,6 +534,11 @@
Play title music
Redă muzica titlului
+
+
+ Volume
+ Volum
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Pune mouse-ul pe o opțiune pentru a afișa o descriere aici
+ Point your mouse at an option to display its description.
+ Indicați mouse-ul asupra unei opțiuni pentru a afișa descrierea acesteia.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Actualizare:\nStabil: Versiuni oficiale lansate în fiecare lună, care pot fi foarte învechite, dar sunt mai fiabile și testate.\nInstabil: Versiuni de dezvoltare care conțin toate cele mai recente funcții și corecții, dar pot conține erori și sunt mai puțin stabile.
+ Actualizare:\nRelease: Versiuni oficiale lansate în fiecare lună, care pot fi foarte învechite, dar sunt mai fiabile și testate.\nNightly: Versiuni de dezvoltare care conțin toate cele mai recente funcții și corecții, dar pot conține erori și sunt mai puțin stabile.
diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts
index 87847de30..f6a24dd2f 100644
--- a/src/qt_gui/translations/ru_RU.ts
+++ b/src/qt_gui/translations/ru_RU.ts
@@ -402,12 +402,12 @@
Enable Fullscreen
- Включить полноэкранный режим
+ Полноэкранный режим
Show Splash
- Показать заставку
+ Показывать заставку
@@ -457,7 +457,7 @@
Vblank Divider
- Разделитель Vblank
+ Делитель Vblank
@@ -527,12 +527,17 @@
GUI Settings
- Настройки GUI
+ Настройки интерфейса
Play title music
- Воспроизвести музыку заголовка
+ Воспроизведение заглавной музыки
+
+
+
+ Volume
+ Громкость
@@ -943,7 +948,7 @@
Can't apply cheats before the game is started
- Невозможно применить читы до начала игрыs
+ Невозможно применить читы до начала игры
@@ -961,7 +966,7 @@
Restore Defaults
- Восстановить умолчания
+ По умолчанию
@@ -970,13 +975,13 @@
- Point your mouse at an options to display a description in here
- Наведите курсор мыши на опцию, чтобы отобразить описание здесь
+ Point your mouse at an option to display its description.
+ Наведите указатель мыши на опцию, чтобы отобразить ее описание.
consoleLanguageGroupBox
- Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык, поддерживаемый игрой, так как он может отличаться в зависимости от региона.
+ Язык консоли:\nУстановите язык, который будет использоваться в играх PS4.\nРекомендуется устанавливать язык который поддерживается игрой, так как он может отличаться в зависимости от региона.
@@ -986,17 +991,17 @@
fullscreenCheckBox
- Включить полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11.
+ Полноэкранный режим:\nАвтоматически переводит игровое окно в полноэкранный режим.\nВы можете отключить это, нажав клавишу F11.
showSplashCheckBox
- Показать заставку:\nОтображает заставку игры (специальное изображение) во время запуска игры.
+ Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска игры.
ps4proCheckBox
- Это PS4 Pro:\nЗаставляет эмулятор работать как PS4 PRO, что может включить специальные функции в играх, поддерживающих это.
+ Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это.
@@ -1006,22 +1011,22 @@
logTypeGroupBox
- Тип журнала:\nУстановите, синхронизировать ли вывод окна журнала для производительности. Это может негативно сказаться на эмуляции.
+ Тип логов:\nУстановите, синхронизировать ли вывод окна логов ради производительности. Это может негативно сказаться на эмуляции.
logFilter
- Фильтр журнала: Фильтрует журнал, чтобы печатать только определенную информацию. Пример: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и регистрирует все последующие уровни.
+ Фильтр логов: Фильтрует логи, чтобы показывать только определенную информацию. Примеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни.
updaterGroupBox
- Обновление:\nСтабильная версия: Официальные версии, которые могут быть очень старыми и выпускаются каждый месяц, но они более надежные и проверенные.\nНестабильная версия: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны.
+ Обновление:\nRelease: Официальные версии, которые выпускаются каждый месяц и могут быть очень старыми, но они более надежные и проверенные.\nNightly: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны.
GUIgroupBox
- Воспроизведение музыки из заголовка:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в графическом интерфейсе.
+ Воспроизведение заглавной музыки:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в интерфейсе.
@@ -1036,7 +1041,7 @@
heightDivider
- Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого может иметь негативные последствия, такие как увеличение скорости игры или разрушение критических функций игры, которые не ожидают этого изменения!
+ Делитель Vblank:\nЧастота кадров, с которой обновляется эмулятор, умножается на это число. Изменение этого параметра может иметь негативные последствия, такие как увеличение скорости игры или нарушение критических функций игры, которые этого не ожидают!
@@ -1046,7 +1051,7 @@
nullGpuCheckBox
- Включить Null GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.
+ Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.
@@ -1056,12 +1061,12 @@
debugDump
- Включить дамп отладки:\nСохраняет символы импорта и экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в каталоге
+ Включить отладочные дампы:\nСохраняет символы импорта, экспорта и информацию о заголовке файла текущей исполняемой программы PS4 в папку.
vkValidationCheckBox
- Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и регистрирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции.
+ Включить слои валидации Vulkan:\nВключает систему, которая проверяет состояние рендерера Vulkan и логирует информацию о его внутреннем состоянии. Это снизит производительность и, вероятно, изменит поведение эмуляции.
@@ -1132,7 +1137,7 @@
Network error:
- Ошибка сети:
+ Сетевая ошибка:
@@ -1182,12 +1187,12 @@
Do you want to update?
- Вы хотите обновить?
+ Вы хотите обновиться?
Show Changelog
- Показать изменения
+ Показать журнал изменений
@@ -1197,7 +1202,7 @@
Update
- Обновить
+ Обновиться
@@ -1207,17 +1212,17 @@
Hide Changelog
- Скрыть изменения
+ Скрыть журнал изменений
Changes
- Изменения
+ Журнал изменений
Network error occurred while trying to access the URL
- Произошла ошибка сети при попытке доступа к URL
+ Произошла сетевая ошибка при попытке доступа к URL
diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts
index 710de63f9..d9f1d3859 100644
--- a/src/qt_gui/translations/sq.ts
+++ b/src/qt_gui/translations/sq.ts
@@ -534,6 +534,11 @@
Play title music
Luaj muzikën e titullit
+
+
+ Volume
+ Volumi
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Vendosni miun në një opsion për të shfaqur një përshkrim këtu
+ Point your mouse at an option to display its description.
+ Hidhni mouse-in mbi një opsion për të shfaqur përshkrimin e tij.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Aktualizimi:\nStabil: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të testuara.\nPanshkuar: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme.
+ Aktualizimi:\nRelease: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të testuara.\nNightly: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme.
diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts
index a4fa289f2..bb1c7c423 100644
--- a/src/qt_gui/translations/tr_TR.ts
+++ b/src/qt_gui/translations/tr_TR.ts
@@ -534,6 +534,11 @@
Play title music
Başlık müziğini çal
+
+
+ Volume
+ Ses seviyesi
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Buraya açıklama göstermek için bir seçeneğin üzerine fareyi getirin
+ Point your mouse at an option to display its description.
+ Seçenek üzerinde farenizi tutarak açıklamasını görüntüleyin.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Güncelleme:\nStabil: Her ay yayınlanan resmi sürümler; çok eski olabilirler, ancak daha güvenilirdir ve test edilmiştir.\nKararsız: Tüm en son özellikler ve düzeltmeler ile birlikte geliştirme sürümleri; hatalar içerebilir ve daha az kararlıdırlar.
+ Güncelleme:\nRelease: Her ay yayınlanan resmi sürümler; çok eski olabilirler, ancak daha güvenilirdir ve test edilmiştir.\nNightly: Tüm en son özellikler ve düzeltmeler ile birlikte geliştirme sürümleri; hatalar içerebilir ve daha az kararlıdırlar.
diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts
index af5eb5249..8dfd03e2d 100644
--- a/src/qt_gui/translations/vi_VN.ts
+++ b/src/qt_gui/translations/vi_VN.ts
@@ -534,6 +534,11 @@
Play title music
Phát nhạc tiêu đề
+
+
+ Volume
+ Âm lượng
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- Di chuột vào tùy chọn để hiển thị mô tả ở đây
+ Point your mouse at an option to display its description.
+ Di chuyển chuột đến tùy chọn để hiển thị mô tả của nó.
@@ -1016,7 +1021,7 @@
updaterGroupBox
- Cập nhật:\nỔn định: Các phiên bản chính thức được phát hành hàng tháng; có thể khá cũ nhưng đáng tin cậy hơn và đã được thử nghiệm.\nKhông ổn định: Các phiên bản phát triển có tất cả các tính năng và sửa lỗi mới nhất; có thể có lỗi và ít ổn định hơn.
+ Cập nhật:\nRelease: Các phiên bản chính thức được phát hành hàng tháng; có thể khá cũ nhưng đáng tin cậy hơn và đã được thử nghiệm.\nNightly: Các phiên bản phát triển có tất cả các tính năng và sửa lỗi mới nhất; có thể có lỗi và ít ổn định hơn.
diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts
index 638fa08cb..1c55cd26e 100644
--- a/src/qt_gui/translations/zh_CN.ts
+++ b/src/qt_gui/translations/zh_CN.ts
@@ -534,6 +534,11 @@
Play title music
播放标题音乐
+
+
+ Volume
+ 音量
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- 将鼠标指针放在选项上以在这里显示说明
+ Point your mouse at an option to display its description.
+ 将鼠标指针指向选项以显示其描述。
@@ -1016,7 +1021,7 @@
updaterGroupBox
- 更新:\n稳定版本: 官方版本,可能非常旧,并且每月发布,但更可靠且经过测试。\n不稳定版本: 开发版本,包含所有最新功能和修复,但可能包含错误且不够稳定。
+ 更新:\nRelease: 官方版本,可能非常旧,并且每月发布,但更可靠且经过测试。\nNightly: 开发版本,包含所有最新功能和修复,但可能包含错误且不够稳定。
diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts
index 4d508d2ad..dfb622a54 100644
--- a/src/qt_gui/translations/zh_TW.ts
+++ b/src/qt_gui/translations/zh_TW.ts
@@ -534,6 +534,11 @@
Play title music
播放標題音樂
+
+
+ Volume
+ 音量
+
MainWindow
@@ -970,8 +975,8 @@
- Point your mouse at an options to display a description in here
- 將鼠標懸停在選項上以在此顯示描述
+ Point your mouse at an option to display its description.
+ 將鼠標指向選項以顯示其描述。
@@ -1016,7 +1021,7 @@
updaterGroupBox
- 更新:\n穩定版: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\n不穩定: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。
+ 更新:\nRelease: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\nNightly: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。
diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp
index 528beee0d..a14da1189 100644
--- a/src/qt_gui/trophy_viewer.cpp
+++ b/src/qt_gui/trophy_viewer.cpp
@@ -28,10 +28,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
QDir dir(trophyDirQt);
if (!dir.exists()) {
- std::filesystem::path path(gameTrpPath_.toStdString());
-#ifdef _WIN64
- path = std::filesystem::path(gameTrpPath_.toStdWString());
-#endif
+ std::filesystem::path path = Common::FS::PathFromQString(gameTrpPath_);
if (!trp.Extract(path))
return;
}
@@ -79,7 +76,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) {
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") {
+ if (reader.attributes().value("unlockstate").toString() == "true") {
trpUnlocked.append("unlocked");
} else {
trpUnlocked.append("locked");
diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp
index f7835aabe..bd2cc39d2 100644
--- a/src/sdl_window.cpp
+++ b/src/sdl_window.cpp
@@ -23,7 +23,7 @@ namespace Frontend {
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
std::string_view window_title)
: width{width_}, height{height_}, controller{controller_} {
- if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ if (!SDL_Init(SDL_INIT_VIDEO)) {
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
}
SDL_InitSubSystem(SDL_INIT_AUDIO);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
index 8aa292b1c..891b26084 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -233,6 +233,7 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
}
if (info.has_discard) {
+ ctx.AddExtension("SPV_EXT_demote_to_helper_invocation");
ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT);
}
if (info.stores.Get(IR::Attribute::Depth)) {
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 92279c5fb..b6bf1eed6 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
@@ -49,12 +49,13 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) {
if (info.num_components == 1) {
return info.id;
} else {
- return ctx.OpAccessChain(ctx.output_f32, info.id, ctx.ConstU32(element));
+ return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element));
}
}
switch (attr) {
case IR::Attribute::Position0: {
return ctx.OpAccessChain(ctx.output_f32, ctx.output_position, ctx.ConstU32(element));
+ }
case IR::Attribute::Position1:
case IR::Attribute::Position2:
case IR::Attribute::Position3: {
@@ -70,17 +71,47 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) {
case IR::Attribute::RenderTarget6:
case IR::Attribute::RenderTarget7: {
const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0);
- if (ctx.frag_num_comp[index] > 1) {
- return ctx.OpAccessChain(ctx.output_f32, ctx.frag_color[index], ctx.ConstU32(element));
+ const auto& info{ctx.frag_outputs.at(index)};
+ if (info.num_components > 1) {
+ return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element));
} else {
- return ctx.frag_color[index];
+ return info.id;
}
}
case IR::Attribute::Depth:
return ctx.frag_depth;
default:
- throw NotImplementedException("Read attribute {}", attr);
+ throw NotImplementedException("Write attribute {}", attr);
}
+}
+
+std::pair OutputAttrComponentType(EmitContext& ctx, IR::Attribute attr) {
+ if (IR::IsParam(attr)) {
+ const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
+ const auto& info{ctx.output_params.at(index)};
+ return {info.component_type, info.is_integer};
+ }
+ switch (attr) {
+ case IR::Attribute::Position0:
+ case IR::Attribute::Position1:
+ case IR::Attribute::Position2:
+ case IR::Attribute::Position3:
+ case IR::Attribute::Depth:
+ return {ctx.F32[1], false};
+ case IR::Attribute::RenderTarget0:
+ case IR::Attribute::RenderTarget1:
+ case IR::Attribute::RenderTarget2:
+ case IR::Attribute::RenderTarget3:
+ case IR::Attribute::RenderTarget4:
+ case IR::Attribute::RenderTarget5:
+ case IR::Attribute::RenderTarget6:
+ case IR::Attribute::RenderTarget7: {
+ const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0);
+ const auto& info{ctx.frag_outputs.at(index)};
+ return {info.component_type, info.is_integer};
+ }
+ default:
+ throw NotImplementedException("Write attribute {}", attr);
}
}
} // Anonymous namespace
@@ -156,17 +187,21 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp) {
// Attribute is disabled or varying component is not written
return ctx.ConstF32(comp == 3 ? 1.0f : 0.0f);
}
- if (param.is_default) {
- return ctx.OpCompositeExtract(param.component_type, param.id, comp);
- }
- if (param.num_components > 1) {
+ Id result;
+ if (param.is_default) {
+ result = ctx.OpCompositeExtract(param.component_type, param.id, comp);
+ } else if (param.num_components > 1) {
const Id pointer{
ctx.OpAccessChain(param.pointer_type, param.id, ctx.ConstU32(comp))};
- return ctx.OpLoad(param.component_type, pointer);
+ result = ctx.OpLoad(param.component_type, pointer);
} else {
- return ctx.OpLoad(param.component_type, param.id);
+ result = ctx.OpLoad(param.component_type, param.id);
}
+ if (param.is_integer) {
+ result = ctx.OpBitcast(ctx.F32[1], result);
+ }
+ return result;
} else {
const auto step_rate = EmitReadStepRate(ctx, param.id.value);
const auto offset = ctx.OpIAdd(
@@ -222,7 +257,12 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen
return;
}
const Id pointer{OutputAttrPointer(ctx, attr, element)};
- ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value));
+ const auto component_type{OutputAttrComponentType(ctx, attr)};
+ if (component_type.second) {
+ ctx.OpStore(pointer, ctx.OpBitcast(component_type.first, value));
+ } else {
+ ctx.OpStore(pointer, value);
+ }
}
template
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index b66f09664..05b308b05 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -120,6 +120,7 @@ void EmitContext::DefineArithmeticTypes() {
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
+ output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32");
full_result_i32x2 = Name(TypeStruct(S32[1], S32[1]), "full_result_i32x2");
full_result_u32x2 = Name(TypeStruct(U32[1], U32[1]), "full_result_u32x2");
@@ -151,21 +152,21 @@ const VectorIds& GetAttributeType(EmitContext& ctx, AmdGpu::NumberFormat fmt) {
UNREACHABLE_MSG("Invalid attribute type {}", fmt);
}
-EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id) {
+EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id,
+ bool output) {
switch (fmt) {
case AmdGpu::NumberFormat::Float:
case AmdGpu::NumberFormat::Unorm:
case AmdGpu::NumberFormat::Snorm:
case AmdGpu::NumberFormat::SnormNz:
- return {id, input_f32, F32[1], 4};
- case AmdGpu::NumberFormat::Uint:
- return {id, input_u32, U32[1], 4};
- case AmdGpu::NumberFormat::Sint:
- return {id, input_s32, S32[1], 4};
case AmdGpu::NumberFormat::Sscaled:
- return {id, input_f32, F32[1], 4};
case AmdGpu::NumberFormat::Uscaled:
- return {id, input_f32, F32[1], 4};
+ case AmdGpu::NumberFormat::Srgb:
+ return {id, output ? output_f32 : input_f32, F32[1], 4, false};
+ case AmdGpu::NumberFormat::Uint:
+ return {id, output ? output_u32 : input_u32, U32[1], 4, true};
+ case AmdGpu::NumberFormat::Sint:
+ return {id, output ? output_s32 : input_s32, S32[1], 4, true};
default:
break;
}
@@ -236,9 +237,13 @@ void EmitContext::DefineInputs() {
: 1;
// Note that we pass index rather than Id
input_params[input.binding] = {
- rate_idx, input_u32,
- U32[1], input.num_components,
- false, input.instance_data_buf,
+ rate_idx,
+ input_u32,
+ U32[1],
+ input.num_components,
+ true,
+ false,
+ input.instance_data_buf,
};
} else {
Id id{DefineInput(type, input.binding)};
@@ -247,7 +252,7 @@ void EmitContext::DefineInputs() {
} else {
Name(id, fmt::format("vs_in_attr{}", input.binding));
}
- input_params[input.binding] = GetAttributeInfo(input.fmt, id);
+ input_params[input.binding] = GetAttributeInfo(input.fmt, id, false);
interfaces.push_back(id);
}
}
@@ -320,10 +325,12 @@ void EmitContext::DefineOutputs() {
continue;
}
const u32 num_components = info.stores.NumComponents(mrt);
- frag_color[i] = DefineOutput(F32[num_components], i);
- frag_num_comp[i] = num_components;
- Name(frag_color[i], fmt::format("frag_color{}", i));
- interfaces.push_back(frag_color[i]);
+ const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format};
+ const Id type{GetAttributeType(*this, num_format)[num_components]};
+ const Id id = DefineOutput(type, i);
+ Name(id, fmt::format("frag_color{}", i));
+ frag_outputs[i] = GetAttributeInfo(num_format, id, true);
+ interfaces.push_back(id);
}
break;
default:
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index fb7b29b3e..3dfc8bd61 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -166,6 +166,7 @@ public:
Id input_s32{};
Id output_u32{};
Id output_f32{};
+ Id output_s32{};
boost::container::small_vector interfaces;
@@ -177,8 +178,6 @@ public:
Id frag_coord{};
Id front_facing{};
Id frag_depth{};
- std::array frag_color{};
- std::array frag_num_comp{};
Id clip_distances{};
Id cull_distances{};
@@ -237,11 +236,13 @@ public:
Id pointer_type;
Id component_type;
u32 num_components;
+ bool is_integer{};
bool is_default{};
s32 buffer_handle{-1};
};
std::array input_params{};
std::array output_params{};
+ std::array frag_outputs{};
private:
void DefineArithmeticTypes();
@@ -254,7 +255,7 @@ private:
void DefineImagesAndSamplers();
void DefineSharedMemory();
- SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id);
+ SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id, bool output);
};
} // namespace Shader::Backend::SPIRV
diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp
index 7d901822d..f82f8fc1b 100644
--- a/src/shader_recompiler/frontend/translate/export.cpp
+++ b/src/shader_recompiler/frontend/translate/export.cpp
@@ -25,7 +25,7 @@ void Translator::EmitExport(const GcnInst& inst) {
return comp;
}
const u32 index = u32(attrib) - u32(IR::Attribute::RenderTarget0);
- switch (runtime_info.fs_info.mrt_swizzles[index]) {
+ switch (runtime_info.fs_info.color_buffers[index].mrt_swizzle) {
case MrtSwizzle::Identity:
return comp;
case MrtSwizzle::Alt:
diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp
index cf92460d3..82769bd29 100644
--- a/src/shader_recompiler/frontend/translate/vector_alu.cpp
+++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp
@@ -909,6 +909,8 @@ void Translator::V_CMP_CLASS_F32(const GcnInst& inst) {
switch (inst.dst[1].field) {
case OperandField::VccLo:
return ir.SetVcc(value);
+ case OperandField::ScalarGPR:
+ return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), value);
default:
UNREACHABLE();
}
diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h
index 1bb065544..115bbe10d 100644
--- a/src/shader_recompiler/runtime_info.h
+++ b/src/shader_recompiler/runtime_info.h
@@ -80,10 +80,16 @@ struct FragmentRuntimeInfo {
auto operator<=>(const PsInput&) const noexcept = default;
};
boost::container::static_vector inputs;
- std::array mrt_swizzles;
+ struct PsColorBuffer {
+ AmdGpu::NumberFormat num_format;
+ MrtSwizzle mrt_swizzle;
+
+ auto operator<=>(const PsColorBuffer&) const noexcept = default;
+ };
+ std::array color_buffers;
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
- return std::ranges::equal(mrt_swizzles, other.mrt_swizzles) &&
+ return std::ranges::equal(color_buffers, other.color_buffers) &&
std::ranges::equal(inputs, other.inputs);
}
};
diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp
index 74e623e3d..3dce871fe 100644
--- a/src/video_core/amdgpu/liverpool.cpp
+++ b/src/video_core/amdgpu/liverpool.cpp
@@ -44,7 +44,7 @@ Liverpool::~Liverpool() {
}
void Liverpool::Process(std::stop_token stoken) {
- Common::SetCurrentThreadName("GPU_CommandProcessor");
+ Common::SetCurrentThreadName("shadPS4:GPU_CommandProcessor");
while (!stoken.stop_requested()) {
{
diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h
index 411b25ed1..b1036c019 100644
--- a/src/video_core/amdgpu/liverpool.h
+++ b/src/video_core/amdgpu/liverpool.h
@@ -253,6 +253,13 @@ struct Liverpool {
}
};
+ struct ModeControl {
+ s32 msaa_enable : 1;
+ s32 vport_scissor_enable : 1;
+ s32 line_stripple_enable : 1;
+ s32 send_unlit_stiles_to_pkr : 1;
+ };
+
enum class ZOrder : u32 {
LateZ = 0,
EarlyZLateZ = 1,
@@ -559,29 +566,39 @@ struct Liverpool {
s16 top_left_x;
s16 top_left_y;
};
- union {
- BitField<0, 15, u32> bottom_right_x;
- BitField<16, 15, u32> bottom_right_y;
+ struct {
+ s16 bottom_right_x;
+ s16 bottom_right_y;
};
+ // From AMD spec: 'Negative numbers clamped to 0'
+ static s16 Clamp(s16 value) {
+ return std::max(s16(0), value);
+ }
+
u32 GetWidth() const {
- return static_cast(bottom_right_x - top_left_x);
+ return static_cast(Clamp(bottom_right_x) - Clamp(top_left_x));
}
u32 GetHeight() const {
- return static_cast(bottom_right_y - top_left_y);
+ return static_cast(Clamp(bottom_right_y) - Clamp(top_left_y));
}
};
+ struct WindowOffset {
+ s32 window_x_offset : 16;
+ s32 window_y_offset : 16;
+ };
+
struct ViewportScissor {
union {
BitField<0, 15, s32> top_left_x;
- BitField<15, 15, s32> top_left_y;
- BitField<30, 1, s32> window_offset_disable;
+ BitField<16, 15, s32> top_left_y;
+ BitField<31, 1, s32> window_offset_disable;
};
- union {
- BitField<0, 15, s32> bottom_right_x;
- BitField<15, 15, s32> bottom_right_y;
+ struct {
+ s16 bottom_right_x;
+ s16 bottom_right_y;
};
u32 GetWidth() const {
@@ -953,10 +970,14 @@ struct Liverpool {
Scissor screen_scissor;
INSERT_PADDING_WORDS(0xA010 - 0xA00C - 2);
DepthBuffer depth_buffer;
- INSERT_PADDING_WORDS(0xA08E - 0xA018);
+ INSERT_PADDING_WORDS(0xA080 - 0xA018);
+ WindowOffset window_offset;
+ ViewportScissor window_scissor;
+ INSERT_PADDING_WORDS(0xA08E - 0xA081 - 2);
ColorBufferMask color_target_mask;
ColorBufferMask color_shader_mask;
- INSERT_PADDING_WORDS(0xA094 - 0xA08E - 2);
+ ViewportScissor generic_scissor;
+ INSERT_PADDING_WORDS(2);
std::array viewport_scissors;
std::array viewport_depths;
INSERT_PADDING_WORDS(0xA103 - 0xA0D4);
@@ -994,7 +1015,9 @@ struct Liverpool {
PolygonControl polygon_control;
ViewportControl viewport_control;
VsOutputControl vs_output_control;
- INSERT_PADDING_WORDS(0xA29E - 0xA207 - 2);
+ INSERT_PADDING_WORDS(0xA292 - 0xA207 - 1);
+ ModeControl mode_control;
+ INSERT_PADDING_WORDS(0xA29D - 0xA292 - 1);
u32 index_size;
u32 max_index_size;
IndexBufferType index_buffer_type;
@@ -1206,8 +1229,11 @@ static_assert(GFX6_3D_REG_INDEX(depth_htile_data_base) == 0xA005);
static_assert(GFX6_3D_REG_INDEX(screen_scissor) == 0xA00C);
static_assert(GFX6_3D_REG_INDEX(depth_buffer.z_info) == 0xA010);
static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017);
+static_assert(GFX6_3D_REG_INDEX(window_offset) == 0xA080);
+static_assert(GFX6_3D_REG_INDEX(window_scissor) == 0xA081);
static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E);
static_assert(GFX6_3D_REG_INDEX(color_shader_mask) == 0xA08F);
+static_assert(GFX6_3D_REG_INDEX(generic_scissor) == 0xA090);
static_assert(GFX6_3D_REG_INDEX(viewport_scissors) == 0xA094);
static_assert(GFX6_3D_REG_INDEX(primitive_restart_index) == 0xA103);
static_assert(GFX6_3D_REG_INDEX(stencil_control) == 0xA10B);
@@ -1227,6 +1253,7 @@ static_assert(GFX6_3D_REG_INDEX(color_control) == 0xA202);
static_assert(GFX6_3D_REG_INDEX(clipper_control) == 0xA204);
static_assert(GFX6_3D_REG_INDEX(viewport_control) == 0xA206);
static_assert(GFX6_3D_REG_INDEX(vs_output_control) == 0xA207);
+static_assert(GFX6_3D_REG_INDEX(mode_control) == 0xA292);
static_assert(GFX6_3D_REG_INDEX(index_size) == 0xA29D);
static_assert(GFX6_3D_REG_INDEX(index_buffer_type) == 0xA29F);
static_assert(GFX6_3D_REG_INDEX(enable_primitive_id) == 0xA2A1);
diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp
index f8afd6991..5a049c185 100644
--- a/src/video_core/buffer_cache/buffer.cpp
+++ b/src/video_core/buffer_cache/buffer.cpp
@@ -95,7 +95,8 @@ Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
// Create buffer object.
const vk::BufferCreateInfo buffer_ci = {
.size = size_bytes,
- .usage = flags,
+ // When maintenance5 is not supported, use all flags since we can't add flags to views.
+ .usage = instance->IsMaintenance5Supported() ? flags : AllFlags,
};
VmaAllocationInfo alloc_info{};
buffer.Create(buffer_ci, usage, &alloc_info);
@@ -119,7 +120,7 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF
: vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer,
};
const vk::BufferViewCreateInfo view_ci = {
- .pNext = &usage_flags,
+ .pNext = instance->IsMaintenance5Supported() ? &usage_flags : nullptr,
.buffer = buffer.buffer,
.format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt),
.offset = offset,
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
index caffee6ba..607543a52 100644
--- a/src/video_core/buffer_cache/buffer_cache.cpp
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -114,6 +114,8 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
std::array host_buffers;
std::array host_offsets;
+ std::array host_sizes;
+ std::array host_strides;
boost::container::static_vector guest_buffers;
struct BufferRange {
@@ -193,11 +195,18 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) {
host_buffers[i] = host_buffer->vk_buffer;
host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address;
+ host_sizes[i] = buffer.GetSize();
+ host_strides[i] = buffer.GetStride();
}
if (num_buffers > 0) {
const auto cmdbuf = scheduler.CommandBuffer();
- cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data());
+ if (instance.IsVertexInputDynamicState()) {
+ cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data());
+ } else {
+ cmdbuf.bindVertexBuffers2EXT(0, num_buffers, host_buffers.data(), host_offsets.data(),
+ host_sizes.data(), host_strides.data());
+ }
}
return has_step_rate;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 3c191c4a1..c10cac6cb 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -46,28 +46,34 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
boost::container::static_vector vertex_bindings;
boost::container::static_vector vertex_attributes;
- const auto& vs_info = stages[u32(Shader::Stage::Vertex)];
- 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) {
- // Skip attribute binding as the data will be pulled by shader
- continue;
- }
+ if (!instance.IsVertexInputDynamicState()) {
+ const auto& vs_info = stages[u32(Shader::Stage::Vertex)];
+ 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) {
+ // Skip attribute binding as the data will be pulled by shader
+ continue;
+ }
- const auto buffer = vs_info->ReadUd(input.sgpr_base, input.dword_offset);
- vertex_attributes.push_back({
- .location = input.binding,
- .binding = input.binding,
- .format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()),
- .offset = 0,
- });
- vertex_bindings.push_back({
- .binding = input.binding,
- .stride = buffer.GetStride(),
- .inputRate = input.instance_step_rate == Shader::Info::VsInput::None
- ? vk::VertexInputRate::eVertex
- : vk::VertexInputRate::eInstance,
- });
+ const auto buffer =
+ vs_info->ReadUd(input.sgpr_base, input.dword_offset);
+ if (buffer.GetSize() == 0) {
+ continue;
+ }
+ vertex_attributes.push_back({
+ .location = input.binding,
+ .binding = input.binding,
+ .format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()),
+ .offset = 0,
+ });
+ vertex_bindings.push_back({
+ .binding = input.binding,
+ .stride = buffer.GetStride(),
+ .inputRate = input.instance_step_rate == Shader::Info::VsInput::None
+ ? vk::VertexInputRate::eVertex
+ : vk::VertexInputRate::eInstance,
+ });
+ }
}
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
@@ -82,11 +88,17 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
"Rectangle List primitive type is only supported for embedded VS");
}
+ auto prim_restart = key.enable_primitive_restart != 0;
+ if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) {
+ LOG_WARNING(Render_Vulkan,
+ "Primitive restart is enabled for list topology but not supported by driver.");
+ prim_restart = false;
+ }
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
.topology = LiverpoolToVK::PrimitiveType(key.prim_type),
- .primitiveRestartEnable = key.enable_primitive_restart != 0,
+ .primitiveRestartEnable = prim_restart,
};
- ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF ||
+ ASSERT_MSG(!prim_restart || key.primitive_restart_index == 0xFFFF ||
key.primitive_restart_index == 0xFFFFFFFF,
"Primitive restart index other than -1 is not supported yet");
@@ -147,6 +159,8 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
}
if (instance.IsVertexInputDynamicState()) {
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
+ } else {
+ dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStrideEXT);
}
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
@@ -273,7 +287,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
.pNext = &pipeline_rendering_ci,
.stageCount = static_cast(shader_stages.size()),
.pStages = shader_stages.data(),
- .pVertexInputState = &vertex_input_info,
+ .pVertexInputState = !instance.IsVertexInputDynamicState() ? &vertex_input_info : nullptr,
.pInputAssemblyState = &input_assembly,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_state,
@@ -379,7 +393,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs,
for (const auto& buffer : stage->buffers) {
const auto vsharp = buffer.GetSharp(*stage);
const bool is_storage = buffer.IsStorage(vsharp);
- if (vsharp) {
+ if (vsharp && vsharp.GetSize() > 0) {
const VAddr address = vsharp.base_address;
if (texture_cache.IsMeta(address)) {
LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)");
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 74817656a..ba4996742 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -26,6 +26,7 @@ using Liverpool = AmdGpu::Liverpool;
struct GraphicsPipelineKey {
std::array stage_hashes;
std::array color_formats;
+ std::array color_num_formats;
std::array mrt_swizzles;
vk::Format depth_format;
vk::Format stencil_format;
@@ -45,6 +46,7 @@ struct GraphicsPipelineKey {
Liverpool::ColorBufferMask cb_shader_mask;
std::array blend_controls;
std::array write_masks;
+ std::array vertex_buffer_formats;
bool operator==(const GraphicsPipelineKey& key) const noexcept {
return std::memcmp(this, &key, sizeof(key)) == 0;
@@ -83,6 +85,16 @@ public:
return key.depth_stencil.depth_enable.Value();
}
+ [[nodiscard]] bool IsPrimitiveListTopology() const {
+ return key.prim_type == Liverpool::PrimitiveType::PointList ||
+ key.prim_type == Liverpool::PrimitiveType::LineList ||
+ key.prim_type == Liverpool::PrimitiveType::TriangleList ||
+ key.prim_type == Liverpool::PrimitiveType::AdjLineList ||
+ key.prim_type == Liverpool::PrimitiveType::AdjTriangleList ||
+ key.prim_type == Liverpool::PrimitiveType::RectList ||
+ key.prim_type == Liverpool::PrimitiveType::QuadList;
+ }
+
private:
void BuildDescSetLayout();
diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp
index c09414a11..022ef16c5 100644
--- a/src/video_core/renderer_vulkan/vk_instance.cpp
+++ b/src/video_core/renderer_vulkan/vk_instance.cpp
@@ -260,9 +260,8 @@ bool Instance::CreateDevice() {
color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
const bool calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME);
const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
- const bool topology_restart =
- add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
- const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
+ list_restart = add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
+ maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
// These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2
// with extensions.
@@ -272,6 +271,7 @@ bool Instance::CreateDevice() {
add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
+ add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
#ifdef __APPLE__
// Required by Vulkan spec if supported.
@@ -414,7 +414,7 @@ bool Instance::CreateDevice() {
if (!workgroup_memory_explicit_layout) {
device_chain.unlink();
}
- if (!topology_restart) {
+ if (!list_restart) {
device_chain.unlink();
}
if (robustness) {
diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h
index e6e39ab1f..e019ffba3 100644
--- a/src/video_core/renderer_vulkan/vk_instance.h
+++ b/src/video_core/renderer_vulkan/vk_instance.h
@@ -138,6 +138,15 @@ public:
return null_descriptor;
}
+ /// Returns true when VK_KHR_maintenance5 is supported.
+ bool IsMaintenance5Supported() const {
+ return maintenance5;
+ }
+
+ bool IsListRestartSupported() const {
+ return list_restart;
+ }
+
/// Returns the vendor ID of the physical device
u32 GetVendorID() const {
return properties.vendorID;
@@ -280,6 +289,8 @@ private:
bool color_write_en{};
bool vertex_input_dynamic_state{};
bool null_descriptor{};
+ bool maintenance5{};
+ bool list_restart{};
u64 min_imported_host_pointer_alignment{};
u32 subgroup_size{};
bool tooling_info{};
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 4aca00730..0ccaf3fac 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -95,10 +95,6 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
case Shader::Stage::Fragment: {
info.num_user_data = regs.ps_program.settings.num_user_regs;
info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4;
- std::ranges::transform(graphics_key.mrt_swizzles, info.fs_info.mrt_swizzles.begin(),
- [](Liverpool::ColorBuffer::SwapMode mode) {
- return static_cast(mode);
- });
const auto& ps_inputs = regs.ps_inputs;
for (u32 i = 0; i < regs.num_interp; i++) {
info.fs_info.inputs.push_back({
@@ -108,6 +104,12 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
.default_value = u8(ps_inputs[i].default_value),
});
}
+ for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
+ info.fs_info.color_buffers[i] = {
+ .num_format = graphics_key.color_num_formats[i],
+ .mrt_swizzle = static_cast(graphics_key.mrt_swizzles[i]),
+ };
+ }
break;
}
case Shader::Stage::Compute: {
@@ -244,9 +246,11 @@ bool PipelineCache::RefreshGraphicsKey() {
// attachments. This might be not a case as HW color buffers can be bound in an arbitrary
// order. We need to do some arrays compaction at this stage
key.color_formats.fill(vk::Format::eUndefined);
+ key.color_num_formats.fill(AmdGpu::NumberFormat::Unorm);
key.blend_controls.fill({});
key.write_masks.fill({});
key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard);
+ key.vertex_buffer_formats.fill(vk::Format::eUndefined);
// First pass of bindings check to idenitfy formats and swizzles and pass them to rhe shader
// recompiler.
@@ -260,6 +264,7 @@ bool PipelineCache::RefreshGraphicsKey() {
const bool is_vo_surface = renderer->IsVideoOutSurface(col_buf);
key.color_formats[remapped_cb] = LiverpoolToVK::AdjustColorBufferFormat(
base_format, col_buf.info.comp_swap.Value(), false /*is_vo_surface*/);
+ key.color_num_formats[remapped_cb] = col_buf.NumFormat();
if (base_format == key.color_formats[remapped_cb]) {
key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value();
}
@@ -310,7 +315,26 @@ bool PipelineCache::RefreshGraphicsKey() {
std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding);
}
- const auto* fs_info = infos[u32(Shader::Stage::Fragment)];
+ const auto* vs_info = infos[static_cast(Shader::Stage::Vertex)];
+ if (vs_info && !instance.IsVertexInputDynamicState()) {
+ u32 vertex_binding = 0;
+ 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) {
+ continue;
+ }
+ const auto& buffer =
+ vs_info->ReadUd(input.sgpr_base, input.dword_offset);
+ if (buffer.GetSize() == 0) {
+ continue;
+ }
+ ASSERT(vertex_binding < MaxVertexBufferCount);
+ key.vertex_buffer_formats[vertex_binding++] =
+ Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt());
+ }
+ }
+
+ const auto* fs_info = infos[static_cast(Shader::Stage::Fragment)];
key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u;
// Second pass to fill remain CB pipeline key data
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index e511c161e..159b489d8 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -368,11 +368,55 @@ void Rasterizer::UpdateViewportScissorState() {
.maxDepth = vp.zscale + vp.zoffset,
});
}
- const auto& sc = regs.screen_scissor;
- scissors.push_back({
- .offset = {sc.top_left_x, sc.top_left_y},
- .extent = {sc.GetWidth(), sc.GetHeight()},
- });
+
+ const bool enable_offset = !regs.window_scissor.window_offset_disable.Value();
+ Liverpool::Scissor scsr{};
+ const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) {
+ return std::max({scr, s16(win + win_offset), s16(gen + win_offset)});
+ };
+
+ scsr.top_left_x = combined_scissor_value_tl(
+ regs.screen_scissor.top_left_x, s16(regs.window_scissor.top_left_x.Value()),
+ s16(regs.generic_scissor.top_left_x.Value()),
+ enable_offset ? regs.window_offset.window_x_offset : 0);
+
+ scsr.top_left_y = combined_scissor_value_tl(
+ regs.screen_scissor.top_left_y, s16(regs.window_scissor.top_left_y.Value()),
+ s16(regs.generic_scissor.top_left_y.Value()),
+ enable_offset ? regs.window_offset.window_y_offset : 0);
+
+ const auto combined_scissor_value_br = [](s16 scr, s16 win, s16 gen, s16 win_offset) {
+ return std::min({scr, s16(win + win_offset), s16(gen + win_offset)});
+ };
+
+ scsr.bottom_right_x = combined_scissor_value_br(
+ regs.screen_scissor.bottom_right_x, regs.window_scissor.bottom_right_x,
+ regs.generic_scissor.bottom_right_x,
+ enable_offset ? regs.window_offset.window_x_offset : 0);
+
+ scsr.bottom_right_y = combined_scissor_value_br(
+ regs.screen_scissor.bottom_right_y, regs.window_scissor.bottom_right_y,
+ regs.generic_scissor.bottom_right_y,
+ enable_offset ? regs.window_offset.window_y_offset : 0);
+
+ for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) {
+ auto vp_scsr = scsr;
+ if (regs.mode_control.vport_scissor_enable) {
+ vp_scsr.top_left_x =
+ std::max(vp_scsr.top_left_x, s16(regs.viewport_scissors[idx].top_left_x.Value()));
+ vp_scsr.top_left_y =
+ std::max(vp_scsr.top_left_y, s16(regs.viewport_scissors[idx].top_left_y.Value()));
+ vp_scsr.bottom_right_x =
+ std::min(vp_scsr.bottom_right_x, regs.viewport_scissors[idx].bottom_right_x);
+ vp_scsr.bottom_right_y =
+ std::min(vp_scsr.bottom_right_y, regs.viewport_scissors[idx].bottom_right_y);
+ }
+ scissors.push_back({
+ .offset = {vp_scsr.top_left_x, vp_scsr.top_left_y},
+ .extent = {vp_scsr.GetWidth(), vp_scsr.GetHeight()},
+ });
+ }
+
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.setViewport(0, viewports);
cmdbuf.setScissor(0, scissors);
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 13c0adb0b..8c268c9be 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -123,7 +123,7 @@ void Swapchain::Present() {
};
auto result = instance.GetPresentQueue().presentKHR(present_info);
- if (result == vk::Result::eErrorOutOfDateKHR) {
+ if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) {
needs_recreation = true;
} else {
ASSERT_MSG(result == vk::Result::eSuccess, "Swapchain presentation failed: {}",