diff --git a/CMakeLists.txt b/CMakeLists.txt
index 78e3c7997..4822658c6 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,6 +106,36 @@ git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH)
string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S")
+# Try to get the upstream remote and branch
+execute_process(
+ COMMAND git rev-parse --abbrev-ref --symbolic-full-name @{u}
+ OUTPUT_VARIABLE GIT_REMOTE_NAME
+ RESULT_VARIABLE GIT_BRANCH_RESULT
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+# Default to origin if there's no upstream set or if the command failed
+if (GIT_BRANCH_RESULT OR GIT_REMOTE_NAME STREQUAL "")
+ set(GIT_REMOTE_NAME "origin")
+else()
+ # Extract remote name if the output contains a remote/branch format
+ string(FIND "${GIT_REMOTE_NAME}" "/" INDEX)
+ if (INDEX GREATER -1)
+ string(SUBSTRING "${GIT_REMOTE_NAME}" 0 "${INDEX}" GIT_REMOTE_NAME)
+ else()
+ # If no remote is present (only a branch name), default to origin
+ set(GIT_REMOTE_NAME "origin")
+ endif()
+endif()
+
+# Get remote link
+execute_process(
+ COMMAND git config --get remote.${GIT_REMOTE_NAME}.url
+ OUTPUT_VARIABLE GIT_REMOTE_URL
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY)
find_package(Boost 1.84.0 CONFIG)
@@ -824,6 +854,10 @@ set(IMGUI src/imgui/imgui_config.h
set(INPUT src/input/controller.cpp
src/input/controller.h
+ src/input/input_handler.cpp
+ src/input/input_handler.h
+ src/input/input_mouse.cpp
+ src/input/input_mouse.h
)
set(EMULATOR src/emulator.cpp
@@ -873,6 +907,10 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/trophy_viewer.h
src/qt_gui/elf_viewer.cpp
src/qt_gui/elf_viewer.h
+ src/qt_gui/kbm_config_dialog.cpp
+ src/qt_gui/kbm_config_dialog.h
+ src/qt_gui/kbm_help_dialog.cpp
+ src/qt_gui/kbm_help_dialog.h
src/qt_gui/main_window_themes.cpp
src/qt_gui/main_window_themes.h
src/qt_gui/settings_dialog.cpp
diff --git a/README.md b/README.md
index fd40d2d63..97e3ab383 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,9 @@ This project began as a fun project. Given our limited free time, it may take so
# Building
+> [!IMPORTANT]
+> If you want to use shadPS4 to play your games, you don't have to follow the build instructions, you can simply download the emulator from either the [**release tab**](https://github.com/shadps4-emu/shadPS4/releases) or the [**action tab**](https://github.com/shadps4-emu/shadPS4/actions).
+
## Windows
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).
@@ -74,7 +77,10 @@ Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shad
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
+# Keyboard and Mouse Mappings
+
+> [!NOTE]
+> Some keyboards may also require you to hold the Fn key to use the F\* keys. Mac users should use the Command key instead of Control, and need to use Command+F11 for full screen to avoid conflicting with system key bindings.
| Button | Function |
|-------------|-------------|
@@ -86,32 +92,35 @@ F12 | Trigger RenderDoc Capture
> [!NOTE]
> Xbox and DualShock controllers work out of the box.
-| Controller button | Keyboard equivelant | Mac alternative |
-|-------------|-------------|--------------|
-LEFT AXIS UP | W | |
-LEFT AXIS DOWN | S | |
-LEFT AXIS LEFT | A | |
-LEFT AXIS RIGHT | D | |
-RIGHT AXIS UP | I | |
-RIGHT AXIS DOWN | K | |
-RIGHT AXIS LEFT | J | |
-RIGHT AXIS RIGHT | L | |
-TRIANGLE | Numpad 8 | C |
-CIRCLE | Numpad 6 | B |
-CROSS | Numpad 2 | N |
-SQUARE | Numpad 4 | V |
-PAD UP | UP | |
-PAD DOWN | DOWN | |
-PAD LEFT | LEFT | |
-PAD RIGHT | RIGHT | |
-OPTIONS | RETURN | |
-BACK BUTTON / TOUCH PAD | SPACE | |
-L1 | Q | |
-R1 | U | |
-L2 | E | |
-R2 | O | |
-L3 | X | |
-R3 | M | |
+| Controller button | Keyboard equivalent |
+|-------------|-------------|
+LEFT AXIS UP | W |
+LEFT AXIS DOWN | S |
+LEFT AXIS LEFT | A |
+LEFT AXIS RIGHT | D |
+RIGHT AXIS UP | I |
+RIGHT AXIS DOWN | K |
+RIGHT AXIS LEFT | J |
+RIGHT AXIS RIGHT | L |
+TRIANGLE | Numpad 8 or C |
+CIRCLE | Numpad 6 or B |
+CROSS | Numpad 2 or N |
+SQUARE | Numpad 4 or V |
+PAD UP | UP |
+PAD DOWN | DOWN |
+PAD LEFT | LEFT |
+PAD RIGHT | RIGHT |
+OPTIONS | RETURN |
+BACK BUTTON / TOUCH PAD | SPACE |
+L1 | Q |
+R1 | U |
+L2 | E |
+R2 | O |
+L3 | X |
+R3 | M |
+
+Keyboard and mouse inputs can be customized in the settings menu by clicking the Controller button, and further details and help on controls are also found there. Custom bindings are saved per-game. Inputs support up to three keys per binding, mouse buttons, mouse movement mapped to joystick input, and more.
+
# Main team
diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml
index d8f51baac..c8c9d5c23 100644
--- a/dist/net.shadps4.shadPS4.metainfo.xml
+++ b/dist/net.shadps4.shadPS4.metainfo.xml
@@ -37,6 +37,9 @@
Game
+
+ https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.6.0
+
https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.5.0
diff --git a/documents/building-linux.md b/documents/building-linux.md
index 4aa66aac6..d9ae2e54c 100644
--- a/documents/building-linux.md
+++ b/documents/building-linux.md
@@ -29,7 +29,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-co
sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers
```
-**Note** : The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.
+**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.
#### OpenSUSE
diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK
index aba997657..0c090001c 160000
--- a/externals/MoltenVK/MoltenVK
+++ b/externals/MoltenVK/MoltenVK
@@ -1 +1 @@
-Subproject commit aba997657b94d6de1794ebad36ce5634341252c7
+Subproject commit 0c090001cb42997031cfe43914340e2639944972
diff --git a/src/common/config.cpp b/src/common/config.cpp
index a57b6d35c..2059da0b3 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -46,8 +46,6 @@ static std::string logType = "async";
static std::string userName = "shadPS4";
static std::string updateChannel;
static std::string chooseHomeTab;
-static u16 deadZoneLeft = 2.0;
-static u16 deadZoneRight = 2.0;
static std::string backButtonBehavior = "left";
static bool useSpecialPad = false;
static int specialPadClass = 1;
@@ -70,6 +68,7 @@ static bool vkGuestMarkers = false;
static bool rdocEnable = false;
static s16 cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default)
+static bool useUnifiedInputConfig = true;
static bool separateupdatefolder = false;
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
@@ -100,6 +99,14 @@ std::string emulator_language = "en";
// Language
u32 m_language = 1; // english
+bool GetUseUnifiedInputConfig() {
+ return useUnifiedInputConfig;
+}
+
+void SetUseUnifiedInputConfig(bool use) {
+ useUnifiedInputConfig = use;
+}
+
std::string getTrophyKey() {
return trophyKey;
}
@@ -151,14 +158,6 @@ bool getEnableDiscordRPC() {
return enableDiscordRPC;
}
-u16 leftDeadZone() {
- return deadZoneLeft;
-}
-
-u16 rightDeadZone() {
- return deadZoneRight;
-}
-
s16 getCursorState() {
return cursorState;
}
@@ -267,18 +266,28 @@ bool vkValidationGpuEnabled() {
return vkValidationGpu;
}
-bool vkCrashDiagnosticEnabled() {
+bool getVkCrashDiagnosticEnabled() {
return vkCrashDiagnostic;
}
-bool vkHostMarkersEnabled() {
- // Forced on when crash diagnostic enabled.
- return vkHostMarkers || vkCrashDiagnostic;
+bool getVkHostMarkersEnabled() {
+ return vkHostMarkers;
}
-bool vkGuestMarkersEnabled() {
- // Forced on when crash diagnostic enabled.
- return vkGuestMarkers || vkCrashDiagnostic;
+bool getVkGuestMarkersEnabled() {
+ return vkGuestMarkers;
+}
+
+void setVkCrashDiagnosticEnabled(bool enable) {
+ vkCrashDiagnostic = enable;
+}
+
+void setVkHostMarkersEnabled(bool enable) {
+ vkHostMarkers = enable;
+}
+
+void setVkGuestMarkersEnabled(bool enable) {
+ vkGuestMarkers = enable;
}
bool getSeparateUpdateEnabled() {
@@ -651,14 +660,13 @@ void load(const std::filesystem::path& path) {
if (data.contains("Input")) {
const toml::value& input = data.at("Input");
- deadZoneLeft = toml::find_or(input, "deadZoneLeft", 2.0);
- deadZoneRight = toml::find_or(input, "deadZoneRight", 2.0);
cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle);
cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5);
backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left");
useSpecialPad = toml::find_or(input, "useSpecialPad", false);
specialPadClass = toml::find_or(input, "specialPadClass", 1);
isMotionControlsEnabled = toml::find_or(input, "isMotionControlsEnabled", true);
+ useUnifiedInputConfig = toml::find_or(input, "useUnifiedInputConfig", true);
}
if (data.contains("GPU")) {
@@ -775,14 +783,13 @@ void save(const std::filesystem::path& path) {
data["General"]["separateUpdateEnabled"] = separateupdatefolder;
data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
- data["Input"]["deadZoneLeft"] = deadZoneLeft;
- data["Input"]["deadZoneRight"] = deadZoneRight;
data["Input"]["cursorState"] = cursorState;
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
data["Input"]["backButtonBehavior"] = backButtonBehavior;
data["Input"]["useSpecialPad"] = useSpecialPad;
data["Input"]["specialPadClass"] = specialPadClass;
data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled;
+ data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig;
data["GPU"]["screenWidth"] = screenWidth;
data["GPU"]["screenHeight"] = screenHeight;
data["GPU"]["nullGpu"] = isNullGpu;
@@ -909,4 +916,112 @@ void setDefaultValues() {
checkCompatibilityOnStartup = false;
}
-} // namespace Config
\ No newline at end of file
+constexpr std::string_view GetDefaultKeyboardConfig() {
+ return R"(#Feeling lost? Check out the Help section!
+
+# Keyboard bindings
+
+triangle = kp8
+circle = kp6
+cross = kp2
+square = kp4
+# Alternatives for users without a keypad
+triangle = c
+circle = b
+cross = n
+square = v
+
+l1 = q
+r1 = u
+l2 = e
+r2 = o
+l3 = x
+r3 = m
+
+options = enter
+touchpad = space
+
+pad_up = up
+pad_down = down
+pad_left = left
+pad_right = right
+
+axis_left_x_minus = a
+axis_left_x_plus = d
+axis_left_y_minus = w
+axis_left_y_plus = s
+
+axis_right_x_minus = j
+axis_right_x_plus = l
+axis_right_y_minus = i
+axis_right_y_plus = k
+
+# Controller bindings
+
+triangle = triangle
+cross = cross
+square = square
+circle = circle
+
+l1 = l1
+l2 = l2
+l3 = l3
+r1 = r1
+r2 = r2
+r3 = r3
+
+options = options
+touchpad = back
+
+pad_up = pad_up
+pad_down = pad_down
+pad_left = pad_left
+pad_right = pad_right
+
+axis_left_x = axis_left_x
+axis_left_y = axis_left_y
+axis_right_x = axis_right_x
+axis_right_y = axis_right_y
+
+# Range of deadzones: 1 (almost none) to 127 (max)
+analog_deadzone = leftjoystick, 2
+analog_deadzone = rightjoystick, 2
+)";
+}
+std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) {
+ // Read configuration file of the game, and if it doesn't exist, generate it from default
+ // If that doesn't exist either, generate that from getDefaultConfig() and try again
+ // If even the folder is missing, we start with that.
+
+ const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "input_config";
+ const auto config_file = config_dir / (game_id + ".ini");
+ const auto default_config_file = config_dir / "default.ini";
+
+ // Ensure the config directory exists
+ if (!std::filesystem::exists(config_dir)) {
+ std::filesystem::create_directories(config_dir);
+ }
+
+ // Check if the default config exists
+ if (!std::filesystem::exists(default_config_file)) {
+ // If the default config is also missing, create it from getDefaultConfig()
+ const auto default_config = GetDefaultKeyboardConfig();
+ std::ofstream default_config_stream(default_config_file);
+ if (default_config_stream) {
+ default_config_stream << default_config;
+ }
+ }
+
+ // if empty, we only need to execute the function up until this point
+ if (game_id.empty()) {
+ return default_config_file;
+ }
+
+ // If game-specific config doesn't exist, create it from the default config
+ if (!std::filesystem::exists(config_file)) {
+ std::filesystem::copy(default_config_file, config_file);
+ }
+ return config_file;
+}
+
+} // namespace Config
diff --git a/src/common/config.h b/src/common/config.h
index 15937606e..77ed69ece 100644
--- a/src/common/config.h
+++ b/src/common/config.h
@@ -37,14 +37,14 @@ std::string getUserName();
std::string getUpdateChannel();
std::string getChooseHomeTab();
-u16 leftDeadZone();
-u16 rightDeadZone();
s16 getCursorState();
int getCursorHideTimeout();
std::string getBackButtonBehavior();
bool getUseSpecialPad();
int getSpecialPadClass();
bool getIsMotionControlsEnabled();
+bool GetUseUnifiedInputConfig();
+void SetUseUnifiedInputConfig(bool use);
u32 getScreenWidth();
u32 getScreenHeight();
@@ -106,9 +106,12 @@ void setRdocEnabled(bool enable);
bool vkValidationEnabled();
bool vkValidationSyncEnabled();
bool vkValidationGpuEnabled();
-bool vkCrashDiagnosticEnabled();
-bool vkHostMarkersEnabled();
-bool vkGuestMarkersEnabled();
+bool getVkCrashDiagnosticEnabled();
+bool getVkHostMarkersEnabled();
+bool getVkGuestMarkersEnabled();
+void setVkCrashDiagnosticEnabled(bool enable);
+void setVkHostMarkersEnabled(bool enable);
+void setVkGuestMarkersEnabled(bool enable);
// Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
@@ -149,6 +152,9 @@ std::string getEmulatorLanguage();
void setDefaultValues();
+// todo: name and function location pending
+std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = "");
+
// settings
u32 GetLanguage();
}; // namespace Config
diff --git a/src/common/elf_info.h b/src/common/elf_info.h
index cb32679bb..d885709cd 100644
--- a/src/common/elf_info.h
+++ b/src/common/elf_info.h
@@ -80,6 +80,7 @@ public:
static constexpr u32 FW_40 = 0x4000000;
static constexpr u32 FW_45 = 0x4500000;
static constexpr u32 FW_50 = 0x5000000;
+ static constexpr u32 FW_55 = 0x5500000;
static constexpr u32 FW_80 = 0x8000000;
static ElfInfo& Instance() {
diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp
index 53eb123dc..a4312fada 100644
--- a/src/common/path_util.cpp
+++ b/src/common/path_util.cpp
@@ -176,6 +176,34 @@ void SetUserPath(PathType shad_path, const fs::path& new_path) {
UserPaths.insert_or_assign(shad_path, new_path);
}
+std::optional FindGameByID(const fs::path& dir, const std::string& game_id,
+ int max_depth) {
+ if (max_depth < 0) {
+ return std::nullopt;
+ }
+
+ // Check if this is the game we're looking for
+ if (dir.filename() == game_id && fs::exists(dir / "sce_sys" / "param.sfo")) {
+ auto eboot_path = dir / "eboot.bin";
+ if (fs::exists(eboot_path)) {
+ return eboot_path;
+ }
+ }
+
+ // Recursively search subdirectories
+ std::error_code ec;
+ for (const auto& entry : fs::directory_iterator(dir, ec)) {
+ if (!entry.is_directory()) {
+ continue;
+ }
+ if (auto found = FindGameByID(entry.path(), game_id, max_depth - 1)) {
+ return found;
+ }
+ }
+
+ return std::nullopt;
+}
+
#ifdef ENABLE_QT_GUI
void PathToQString(QString& result, const std::filesystem::path& path) {
#ifdef _WIN32
diff --git a/src/common/path_util.h b/src/common/path_util.h
index 09b7a3337..7190378d6 100644
--- a/src/common/path_util.h
+++ b/src/common/path_util.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include
#include
#ifdef ENABLE_QT_GUI
@@ -115,4 +116,18 @@ void PathToQString(QString& result, const std::filesystem::path& path);
[[nodiscard]] std::filesystem::path PathFromQString(const QString& path);
#endif
+/**
+ * Recursively searches for a game directory by its ID.
+ * Limits search depth to prevent excessive filesystem traversal.
+ *
+ * @param dir Base directory to start the search from
+ * @param game_id The game ID to search for
+ * @param max_depth Maximum directory depth to search
+ *
+ * @returns Path to eboot.bin if found, std::nullopt otherwise
+ */
+[[nodiscard]] std::optional FindGameByID(const std::filesystem::path& dir,
+ const std::string& game_id,
+ int max_depth);
+
} // namespace Common::FS
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index 642e6373d..2de04e0be 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -6,14 +6,18 @@
#define GIT_REV "@GIT_REV@"
#define GIT_BRANCH "@GIT_BRANCH@"
#define GIT_DESC "@GIT_DESC@"
+#define GIT_REMOTE_NAME "@GIT_REMOTE_NAME@"
+#define GIT_REMOTE_URL "@GIT_REMOTE_URL@"
#define BUILD_DATE "@BUILD_DATE@"
namespace Common {
-const char g_scm_rev[] = GIT_REV;
-const char g_scm_branch[] = GIT_BRANCH;
-const char g_scm_desc[] = GIT_DESC;
-const char g_scm_date[] = BUILD_DATE;
+const char g_scm_rev[] = GIT_REV;
+const char g_scm_branch[] = GIT_BRANCH;
+const char g_scm_desc[] = GIT_DESC;
+const char g_scm_remote_name[] = GIT_REMOTE_NAME;
+const char g_scm_remote_url[] = GIT_REMOTE_URL;
+const char g_scm_date[] = BUILD_DATE;
} // namespace
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 005099d2d..f38efff42 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -8,6 +8,8 @@ namespace Common {
extern const char g_scm_rev[];
extern const char g_scm_branch[];
extern const char g_scm_desc[];
+extern const char g_scm_remote_name[];
+extern const char g_scm_remote_url[];
extern const char g_scm_date[];
} // namespace Common
diff --git a/src/common/version.h b/src/common/version.h
index c903b1db6..e7f6cc817 100644
--- a/src/common/version.h
+++ b/src/common/version.h
@@ -8,7 +8,7 @@
namespace Common {
-constexpr char VERSION[] = "0.5.1 WIP";
+constexpr char VERSION[] = "0.6.1 WIP";
constexpr bool isRelease = false;
} // namespace Common
diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp
index b812e5444..57d528a81 100644
--- a/src/core/cpu_patches.cpp
+++ b/src/core/cpu_patches.cpp
@@ -30,16 +30,6 @@
using namespace Xbyak::util;
-#define MAYBE_AVX(OPCODE, ...) \
- [&] { \
- Cpu cpu; \
- if (cpu.has(Cpu::tAVX)) { \
- c.v##OPCODE(__VA_ARGS__); \
- } else { \
- c.OPCODE(__VA_ARGS__); \
- } \
- }()
-
namespace Core {
static Xbyak::Reg ZydisToXbyakRegister(const ZydisRegister reg) {
@@ -643,7 +633,7 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64.");
// Get lower qword from xmm register
- MAYBE_AVX(movq, scratch1, xmm_dst);
+ c.vmovq(scratch1, xmm_dst);
if (index != 0) {
c.shr(scratch1, index);
@@ -656,7 +646,7 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
// Writeback to xmm register, extrq instruction says top 64-bits are undefined so we don't
// care to preserve them
- MAYBE_AVX(movq, xmm_dst, scratch1);
+ c.vmovq(xmm_dst, scratch1);
c.pop(scratch2);
c.pop(scratch1);
@@ -690,7 +680,7 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
c.push(mask);
// Construct the mask out of the length that resides in bottom 6 bits of source xmm
- MAYBE_AVX(movq, scratch1, xmm_src);
+ c.vmovq(scratch1, xmm_src);
c.mov(scratch2, scratch1);
c.and_(scratch2, 0x3F);
c.jz(length_zero);
@@ -711,10 +701,10 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
c.and_(scratch1, 0x3F);
c.mov(scratch2, scratch1); // cl now contains the shift amount
- MAYBE_AVX(movq, scratch1, xmm_dst);
+ c.vmovq(scratch1, xmm_dst);
c.shr(scratch1, cl);
c.and_(scratch1, mask);
- MAYBE_AVX(movq, xmm_dst, scratch1);
+ c.vmovq(xmm_dst, scratch1);
c.pop(mask);
c.pop(scratch2);
@@ -765,8 +755,8 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64.");
- MAYBE_AVX(movq, scratch1, xmm_src);
- MAYBE_AVX(movq, scratch2, xmm_dst);
+ c.vmovq(scratch1, xmm_src);
+ c.vmovq(scratch2, xmm_dst);
c.mov(mask, mask_value);
// src &= mask
@@ -784,12 +774,7 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
c.or_(scratch2, scratch1);
// Insert scratch2 into low 64 bits of dst, upper 64 bits are unaffected
- Cpu cpu;
- if (cpu.has(Cpu::tAVX)) {
- c.vpinsrq(xmm_dst, xmm_dst, scratch2, 0);
- } else {
- c.pinsrq(xmm_dst, scratch2, 0);
- }
+ c.vpinsrq(xmm_dst, xmm_dst, scratch2, 0);
c.pop(mask);
c.pop(scratch2);
@@ -816,7 +801,7 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
c.push(mask);
// Get upper 64 bits of src and copy it to mask and index
- MAYBE_AVX(pextrq, index, xmm_src, 1);
+ c.vpextrq(index, xmm_src, 1);
c.mov(mask, index);
// When length is 0, set it to 64
@@ -839,7 +824,7 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
c.and_(index, 0x3F);
// src &= mask
- MAYBE_AVX(movq, scratch1, xmm_src);
+ c.vmovq(scratch1, xmm_src);
c.and_(scratch1, mask);
// mask = ~(mask << index)
@@ -851,12 +836,12 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
c.shl(scratch1, cl);
// dst = (dst & mask) | src
- MAYBE_AVX(movq, scratch2, xmm_dst);
+ c.vmovq(scratch2, xmm_dst);
c.and_(scratch2, mask);
c.or_(scratch2, scratch1);
// Upper 64 bits are undefined in insertq
- MAYBE_AVX(movq, xmm_dst, scratch2);
+ c.vmovq(xmm_dst, scratch2);
c.pop(mask);
c.pop(index);
diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp
index f0ad59c3b..dea8115e9 100644
--- a/src/core/libraries/audio/audioout.cpp
+++ b/src/core/libraries/audio/audioout.cpp
@@ -89,6 +89,9 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
LOG_INFO(Lib_AudioOut, "handle = {}", handle);
+ if (audio == nullptr) {
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
@@ -171,6 +174,9 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
}
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
+ if (audio == nullptr) {
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
@@ -305,6 +311,10 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
user_id, magic_enum::enum_name(port_type), index, length, sample_rate,
magic_enum::enum_name(param_type.data_format.Value()),
magic_enum::enum_name(param_type.attributes.Value()));
+ if (audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "Audio out not initialized");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) &&
(port_type != OrbisAudioOutPort::Aux)) {
LOG_ERROR(Lib_AudioOut, "Invalid port type");
@@ -368,6 +378,9 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() {
}
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
+ if (audio == nullptr) {
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
@@ -489,6 +502,9 @@ int PS4_SYSV_ABI sceAudioOutSetUsbVolume() {
}
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
+ if (audio == nullptr) {
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp
index 18709bcb2..173b78382 100644
--- a/src/core/libraries/pad/pad.cpp
+++ b/src/core/libraries/pad/pad.cpp
@@ -95,8 +95,8 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920;
pInfo->touchPadInfo.resolution.y = 950;
- pInfo->stickInfo.deadZoneLeft = Config::leftDeadZone();
- pInfo->stickInfo.deadZoneRight = Config::rightDeadZone();
+ pInfo->stickInfo.deadZoneLeft = 1;
+ pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connected = false;
@@ -106,8 +106,8 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920;
pInfo->touchPadInfo.resolution.y = 950;
- pInfo->stickInfo.deadZoneLeft = Config::leftDeadZone();
- pInfo->stickInfo.deadZoneRight = Config::rightDeadZone();
+ pInfo->stickInfo.deadZoneLeft = 1;
+ pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connected = true;
diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp
index 5261cdb11..f85845f70 100644
--- a/src/core/libraries/save_data/save_backup.cpp
+++ b/src/core/libraries/save_data/save_backup.cpp
@@ -121,15 +121,17 @@ static void BackupThreadBody() {
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.front().done = true;
}
- std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front();
- g_result_queue.push_back(std::move(req));
- if (g_result_queue.size() > 20) {
- g_result_queue.pop_front();
+ if (req.origin != OrbisSaveDataEventType::__DO_NOT_SAVE) {
+ g_result_queue.push_back(std::move(req));
+ if (g_result_queue.size() > 20) {
+ g_result_queue.pop_front();
+ }
}
}
+ std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
}
g_backup_status = WorkerStatus::NotStarted;
}
@@ -141,6 +143,15 @@ void StartThread() {
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_status = WorkerStatus::Waiting;
g_backup_thread = std::jthread{BackupThreadBody};
+ static std::once_flag flag;
+ std::call_once(flag, [] {
+ std::at_quick_exit([] {
+ StopThread();
+ while (GetWorkerStatus() != WorkerStatus::NotStarted) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ });
+ });
}
void StopThread() {
@@ -148,12 +159,12 @@ void StopThread() {
return;
}
LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
+ g_backup_status = WorkerStatus::Stopping;
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.emplace_back(BackupRequest{});
}
g_backup_thread_semaphore.release();
- g_backup_status = WorkerStatus::Stopping;
}
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h
index e49c69f60..83a263c9b 100644
--- a/src/core/libraries/save_data/save_backup.h
+++ b/src/core/libraries/save_data/save_backup.h
@@ -25,6 +25,8 @@ enum class OrbisSaveDataEventType : u32 {
UMOUNT_BACKUP = 1,
BACKUP = 2,
SAVE_DATA_MEMORY_SYNC = 3,
+
+ __DO_NOT_SAVE = 1000000, // This value is only for the backup thread
};
struct BackupRequest {
diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp
index c2b7dca3c..26708d2d6 100644
--- a/src/core/libraries/save_data/save_instance.cpp
+++ b/src/core/libraries/save_data/save_instance.cpp
@@ -10,6 +10,7 @@
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
+#include "save_backup.h"
#include "save_instance.h"
constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB
@@ -45,14 +46,13 @@ static const std::unordered_map default_title = {
namespace Libraries::SaveData {
-std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
- std::string_view game_serial) {
+fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
+ std::string_view game_serial) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
}
-std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id,
- std::string_view game_serial,
- std::string_view dir_name) {
+fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
+ std::string_view dir_name) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
}
@@ -65,7 +65,7 @@ uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
return *(uint64_t*)value.data();
}
-std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) {
+fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) {
return dir_path / sce_sys / "param.sfo";
}
@@ -129,7 +129,6 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
save_path = std::move(other.save_path);
param_sfo_path = std::move(other.param_sfo_path);
corrupt_file_path = std::move(other.corrupt_file_path);
- corrupt_file = std::move(other.corrupt_file);
param_sfo = std::move(other.param_sfo);
mount_point = std::move(other.mount_point);
max_blocks = other.max_blocks;
@@ -142,7 +141,8 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
return *this;
}
-void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) {
+void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt,
+ bool dont_restore_backup) {
if (mounted) {
UNREACHABLE_MSG("Save instance is already mounted");
}
@@ -161,25 +161,27 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
}
exists = true;
} else {
+ std::optional err;
if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
- throw std::filesystem::filesystem_error(
- "Corrupted save data", corrupt_file_path,
- std::make_error_code(std::errc::illegal_byte_sequence));
+ err = fs::filesystem_error("Corrupted save data", corrupt_file_path,
+ std::make_error_code(std::errc::illegal_byte_sequence));
+ } else if (!param_sfo.Open(param_sfo_path)) {
+ err = fs::filesystem_error("Failed to read param.sfo", param_sfo_path,
+ std::make_error_code(std::errc::illegal_byte_sequence));
}
- if (!param_sfo.Open(param_sfo_path)) {
- throw std::filesystem::filesystem_error(
- "Failed to read param.sfo", param_sfo_path,
- std::make_error_code(std::errc::illegal_byte_sequence));
+ if (err.has_value()) {
+ if (dont_restore_backup) {
+ throw err.value();
+ }
+ if (Backup::Restore(save_path)) {
+ return SetupAndMount(read_only, copy_icon, ignore_corrupt, true);
+ }
}
}
if (!ignore_corrupt && !read_only) {
- int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write);
- if (err != 0) {
- throw std::filesystem::filesystem_error(
- "Failed to open corrupted file", corrupt_file_path,
- std::make_error_code(std::errc::illegal_byte_sequence));
- }
+ Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
+ f.Close();
}
max_blocks = static_cast(GetMaxBlockFromSFO(param_sfo));
@@ -197,12 +199,11 @@ void SaveInstance::Umount() {
mounted = false;
const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) {
- throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
- std::make_error_code(std::errc::permission_denied));
+ throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
+ std::make_error_code(std::errc::permission_denied));
}
param_sfo = PSF();
- corrupt_file.Close();
fs::remove(corrupt_file_path);
g_mnt->Unmount(save_path, mount_point);
}
@@ -216,8 +217,8 @@ void SaveInstance::CreateFiles() {
const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) {
- throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
- std::make_error_code(std::errc::permission_denied));
+ throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
+ std::make_error_code(std::errc::permission_denied));
}
}
diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h
index 3be5c4595..6e7ac8f66 100644
--- a/src/core/libraries/save_data/save_instance.h
+++ b/src/core/libraries/save_data/save_instance.h
@@ -42,8 +42,6 @@ class SaveInstance {
std::filesystem::path param_sfo_path;
std::filesystem::path corrupt_file_path;
- Common::FS::IOFile corrupt_file;
-
PSF param_sfo;
std::string mount_point;
@@ -80,7 +78,8 @@ public:
SaveInstance& operator=(const SaveInstance& other) = delete;
SaveInstance& operator=(SaveInstance&& other) noexcept;
- void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false);
+ void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false,
+ bool dont_restore_backup = false);
void Umount();
diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp
index 84179bc27..13e122c60 100644
--- a/src/core/libraries/save_data/save_memory.cpp
+++ b/src/core/libraries/save_data/save_memory.cpp
@@ -6,14 +6,16 @@
#include
#include
#include
+#include
#include
#include
#include
#include "common/assert.h"
+#include "common/elf_info.h"
#include "common/logging/log.h"
-#include "common/polyfill_thread.h"
+#include "common/path_util.h"
#include "common/singleton.h"
#include "common/thread.h"
#include "core/file_sys/fs.h"
@@ -23,265 +25,202 @@ using Common::FS::IOFile;
namespace fs = std::filesystem;
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
-constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory";
+constexpr std::string_view StandardDirnameSaveDataMemory = "sce_sdmemory";
constexpr std::string_view FilenameSaveDataMemory = "memory.dat";
+constexpr std::string_view IconName = "icon0.png";
+constexpr std::string_view CorruptFileName = "corrupted";
namespace Libraries::SaveData::SaveMemory {
static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance();
-static OrbisUserServiceUserId g_user_id{};
-static std::string g_game_serial{};
-static std::filesystem::path g_save_path{};
-static std::filesystem::path g_param_sfo_path{};
-static PSF g_param_sfo;
+struct SlotData {
+ OrbisUserServiceUserId user_id;
+ std::string game_serial;
+ std::filesystem::path folder_path;
+ PSF sfo;
+ std::vector memory_cache;
+};
-static bool g_save_memory_initialized = false;
-static std::mutex g_saving_memory_mutex;
-static std::vector g_save_memory;
+static std::mutex g_slot_mtx;
+static std::unordered_map g_attached_slots;
-static std::filesystem::path g_icon_path;
-static std::vector g_icon_memory;
+void PersistMemory(u32 slot_id, bool lock) {
+ std::unique_lock lck{g_slot_mtx, std::defer_lock};
+ if (lock) {
+ lck.lock();
+ }
+ auto& data = g_attached_slots[slot_id];
+ auto memoryPath = data.folder_path / FilenameSaveDataMemory;
+ fs::create_directories(memoryPath.parent_path());
-static std::condition_variable g_trigger_save_memory;
-static std::atomic_bool g_saving_memory = false;
-static std::atomic_bool g_save_event = false;
-static std::jthread g_save_memory_thread;
-
-static std::atomic_bool g_memory_dirty = false;
-static std::atomic_bool g_param_dirty = false;
-static std::atomic_bool g_icon_dirty = false;
-
-static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) {
- const auto& dir = path.parent_path();
- const auto& name = path.filename();
- const auto tmp_path = dir / (name.string() + ".tmp");
-
- IOFile file(tmp_path, Common::FS::FileAccessMode::Write);
- file.WriteRaw(buf, count);
- file.Close();
-
- fs::remove(path);
- fs::rename(tmp_path, path);
-}
-
-[[noreturn]] void SaveThreadLoop() {
- Common::SetCurrentThreadName("shadPS4:SaveData:SaveDataMemoryThread");
- std::mutex mtx;
- while (true) {
- {
- std::unique_lock lk{mtx};
- g_trigger_save_memory.wait(lk);
- }
- // Save the memory
- g_saving_memory = true;
- std::scoped_lock lk{g_saving_memory_mutex};
+ int n = 0;
+ std::string errMsg;
+ while (n++ < 10) {
try {
- LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", fmt::UTF(g_save_path.u8string()));
-
- if (g_memory_dirty) {
- g_memory_dirty = false;
- SaveFileSafe(g_save_memory.data(), g_save_memory.size(),
- g_save_path / FilenameSaveDataMemory);
+ IOFile f;
+ int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
+ if (f.IsOpen()) {
+ f.WriteRaw(data.memory_cache.data(), data.memory_cache.size());
+ f.Close();
+ return;
}
- if (g_param_dirty) {
- g_param_dirty = false;
- static std::vector buf;
- g_param_sfo.Encode(buf);
- SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path);
- }
- if (g_icon_dirty) {
- g_icon_dirty = false;
- SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path);
- }
-
- if (g_save_event) {
- Backup::PushBackupEvent(Backup::BackupRequest{
- .user_id = g_user_id,
- .title_id = g_game_serial,
- .dir_name = std::string{DirnameSaveDataMemory},
- .origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC,
- .save_path = g_save_path,
- });
- g_save_event = false;
- }
- } catch (const fs::filesystem_error& e) {
- LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what());
- MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{
- MsgDialog::MsgDialogState::UserState{
- .type = MsgDialog::ButtonType::OK,
- .msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}",
- e.code().message(), e.what()),
- },
- });
+ const auto err = std::error_code{r, std::iostream_category()};
+ throw std::filesystem::filesystem_error{err.message(), err};
+ } catch (const std::filesystem::filesystem_error& e) {
+ errMsg = std::string{e.what()};
+ std::this_thread::sleep_for(std::chrono::seconds(1));
}
- g_saving_memory = false;
}
+ const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{
+ .type = MsgDialog::ButtonType::OK,
+ .msg = "Failed to persist save memory:\n" + errMsg + "\nat " +
+ Common::FS::PathToUTF8String(memoryPath),
+ }};
+ MsgDialog::ShowMsgDialog(dialog);
}
-void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) {
- g_user_id = user_id;
- g_game_serial = std::move(_game_serial);
- g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory);
- g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path);
- g_param_sfo = PSF();
- g_icon_path = g_save_path / sce_sys / "icon0.png";
+std::string GetSaveDir(u32 slot_id) {
+ std::string dir(StandardDirnameSaveDataMemory);
+ if (slot_id > 0) {
+ dir += std::to_string(slot_id);
+ }
+ return dir;
}
-const std::filesystem::path& GetSavePath() {
- return g_save_path;
+std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
+ std::string_view game_serial) {
+ std::string dir(StandardDirnameSaveDataMemory);
+ if (slot_id > 0) {
+ dir += std::to_string(slot_id);
+ }
+ return SaveInstance::MakeDirSavePath(user_id, Common::ElfInfo::Instance().GameSerial(), dir);
}
-size_t CreateSaveMemory(size_t memory_size) {
- size_t existed_size = 0;
+size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) {
+ std::lock_guard lck{g_slot_mtx};
- static std::once_flag init_save_thread_flag;
- std::call_once(init_save_thread_flag,
- [] { g_save_memory_thread = std::jthread{SaveThreadLoop}; });
+ const auto save_dir = GetSavePath(user_id, slot_id, game_serial);
- g_save_memory.resize(memory_size);
- SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory},
- g_game_serial);
+ auto& data = g_attached_slots[slot_id];
+ data = SlotData{
+ .user_id = user_id,
+ .game_serial = std::string{game_serial},
+ .folder_path = save_dir,
+ .sfo = {},
+ .memory_cache = {},
+ };
- g_save_memory_initialized = true;
+ SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial});
- if (!fs::exists(g_param_sfo_path)) {
- // Create save memory
- fs::create_directories(g_save_path / sce_sys);
-
- IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write};
- bool ok = memory_file.SetSize(memory_size);
- if (!ok) {
- LOG_ERROR(Lib_SaveData, "Failed to set memory size");
- throw std::filesystem::filesystem_error(
- "Failed to set save memory size", g_save_path / FilenameSaveDataMemory,
- std::make_error_code(std::errc::no_space_on_device));
- }
- memory_file.Close();
- } else {
- // Load save memory
-
- bool ok = g_param_sfo.Open(g_param_sfo_path);
- if (!ok) {
- LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}",
- fmt::UTF(g_param_sfo_path.u8string()));
- throw std::filesystem::filesystem_error(
- "failed to open SFO", g_param_sfo_path,
- std::make_error_code(std::errc::illegal_byte_sequence));
- }
-
- IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
- if (!memory_file.IsOpen()) {
- LOG_ERROR(Lib_SaveData, "Failed to open save memory");
- throw std::filesystem::filesystem_error(
- "failed to open save memory", g_save_path / FilenameSaveDataMemory,
- std::make_error_code(std::errc::permission_denied));
- }
- size_t save_size = memory_file.GetSize();
- existed_size = save_size;
- memory_file.Seek(0);
- memory_file.ReadRaw(g_save_memory.data(), std::min(save_size, memory_size));
- memory_file.Close();
+ auto param_sfo_path = SaveInstance::GetParamSFOPath(save_dir);
+ if (!fs::exists(param_sfo_path)) {
+ return 0;
}
- return existed_size;
+ if (!data.sfo.Open(param_sfo_path) || fs::exists(save_dir / CorruptFileName)) {
+ if (!Backup::Restore(save_dir)) { // Could not restore the backup
+ return 0;
+ }
+ }
+
+ const auto memory = save_dir / FilenameSaveDataMemory;
+ if (fs::exists(memory)) {
+ return fs::file_size(memory);
+ }
+
+ return 0;
}
-void SetIcon(void* buf, size_t buf_size) {
+void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
+ std::lock_guard lck{g_slot_mtx};
+ const auto& data = g_attached_slots[slot_id];
+ const auto icon_path = data.folder_path / sce_sys / "icon0.png";
if (buf == nullptr) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
- if (fs::exists(src_icon)) {
- if (fs::exists(g_icon_path)) {
- fs::remove(g_icon_path);
- }
- fs::copy_file(src_icon, g_icon_path);
+ if (fs::exists(icon_path)) {
+ fs::remove(icon_path);
}
- if (fs::exists(g_icon_path)) {
- IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
- size_t size = file.GetSize();
- file.Seek(0);
- g_icon_memory.resize(size);
- file.ReadRaw(g_icon_memory.data(), size);
- file.Close();
+ if (fs::exists(src_icon)) {
+ fs::create_directories(icon_path.parent_path());
+ fs::copy_file(src_icon, icon_path);
}
} else {
- g_icon_memory.resize(buf_size);
- std::memcpy(g_icon_memory.data(), buf, buf_size);
- IOFile file(g_icon_path, Common::FS::FileAccessMode::Write);
- file.Seek(0);
- file.WriteRaw(g_icon_memory.data(), buf_size);
+ IOFile file(icon_path, Common::FS::FileAccessMode::Write);
+ file.WriteRaw(buf, buf_size);
file.Close();
}
}
-void WriteIcon(void* buf, size_t buf_size) {
- if (buf_size != g_icon_memory.size()) {
- g_icon_memory.resize(buf_size);
+bool IsSaveMemoryInitialized(u32 slot_id) {
+ std::lock_guard lck{g_slot_mtx};
+ return g_attached_slots.contains(slot_id);
+}
+
+PSF& GetParamSFO(u32 slot_id) {
+ std::lock_guard lck{g_slot_mtx};
+ auto& data = g_attached_slots[slot_id];
+ return data.sfo;
+}
+
+std::vector GetIcon(u32 slot_id) {
+ std::lock_guard lck{g_slot_mtx};
+ auto& data = g_attached_slots[slot_id];
+ const auto icon_path = data.folder_path / sce_sys / "icon0.png";
+ IOFile f{icon_path, Common::FS::FileAccessMode::Read};
+ if (!f.IsOpen()) {
+ return {};
}
- std::memcpy(g_icon_memory.data(), buf, buf_size);
- g_icon_dirty = true;
+ const u64 size = f.GetSize();
+ std::vector ret;
+ ret.resize(size);
+ f.ReadSpan(std::span{ret});
+ return ret;
}
-bool IsSaveMemoryInitialized() {
- return g_save_memory_initialized;
-}
-
-PSF& GetParamSFO() {
- return g_param_sfo;
-}
-
-std::span GetIcon() {
- return {g_icon_memory};
-}
-
-void SaveSFO(bool sync) {
- if (!sync) {
- g_param_dirty = true;
- return;
- }
- const bool ok = g_param_sfo.Encode(g_param_sfo_path);
+void SaveSFO(u32 slot_id) {
+ std::lock_guard lck{g_slot_mtx};
+ const auto& data = g_attached_slots[slot_id];
+ const auto sfo_path = SaveInstance::GetParamSFOPath(data.folder_path);
+ fs::create_directories(sfo_path.parent_path());
+ const bool ok = data.sfo.Encode(sfo_path);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo");
- throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path,
+ throw std::filesystem::filesystem_error("Failed to write param.sfo", sfo_path,
std::make_error_code(std::errc::permission_denied));
}
}
-bool IsSaving() {
- return g_saving_memory;
+
+void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
+ std::lock_guard lk{g_slot_mtx};
+ auto& data = g_attached_slots[slot_id];
+ auto& memory = data.memory_cache;
+ if (memory.empty()) { // Load file
+ IOFile f{data.folder_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
+ if (f.IsOpen()) {
+ memory.resize(f.GetSize());
+ f.Seek(0);
+ f.ReadSpan(std::span{memory});
+ }
+ }
+ s64 read_size = buf_size;
+ if (read_size + offset > memory.size()) {
+ read_size = memory.size() - offset;
+ }
+ std::memcpy(buf, memory.data() + offset, read_size);
}
-bool TriggerSaveWithoutEvent() {
- if (g_saving_memory) {
- return false;
+void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
+ std::lock_guard lk{g_slot_mtx};
+ auto& data = g_attached_slots[slot_id];
+ auto& memory = data.memory_cache;
+ if (offset + buf_size > memory.size()) {
+ memory.resize(offset + buf_size);
}
- g_trigger_save_memory.notify_one();
- return true;
-}
-
-bool TriggerSave() {
- if (g_saving_memory) {
- return false;
- }
- g_save_event = true;
- g_trigger_save_memory.notify_one();
- return true;
-}
-
-void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
- std::scoped_lock lk{g_saving_memory_mutex};
- if (offset + buf_size > g_save_memory.size()) {
- UNREACHABLE_MSG("ReadMemory out of bounds");
- }
- std::memcpy(buf, g_save_memory.data() + offset, buf_size);
-}
-
-void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
- std::scoped_lock lk{g_saving_memory_mutex};
- if (offset + buf_size > g_save_memory.size()) {
- g_save_memory.resize(offset + buf_size);
- }
- std::memcpy(g_save_memory.data() + offset, buf, buf_size);
- g_memory_dirty = true;
+ std::memcpy(memory.data() + offset, buf, buf_size);
+ PersistMemory(slot_id, false);
+ Backup::NewRequest(data.user_id, data.game_serial, GetSaveDir(slot_id),
+ Backup::OrbisSaveDataEventType::__DO_NOT_SAVE);
}
} // namespace Libraries::SaveData::SaveMemory
\ No newline at end of file
diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h
index 04eeaa652..681865634 100644
--- a/src/core/libraries/save_data/save_memory.h
+++ b/src/core/libraries/save_data/save_memory.h
@@ -3,7 +3,7 @@
#pragma once
-#include
+#include
#include "save_backup.h"
class PSF;
@@ -14,36 +14,30 @@ using OrbisUserServiceUserId = s32;
namespace Libraries::SaveData::SaveMemory {
-void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial);
+void PersistMemory(u32 slot_id, bool lock = true);
-[[nodiscard]] const std::filesystem::path& GetSavePath();
+[[nodiscard]] std::string GetSaveDir(u32 slot_id);
-// returns the size of the existed save memory
-size_t CreateSaveMemory(size_t memory_size);
+[[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
+ std::string_view game_serial);
-// Initialize the icon. Set buf to null to read the standard icon.
-void SetIcon(void* buf, size_t buf_size);
+// returns the size of the save memory if exists
+size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial);
-// Update the icon
-void WriteIcon(void* buf, size_t buf_size);
+// Write the icon. Set buf to null to read the standard icon.
+void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0);
-[[nodiscard]] bool IsSaveMemoryInitialized();
+[[nodiscard]] bool IsSaveMemoryInitialized(u32 slot_id);
-[[nodiscard]] PSF& GetParamSFO();
+[[nodiscard]] PSF& GetParamSFO(u32 slot_id);
-[[nodiscard]] std::span GetIcon();
+[[nodiscard]] std::vector GetIcon(u32 slot_id);
// Save now or wait for the background thread to save
-void SaveSFO(bool sync = false);
+void SaveSFO(u32 slot_id);
-[[nodiscard]] bool IsSaving();
+void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
-bool TriggerSaveWithoutEvent();
-
-bool TriggerSave();
-
-void ReadMemory(void* buf, size_t buf_size, int64_t offset);
-
-void WriteMemory(void* buf, size_t buf_size, int64_t offset);
+void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
} // namespace Libraries::SaveData::SaveMemory
\ No newline at end of file
diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp
index b573ded1e..e9ad77d69 100644
--- a/src/core/libraries/save_data/savedata.cpp
+++ b/src/core/libraries/save_data/savedata.cpp
@@ -177,7 +177,8 @@ struct OrbisSaveDataMemoryGet2 {
OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
- std::array _reserved;
+ u32 slotId;
+ std::array _reserved;
};
struct OrbisSaveDataMemorySet2 {
@@ -186,6 +187,8 @@ struct OrbisSaveDataMemorySet2 {
const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param;
const OrbisSaveDataIcon* icon;
+ u32 dataNum;
+ u32 slotId;
std::array _reserved;
};
@@ -198,7 +201,8 @@ struct OrbisSaveDataMemorySetup2 {
const OrbisSaveDataParam* initParam;
// +4.5
const OrbisSaveDataIcon* initIcon;
- std::array _reserved;
+ u32 slotId;
+ std::array _reserved;
};
struct OrbisSaveDataMemorySetupResult {
@@ -206,9 +210,16 @@ struct OrbisSaveDataMemorySetupResult {
std::array _reserved;
};
+enum OrbisSaveDataMemorySyncOption : u32 {
+ NONE = 0,
+ BLOCKING = 1,
+};
+
struct OrbisSaveDataMemorySync {
OrbisUserServiceUserId userId;
- std::array _reserved;
+ u32 slotId;
+ OrbisSaveDataMemorySyncOption option;
+ std::array _reserved;
};
struct OrbisSaveDataMount2 {
@@ -327,6 +338,7 @@ static void initialize() {
g_initialized = true;
g_game_serial = ElfInfo::Instance().GameSerial();
g_fw_ver = ElfInfo::Instance().FirmwareVer();
+ Backup::StartThread();
}
// game_00other | game*other
@@ -558,7 +570,6 @@ Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
}
}
- Backup::StartThread();
Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP);
return Error::OK;
@@ -1136,22 +1147,27 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getPar
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
- if (!SaveMemory::IsSaveMemoryInitialized()) {
+
+ u32 slot_id = 0;
+ if (g_fw_ver > ElfInfo::FW_50) {
+ slot_id = getParam->slotId;
+ }
+ if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY;
}
LOG_DEBUG(Lib_SaveData, "called");
auto data = getParam->data;
if (data != nullptr) {
- SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset);
+ SaveMemory::ReadMemory(slot_id, data->buf, data->bufSize, data->offset);
}
auto param = getParam->param;
if (param != nullptr) {
- param->FromSFO(SaveMemory::GetParamSFO());
+ param->FromSFO(SaveMemory::GetParamSFO(slot_id));
}
auto icon = getParam->icon;
if (icon != nullptr) {
- auto icon_mem = SaveMemory::GetIcon();
+ auto icon_mem = SaveMemory::GetIcon(slot_id);
size_t total = std::min(icon->bufSize, icon_mem.size());
std::memcpy(icon->buf, icon_mem.data(), total);
icon->dataSize = total;
@@ -1494,36 +1510,37 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
- if (!SaveMemory::IsSaveMemoryInitialized()) {
+ u32 slot_id = 0;
+ u32 data_num = 1;
+ if (g_fw_ver > ElfInfo::FW_50) {
+ slot_id = setParam->slotId;
+ if (setParam->dataNum > 1) {
+ data_num = setParam->dataNum;
+ }
+ }
+ if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY;
}
- if (SaveMemory::IsSaving()) {
- int count = 0;
- while (++count < 100 && SaveMemory::IsSaving()) { // try for more 10 seconds
- std::this_thread::sleep_for(chrono::milliseconds(100));
- }
- if (SaveMemory::IsSaving()) {
- LOG_TRACE(Lib_SaveData, "called while saving");
- return Error::BUSY_FOR_SAVING;
- }
- }
+
LOG_DEBUG(Lib_SaveData, "called");
auto data = setParam->data;
if (data != nullptr) {
- SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset);
+ for (int i = 0; i < data_num; i++) {
+ SaveMemory::WriteMemory(slot_id, data[i].buf, data[i].bufSize, data[i].offset);
+ }
}
auto param = setParam->param;
if (param != nullptr) {
- param->ToSFO(SaveMemory::GetParamSFO());
- SaveMemory::SaveSFO();
- }
- auto icon = setParam->icon;
- if (icon != nullptr) {
- SaveMemory::WriteIcon(icon->buf, icon->bufSize);
+ param->ToSFO(SaveMemory::GetParamSFO(slot_id));
+ SaveMemory::SaveSFO(slot_id);
+ }
+
+ auto icon = setParam->icon;
+ if (icon != nullptr) {
+ SaveMemory::SetIcon(slot_id, icon->buf, icon->bufSize);
}
- SaveMemory::TriggerSaveWithoutEvent();
return Error::OK;
}
@@ -1563,9 +1580,12 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
}
LOG_DEBUG(Lib_SaveData, "called");
- SaveMemory::SetDirectories(setupParam->userId, g_game_serial);
+ u32 slot_id = 0;
+ if (g_fw_ver > ElfInfo::FW_50) {
+ slot_id = setupParam->slotId;
+ }
- const auto& save_path = SaveMemory::GetSavePath();
+ const auto& save_path = SaveMemory::GetSavePath(setupParam->userId, slot_id, g_game_serial);
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetSavePath() == save_path) {
return Error::BUSY;
@@ -1573,21 +1593,21 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
}
try {
- size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize);
+ size_t existed_size =
+ SaveMemory::SetupSaveMemory(setupParam->userId, slot_id, g_game_serial);
if (existed_size == 0) { // Just created
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
- auto& sfo = SaveMemory::GetParamSFO();
+ auto& sfo = SaveMemory::GetParamSFO(slot_id);
setupParam->initParam->ToSFO(sfo);
}
- SaveMemory::SaveSFO();
+ SaveMemory::SaveSFO(slot_id);
auto init_icon = setupParam->initIcon;
if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
- SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize);
+ SaveMemory::SetIcon(slot_id, init_icon->buf, init_icon->bufSize);
} else {
- SaveMemory::SetIcon(nullptr, 0);
+ SaveMemory::SetIcon(slot_id);
}
- SaveMemory::TriggerSaveWithoutEvent();
}
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size;
@@ -1631,15 +1651,23 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
- if (!SaveMemory::IsSaveMemoryInitialized()) {
+
+ u32 slot_id = 0;
+ if (g_fw_ver > ElfInfo::FW_50) {
+ slot_id = syncParam->slotId;
+ }
+
+ if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY;
}
LOG_DEBUG(Lib_SaveData, "called");
- bool ok = SaveMemory::TriggerSave();
- if (!ok) {
- return Error::BUSY_FOR_SAVING;
- }
+
+ SaveMemory::PersistMemory(slot_id);
+ const auto& save_path = SaveMemory::GetSaveDir(slot_id);
+ Backup::NewRequest(syncParam->userId, g_game_serial, save_path,
+ OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC);
+
return Error::OK;
}
diff --git a/src/emulator.cpp b/src/emulator.cpp
index 97215c06f..ba8d8917c 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -33,6 +33,7 @@
#include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np_trophy/np_trophy.h"
#include "core/libraries/rtc/rtc.h"
+#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h"
#include "core/memory.h"
#include "emulator.h"
@@ -56,6 +57,7 @@ Emulator::Emulator() {
LOG_INFO(Loader, "Revision {}", Common::g_scm_rev);
LOG_INFO(Loader, "Branch {}", Common::g_scm_branch);
LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
+ LOG_INFO(Loader, "Remote {}", Common::g_scm_remote_url);
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
@@ -66,9 +68,9 @@ Emulator::Emulator() {
LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled());
LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled());
LOG_INFO(Config, "Vulkan vkValidationGpu: {}", Config::vkValidationGpuEnabled());
- LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::vkCrashDiagnosticEnabled());
- LOG_INFO(Config, "Vulkan hostMarkers: {}", Config::vkHostMarkersEnabled());
- LOG_INFO(Config, "Vulkan guestMarkers: {}", Config::vkGuestMarkersEnabled());
+ LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::getVkCrashDiagnosticEnabled());
+ LOG_INFO(Config, "Vulkan hostMarkers: {}", Config::getVkHostMarkersEnabled());
+ LOG_INFO(Config, "Vulkan guestMarkers: {}", Config::getVkGuestMarkersEnabled());
LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled());
// Create stdin/stdout/stderr
@@ -198,8 +200,16 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector(
Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title);
@@ -271,7 +281,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vectorNavWindow != nullptr &&
- io.Ctx->NavWindow->ID != dock_id;
+ (io.Ctx->NavWindow->Flags & ImGuiWindowFlags_NoNav) == 0;
}
case SDL_EVENT_TEXT_INPUT:
case SDL_EVENT_KEY_DOWN: {
@@ -208,7 +208,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
return;
}
- if (Config::vkHostMarkersEnabled()) {
+ if (Config::getVkHostMarkersEnabled()) {
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
.pLabelName = "ImGui Render",
});
@@ -233,7 +233,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
cmdbuf.beginRendering(render_info);
Vulkan::RenderDrawData(*draw_data, cmdbuf);
cmdbuf.endRendering();
- if (Config::vkHostMarkersEnabled()) {
+ if (Config::getVkHostMarkersEnabled()) {
cmdbuf.endDebugUtilsLabelEXT();
}
}
diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp
index d7516a3a5..e217cd130 100644
--- a/src/imgui/renderer/texture_manager.cpp
+++ b/src/imgui/renderer/texture_manager.cpp
@@ -152,7 +152,7 @@ void WorkerLoop() {
g_job_list.pop_front();
g_job_list_mtx.unlock();
- if (Config::vkCrashDiagnosticEnabled()) {
+ if (Config::getVkCrashDiagnosticEnabled()) {
// FIXME: Crash diagnostic hangs when building the command buffer here
continue;
}
diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp
new file mode 100644
index 000000000..5394e4818
--- /dev/null
+++ b/src/input/input_handler.cpp
@@ -0,0 +1,673 @@
+// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "input_handler.h"
+
+#include
+#include
+#include
+#include