diff options
28 files changed, 901 insertions, 257 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 707d514c3..4bf55d664 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -35,6 +35,8 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) | |||
| 35 | 35 | ||
| 36 | option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON) | 36 | option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON) |
| 37 | 37 | ||
| 38 | option(YUZU_TESTS "Compile tests" ON) | ||
| 39 | |||
| 38 | # Default to a Release build | 40 | # Default to a Release build |
| 39 | get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) | 41 | get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) |
| 40 | if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) | 42 | if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) |
| @@ -168,8 +170,7 @@ macro(yuzu_find_packages) | |||
| 168 | # Capitalization matters here. We need the naming to match the generated paths from Conan | 170 | # Capitalization matters here. We need the naming to match the generated paths from Conan |
| 169 | set(REQUIRED_LIBS | 171 | set(REQUIRED_LIBS |
| 170 | # Cmake Pkg Prefix Version Conan Pkg | 172 | # Cmake Pkg Prefix Version Conan Pkg |
| 171 | "Catch2 2.13.7 catch2/2.13.7" | 173 | "fmt 8.0.1 fmt/8.1.1" |
| 172 | "fmt 8.0.1 fmt/8.0.1" | ||
| 173 | "lz4 1.8 lz4/1.9.2" | 174 | "lz4 1.8 lz4/1.9.2" |
| 174 | "nlohmann_json 3.8 nlohmann_json/3.8.0" | 175 | "nlohmann_json 3.8 nlohmann_json/3.8.0" |
| 175 | "ZLIB 1.2 zlib/1.2.11" | 176 | "ZLIB 1.2 zlib/1.2.11" |
| @@ -177,6 +178,11 @@ macro(yuzu_find_packages) | |||
| 177 | # can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068 | 178 | # can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068 |
| 178 | #"opus 1.3 opus/1.3.1" | 179 | #"opus 1.3 opus/1.3.1" |
| 179 | ) | 180 | ) |
| 181 | if (YUZU_TESTS) | ||
| 182 | list(APPEND REQUIRED_LIBS | ||
| 183 | "Catch2 2.13.7 catch2/2.13.7" | ||
| 184 | ) | ||
| 185 | endif() | ||
| 180 | 186 | ||
| 181 | foreach(PACKAGE ${REQUIRED_LIBS}) | 187 | foreach(PACKAGE ${REQUIRED_LIBS}) |
| 182 | string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE}) | 188 | string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE}) |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 158113516..82e8ef18c 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -13,10 +13,6 @@ if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) | |||
| 13 | target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES) | 13 | target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES) |
| 14 | endif() | 14 | endif() |
| 15 | 15 | ||
| 16 | # Catch | ||
| 17 | add_library(catch-single-include INTERFACE) | ||
| 18 | target_include_directories(catch-single-include INTERFACE catch/single_include) | ||
| 19 | |||
| 20 | # Dynarmic | 16 | # Dynarmic |
| 21 | if (ARCHITECTURE_x86_64) | 17 | if (ARCHITECTURE_x86_64) |
| 22 | set(DYNARMIC_TESTS OFF) | 18 | set(DYNARMIC_TESTS OFF) |
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 19d16147d..1bdf70b76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -151,7 +151,10 @@ add_subdirectory(audio_core) | |||
| 151 | add_subdirectory(video_core) | 151 | add_subdirectory(video_core) |
| 152 | add_subdirectory(input_common) | 152 | add_subdirectory(input_common) |
| 153 | add_subdirectory(shader_recompiler) | 153 | add_subdirectory(shader_recompiler) |
| 154 | add_subdirectory(tests) | 154 | |
| 155 | if (YUZU_TESTS) | ||
| 156 | add_subdirectory(tests) | ||
| 157 | endif() | ||
| 155 | 158 | ||
| 156 | if (ENABLE_SDL2) | 159 | if (ENABLE_SDL2) |
| 157 | add_subdirectory(yuzu_cmd) | 160 | add_subdirectory(yuzu_cmd) |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 790193b00..adf70eb8b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -85,6 +85,7 @@ add_library(common STATIC | |||
| 85 | logging/backend.h | 85 | logging/backend.h |
| 86 | logging/filter.cpp | 86 | logging/filter.cpp |
| 87 | logging/filter.h | 87 | logging/filter.h |
| 88 | logging/formatter.h | ||
| 88 | logging/log.h | 89 | logging/log.h |
| 89 | logging/log_entry.h | 90 | logging/log_entry.h |
| 90 | logging/text_formatter.cpp | 91 | logging/text_formatter.cpp |
diff --git a/src/common/logging/formatter.h b/src/common/logging/formatter.h new file mode 100644 index 000000000..552cde75a --- /dev/null +++ b/src/common/logging/formatter.h | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | // Copyright 2022 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <type_traits> | ||
| 8 | |||
| 9 | #include <fmt/format.h> | ||
| 10 | |||
| 11 | // adapted from https://github.com/fmtlib/fmt/issues/2704 | ||
| 12 | // a generic formatter for enum classes | ||
| 13 | #if FMT_VERSION >= 80100 | ||
| 14 | template <typename T> | ||
| 15 | struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>> | ||
| 16 | : formatter<std::underlying_type_t<T>> { | ||
| 17 | template <typename FormatContext> | ||
| 18 | auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { | ||
| 19 | return fmt::formatter<std::underlying_type_t<T>>::format( | ||
| 20 | static_cast<std::underlying_type_t<T>>(value), ctx); | ||
| 21 | } | ||
| 22 | }; | ||
| 23 | #endif | ||
diff --git a/src/common/logging/log.h b/src/common/logging/log.h index c186d55ef..0c80d01ee 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h | |||
| @@ -7,8 +7,9 @@ | |||
| 7 | #include <algorithm> | 7 | #include <algorithm> |
| 8 | #include <string_view> | 8 | #include <string_view> |
| 9 | 9 | ||
| 10 | #include <fmt/core.h> | 10 | #include <fmt/format.h> |
| 11 | 11 | ||
| 12 | #include "common/logging/formatter.h" | ||
| 12 | #include "common/logging/types.h" | 13 | #include "common/logging/types.h" |
| 13 | 14 | ||
| 14 | namespace Common::Log { | 15 | namespace Common::Log { |
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 52a56ef1a..13edb7332 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -351,6 +351,19 @@ void EmulatedController::DisableConfiguration() { | |||
| 351 | } | 351 | } |
| 352 | } | 352 | } |
| 353 | 353 | ||
| 354 | void EmulatedController::EnableSystemButtons() { | ||
| 355 | system_buttons_enabled = true; | ||
| 356 | } | ||
| 357 | |||
| 358 | void EmulatedController::DisableSystemButtons() { | ||
| 359 | system_buttons_enabled = false; | ||
| 360 | } | ||
| 361 | |||
| 362 | void EmulatedController::ResetSystemButtons() { | ||
| 363 | controller.home_button_state.home.Assign(false); | ||
| 364 | controller.capture_button_state.capture.Assign(false); | ||
| 365 | } | ||
| 366 | |||
| 354 | bool EmulatedController::IsConfiguring() const { | 367 | bool EmulatedController::IsConfiguring() const { |
| 355 | return is_configuring; | 368 | return is_configuring; |
| 356 | } | 369 | } |
| @@ -600,7 +613,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback | |||
| 600 | controller.npad_button_state.right_sr.Assign(current_status.value); | 613 | controller.npad_button_state.right_sr.Assign(current_status.value); |
| 601 | break; | 614 | break; |
| 602 | case Settings::NativeButton::Home: | 615 | case Settings::NativeButton::Home: |
| 616 | if (!system_buttons_enabled) { | ||
| 617 | break; | ||
| 618 | } | ||
| 619 | controller.home_button_state.home.Assign(current_status.value); | ||
| 620 | break; | ||
| 603 | case Settings::NativeButton::Screenshot: | 621 | case Settings::NativeButton::Screenshot: |
| 622 | if (!system_buttons_enabled) { | ||
| 623 | break; | ||
| 624 | } | ||
| 625 | controller.capture_button_state.capture.Assign(current_status.value); | ||
| 604 | break; | 626 | break; |
| 605 | } | 627 | } |
| 606 | } | 628 | } |
| @@ -1081,6 +1103,20 @@ BatteryValues EmulatedController::GetBatteryValues() const { | |||
| 1081 | return controller.battery_values; | 1103 | return controller.battery_values; |
| 1082 | } | 1104 | } |
| 1083 | 1105 | ||
| 1106 | HomeButtonState EmulatedController::GetHomeButtons() const { | ||
| 1107 | if (is_configuring) { | ||
| 1108 | return {}; | ||
| 1109 | } | ||
| 1110 | return controller.home_button_state; | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | CaptureButtonState EmulatedController::GetCaptureButtons() const { | ||
| 1114 | if (is_configuring) { | ||
| 1115 | return {}; | ||
| 1116 | } | ||
| 1117 | return controller.capture_button_state; | ||
| 1118 | } | ||
| 1119 | |||
| 1084 | NpadButtonState EmulatedController::GetNpadButtons() const { | 1120 | NpadButtonState EmulatedController::GetNpadButtons() const { |
| 1085 | if (is_configuring) { | 1121 | if (is_configuring) { |
| 1086 | return {}; | 1122 | return {}; |
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index c0994ab4d..a63a83cce 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h | |||
| @@ -101,6 +101,8 @@ struct ControllerStatus { | |||
| 101 | VibrationValues vibration_values{}; | 101 | VibrationValues vibration_values{}; |
| 102 | 102 | ||
| 103 | // Data for HID serices | 103 | // Data for HID serices |
| 104 | HomeButtonState home_button_state{}; | ||
| 105 | CaptureButtonState capture_button_state{}; | ||
| 104 | NpadButtonState npad_button_state{}; | 106 | NpadButtonState npad_button_state{}; |
| 105 | DebugPadButton debug_pad_button_state{}; | 107 | DebugPadButton debug_pad_button_state{}; |
| 106 | AnalogSticks analog_stick_state{}; | 108 | AnalogSticks analog_stick_state{}; |
| @@ -198,6 +200,15 @@ public: | |||
| 198 | /// Returns the emulated controller into normal mode, allowing the modification of the HID state | 200 | /// Returns the emulated controller into normal mode, allowing the modification of the HID state |
| 199 | void DisableConfiguration(); | 201 | void DisableConfiguration(); |
| 200 | 202 | ||
| 203 | /// Enables Home and Screenshot buttons | ||
| 204 | void EnableSystemButtons(); | ||
| 205 | |||
| 206 | /// Disables Home and Screenshot buttons | ||
| 207 | void DisableSystemButtons(); | ||
| 208 | |||
| 209 | /// Sets Home and Screenshot buttons to false | ||
| 210 | void ResetSystemButtons(); | ||
| 211 | |||
| 201 | /// Returns true if the emulated controller is in configuring mode | 212 | /// Returns true if the emulated controller is in configuring mode |
| 202 | bool IsConfiguring() const; | 213 | bool IsConfiguring() const; |
| 203 | 214 | ||
| @@ -261,7 +272,13 @@ public: | |||
| 261 | /// Returns the latest battery status from the controller with parameters | 272 | /// Returns the latest battery status from the controller with parameters |
| 262 | BatteryValues GetBatteryValues() const; | 273 | BatteryValues GetBatteryValues() const; |
| 263 | 274 | ||
| 264 | /// Returns the latest status of button input for the npad service | 275 | /// Returns the latest status of button input for the hid::HomeButton service |
| 276 | HomeButtonState GetHomeButtons() const; | ||
| 277 | |||
| 278 | /// Returns the latest status of button input for the hid::CaptureButton service | ||
| 279 | CaptureButtonState GetCaptureButtons() const; | ||
| 280 | |||
| 281 | /// Returns the latest status of button input for the hid::Npad service | ||
| 265 | NpadButtonState GetNpadButtons() const; | 282 | NpadButtonState GetNpadButtons() const; |
| 266 | 283 | ||
| 267 | /// Returns the latest status of button input for the debug pad service | 284 | /// Returns the latest status of button input for the debug pad service |
| @@ -383,6 +400,7 @@ private: | |||
| 383 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; | 400 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; |
| 384 | bool is_connected{false}; | 401 | bool is_connected{false}; |
| 385 | bool is_configuring{false}; | 402 | bool is_configuring{false}; |
| 403 | bool system_buttons_enabled{true}; | ||
| 386 | f32 motion_sensitivity{0.01f}; | 404 | f32 motion_sensitivity{0.01f}; |
| 387 | bool force_update_motion{false}; | 405 | bool force_update_motion{false}; |
| 388 | 406 | ||
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 4eca68533..778b328b9 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h | |||
| @@ -378,6 +378,26 @@ struct LedPattern { | |||
| 378 | }; | 378 | }; |
| 379 | }; | 379 | }; |
| 380 | 380 | ||
| 381 | struct HomeButtonState { | ||
| 382 | union { | ||
| 383 | u64 raw{}; | ||
| 384 | |||
| 385 | // Buttons | ||
| 386 | BitField<0, 1, u64> home; | ||
| 387 | }; | ||
| 388 | }; | ||
| 389 | static_assert(sizeof(HomeButtonState) == 0x8, "HomeButtonState has incorrect size."); | ||
| 390 | |||
| 391 | struct CaptureButtonState { | ||
| 392 | union { | ||
| 393 | u64 raw{}; | ||
| 394 | |||
| 395 | // Buttons | ||
| 396 | BitField<0, 1, u64> capture; | ||
| 397 | }; | ||
| 398 | }; | ||
| 399 | static_assert(sizeof(CaptureButtonState) == 0x8, "CaptureButtonState has incorrect size."); | ||
| 400 | |||
| 381 | struct NpadButtonState { | 401 | struct NpadButtonState { |
| 382 | union { | 402 | union { |
| 383 | NpadButton raw{}; | 403 | NpadButton raw{}; |
diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h index 9e51c33ce..dcca47f1b 100644 --- a/src/core/hle/kernel/k_memory_block.h +++ b/src/core/hle/kernel/k_memory_block.h | |||
| @@ -70,12 +70,12 @@ enum class KMemoryState : u32 { | |||
| 70 | ThreadLocal = | 70 | ThreadLocal = |
| 71 | static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted, | 71 | static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted, |
| 72 | 72 | ||
| 73 | Transferred = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc | | 73 | Transfered = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc | |
| 74 | FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | | 74 | FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | |
| 75 | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, | 75 | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, |
| 76 | 76 | ||
| 77 | SharedTransferred = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc | | 77 | SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc | |
| 78 | FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, | 78 | FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, |
| 79 | 79 | ||
| 80 | SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped | | 80 | SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped | |
| 81 | FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, | 81 | FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, |
| @@ -93,6 +93,8 @@ enum class KMemoryState : u32 { | |||
| 93 | GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped | | 93 | GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped | |
| 94 | FlagReferenceCounted | FlagCanDebug, | 94 | FlagReferenceCounted | FlagCanDebug, |
| 95 | CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted, | 95 | CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted, |
| 96 | |||
| 97 | Coverage = static_cast<u32>(Svc::MemoryState::Coverage) | FlagMapped, | ||
| 96 | }; | 98 | }; |
| 97 | DECLARE_ENUM_FLAG_OPERATORS(KMemoryState); | 99 | DECLARE_ENUM_FLAG_OPERATORS(KMemoryState); |
| 98 | 100 | ||
| @@ -108,8 +110,8 @@ static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x03FFBD09); | |||
| 108 | static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x005C3C0A); | 110 | static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x005C3C0A); |
| 109 | static_assert(static_cast<u32>(KMemoryState::Stack) == 0x005C3C0B); | 111 | static_assert(static_cast<u32>(KMemoryState::Stack) == 0x005C3C0B); |
| 110 | static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0040200C); | 112 | static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0040200C); |
| 111 | static_assert(static_cast<u32>(KMemoryState::Transferred) == 0x015C3C0D); | 113 | static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x015C3C0D); |
| 112 | static_assert(static_cast<u32>(KMemoryState::SharedTransferred) == 0x005C380E); | 114 | static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x005C380E); |
| 113 | static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0040380F); | 115 | static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0040380F); |
| 114 | static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010); | 116 | static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010); |
| 115 | static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x005C3811); | 117 | static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x005C3811); |
| @@ -117,6 +119,7 @@ static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x004C2812); | |||
| 117 | static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00002013); | 119 | static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00002013); |
| 118 | static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x00402214); | 120 | static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x00402214); |
| 119 | static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x00402015); | 121 | static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x00402015); |
| 122 | static_assert(static_cast<u32>(KMemoryState::Coverage) == 0x00002016); | ||
| 120 | 123 | ||
| 121 | enum class KMemoryPermission : u8 { | 124 | enum class KMemoryPermission : u8 { |
| 122 | None = 0, | 125 | None = 0, |
| @@ -155,7 +158,13 @@ enum class KMemoryPermission : u8 { | |||
| 155 | DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission); | 158 | DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission); |
| 156 | 159 | ||
| 157 | constexpr KMemoryPermission ConvertToKMemoryPermission(Svc::MemoryPermission perm) { | 160 | constexpr KMemoryPermission ConvertToKMemoryPermission(Svc::MemoryPermission perm) { |
| 158 | return static_cast<KMemoryPermission>(perm); | 161 | return static_cast<KMemoryPermission>( |
| 162 | (static_cast<KMemoryPermission>(perm) & KMemoryPermission::UserMask) | | ||
| 163 | KMemoryPermission::KernelRead | | ||
| 164 | ((static_cast<KMemoryPermission>(perm) & KMemoryPermission::UserWrite) | ||
| 165 | << KMemoryPermission::KernelShift) | | ||
| 166 | (perm == Svc::MemoryPermission::None ? KMemoryPermission::NotMapped | ||
| 167 | : KMemoryPermission::None)); | ||
| 159 | } | 168 | } |
| 160 | 169 | ||
| 161 | enum class KMemoryAttribute : u8 { | 170 | enum class KMemoryAttribute : u8 { |
| @@ -169,6 +178,8 @@ enum class KMemoryAttribute : u8 { | |||
| 169 | DeviceShared = static_cast<u8>(Svc::MemoryAttribute::DeviceShared), | 178 | DeviceShared = static_cast<u8>(Svc::MemoryAttribute::DeviceShared), |
| 170 | Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached), | 179 | Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached), |
| 171 | 180 | ||
| 181 | SetMask = Uncached, | ||
| 182 | |||
| 172 | IpcAndDeviceMapped = IpcLocked | DeviceShared, | 183 | IpcAndDeviceMapped = IpcLocked | DeviceShared, |
| 173 | LockedAndIpcLocked = Locked | IpcLocked, | 184 | LockedAndIpcLocked = Locked | IpcLocked, |
| 174 | DeviceSharedAndUncached = DeviceShared | Uncached | 185 | DeviceSharedAndUncached = DeviceShared | Uncached |
| @@ -215,6 +226,15 @@ struct KMemoryInfo { | |||
| 215 | constexpr VAddr GetLastAddress() const { | 226 | constexpr VAddr GetLastAddress() const { |
| 216 | return GetEndAddress() - 1; | 227 | return GetEndAddress() - 1; |
| 217 | } | 228 | } |
| 229 | constexpr KMemoryState GetState() const { | ||
| 230 | return state; | ||
| 231 | } | ||
| 232 | constexpr KMemoryAttribute GetAttribute() const { | ||
| 233 | return attribute; | ||
| 234 | } | ||
| 235 | constexpr KMemoryPermission GetPermission() const { | ||
| 236 | return perm; | ||
| 237 | } | ||
| 218 | }; | 238 | }; |
| 219 | 239 | ||
| 220 | class KMemoryBlock final { | 240 | class KMemoryBlock final { |
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 4da509224..6077985b5 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp | |||
| @@ -305,8 +305,8 @@ ResultCode KPageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std: | |||
| 305 | 305 | ||
| 306 | KMemoryState state{}; | 306 | KMemoryState state{}; |
| 307 | KMemoryPermission perm{}; | 307 | KMemoryPermission perm{}; |
| 308 | CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, src_addr, size, KMemoryState::All, | 308 | CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, nullptr, src_addr, size, |
| 309 | KMemoryState::Normal, KMemoryPermission::All, | 309 | KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All, |
| 310 | KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, | 310 | KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, |
| 311 | KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); | 311 | KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); |
| 312 | 312 | ||
| @@ -344,14 +344,14 @@ ResultCode KPageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, st | |||
| 344 | 344 | ||
| 345 | const std::size_t num_pages{size / PageSize}; | 345 | const std::size_t num_pages{size / PageSize}; |
| 346 | 346 | ||
| 347 | CASCADE_CODE(CheckMemoryState(nullptr, nullptr, nullptr, src_addr, size, KMemoryState::All, | 347 | CASCADE_CODE(CheckMemoryState(nullptr, nullptr, nullptr, nullptr, src_addr, size, |
| 348 | KMemoryState::Normal, KMemoryPermission::None, | 348 | KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None, |
| 349 | KMemoryPermission::None, KMemoryAttribute::Mask, | 349 | KMemoryPermission::None, KMemoryAttribute::Mask, |
| 350 | KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); | 350 | KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); |
| 351 | 351 | ||
| 352 | KMemoryState state{}; | 352 | KMemoryState state{}; |
| 353 | CASCADE_CODE(CheckMemoryState( | 353 | CASCADE_CODE(CheckMemoryState( |
| 354 | &state, nullptr, nullptr, dst_addr, PageSize, KMemoryState::FlagCanCodeAlias, | 354 | &state, nullptr, nullptr, nullptr, dst_addr, PageSize, KMemoryState::FlagCanCodeAlias, |
| 355 | KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None, | 355 | KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None, |
| 356 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); | 356 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); |
| 357 | CASCADE_CODE(CheckMemoryState(dst_addr, size, KMemoryState::All, state, KMemoryPermission::None, | 357 | CASCADE_CODE(CheckMemoryState(dst_addr, size, KMemoryState::All, state, KMemoryPermission::None, |
| @@ -553,7 +553,7 @@ ResultCode KPageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) { | |||
| 553 | 553 | ||
| 554 | KMemoryState src_state{}; | 554 | KMemoryState src_state{}; |
| 555 | CASCADE_CODE(CheckMemoryState( | 555 | CASCADE_CODE(CheckMemoryState( |
| 556 | &src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, | 556 | &src_state, nullptr, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, |
| 557 | KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::ReadAndWrite, | 557 | KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::ReadAndWrite, |
| 558 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); | 558 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); |
| 559 | 559 | ||
| @@ -592,13 +592,13 @@ ResultCode KPageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) { | |||
| 592 | 592 | ||
| 593 | KMemoryState src_state{}; | 593 | KMemoryState src_state{}; |
| 594 | CASCADE_CODE(CheckMemoryState( | 594 | CASCADE_CODE(CheckMemoryState( |
| 595 | &src_state, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, | 595 | &src_state, nullptr, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, |
| 596 | KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::None, | 596 | KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::None, |
| 597 | KMemoryAttribute::Mask, KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); | 597 | KMemoryAttribute::Mask, KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); |
| 598 | 598 | ||
| 599 | KMemoryPermission dst_perm{}; | 599 | KMemoryPermission dst_perm{}; |
| 600 | CASCADE_CODE(CheckMemoryState(nullptr, &dst_perm, nullptr, dst_addr, size, KMemoryState::All, | 600 | CASCADE_CODE(CheckMemoryState(nullptr, &dst_perm, nullptr, nullptr, dst_addr, size, |
| 601 | KMemoryState::Stack, KMemoryPermission::None, | 601 | KMemoryState::All, KMemoryState::Stack, KMemoryPermission::None, |
| 602 | KMemoryPermission::None, KMemoryAttribute::Mask, | 602 | KMemoryPermission::None, KMemoryAttribute::Mask, |
| 603 | KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); | 603 | KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); |
| 604 | 604 | ||
| @@ -721,7 +721,7 @@ ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, | |||
| 721 | KMemoryPermission prev_perm{}; | 721 | KMemoryPermission prev_perm{}; |
| 722 | 722 | ||
| 723 | CASCADE_CODE(CheckMemoryState( | 723 | CASCADE_CODE(CheckMemoryState( |
| 724 | &prev_state, &prev_perm, nullptr, addr, size, KMemoryState::FlagCode, | 724 | &prev_state, &prev_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCode, |
| 725 | KMemoryState::FlagCode, KMemoryPermission::None, KMemoryPermission::None, | 725 | KMemoryState::FlagCode, KMemoryPermission::None, KMemoryPermission::None, |
| 726 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); | 726 | KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); |
| 727 | 727 | ||
| @@ -782,7 +782,7 @@ ResultCode KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemo | |||
| 782 | KMemoryAttribute attribute{}; | 782 | KMemoryAttribute attribute{}; |
| 783 | 783 | ||
| 784 | CASCADE_CODE(CheckMemoryState( | 784 | CASCADE_CODE(CheckMemoryState( |
| 785 | &state, nullptr, &attribute, addr, size, | 785 | &state, nullptr, &attribute, nullptr, addr, size, |
| 786 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, | 786 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, |
| 787 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryPermission::All, | 787 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryPermission::All, |
| 788 | KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryAttribute::None, | 788 | KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryAttribute::None, |
| @@ -799,7 +799,7 @@ ResultCode KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) { | |||
| 799 | KMemoryState state{}; | 799 | KMemoryState state{}; |
| 800 | 800 | ||
| 801 | CASCADE_CODE( | 801 | CASCADE_CODE( |
| 802 | CheckMemoryState(&state, nullptr, nullptr, addr, size, | 802 | CheckMemoryState(&state, nullptr, nullptr, nullptr, addr, size, |
| 803 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, | 803 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, |
| 804 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, | 804 | KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, |
| 805 | KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::Mask, | 805 | KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::Mask, |
| @@ -820,7 +820,7 @@ ResultCode KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, | |||
| 820 | KMemoryState old_state; | 820 | KMemoryState old_state; |
| 821 | KMemoryPermission old_perm; | 821 | KMemoryPermission old_perm; |
| 822 | R_TRY(this->CheckMemoryState( | 822 | R_TRY(this->CheckMemoryState( |
| 823 | std::addressof(old_state), std::addressof(old_perm), nullptr, addr, size, | 823 | std::addressof(old_state), std::addressof(old_perm), nullptr, nullptr, addr, size, |
| 824 | KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect, KMemoryPermission::None, | 824 | KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect, KMemoryPermission::None, |
| 825 | KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); | 825 | KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); |
| 826 | 826 | ||
| @@ -837,24 +837,36 @@ ResultCode KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, | |||
| 837 | return ResultSuccess; | 837 | return ResultSuccess; |
| 838 | } | 838 | } |
| 839 | 839 | ||
| 840 | ResultCode KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, KMemoryAttribute mask, | 840 | ResultCode KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr) { |
| 841 | KMemoryAttribute value) { | 841 | const size_t num_pages = size / PageSize; |
| 842 | std::lock_guard lock{page_table_lock}; | 842 | ASSERT((static_cast<KMemoryAttribute>(mask) | KMemoryAttribute::SetMask) == |
| 843 | KMemoryAttribute::SetMask); | ||
| 843 | 844 | ||
| 844 | KMemoryState state{}; | 845 | // Lock the table. |
| 845 | KMemoryPermission perm{}; | 846 | std::lock_guard lock{page_table_lock}; |
| 846 | KMemoryAttribute attribute{}; | ||
| 847 | 847 | ||
| 848 | CASCADE_CODE(CheckMemoryState( | 848 | // Verify we can change the memory attribute. |
| 849 | &state, &perm, &attribute, addr, size, KMemoryState::FlagCanChangeAttribute, | 849 | KMemoryState old_state; |
| 850 | KMemoryPermission old_perm; | ||
| 851 | KMemoryAttribute old_attr; | ||
| 852 | size_t num_allocator_blocks; | ||
| 853 | constexpr auto AttributeTestMask = | ||
| 854 | ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared); | ||
| 855 | R_TRY(this->CheckMemoryState( | ||
| 856 | std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), | ||
| 857 | std::addressof(num_allocator_blocks), addr, size, KMemoryState::FlagCanChangeAttribute, | ||
| 850 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, | 858 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, |
| 851 | KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, | 859 | AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask)); |
| 852 | KMemoryAttribute::DeviceSharedAndUncached)); | 860 | |
| 861 | // Determine the new attribute. | ||
| 862 | const auto new_attr = ((old_attr & static_cast<KMemoryAttribute>(~mask)) | | ||
| 863 | static_cast<KMemoryAttribute>(attr & mask)); | ||
| 853 | 864 | ||
| 854 | attribute = attribute & ~mask; | 865 | // Perform operation. |
| 855 | attribute = attribute | (mask & value); | 866 | this->Operate(addr, num_pages, old_perm, OperationType::ChangePermissionsAndRefresh); |
| 856 | 867 | ||
| 857 | block_manager->Update(addr, size / PageSize, state, perm, attribute); | 868 | // Update the blocks. |
| 869 | block_manager->Update(addr, num_pages, old_state, old_perm, new_attr); | ||
| 858 | 870 | ||
| 859 | return ResultSuccess; | 871 | return ResultSuccess; |
| 860 | } | 872 | } |
| @@ -1019,7 +1031,7 @@ ResultCode KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) { | |||
| 1019 | 1031 | ||
| 1020 | KMemoryPermission perm{}; | 1032 | KMemoryPermission perm{}; |
| 1021 | if (const ResultCode result{CheckMemoryState( | 1033 | if (const ResultCode result{CheckMemoryState( |
| 1022 | nullptr, &perm, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, | 1034 | nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, |
| 1023 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, | 1035 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, |
| 1024 | KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, | 1036 | KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, |
| 1025 | KMemoryAttribute::DeviceSharedAndUncached)}; | 1037 | KMemoryAttribute::DeviceSharedAndUncached)}; |
| @@ -1042,7 +1054,7 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) | |||
| 1042 | 1054 | ||
| 1043 | KMemoryPermission perm{}; | 1055 | KMemoryPermission perm{}; |
| 1044 | if (const ResultCode result{CheckMemoryState( | 1056 | if (const ResultCode result{CheckMemoryState( |
| 1045 | nullptr, &perm, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, | 1057 | nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, |
| 1046 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, | 1058 | KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, |
| 1047 | KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, | 1059 | KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, |
| 1048 | KMemoryAttribute::DeviceSharedAndUncached)}; | 1060 | KMemoryAttribute::DeviceSharedAndUncached)}; |
| @@ -1068,7 +1080,7 @@ ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) { | |||
| 1068 | KMemoryPermission old_perm{}; | 1080 | KMemoryPermission old_perm{}; |
| 1069 | 1081 | ||
| 1070 | if (const ResultCode result{CheckMemoryState( | 1082 | if (const ResultCode result{CheckMemoryState( |
| 1071 | nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, | 1083 | nullptr, &old_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, |
| 1072 | KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, | 1084 | KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, |
| 1073 | KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)}; | 1085 | KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)}; |
| 1074 | result.IsError()) { | 1086 | result.IsError()) { |
| @@ -1095,7 +1107,7 @@ ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) { | |||
| 1095 | KMemoryPermission old_perm{}; | 1107 | KMemoryPermission old_perm{}; |
| 1096 | 1108 | ||
| 1097 | if (const ResultCode result{CheckMemoryState( | 1109 | if (const ResultCode result{CheckMemoryState( |
| 1098 | nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, | 1110 | nullptr, &old_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, |
| 1099 | KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None, | 1111 | KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None, |
| 1100 | KMemoryAttribute::All, KMemoryAttribute::Locked)}; | 1112 | KMemoryAttribute::All, KMemoryAttribute::Locked)}; |
| 1101 | result.IsError()) { | 1113 | result.IsError()) { |
| @@ -1225,18 +1237,19 @@ constexpr VAddr KPageTable::GetRegionAddress(KMemoryState state) const { | |||
| 1225 | return alias_region_start; | 1237 | return alias_region_start; |
| 1226 | case KMemoryState::Stack: | 1238 | case KMemoryState::Stack: |
| 1227 | return stack_region_start; | 1239 | return stack_region_start; |
| 1228 | case KMemoryState::Io: | ||
| 1229 | case KMemoryState::Static: | 1240 | case KMemoryState::Static: |
| 1230 | case KMemoryState::ThreadLocal: | 1241 | case KMemoryState::ThreadLocal: |
| 1231 | return kernel_map_region_start; | 1242 | return kernel_map_region_start; |
| 1243 | case KMemoryState::Io: | ||
| 1232 | case KMemoryState::Shared: | 1244 | case KMemoryState::Shared: |
| 1233 | case KMemoryState::AliasCode: | 1245 | case KMemoryState::AliasCode: |
| 1234 | case KMemoryState::AliasCodeData: | 1246 | case KMemoryState::AliasCodeData: |
| 1235 | case KMemoryState::Transferred: | 1247 | case KMemoryState::Transfered: |
| 1236 | case KMemoryState::SharedTransferred: | 1248 | case KMemoryState::SharedTransfered: |
| 1237 | case KMemoryState::SharedCode: | 1249 | case KMemoryState::SharedCode: |
| 1238 | case KMemoryState::GeneratedCode: | 1250 | case KMemoryState::GeneratedCode: |
| 1239 | case KMemoryState::CodeOut: | 1251 | case KMemoryState::CodeOut: |
| 1252 | case KMemoryState::Coverage: | ||
| 1240 | return alias_code_region_start; | 1253 | return alias_code_region_start; |
| 1241 | case KMemoryState::Code: | 1254 | case KMemoryState::Code: |
| 1242 | case KMemoryState::CodeData: | 1255 | case KMemoryState::CodeData: |
| @@ -1260,18 +1273,19 @@ constexpr std::size_t KPageTable::GetRegionSize(KMemoryState state) const { | |||
| 1260 | return alias_region_end - alias_region_start; | 1273 | return alias_region_end - alias_region_start; |
| 1261 | case KMemoryState::Stack: | 1274 | case KMemoryState::Stack: |
| 1262 | return stack_region_end - stack_region_start; | 1275 | return stack_region_end - stack_region_start; |
| 1263 | case KMemoryState::Io: | ||
| 1264 | case KMemoryState::Static: | 1276 | case KMemoryState::Static: |
| 1265 | case KMemoryState::ThreadLocal: | 1277 | case KMemoryState::ThreadLocal: |
| 1266 | return kernel_map_region_end - kernel_map_region_start; | 1278 | return kernel_map_region_end - kernel_map_region_start; |
| 1279 | case KMemoryState::Io: | ||
| 1267 | case KMemoryState::Shared: | 1280 | case KMemoryState::Shared: |
| 1268 | case KMemoryState::AliasCode: | 1281 | case KMemoryState::AliasCode: |
| 1269 | case KMemoryState::AliasCodeData: | 1282 | case KMemoryState::AliasCodeData: |
| 1270 | case KMemoryState::Transferred: | 1283 | case KMemoryState::Transfered: |
| 1271 | case KMemoryState::SharedTransferred: | 1284 | case KMemoryState::SharedTransfered: |
| 1272 | case KMemoryState::SharedCode: | 1285 | case KMemoryState::SharedCode: |
| 1273 | case KMemoryState::GeneratedCode: | 1286 | case KMemoryState::GeneratedCode: |
| 1274 | case KMemoryState::CodeOut: | 1287 | case KMemoryState::CodeOut: |
| 1288 | case KMemoryState::Coverage: | ||
| 1275 | return alias_code_region_end - alias_code_region_start; | 1289 | return alias_code_region_end - alias_code_region_start; |
| 1276 | case KMemoryState::Code: | 1290 | case KMemoryState::Code: |
| 1277 | case KMemoryState::CodeData: | 1291 | case KMemoryState::CodeData: |
| @@ -1283,15 +1297,18 @@ constexpr std::size_t KPageTable::GetRegionSize(KMemoryState state) const { | |||
| 1283 | } | 1297 | } |
| 1284 | 1298 | ||
| 1285 | bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) const { | 1299 | bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) const { |
| 1286 | const VAddr end{addr + size}; | 1300 | const VAddr end = addr + size; |
| 1287 | const VAddr last{end - 1}; | 1301 | const VAddr last = end - 1; |
| 1288 | const VAddr region_start{GetRegionAddress(state)}; | 1302 | |
| 1289 | const std::size_t region_size{GetRegionSize(state)}; | 1303 | const VAddr region_start = this->GetRegionAddress(state); |
| 1290 | const bool is_in_region{region_start <= addr && addr < end && | 1304 | const size_t region_size = this->GetRegionSize(state); |
| 1291 | last <= region_start + region_size - 1}; | 1305 | |
| 1292 | const bool is_in_heap{!(end <= heap_region_start || heap_region_end <= addr)}; | 1306 | const bool is_in_region = |
| 1293 | const bool is_in_alias{!(end <= alias_region_start || alias_region_end <= addr)}; | 1307 | region_start <= addr && addr < end && last <= region_start + region_size - 1; |
| 1294 | 1308 | const bool is_in_heap = !(end <= heap_region_start || heap_region_end <= addr || | |
| 1309 | heap_region_start == heap_region_end); | ||
| 1310 | const bool is_in_alias = !(end <= alias_region_start || alias_region_end <= addr || | ||
| 1311 | alias_region_start == alias_region_end); | ||
| 1295 | switch (state) { | 1312 | switch (state) { |
| 1296 | case KMemoryState::Free: | 1313 | case KMemoryState::Free: |
| 1297 | case KMemoryState::Kernel: | 1314 | case KMemoryState::Kernel: |
| @@ -1305,11 +1322,12 @@ bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) co | |||
| 1305 | case KMemoryState::AliasCodeData: | 1322 | case KMemoryState::AliasCodeData: |
| 1306 | case KMemoryState::Stack: | 1323 | case KMemoryState::Stack: |
| 1307 | case KMemoryState::ThreadLocal: | 1324 | case KMemoryState::ThreadLocal: |
| 1308 | case KMemoryState::Transferred: | 1325 | case KMemoryState::Transfered: |
| 1309 | case KMemoryState::SharedTransferred: | 1326 | case KMemoryState::SharedTransfered: |
| 1310 | case KMemoryState::SharedCode: | 1327 | case KMemoryState::SharedCode: |
| 1311 | case KMemoryState::GeneratedCode: | 1328 | case KMemoryState::GeneratedCode: |
| 1312 | case KMemoryState::CodeOut: | 1329 | case KMemoryState::CodeOut: |
| 1330 | case KMemoryState::Coverage: | ||
| 1313 | return is_in_region && !is_in_heap && !is_in_alias; | 1331 | return is_in_region && !is_in_heap && !is_in_alias; |
| 1314 | case KMemoryState::Normal: | 1332 | case KMemoryState::Normal: |
| 1315 | ASSERT(is_in_heap); | 1333 | ASSERT(is_in_heap); |
| @@ -1324,100 +1342,91 @@ bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) co | |||
| 1324 | } | 1342 | } |
| 1325 | } | 1343 | } |
| 1326 | 1344 | ||
| 1327 | constexpr ResultCode KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, | 1345 | ResultCode KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, |
| 1328 | KMemoryState state, KMemoryPermission perm_mask, | 1346 | KMemoryState state, KMemoryPermission perm_mask, |
| 1329 | KMemoryPermission perm, | 1347 | KMemoryPermission perm, KMemoryAttribute attr_mask, |
| 1330 | KMemoryAttribute attr_mask, | 1348 | KMemoryAttribute attr) const { |
| 1331 | KMemoryAttribute attr) const { | 1349 | // Validate the states match expectation. |
| 1332 | // Validate the states match expectation | 1350 | R_UNLESS((info.state & state_mask) == state, ResultInvalidCurrentMemory); |
| 1333 | if ((info.state & state_mask) != state) { | 1351 | R_UNLESS((info.perm & perm_mask) == perm, ResultInvalidCurrentMemory); |
| 1334 | return ResultInvalidCurrentMemory; | 1352 | R_UNLESS((info.attribute & attr_mask) == attr, ResultInvalidCurrentMemory); |
| 1335 | } | ||
| 1336 | if ((info.perm & perm_mask) != perm) { | ||
| 1337 | return ResultInvalidCurrentMemory; | ||
| 1338 | } | ||
| 1339 | if ((info.attribute & attr_mask) != attr) { | ||
| 1340 | return ResultInvalidCurrentMemory; | ||
| 1341 | } | ||
| 1342 | 1353 | ||
| 1343 | return ResultSuccess; | 1354 | return ResultSuccess; |
| 1344 | } | 1355 | } |
| 1345 | 1356 | ||
| 1346 | ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, | 1357 | ResultCode KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr, |
| 1347 | KMemoryAttribute* out_attr, VAddr addr, std::size_t size, | 1358 | std::size_t size, KMemoryState state_mask, |
| 1348 | KMemoryState state_mask, KMemoryState state, | 1359 | KMemoryState state, KMemoryPermission perm_mask, |
| 1349 | KMemoryPermission perm_mask, KMemoryPermission perm, | 1360 | KMemoryPermission perm, |
| 1350 | KMemoryAttribute attr_mask, KMemoryAttribute attr, | 1361 | KMemoryAttribute attr_mask, |
| 1351 | KMemoryAttribute ignore_attr) { | 1362 | KMemoryAttribute attr) const { |
| 1352 | std::lock_guard lock{page_table_lock}; | 1363 | ASSERT(this->IsLockedByCurrentThread()); |
| 1353 | 1364 | ||
| 1354 | // Get information about the first block | 1365 | // Get information about the first block. |
| 1355 | const VAddr last_addr{addr + size - 1}; | 1366 | const VAddr last_addr = addr + size - 1; |
| 1356 | KMemoryBlockManager::const_iterator it{block_manager->FindIterator(addr)}; | 1367 | KMemoryBlockManager::const_iterator it = block_manager->FindIterator(addr); |
| 1357 | KMemoryInfo info{it->GetMemoryInfo()}; | 1368 | KMemoryInfo info = it->GetMemoryInfo(); |
| 1358 | 1369 | ||
| 1359 | // Validate all blocks in the range have correct state | 1370 | // If the start address isn't aligned, we need a block. |
| 1360 | const KMemoryState first_state{info.state}; | 1371 | const size_t blocks_for_start_align = |
| 1361 | const KMemoryPermission first_perm{info.perm}; | 1372 | (Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0; |
| 1362 | const KMemoryAttribute first_attr{info.attribute}; | ||
| 1363 | 1373 | ||
| 1364 | while (true) { | 1374 | while (true) { |
| 1365 | // Validate the current block | 1375 | // Validate against the provided masks. |
| 1366 | if (!(info.state == first_state)) { | 1376 | R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr)); |
| 1367 | return ResultInvalidCurrentMemory; | ||
| 1368 | } | ||
| 1369 | if (!(info.perm == first_perm)) { | ||
| 1370 | return ResultInvalidCurrentMemory; | ||
| 1371 | } | ||
| 1372 | if (!((info.attribute | static_cast<KMemoryAttribute>(ignore_attr)) == | ||
| 1373 | (first_attr | static_cast<KMemoryAttribute>(ignore_attr)))) { | ||
| 1374 | return ResultInvalidCurrentMemory; | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | // Validate against the provided masks | ||
| 1378 | CASCADE_CODE(CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr)); | ||
| 1379 | 1377 | ||
| 1380 | // Break once we're done | 1378 | // Break once we're done. |
| 1381 | if (last_addr <= info.GetLastAddress()) { | 1379 | if (last_addr <= info.GetLastAddress()) { |
| 1382 | break; | 1380 | break; |
| 1383 | } | 1381 | } |
| 1384 | 1382 | ||
| 1385 | // Advance our iterator | 1383 | // Advance our iterator. |
| 1386 | it++; | 1384 | it++; |
| 1387 | ASSERT(it != block_manager->cend()); | 1385 | ASSERT(it != block_manager->cend()); |
| 1388 | info = it->GetMemoryInfo(); | 1386 | info = it->GetMemoryInfo(); |
| 1389 | } | 1387 | } |
| 1390 | 1388 | ||
| 1391 | // Write output state | 1389 | // If the end address isn't aligned, we need a block. |
| 1392 | if (out_state) { | 1390 | const size_t blocks_for_end_align = |
| 1393 | *out_state = first_state; | 1391 | (Common::AlignUp(addr + size, PageSize) != info.GetEndAddress()) ? 1 : 0; |
| 1394 | } | 1392 | |
| 1395 | if (out_perm) { | 1393 | if (out_blocks_needed != nullptr) { |
| 1396 | *out_perm = first_perm; | 1394 | *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; |
| 1397 | } | ||
| 1398 | if (out_attr) { | ||
| 1399 | *out_attr = first_attr & static_cast<KMemoryAttribute>(~ignore_attr); | ||
| 1400 | } | 1395 | } |
| 1401 | 1396 | ||
| 1402 | return ResultSuccess; | 1397 | return ResultSuccess; |
| 1403 | } | 1398 | } |
| 1404 | 1399 | ||
| 1405 | ResultCode KPageTable::CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size, | 1400 | ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, |
| 1406 | KMemoryState state_mask, KMemoryState state, | 1401 | KMemoryAttribute* out_attr, std::size_t* out_blocks_needed, |
| 1407 | KMemoryPermission perm_mask, KMemoryPermission perm, | 1402 | VAddr addr, std::size_t size, KMemoryState state_mask, |
| 1408 | KMemoryAttribute attr_mask, KMemoryAttribute attr) const { | 1403 | KMemoryState state, KMemoryPermission perm_mask, |
| 1404 | KMemoryPermission perm, KMemoryAttribute attr_mask, | ||
| 1405 | KMemoryAttribute attr, KMemoryAttribute ignore_attr) const { | ||
| 1406 | ASSERT(this->IsLockedByCurrentThread()); | ||
| 1407 | |||
| 1409 | // Get information about the first block. | 1408 | // Get information about the first block. |
| 1410 | const VAddr last_addr = addr + size - 1; | 1409 | const VAddr last_addr = addr + size - 1; |
| 1411 | KMemoryBlockManager::const_iterator it{block_manager->FindIterator(addr)}; | 1410 | KMemoryBlockManager::const_iterator it = block_manager->FindIterator(addr); |
| 1412 | KMemoryInfo info = it->GetMemoryInfo(); | 1411 | KMemoryInfo info = it->GetMemoryInfo(); |
| 1413 | 1412 | ||
| 1414 | // If the start address isn't aligned, we need a block. | 1413 | // If the start address isn't aligned, we need a block. |
| 1415 | const size_t blocks_for_start_align = | 1414 | const size_t blocks_for_start_align = |
| 1416 | (Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0; | 1415 | (Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0; |
| 1417 | 1416 | ||
| 1417 | // Validate all blocks in the range have correct state. | ||
| 1418 | const KMemoryState first_state = info.state; | ||
| 1419 | const KMemoryPermission first_perm = info.perm; | ||
| 1420 | const KMemoryAttribute first_attr = info.attribute; | ||
| 1418 | while (true) { | 1421 | while (true) { |
| 1422 | // Validate the current block. | ||
| 1423 | R_UNLESS(info.state == first_state, ResultInvalidCurrentMemory); | ||
| 1424 | R_UNLESS(info.perm == first_perm, ResultInvalidCurrentMemory); | ||
| 1425 | R_UNLESS((info.attribute | ignore_attr) == (first_attr | ignore_attr), | ||
| 1426 | ResultInvalidCurrentMemory); | ||
| 1427 | |||
| 1419 | // Validate against the provided masks. | 1428 | // Validate against the provided masks. |
| 1420 | R_TRY(CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr)); | 1429 | R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr)); |
| 1421 | 1430 | ||
| 1422 | // Break once we're done. | 1431 | // Break once we're done. |
| 1423 | if (last_addr <= info.GetLastAddress()) { | 1432 | if (last_addr <= info.GetLastAddress()) { |
| @@ -1426,6 +1435,7 @@ ResultCode KPageTable::CheckMemoryState(size_t* out_blocks_needed, VAddr addr, s | |||
| 1426 | 1435 | ||
| 1427 | // Advance our iterator. | 1436 | // Advance our iterator. |
| 1428 | it++; | 1437 | it++; |
| 1438 | ASSERT(it != block_manager->cend()); | ||
| 1429 | info = it->GetMemoryInfo(); | 1439 | info = it->GetMemoryInfo(); |
| 1430 | } | 1440 | } |
| 1431 | 1441 | ||
| @@ -1433,10 +1443,19 @@ ResultCode KPageTable::CheckMemoryState(size_t* out_blocks_needed, VAddr addr, s | |||
| 1433 | const size_t blocks_for_end_align = | 1443 | const size_t blocks_for_end_align = |
| 1434 | (Common::AlignUp(addr + size, PageSize) != info.GetEndAddress()) ? 1 : 0; | 1444 | (Common::AlignUp(addr + size, PageSize) != info.GetEndAddress()) ? 1 : 0; |
| 1435 | 1445 | ||
| 1446 | // Write output state. | ||
| 1447 | if (out_state != nullptr) { | ||
| 1448 | *out_state = first_state; | ||
| 1449 | } | ||
| 1450 | if (out_perm != nullptr) { | ||
| 1451 | *out_perm = first_perm; | ||
| 1452 | } | ||
| 1453 | if (out_attr != nullptr) { | ||
| 1454 | *out_attr = static_cast<KMemoryAttribute>(first_attr & ~ignore_attr); | ||
| 1455 | } | ||
| 1436 | if (out_blocks_needed != nullptr) { | 1456 | if (out_blocks_needed != nullptr) { |
| 1437 | *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; | 1457 | *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; |
| 1438 | } | 1458 | } |
| 1439 | |||
| 1440 | return ResultSuccess; | 1459 | return ResultSuccess; |
| 1441 | } | 1460 | } |
| 1442 | 1461 | ||
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 564410dca..9235b822a 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h | |||
| @@ -48,8 +48,7 @@ public: | |||
| 48 | ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm); | 48 | ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm); |
| 49 | ResultCode ResetTransferMemory(VAddr addr, std::size_t size); | 49 | ResultCode ResetTransferMemory(VAddr addr, std::size_t size); |
| 50 | ResultCode SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm); | 50 | ResultCode SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm); |
| 51 | ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, KMemoryAttribute mask, | 51 | ResultCode SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr); |
| 52 | KMemoryAttribute value); | ||
| 53 | ResultCode SetMaxHeapSize(std::size_t size); | 52 | ResultCode SetMaxHeapSize(std::size_t size); |
| 54 | ResultCode SetHeapSize(VAddr* out, std::size_t size); | 53 | ResultCode SetHeapSize(VAddr* out, std::size_t size); |
| 55 | ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, | 54 | ResultVal<VAddr> AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, |
| @@ -102,28 +101,50 @@ private: | |||
| 102 | constexpr VAddr GetRegionAddress(KMemoryState state) const; | 101 | constexpr VAddr GetRegionAddress(KMemoryState state) const; |
| 103 | constexpr std::size_t GetRegionSize(KMemoryState state) const; | 102 | constexpr std::size_t GetRegionSize(KMemoryState state) const; |
| 104 | 103 | ||
| 105 | constexpr ResultCode CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, | 104 | ResultCode CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr, |
| 105 | std::size_t size, KMemoryState state_mask, | ||
| 106 | KMemoryState state, KMemoryPermission perm_mask, | 106 | KMemoryState state, KMemoryPermission perm_mask, |
| 107 | KMemoryPermission perm, KMemoryAttribute attr_mask, | 107 | KMemoryPermission perm, KMemoryAttribute attr_mask, |
| 108 | KMemoryAttribute attr) const; | 108 | KMemoryAttribute attr) const; |
| 109 | ResultCode CheckMemoryStateContiguous(VAddr addr, std::size_t size, KMemoryState state_mask, | ||
| 110 | KMemoryState state, KMemoryPermission perm_mask, | ||
| 111 | KMemoryPermission perm, KMemoryAttribute attr_mask, | ||
| 112 | KMemoryAttribute attr) const { | ||
| 113 | return this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask, | ||
| 114 | perm, attr_mask, attr); | ||
| 115 | } | ||
| 116 | |||
| 117 | ResultCode CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, | ||
| 118 | KMemoryState state, KMemoryPermission perm_mask, | ||
| 119 | KMemoryPermission perm, KMemoryAttribute attr_mask, | ||
| 120 | KMemoryAttribute attr) const; | ||
| 109 | ResultCode CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, | 121 | ResultCode CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, |
| 110 | KMemoryAttribute* out_attr, VAddr addr, std::size_t size, | 122 | KMemoryAttribute* out_attr, std::size_t* out_blocks_needed, |
| 123 | VAddr addr, std::size_t size, KMemoryState state_mask, | ||
| 124 | KMemoryState state, KMemoryPermission perm_mask, | ||
| 125 | KMemoryPermission perm, KMemoryAttribute attr_mask, | ||
| 126 | KMemoryAttribute attr, | ||
| 127 | KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const; | ||
| 128 | ResultCode CheckMemoryState(std::size_t* out_blocks_needed, VAddr addr, std::size_t size, | ||
| 111 | KMemoryState state_mask, KMemoryState state, | 129 | KMemoryState state_mask, KMemoryState state, |
| 112 | KMemoryPermission perm_mask, KMemoryPermission perm, | 130 | KMemoryPermission perm_mask, KMemoryPermission perm, |
| 113 | KMemoryAttribute attr_mask, KMemoryAttribute attr, | 131 | KMemoryAttribute attr_mask, KMemoryAttribute attr, |
| 114 | KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr); | 132 | KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const { |
| 115 | ResultCode CheckMemoryState(VAddr addr, std::size_t size, KMemoryState state_mask, | 133 | return CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size, |
| 134 | state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr); | ||
| 135 | } | ||
| 136 | ResultCode CheckMemoryState(VAddr addr, size_t size, KMemoryState state_mask, | ||
| 116 | KMemoryState state, KMemoryPermission perm_mask, | 137 | KMemoryState state, KMemoryPermission perm_mask, |
| 117 | KMemoryPermission perm, KMemoryAttribute attr_mask, | 138 | KMemoryPermission perm, KMemoryAttribute attr_mask, |
| 118 | KMemoryAttribute attr, | 139 | KMemoryAttribute attr, |
| 119 | KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) { | 140 | KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const { |
| 120 | return CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask, | 141 | return this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, |
| 121 | perm, attr_mask, attr, ignore_attr); | 142 | attr_mask, attr, ignore_attr); |
| 143 | } | ||
| 144 | |||
| 145 | bool IsLockedByCurrentThread() const { | ||
| 146 | return true; | ||
| 122 | } | 147 | } |
| 123 | ResultCode CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size, | ||
| 124 | KMemoryState state_mask, KMemoryState state, | ||
| 125 | KMemoryPermission perm_mask, KMemoryPermission perm, | ||
| 126 | KMemoryAttribute attr_mask, KMemoryAttribute attr) const; | ||
| 127 | 148 | ||
| 128 | std::recursive_mutex page_table_lock; | 149 | std::recursive_mutex page_table_lock; |
| 129 | std::unique_ptr<KMemoryBlockManager> block_manager; | 150 | std::unique_ptr<KMemoryBlockManager> block_manager; |
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 250ef9042..4362508a3 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp | |||
| @@ -168,6 +168,9 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { | |||
| 168 | 168 | ||
| 169 | static ResultCode SetMemoryPermission(Core::System& system, VAddr address, u64 size, | 169 | static ResultCode SetMemoryPermission(Core::System& system, VAddr address, u64 size, |
| 170 | MemoryPermission perm) { | 170 | MemoryPermission perm) { |
| 171 | LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, | ||
| 172 | perm); | ||
| 173 | |||
| 171 | // Validate address / size. | 174 | // Validate address / size. |
| 172 | R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); | 175 | R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); |
| 173 | R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); | 176 | R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); |
| @@ -186,46 +189,33 @@ static ResultCode SetMemoryPermission(Core::System& system, VAddr address, u64 s | |||
| 186 | } | 189 | } |
| 187 | 190 | ||
| 188 | static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, | 191 | static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, |
| 189 | u32 attribute) { | 192 | u32 attr) { |
| 190 | LOG_DEBUG(Kernel_SVC, | 193 | LOG_DEBUG(Kernel_SVC, |
| 191 | "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, | 194 | "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, |
| 192 | size, mask, attribute); | 195 | size, mask, attr); |
| 193 | |||
| 194 | if (!Common::Is4KBAligned(address)) { | ||
| 195 | LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address); | ||
| 196 | return ResultInvalidAddress; | ||
| 197 | } | ||
| 198 | 196 | ||
| 199 | if (size == 0 || !Common::Is4KBAligned(size)) { | 197 | // Validate address / size. |
| 200 | LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.", | 198 | R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); |
| 201 | size); | 199 | R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); |
| 202 | return ResultInvalidAddress; | 200 | R_UNLESS(size > 0, ResultInvalidSize); |
| 203 | } | 201 | R_UNLESS((address < address + size), ResultInvalidCurrentMemory); |
| 204 | |||
| 205 | if (!IsValidAddressRange(address, size)) { | ||
| 206 | LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})", | ||
| 207 | address, size); | ||
| 208 | return ResultInvalidCurrentMemory; | ||
| 209 | } | ||
| 210 | 202 | ||
| 211 | const auto attributes{static_cast<MemoryAttribute>(mask | attribute)}; | 203 | // Validate the attribute and mask. |
| 212 | if (attributes != static_cast<MemoryAttribute>(mask) || | 204 | constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); |
| 213 | (attributes | MemoryAttribute::Uncached) != MemoryAttribute::Uncached) { | 205 | R_UNLESS((mask | attr) == mask, ResultInvalidCombination); |
| 214 | LOG_ERROR(Kernel_SVC, | 206 | R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); |
| 215 | "Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}", | ||
| 216 | attribute, mask); | ||
| 217 | return ResultInvalidCombination; | ||
| 218 | } | ||
| 219 | 207 | ||
| 208 | // Validate that the region is in range for the current process. | ||
| 220 | auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; | 209 | auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; |
| 210 | R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); | ||
| 221 | 211 | ||
| 222 | return page_table.SetMemoryAttribute(address, size, static_cast<KMemoryAttribute>(mask), | 212 | // Set the memory attribute. |
| 223 | static_cast<KMemoryAttribute>(attribute)); | 213 | return page_table.SetMemoryAttribute(address, size, mask, attr); |
| 224 | } | 214 | } |
| 225 | 215 | ||
| 226 | static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, | 216 | static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, |
| 227 | u32 attribute) { | 217 | u32 attr) { |
| 228 | return SetMemoryAttribute(system, address, size, mask, attribute); | 218 | return SetMemoryAttribute(system, address, size, mask, attr); |
| 229 | } | 219 | } |
| 230 | 220 | ||
| 231 | /// Maps a memory range into a different range. | 221 | /// Maps a memory range into a different range. |
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index ec463b97c..365e22e4e 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h | |||
| @@ -32,6 +32,7 @@ enum class MemoryState : u32 { | |||
| 32 | Kernel = 0x13, | 32 | Kernel = 0x13, |
| 33 | GeneratedCode = 0x14, | 33 | GeneratedCode = 0x14, |
| 34 | CodeOut = 0x15, | 34 | CodeOut = 0x15, |
| 35 | Coverage = 0x16, | ||
| 35 | }; | 36 | }; |
| 36 | DECLARE_ENUM_FLAG_OPERATORS(MemoryState); | 37 | DECLARE_ENUM_FLAG_OPERATORS(MemoryState); |
| 37 | 38 | ||
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 0cda9df62..757117f2b 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp | |||
| @@ -663,6 +663,7 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding() const { | |||
| 663 | {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, | 663 | {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, |
| 664 | {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, | 664 | {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, |
| 665 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | 665 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, |
| 666 | {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, | ||
| 666 | }; | 667 | }; |
| 667 | } | 668 | } |
| 668 | 669 | ||
| @@ -699,6 +700,7 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding( | |||
| 699 | {Settings::NativeButton::SL, sl_button}, | 700 | {Settings::NativeButton::SL, sl_button}, |
| 700 | {Settings::NativeButton::SR, sr_button}, | 701 | {Settings::NativeButton::SR, sr_button}, |
| 701 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | 702 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, |
| 703 | {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, | ||
| 702 | }; | 704 | }; |
| 703 | } | 705 | } |
| 704 | 706 | ||
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index e9a5d2e26..4cde3606f 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h | |||
| @@ -24,7 +24,7 @@ namespace InputCommon { | |||
| 24 | class SDLJoystick; | 24 | class SDLJoystick; |
| 25 | 25 | ||
| 26 | using ButtonBindings = | 26 | using ButtonBindings = |
| 27 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; | 27 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>; |
| 28 | using ZButtonBindings = | 28 | using ZButtonBindings = |
| 29 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; | 29 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; |
| 30 | 30 | ||
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index 5bdd5dac3..944e141bf 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp | |||
| @@ -23,7 +23,7 @@ enum class Tas::TasAxis : u8 { | |||
| 23 | }; | 23 | }; |
| 24 | 24 | ||
| 25 | // Supported keywords and buttons from a TAS file | 25 | // Supported keywords and buttons from a TAS file |
| 26 | constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { | 26 | constexpr std::array<std::pair<std::string_view, TasButton>, 18> text_to_tas_button = { |
| 27 | std::pair{"KEY_A", TasButton::BUTTON_A}, | 27 | std::pair{"KEY_A", TasButton::BUTTON_A}, |
| 28 | {"KEY_B", TasButton::BUTTON_B}, | 28 | {"KEY_B", TasButton::BUTTON_B}, |
| 29 | {"KEY_X", TasButton::BUTTON_X}, | 29 | {"KEY_X", TasButton::BUTTON_X}, |
| @@ -40,8 +40,9 @@ constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_but | |||
| 40 | {"KEY_DDOWN", TasButton::BUTTON_DOWN}, | 40 | {"KEY_DDOWN", TasButton::BUTTON_DOWN}, |
| 41 | {"KEY_SL", TasButton::BUTTON_SL}, | 41 | {"KEY_SL", TasButton::BUTTON_SL}, |
| 42 | {"KEY_SR", TasButton::BUTTON_SR}, | 42 | {"KEY_SR", TasButton::BUTTON_SR}, |
| 43 | {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, | 43 | // These buttons are disabled to avoid TAS input from activating hotkeys |
| 44 | {"KEY_HOME", TasButton::BUTTON_HOME}, | 44 | // {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, |
| 45 | // {"KEY_HOME", TasButton::BUTTON_HOME}, | ||
| 45 | {"KEY_ZL", TasButton::TRIGGER_ZL}, | 46 | {"KEY_ZL", TasButton::TRIGGER_ZL}, |
| 46 | {"KEY_ZR", TasButton::TRIGGER_ZR}, | 47 | {"KEY_ZR", TasButton::TRIGGER_ZR}, |
| 47 | }; | 48 | }; |
| @@ -105,10 +106,16 @@ void Tas::LoadTasFile(size_t player_index, size_t file_index) { | |||
| 105 | continue; | 106 | continue; |
| 106 | } | 107 | } |
| 107 | 108 | ||
| 108 | const auto num_frames = std::stoi(seg_list[0]); | 109 | try { |
| 109 | while (frame_no < num_frames) { | 110 | const auto num_frames = std::stoi(seg_list[0]); |
| 110 | commands[player_index].emplace_back(); | 111 | while (frame_no < num_frames) { |
| 111 | frame_no++; | 112 | commands[player_index].emplace_back(); |
| 113 | frame_no++; | ||
| 114 | } | ||
| 115 | } catch (const std::invalid_argument&) { | ||
| 116 | LOG_ERROR(Input, "Invalid argument: '{}' at command {}", seg_list[0], frame_no); | ||
| 117 | } catch (const std::out_of_range&) { | ||
| 118 | LOG_ERROR(Input, "Out of range: '{}' at command {}", seg_list[0], frame_no); | ||
| 112 | } | 119 | } |
| 113 | 120 | ||
| 114 | TASCommand command = { | 121 | TASCommand command = { |
| @@ -233,10 +240,21 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const { | |||
| 233 | } | 240 | } |
| 234 | } | 241 | } |
| 235 | 242 | ||
| 236 | const float x = std::stof(seg_list.at(0)) / 32767.0f; | 243 | if (seg_list.size() < 2) { |
| 237 | const float y = std::stof(seg_list.at(1)) / 32767.0f; | 244 | LOG_ERROR(Input, "Invalid axis data: '{}'", line); |
| 245 | return {}; | ||
| 246 | } | ||
| 238 | 247 | ||
| 239 | return {x, y}; | 248 | try { |
| 249 | const float x = std::stof(seg_list.at(0)) / 32767.0f; | ||
| 250 | const float y = std::stof(seg_list.at(1)) / 32767.0f; | ||
| 251 | return {x, y}; | ||
| 252 | } catch (const std::invalid_argument&) { | ||
| 253 | LOG_ERROR(Input, "Invalid argument: '{}'", line); | ||
| 254 | } catch (const std::out_of_range&) { | ||
| 255 | LOG_ERROR(Input, "Out of range: '{}'", line); | ||
| 256 | } | ||
| 257 | return {}; | ||
| 240 | } | 258 | } |
| 241 | 259 | ||
| 242 | u64 Tas::ReadCommandButtons(const std::string& line) const { | 260 | u64 Tas::ReadCommandButtons(const std::string& line) const { |
diff --git a/src/shader_recompiler/exception.h b/src/shader_recompiler/exception.h index 277be8541..d98b6029b 100644 --- a/src/shader_recompiler/exception.h +++ b/src/shader_recompiler/exception.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include <string_view> | 9 | #include <string_view> |
| 10 | #include <utility> | 10 | #include <utility> |
| 11 | 11 | ||
| 12 | #include <fmt/format.h> | 12 | #include "common/logging/formatter.h" |
| 13 | 13 | ||
| 14 | namespace Shader { | 14 | namespace Shader { |
| 15 | 15 | ||
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4a20c0768..a69ccb264 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -16,6 +16,6 @@ add_executable(tests | |||
| 16 | create_target_directory_groups(tests) | 16 | create_target_directory_groups(tests) |
| 17 | 17 | ||
| 18 | target_link_libraries(tests PRIVATE common core input_common) | 18 | target_link_libraries(tests PRIVATE common core input_common) |
| 19 | target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads) | 19 | target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads) |
| 20 | 20 | ||
| 21 | add_test(NAME tests COMMAND tests) | 21 | add_test(NAME tests COMMAND tests) |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0f679c37e..99a7397fc 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -66,27 +66,27 @@ const std::array<int, 2> Config::default_stick_mod = { | |||
| 66 | // UISetting::values.shortcuts, which is alphabetically ordered. | 66 | // UISetting::values.shortcuts, which is alphabetically ordered. |
| 67 | // clang-format off | 67 | // clang-format off |
| 68 | const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{ | 68 | const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{ |
| 69 | {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, | 69 | {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, |
| 70 | {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, | 70 | {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, |
| 71 | {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, | 71 | {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, |
| 72 | {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, | 72 | {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 73 | {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}}, | 73 | {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 74 | {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, | 74 | {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, |
| 75 | {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, | 75 | {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, |
| 76 | {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, | 76 | {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 77 | {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, | 77 | {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, |
| 78 | {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, | 78 | {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, |
| 79 | {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, | 79 | {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 80 | {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, | 80 | {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 81 | {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, | 81 | {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 82 | {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}}, | 82 | {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 83 | {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}}, | 83 | {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 84 | {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}}, | 84 | {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 85 | {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, | 85 | {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 86 | {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}, | 86 | {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, |
| 87 | {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, | 87 | {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 88 | {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, | 88 | {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 89 | {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, | 89 | {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 90 | }}; | 90 | }}; |
| 91 | // clang-format on | 91 | // clang-format on |
| 92 | 92 | ||
| @@ -679,7 +679,6 @@ void Config::ReadShortcutValues() { | |||
| 679 | qt_config->beginGroup(QStringLiteral("Shortcuts")); | 679 | qt_config->beginGroup(QStringLiteral("Shortcuts")); |
| 680 | 680 | ||
| 681 | for (const auto& [name, group, shortcut] : default_hotkeys) { | 681 | for (const auto& [name, group, shortcut] : default_hotkeys) { |
| 682 | const auto& [keyseq, context] = shortcut; | ||
| 683 | qt_config->beginGroup(group); | 682 | qt_config->beginGroup(group); |
| 684 | qt_config->beginGroup(name); | 683 | qt_config->beginGroup(name); |
| 685 | // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 | 684 | // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 |
| @@ -688,7 +687,10 @@ void Config::ReadShortcutValues() { | |||
| 688 | UISettings::values.shortcuts.push_back( | 687 | UISettings::values.shortcuts.push_back( |
| 689 | {name, | 688 | {name, |
| 690 | group, | 689 | group, |
| 691 | {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}}); | 690 | {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(), |
| 691 | ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq) | ||
| 692 | .toString(), | ||
| 693 | shortcut.context}}); | ||
| 692 | qt_config->endGroup(); | 694 | qt_config->endGroup(); |
| 693 | qt_config->endGroup(); | 695 | qt_config->endGroup(); |
| 694 | } | 696 | } |
| @@ -1227,8 +1229,10 @@ void Config::SaveShortcutValues() { | |||
| 1227 | 1229 | ||
| 1228 | qt_config->beginGroup(group); | 1230 | qt_config->beginGroup(group); |
| 1229 | qt_config->beginGroup(name); | 1231 | qt_config->beginGroup(name); |
| 1230 | WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first); | 1232 | WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq); |
| 1231 | WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second); | 1233 | WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq, |
| 1234 | default_hotkey.controller_keyseq); | ||
| 1235 | WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context); | ||
| 1232 | qt_config->endGroup(); | 1236 | qt_config->endGroup(); |
| 1233 | qt_config->endGroup(); | 1237 | qt_config->endGroup(); |
| 1234 | } | 1238 | } |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 642a5f966..464e7a489 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -45,7 +45,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, | |||
| 45 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, | 45 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, |
| 46 | graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)}, | 46 | graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)}, |
| 47 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, | 47 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, |
| 48 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)}, | 48 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, |
| 49 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, | 49 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, |
| 50 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, | 50 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, |
| 51 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, | 51 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, |
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index ed76fe18e..be10e0a31 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp | |||
| @@ -5,15 +5,24 @@ | |||
| 5 | #include <QMenu> | 5 | #include <QMenu> |
| 6 | #include <QMessageBox> | 6 | #include <QMessageBox> |
| 7 | #include <QStandardItemModel> | 7 | #include <QStandardItemModel> |
| 8 | #include "common/settings.h" | 8 | #include <QTimer> |
| 9 | |||
| 10 | #include "core/hid/emulated_controller.h" | ||
| 11 | #include "core/hid/hid_core.h" | ||
| 12 | |||
| 9 | #include "ui_configure_hotkeys.h" | 13 | #include "ui_configure_hotkeys.h" |
| 10 | #include "yuzu/configuration/config.h" | 14 | #include "yuzu/configuration/config.h" |
| 11 | #include "yuzu/configuration/configure_hotkeys.h" | 15 | #include "yuzu/configuration/configure_hotkeys.h" |
| 12 | #include "yuzu/hotkeys.h" | 16 | #include "yuzu/hotkeys.h" |
| 13 | #include "yuzu/util/sequence_dialog/sequence_dialog.h" | 17 | #include "yuzu/util/sequence_dialog/sequence_dialog.h" |
| 14 | 18 | ||
| 15 | ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) | 19 | constexpr int name_column = 0; |
| 16 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) { | 20 | constexpr int hotkey_column = 1; |
| 21 | constexpr int controller_column = 2; | ||
| 22 | |||
| 23 | ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent) | ||
| 24 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()), | ||
| 25 | timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { | ||
| 17 | ui->setupUi(this); | 26 | ui->setupUi(this); |
| 18 | setFocusPolicy(Qt::ClickFocus); | 27 | setFocusPolicy(Qt::ClickFocus); |
| 19 | 28 | ||
| @@ -26,16 +35,24 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) | |||
| 26 | ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu); | 35 | ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu); |
| 27 | ui->hotkey_list->setModel(model); | 36 | ui->hotkey_list->setModel(model); |
| 28 | 37 | ||
| 29 | // TODO(Kloen): Make context configurable as well (hiding the column for now) | 38 | ui->hotkey_list->setColumnWidth(name_column, 200); |
| 30 | ui->hotkey_list->hideColumn(2); | 39 | ui->hotkey_list->resizeColumnToContents(hotkey_column); |
| 31 | |||
| 32 | ui->hotkey_list->setColumnWidth(0, 200); | ||
| 33 | ui->hotkey_list->resizeColumnToContents(1); | ||
| 34 | 40 | ||
| 35 | connect(ui->button_restore_defaults, &QPushButton::clicked, this, | 41 | connect(ui->button_restore_defaults, &QPushButton::clicked, this, |
| 36 | &ConfigureHotkeys::RestoreDefaults); | 42 | &ConfigureHotkeys::RestoreDefaults); |
| 37 | connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll); | 43 | connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll); |
| 38 | 44 | ||
| 45 | controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 46 | |||
| 47 | connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); | ||
| 48 | |||
| 49 | connect(poll_timer.get(), &QTimer::timeout, [this] { | ||
| 50 | const auto buttons = controller->GetNpadButtons(); | ||
| 51 | if (buttons.raw != Core::HID::NpadButton::None) { | ||
| 52 | SetPollingResult(buttons.raw, false); | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | }); | ||
| 39 | RetranslateUI(); | 56 | RetranslateUI(); |
| 40 | } | 57 | } |
| 41 | 58 | ||
| @@ -49,15 +66,18 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { | |||
| 49 | auto* action = new QStandardItem(hotkey.first); | 66 | auto* action = new QStandardItem(hotkey.first); |
| 50 | auto* keyseq = | 67 | auto* keyseq = |
| 51 | new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); | 68 | new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); |
| 69 | auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq); | ||
| 52 | action->setEditable(false); | 70 | action->setEditable(false); |
| 53 | keyseq->setEditable(false); | 71 | keyseq->setEditable(false); |
| 54 | parent_item->appendRow({action, keyseq}); | 72 | controller_keyseq->setEditable(false); |
| 73 | parent_item->appendRow({action, keyseq, controller_keyseq}); | ||
| 55 | } | 74 | } |
| 56 | model->appendRow(parent_item); | 75 | model->appendRow(parent_item); |
| 57 | } | 76 | } |
| 58 | 77 | ||
| 59 | ui->hotkey_list->expandAll(); | 78 | ui->hotkey_list->expandAll(); |
| 60 | ui->hotkey_list->resizeColumnToContents(0); | 79 | ui->hotkey_list->resizeColumnToContents(name_column); |
| 80 | ui->hotkey_list->resizeColumnToContents(hotkey_column); | ||
| 61 | } | 81 | } |
| 62 | 82 | ||
| 63 | void ConfigureHotkeys::changeEvent(QEvent* event) { | 83 | void ConfigureHotkeys::changeEvent(QEvent* event) { |
| @@ -71,7 +91,7 @@ void ConfigureHotkeys::changeEvent(QEvent* event) { | |||
| 71 | void ConfigureHotkeys::RetranslateUI() { | 91 | void ConfigureHotkeys::RetranslateUI() { |
| 72 | ui->retranslateUi(this); | 92 | ui->retranslateUi(this); |
| 73 | 93 | ||
| 74 | model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")}); | 94 | model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")}); |
| 75 | } | 95 | } |
| 76 | 96 | ||
| 77 | void ConfigureHotkeys::Configure(QModelIndex index) { | 97 | void ConfigureHotkeys::Configure(QModelIndex index) { |
| @@ -79,7 +99,15 @@ void ConfigureHotkeys::Configure(QModelIndex index) { | |||
| 79 | return; | 99 | return; |
| 80 | } | 100 | } |
| 81 | 101 | ||
| 82 | index = index.sibling(index.row(), 1); | 102 | // Controller configuration is selected |
| 103 | if (index.column() == controller_column) { | ||
| 104 | ConfigureController(index); | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Swap to the hotkey column | ||
| 109 | index = index.sibling(index.row(), hotkey_column); | ||
| 110 | |||
| 83 | const auto previous_key = model->data(index); | 111 | const auto previous_key = model->data(index); |
| 84 | 112 | ||
| 85 | SequenceDialog hotkey_dialog{this}; | 113 | SequenceDialog hotkey_dialog{this}; |
| @@ -99,13 +127,113 @@ void ConfigureHotkeys::Configure(QModelIndex index) { | |||
| 99 | model->setData(index, key_sequence.toString(QKeySequence::NativeText)); | 127 | model->setData(index, key_sequence.toString(QKeySequence::NativeText)); |
| 100 | } | 128 | } |
| 101 | } | 129 | } |
| 130 | void ConfigureHotkeys::ConfigureController(QModelIndex index) { | ||
| 131 | if (timeout_timer->isActive()) { | ||
| 132 | return; | ||
| 133 | } | ||
| 134 | |||
| 135 | const auto previous_key = model->data(index); | ||
| 136 | |||
| 137 | input_setter = [this, index, previous_key](const Core::HID::NpadButton button, | ||
| 138 | const bool cancel) { | ||
| 139 | if (cancel) { | ||
| 140 | model->setData(index, previous_key); | ||
| 141 | return; | ||
| 142 | } | ||
| 143 | |||
| 144 | const QString button_string = tr("Home+%1").arg(GetButtonName(button)); | ||
| 145 | |||
| 146 | const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string); | ||
| 147 | |||
| 148 | if (key_sequence_used) { | ||
| 149 | QMessageBox::warning( | ||
| 150 | this, tr("Conflicting Key Sequence"), | ||
| 151 | tr("The entered key sequence is already assigned to: %1").arg(used_action)); | ||
| 152 | model->setData(index, previous_key); | ||
| 153 | } else { | ||
| 154 | model->setData(index, button_string); | ||
| 155 | } | ||
| 156 | }; | ||
| 157 | |||
| 158 | model->setData(index, tr("[waiting]")); | ||
| 159 | timeout_timer->start(2500); // Cancel after 2.5 seconds | ||
| 160 | poll_timer->start(200); // Check for new inputs every 200ms | ||
| 161 | // We need to disable configuration to be able to read npad buttons | ||
| 162 | controller->DisableConfiguration(); | ||
| 163 | controller->DisableSystemButtons(); | ||
| 164 | } | ||
| 165 | |||
| 166 | void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) { | ||
| 167 | timeout_timer->stop(); | ||
| 168 | poll_timer->stop(); | ||
| 169 | // Re-Enable configuration | ||
| 170 | controller->EnableConfiguration(); | ||
| 171 | controller->EnableSystemButtons(); | ||
| 172 | |||
| 173 | (*input_setter)(button, cancel); | ||
| 174 | |||
| 175 | input_setter = std::nullopt; | ||
| 176 | } | ||
| 177 | |||
| 178 | QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const { | ||
| 179 | Core::HID::NpadButtonState state{button}; | ||
| 180 | if (state.a) { | ||
| 181 | return tr("A"); | ||
| 182 | } | ||
| 183 | if (state.b) { | ||
| 184 | return tr("B"); | ||
| 185 | } | ||
| 186 | if (state.x) { | ||
| 187 | return tr("X"); | ||
| 188 | } | ||
| 189 | if (state.y) { | ||
| 190 | return tr("Y"); | ||
| 191 | } | ||
| 192 | if (state.l || state.right_sl || state.left_sl) { | ||
| 193 | return tr("L"); | ||
| 194 | } | ||
| 195 | if (state.r || state.right_sr || state.left_sr) { | ||
| 196 | return tr("R"); | ||
| 197 | } | ||
| 198 | if (state.zl) { | ||
| 199 | return tr("ZL"); | ||
| 200 | } | ||
| 201 | if (state.zr) { | ||
| 202 | return tr("ZR"); | ||
| 203 | } | ||
| 204 | if (state.left) { | ||
| 205 | return tr("Dpad_Left"); | ||
| 206 | } | ||
| 207 | if (state.right) { | ||
| 208 | return tr("Dpad_Right"); | ||
| 209 | } | ||
| 210 | if (state.up) { | ||
| 211 | return tr("Dpad_Up"); | ||
| 212 | } | ||
| 213 | if (state.down) { | ||
| 214 | return tr("Dpad_Down"); | ||
| 215 | } | ||
| 216 | if (state.stick_l) { | ||
| 217 | return tr("Left_Stick"); | ||
| 218 | } | ||
| 219 | if (state.stick_r) { | ||
| 220 | return tr("Right_Stick"); | ||
| 221 | } | ||
| 222 | if (state.minus) { | ||
| 223 | return tr("Minus"); | ||
| 224 | } | ||
| 225 | if (state.plus) { | ||
| 226 | return tr("Plus"); | ||
| 227 | } | ||
| 228 | return tr("Invalid"); | ||
| 229 | } | ||
| 102 | 230 | ||
| 103 | std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { | 231 | std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { |
| 104 | for (int r = 0; r < model->rowCount(); ++r) { | 232 | for (int r = 0; r < model->rowCount(); ++r) { |
| 105 | const QStandardItem* const parent = model->item(r, 0); | 233 | const QStandardItem* const parent = model->item(r, 0); |
| 106 | 234 | ||
| 107 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 235 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 108 | const QStandardItem* const key_seq_item = parent->child(r2, 1); | 236 | const QStandardItem* const key_seq_item = parent->child(r2, hotkey_column); |
| 109 | const auto key_seq_str = key_seq_item->text(); | 237 | const auto key_seq_str = key_seq_item->text(); |
| 110 | const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); | 238 | const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); |
| 111 | 239 | ||
| @@ -118,12 +246,31 @@ std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) | |||
| 118 | return std::make_pair(false, QString()); | 246 | return std::make_pair(false, QString()); |
| 119 | } | 247 | } |
| 120 | 248 | ||
| 249 | std::pair<bool, QString> ConfigureHotkeys::IsUsedControllerKey(const QString& key_sequence) const { | ||
| 250 | for (int r = 0; r < model->rowCount(); ++r) { | ||
| 251 | const QStandardItem* const parent = model->item(r, 0); | ||
| 252 | |||
| 253 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | ||
| 254 | const QStandardItem* const key_seq_item = parent->child(r2, controller_column); | ||
| 255 | const auto key_seq_str = key_seq_item->text(); | ||
| 256 | |||
| 257 | if (key_sequence == key_seq_str) { | ||
| 258 | return std::make_pair(true, parent->child(r2, 0)->text()); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | return std::make_pair(false, QString()); | ||
| 264 | } | ||
| 265 | |||
| 121 | void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { | 266 | void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { |
| 122 | for (int key_id = 0; key_id < model->rowCount(); key_id++) { | 267 | for (int key_id = 0; key_id < model->rowCount(); key_id++) { |
| 123 | const QStandardItem* parent = model->item(key_id, 0); | 268 | const QStandardItem* parent = model->item(key_id, 0); |
| 124 | for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { | 269 | for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { |
| 125 | const QStandardItem* action = parent->child(key_column_id, 0); | 270 | const QStandardItem* action = parent->child(key_column_id, name_column); |
| 126 | const QStandardItem* keyseq = parent->child(key_column_id, 1); | 271 | const QStandardItem* keyseq = parent->child(key_column_id, hotkey_column); |
| 272 | const QStandardItem* controller_keyseq = | ||
| 273 | parent->child(key_column_id, controller_column); | ||
| 127 | for (auto& [group, sub_actions] : registry.hotkey_groups) { | 274 | for (auto& [group, sub_actions] : registry.hotkey_groups) { |
| 128 | if (group != parent->text()) | 275 | if (group != parent->text()) |
| 129 | continue; | 276 | continue; |
| @@ -131,6 +278,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { | |||
| 131 | if (action_name != action->text()) | 278 | if (action_name != action->text()) |
| 132 | continue; | 279 | continue; |
| 133 | hotkey.keyseq = QKeySequence(keyseq->text()); | 280 | hotkey.keyseq = QKeySequence(keyseq->text()); |
| 281 | hotkey.controller_keyseq = controller_keyseq->text(); | ||
| 134 | } | 282 | } |
| 135 | } | 283 | } |
| 136 | } | 284 | } |
| @@ -144,7 +292,12 @@ void ConfigureHotkeys::RestoreDefaults() { | |||
| 144 | const QStandardItem* parent = model->item(r, 0); | 292 | const QStandardItem* parent = model->item(r, 0); |
| 145 | 293 | ||
| 146 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 294 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 147 | model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first); | 295 | model->item(r, 0) |
| 296 | ->child(r2, hotkey_column) | ||
| 297 | ->setText(Config::default_hotkeys[r2].shortcut.keyseq); | ||
| 298 | model->item(r, 0) | ||
| 299 | ->child(r2, controller_column) | ||
| 300 | ->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq); | ||
| 148 | } | 301 | } |
| 149 | } | 302 | } |
| 150 | } | 303 | } |
| @@ -154,7 +307,8 @@ void ConfigureHotkeys::ClearAll() { | |||
| 154 | const QStandardItem* parent = model->item(r, 0); | 307 | const QStandardItem* parent = model->item(r, 0); |
| 155 | 308 | ||
| 156 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 309 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 157 | model->item(r, 0)->child(r2, 1)->setText(QString{}); | 310 | model->item(r, 0)->child(r2, hotkey_column)->setText(QString{}); |
| 311 | model->item(r, 0)->child(r2, controller_column)->setText(QString{}); | ||
| 158 | } | 312 | } |
| 159 | } | 313 | } |
| 160 | } | 314 | } |
| @@ -165,28 +319,52 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { | |||
| 165 | return; | 319 | return; |
| 166 | } | 320 | } |
| 167 | 321 | ||
| 168 | const auto selected = index.sibling(index.row(), 1); | 322 | // Swap to the hotkey column if the controller hotkey column is not selected |
| 323 | if (index.column() != controller_column) { | ||
| 324 | index = index.sibling(index.row(), hotkey_column); | ||
| 325 | } | ||
| 326 | |||
| 169 | QMenu context_menu; | 327 | QMenu context_menu; |
| 170 | 328 | ||
| 171 | QAction* restore_default = context_menu.addAction(tr("Restore Default")); | 329 | QAction* restore_default = context_menu.addAction(tr("Restore Default")); |
| 172 | QAction* clear = context_menu.addAction(tr("Clear")); | 330 | QAction* clear = context_menu.addAction(tr("Clear")); |
| 173 | 331 | ||
| 174 | connect(restore_default, &QAction::triggered, [this, selected] { | 332 | connect(restore_default, &QAction::triggered, [this, index] { |
| 175 | const QKeySequence& default_key_sequence = QKeySequence::fromString( | 333 | if (index.column() == controller_column) { |
| 176 | Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText); | 334 | RestoreControllerHotkey(index); |
| 177 | const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); | 335 | return; |
| 178 | |||
| 179 | if (key_sequence_used && | ||
| 180 | default_key_sequence != QKeySequence(model->data(selected).toString())) { | ||
| 181 | |||
| 182 | QMessageBox::warning( | ||
| 183 | this, tr("Conflicting Key Sequence"), | ||
| 184 | tr("The default key sequence is already assigned to: %1").arg(used_action)); | ||
| 185 | } else { | ||
| 186 | model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); | ||
| 187 | } | 336 | } |
| 337 | RestoreHotkey(index); | ||
| 188 | }); | 338 | }); |
| 189 | connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); }); | 339 | connect(clear, &QAction::triggered, [this, index] { model->setData(index, QString{}); }); |
| 190 | 340 | ||
| 191 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); | 341 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); |
| 192 | } | 342 | } |
| 343 | |||
| 344 | void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) { | ||
| 345 | const QString& default_key_sequence = | ||
| 346 | Config::default_hotkeys[index.row()].shortcut.controller_keyseq; | ||
| 347 | const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence); | ||
| 348 | |||
| 349 | if (key_sequence_used && default_key_sequence != model->data(index).toString()) { | ||
| 350 | QMessageBox::warning( | ||
| 351 | this, tr("Conflicting Button Sequence"), | ||
| 352 | tr("The default button sequence is already assigned to: %1").arg(used_action)); | ||
| 353 | } else { | ||
| 354 | model->setData(index, default_key_sequence); | ||
| 355 | } | ||
| 356 | } | ||
| 357 | |||
| 358 | void ConfigureHotkeys::RestoreHotkey(QModelIndex index) { | ||
| 359 | const QKeySequence& default_key_sequence = QKeySequence::fromString( | ||
| 360 | Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText); | ||
| 361 | const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); | ||
| 362 | |||
| 363 | if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) { | ||
| 364 | QMessageBox::warning( | ||
| 365 | this, tr("Conflicting Key Sequence"), | ||
| 366 | tr("The default key sequence is already assigned to: %1").arg(used_action)); | ||
| 367 | } else { | ||
| 368 | model->setData(index, default_key_sequence.toString(QKeySequence::NativeText)); | ||
| 369 | } | ||
| 370 | } | ||
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h index a2ec3323e..f943ec538 100644 --- a/src/yuzu/configuration/configure_hotkeys.h +++ b/src/yuzu/configuration/configure_hotkeys.h | |||
| @@ -7,6 +7,16 @@ | |||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <QWidget> | 8 | #include <QWidget> |
| 9 | 9 | ||
| 10 | namespace Common { | ||
| 11 | class ParamPackage; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace Core::HID { | ||
| 15 | class HIDCore; | ||
| 16 | class EmulatedController; | ||
| 17 | enum class NpadButton : u64; | ||
| 18 | } // namespace Core::HID | ||
| 19 | |||
| 10 | namespace Ui { | 20 | namespace Ui { |
| 11 | class ConfigureHotkeys; | 21 | class ConfigureHotkeys; |
| 12 | } | 22 | } |
| @@ -18,7 +28,7 @@ class ConfigureHotkeys : public QWidget { | |||
| 18 | Q_OBJECT | 28 | Q_OBJECT |
| 19 | 29 | ||
| 20 | public: | 30 | public: |
| 21 | explicit ConfigureHotkeys(QWidget* parent = nullptr); | 31 | explicit ConfigureHotkeys(Core::HID::HIDCore& hid_core_, QWidget* parent = nullptr); |
| 22 | ~ConfigureHotkeys() override; | 32 | ~ConfigureHotkeys() override; |
| 23 | 33 | ||
| 24 | void ApplyConfiguration(HotkeyRegistry& registry); | 34 | void ApplyConfiguration(HotkeyRegistry& registry); |
| @@ -35,13 +45,24 @@ private: | |||
| 35 | void RetranslateUI(); | 45 | void RetranslateUI(); |
| 36 | 46 | ||
| 37 | void Configure(QModelIndex index); | 47 | void Configure(QModelIndex index); |
| 48 | void ConfigureController(QModelIndex index); | ||
| 38 | std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const; | 49 | std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const; |
| 50 | std::pair<bool, QString> IsUsedControllerKey(const QString& key_sequence) const; | ||
| 39 | 51 | ||
| 40 | void RestoreDefaults(); | 52 | void RestoreDefaults(); |
| 41 | void ClearAll(); | 53 | void ClearAll(); |
| 42 | void PopupContextMenu(const QPoint& menu_location); | 54 | void PopupContextMenu(const QPoint& menu_location); |
| 55 | void RestoreControllerHotkey(QModelIndex index); | ||
| 56 | void RestoreHotkey(QModelIndex index); | ||
| 43 | 57 | ||
| 44 | std::unique_ptr<Ui::ConfigureHotkeys> ui; | 58 | std::unique_ptr<Ui::ConfigureHotkeys> ui; |
| 45 | 59 | ||
| 46 | QStandardItemModel* model; | 60 | QStandardItemModel* model; |
| 61 | |||
| 62 | void SetPollingResult(Core::HID::NpadButton button, bool cancel); | ||
| 63 | QString GetButtonName(Core::HID::NpadButton button) const; | ||
| 64 | Core::HID::EmulatedController* controller; | ||
| 65 | std::unique_ptr<QTimer> timeout_timer; | ||
| 66 | std::unique_ptr<QTimer> poll_timer; | ||
| 67 | std::optional<std::function<void(Core::HID::NpadButton, bool)>> input_setter; | ||
| 47 | }; | 68 | }; |
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index e7e58f314..d96497c4e 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp | |||
| @@ -2,10 +2,13 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <sstream> | ||
| 5 | #include <QKeySequence> | 6 | #include <QKeySequence> |
| 6 | #include <QShortcut> | 7 | #include <QShortcut> |
| 7 | #include <QTreeWidgetItem> | 8 | #include <QTreeWidgetItem> |
| 8 | #include <QtGlobal> | 9 | #include <QtGlobal> |
| 10 | |||
| 11 | #include "core/hid/emulated_controller.h" | ||
| 9 | #include "yuzu/hotkeys.h" | 12 | #include "yuzu/hotkeys.h" |
| 10 | #include "yuzu/uisettings.h" | 13 | #include "yuzu/uisettings.h" |
| 11 | 14 | ||
| @@ -18,8 +21,9 @@ void HotkeyRegistry::SaveHotkeys() { | |||
| 18 | for (const auto& hotkey : group.second) { | 21 | for (const auto& hotkey : group.second) { |
| 19 | UISettings::values.shortcuts.push_back( | 22 | UISettings::values.shortcuts.push_back( |
| 20 | {hotkey.first, group.first, | 23 | {hotkey.first, group.first, |
| 21 | UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), | 24 | UISettings::ContextualShortcut({hotkey.second.keyseq.toString(), |
| 22 | hotkey.second.context)}); | 25 | hotkey.second.controller_keyseq, |
| 26 | hotkey.second.context})}); | ||
| 23 | } | 27 | } |
| 24 | } | 28 | } |
| 25 | } | 29 | } |
| @@ -29,28 +33,49 @@ void HotkeyRegistry::LoadHotkeys() { | |||
| 29 | // beginGroup() | 33 | // beginGroup() |
| 30 | for (auto shortcut : UISettings::values.shortcuts) { | 34 | for (auto shortcut : UISettings::values.shortcuts) { |
| 31 | Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; | 35 | Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; |
| 32 | if (!shortcut.shortcut.first.isEmpty()) { | 36 | if (!shortcut.shortcut.keyseq.isEmpty()) { |
| 33 | hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText); | 37 | hk.keyseq = |
| 34 | hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second); | 38 | QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText); |
| 39 | hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context); | ||
| 40 | } | ||
| 41 | if (!shortcut.shortcut.controller_keyseq.isEmpty()) { | ||
| 42 | hk.controller_keyseq = shortcut.shortcut.controller_keyseq; | ||
| 35 | } | 43 | } |
| 36 | if (hk.shortcut) { | 44 | if (hk.shortcut) { |
| 37 | hk.shortcut->disconnect(); | 45 | hk.shortcut->disconnect(); |
| 38 | hk.shortcut->setKey(hk.keyseq); | 46 | hk.shortcut->setKey(hk.keyseq); |
| 39 | } | 47 | } |
| 48 | if (hk.controller_shortcut) { | ||
| 49 | hk.controller_shortcut->disconnect(); | ||
| 50 | hk.controller_shortcut->SetKey(hk.controller_keyseq); | ||
| 51 | } | ||
| 40 | } | 52 | } |
| 41 | } | 53 | } |
| 42 | 54 | ||
| 43 | QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { | 55 | QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { |
| 44 | Hotkey& hk = hotkey_groups[group][action]; | 56 | Hotkey& hk = hotkey_groups[group][action]; |
| 45 | 57 | ||
| 46 | if (!hk.shortcut) | 58 | if (!hk.shortcut) { |
| 47 | hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); | 59 | hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); |
| 60 | } | ||
| 48 | 61 | ||
| 49 | hk.shortcut->setAutoRepeat(false); | 62 | hk.shortcut->setAutoRepeat(false); |
| 50 | 63 | ||
| 51 | return hk.shortcut; | 64 | return hk.shortcut; |
| 52 | } | 65 | } |
| 53 | 66 | ||
| 67 | ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action, | ||
| 68 | Core::HID::EmulatedController* controller) { | ||
| 69 | Hotkey& hk = hotkey_groups[group][action]; | ||
| 70 | |||
| 71 | if (!hk.controller_shortcut) { | ||
| 72 | hk.controller_shortcut = new ControllerShortcut(controller); | ||
| 73 | hk.controller_shortcut->SetKey(hk.controller_keyseq); | ||
| 74 | } | ||
| 75 | |||
| 76 | return hk.controller_shortcut; | ||
| 77 | } | ||
| 78 | |||
| 54 | QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { | 79 | QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { |
| 55 | return hotkey_groups[group][action].keyseq; | 80 | return hotkey_groups[group][action].keyseq; |
| 56 | } | 81 | } |
| @@ -59,3 +84,131 @@ Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group, | |||
| 59 | const QString& action) { | 84 | const QString& action) { |
| 60 | return hotkey_groups[group][action].context; | 85 | return hotkey_groups[group][action].context; |
| 61 | } | 86 | } |
| 87 | |||
| 88 | ControllerShortcut::ControllerShortcut(Core::HID::EmulatedController* controller) { | ||
| 89 | emulated_controller = controller; | ||
| 90 | Core::HID::ControllerUpdateCallback engine_callback{ | ||
| 91 | .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); }, | ||
| 92 | .is_npad_service = false, | ||
| 93 | }; | ||
| 94 | callback_key = emulated_controller->SetCallback(engine_callback); | ||
| 95 | is_enabled = true; | ||
| 96 | } | ||
| 97 | |||
| 98 | ControllerShortcut::~ControllerShortcut() { | ||
| 99 | emulated_controller->DeleteCallback(callback_key); | ||
| 100 | } | ||
| 101 | |||
| 102 | void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) { | ||
| 103 | button_sequence = buttons; | ||
| 104 | } | ||
| 105 | |||
| 106 | void ControllerShortcut::SetKey(const QString& buttons_shortcut) { | ||
| 107 | ControllerButtonSequence sequence{}; | ||
| 108 | name = buttons_shortcut.toStdString(); | ||
| 109 | std::istringstream command_line(buttons_shortcut.toStdString()); | ||
| 110 | std::string line; | ||
| 111 | while (std::getline(command_line, line, '+')) { | ||
| 112 | if (line.empty()) { | ||
| 113 | continue; | ||
| 114 | } | ||
| 115 | if (line == "A") { | ||
| 116 | sequence.npad.a.Assign(1); | ||
| 117 | } | ||
| 118 | if (line == "B") { | ||
| 119 | sequence.npad.b.Assign(1); | ||
| 120 | } | ||
| 121 | if (line == "X") { | ||
| 122 | sequence.npad.x.Assign(1); | ||
| 123 | } | ||
| 124 | if (line == "Y") { | ||
| 125 | sequence.npad.y.Assign(1); | ||
| 126 | } | ||
| 127 | if (line == "L") { | ||
| 128 | sequence.npad.l.Assign(1); | ||
| 129 | } | ||
| 130 | if (line == "R") { | ||
| 131 | sequence.npad.r.Assign(1); | ||
| 132 | } | ||
| 133 | if (line == "ZL") { | ||
| 134 | sequence.npad.zl.Assign(1); | ||
| 135 | } | ||
| 136 | if (line == "ZR") { | ||
| 137 | sequence.npad.zr.Assign(1); | ||
| 138 | } | ||
| 139 | if (line == "Dpad_Left") { | ||
| 140 | sequence.npad.left.Assign(1); | ||
| 141 | } | ||
| 142 | if (line == "Dpad_Right") { | ||
| 143 | sequence.npad.right.Assign(1); | ||
| 144 | } | ||
| 145 | if (line == "Dpad_Up") { | ||
| 146 | sequence.npad.up.Assign(1); | ||
| 147 | } | ||
| 148 | if (line == "Dpad_Down") { | ||
| 149 | sequence.npad.down.Assign(1); | ||
| 150 | } | ||
| 151 | if (line == "Left_Stick") { | ||
| 152 | sequence.npad.stick_l.Assign(1); | ||
| 153 | } | ||
| 154 | if (line == "Right_Stick") { | ||
| 155 | sequence.npad.stick_r.Assign(1); | ||
| 156 | } | ||
| 157 | if (line == "Minus") { | ||
| 158 | sequence.npad.minus.Assign(1); | ||
| 159 | } | ||
| 160 | if (line == "Plus") { | ||
| 161 | sequence.npad.plus.Assign(1); | ||
| 162 | } | ||
| 163 | if (line == "Home") { | ||
| 164 | sequence.home.home.Assign(1); | ||
| 165 | } | ||
| 166 | if (line == "Screenshot") { | ||
| 167 | sequence.capture.capture.Assign(1); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | button_sequence = sequence; | ||
| 172 | } | ||
| 173 | |||
| 174 | ControllerButtonSequence ControllerShortcut::ButtonSequence() const { | ||
| 175 | return button_sequence; | ||
| 176 | } | ||
| 177 | |||
| 178 | void ControllerShortcut::SetEnabled(bool enable) { | ||
| 179 | is_enabled = enable; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool ControllerShortcut::IsEnabled() const { | ||
| 183 | return is_enabled; | ||
| 184 | } | ||
| 185 | |||
| 186 | void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) { | ||
| 187 | if (!is_enabled) { | ||
| 188 | return; | ||
| 189 | } | ||
| 190 | if (type != Core::HID::ControllerTriggerType::Button) { | ||
| 191 | return; | ||
| 192 | } | ||
| 193 | if (button_sequence.npad.raw == Core::HID::NpadButton::None && | ||
| 194 | button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) { | ||
| 195 | return; | ||
| 196 | } | ||
| 197 | |||
| 198 | const auto player_npad_buttons = | ||
| 199 | emulated_controller->GetNpadButtons().raw & button_sequence.npad.raw; | ||
| 200 | const u64 player_capture_buttons = | ||
| 201 | emulated_controller->GetCaptureButtons().raw & button_sequence.capture.raw; | ||
| 202 | const u64 player_home_buttons = | ||
| 203 | emulated_controller->GetHomeButtons().raw & button_sequence.home.raw; | ||
| 204 | |||
| 205 | if (player_npad_buttons == button_sequence.npad.raw && | ||
| 206 | player_capture_buttons == button_sequence.capture.raw && | ||
| 207 | player_home_buttons == button_sequence.home.raw && !active) { | ||
| 208 | // Force user to press the home or capture button again | ||
| 209 | active = true; | ||
| 210 | emit Activated(); | ||
| 211 | return; | ||
| 212 | } | ||
| 213 | active = false; | ||
| 214 | } | ||
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index 248fadaf3..57a7c7da5 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h | |||
| @@ -5,11 +5,53 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <map> | 7 | #include <map> |
| 8 | #include "core/hid/hid_types.h" | ||
| 8 | 9 | ||
| 9 | class QDialog; | 10 | class QDialog; |
| 10 | class QKeySequence; | 11 | class QKeySequence; |
| 11 | class QSettings; | 12 | class QSettings; |
| 12 | class QShortcut; | 13 | class QShortcut; |
| 14 | class ControllerShortcut; | ||
| 15 | |||
| 16 | namespace Core::HID { | ||
| 17 | enum class ControllerTriggerType; | ||
| 18 | class EmulatedController; | ||
| 19 | } // namespace Core::HID | ||
| 20 | |||
| 21 | struct ControllerButtonSequence { | ||
| 22 | Core::HID::CaptureButtonState capture{}; | ||
| 23 | Core::HID::HomeButtonState home{}; | ||
| 24 | Core::HID::NpadButtonState npad{}; | ||
| 25 | }; | ||
| 26 | |||
| 27 | class ControllerShortcut : public QObject { | ||
| 28 | Q_OBJECT | ||
| 29 | |||
| 30 | public: | ||
| 31 | explicit ControllerShortcut(Core::HID::EmulatedController* controller); | ||
| 32 | ~ControllerShortcut(); | ||
| 33 | |||
| 34 | void SetKey(const ControllerButtonSequence& buttons); | ||
| 35 | void SetKey(const QString& buttons_shortcut); | ||
| 36 | |||
| 37 | ControllerButtonSequence ButtonSequence() const; | ||
| 38 | |||
| 39 | void SetEnabled(bool enable); | ||
| 40 | bool IsEnabled() const; | ||
| 41 | |||
| 42 | Q_SIGNALS: | ||
| 43 | void Activated(); | ||
| 44 | |||
| 45 | private: | ||
| 46 | void ControllerUpdateEvent(Core::HID::ControllerTriggerType type); | ||
| 47 | |||
| 48 | bool is_enabled{}; | ||
| 49 | bool active{}; | ||
| 50 | int callback_key{}; | ||
| 51 | ControllerButtonSequence button_sequence{}; | ||
| 52 | std::string name{}; | ||
| 53 | Core::HID::EmulatedController* emulated_controller = nullptr; | ||
| 54 | }; | ||
| 13 | 55 | ||
| 14 | class HotkeyRegistry final { | 56 | class HotkeyRegistry final { |
| 15 | public: | 57 | public: |
| @@ -46,6 +88,8 @@ public: | |||
| 46 | * QShortcut's parent. | 88 | * QShortcut's parent. |
| 47 | */ | 89 | */ |
| 48 | QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); | 90 | QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); |
| 91 | ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action, | ||
| 92 | Core::HID::EmulatedController* controller); | ||
| 49 | 93 | ||
| 50 | /** | 94 | /** |
| 51 | * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut. | 95 | * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut. |
| @@ -68,7 +112,9 @@ public: | |||
| 68 | private: | 112 | private: |
| 69 | struct Hotkey { | 113 | struct Hotkey { |
| 70 | QKeySequence keyseq; | 114 | QKeySequence keyseq; |
| 115 | QString controller_keyseq; | ||
| 71 | QShortcut* shortcut = nullptr; | 116 | QShortcut* shortcut = nullptr; |
| 117 | ControllerShortcut* controller_shortcut = nullptr; | ||
| 72 | Qt::ShortcutContext context = Qt::WindowShortcut; | 118 | Qt::ShortcutContext context = Qt::WindowShortcut; |
| 73 | }; | 119 | }; |
| 74 | 120 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 53f11a9ac..e8a4ac918 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include "core/hle/service/am/applet_ae.h" | 32 | #include "core/hle/service/am/applet_ae.h" |
| 33 | #include "core/hle/service/am/applet_oe.h" | 33 | #include "core/hle/service/am/applet_oe.h" |
| 34 | #include "core/hle/service/am/applets/applets.h" | 34 | #include "core/hle/service/am/applets/applets.h" |
| 35 | #include "yuzu/util/controller_navigation.h" | ||
| 35 | 36 | ||
| 36 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows | 37 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows |
| 37 | // defines. | 38 | // defines. |
| @@ -966,6 +967,12 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name | |||
| 966 | action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); | 967 | action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); |
| 967 | 968 | ||
| 968 | this->addAction(action); | 969 | this->addAction(action); |
| 970 | |||
| 971 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 972 | const auto* controller_hotkey = | ||
| 973 | hotkey_registry.GetControllerHotkey(main_window, action_name, controller); | ||
| 974 | connect(controller_hotkey, &ControllerShortcut::Activated, this, | ||
| 975 | [action] { action->trigger(); }); | ||
| 969 | } | 976 | } |
| 970 | 977 | ||
| 971 | void GMainWindow::InitializeHotkeys() { | 978 | void GMainWindow::InitializeHotkeys() { |
| @@ -987,8 +994,12 @@ void GMainWindow::InitializeHotkeys() { | |||
| 987 | 994 | ||
| 988 | static const QString main_window = QStringLiteral("Main Window"); | 995 | static const QString main_window = QStringLiteral("Main Window"); |
| 989 | const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { | 996 | const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { |
| 990 | const QShortcut* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); | 997 | const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); |
| 998 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 999 | const auto* controller_hotkey = | ||
| 1000 | hotkey_registry.GetControllerHotkey(main_window, action_name, controller); | ||
| 991 | connect(hotkey, &QShortcut::activated, this, function); | 1001 | connect(hotkey, &QShortcut::activated, this, function); |
| 1002 | connect(controller_hotkey, &ControllerShortcut::Activated, this, function); | ||
| 992 | }; | 1003 | }; |
| 993 | 1004 | ||
| 994 | connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] { | 1005 | connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] { |
| @@ -1165,8 +1176,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1165 | connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode); | 1176 | connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode); |
| 1166 | connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars); | 1177 | connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars); |
| 1167 | connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar); | 1178 | connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar); |
| 1168 | 1179 | connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar); | |
| 1169 | connect(ui->action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | ||
| 1170 | 1180 | ||
| 1171 | connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720); | 1181 | connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720); |
| 1172 | connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900); | 1182 | connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900); |
| @@ -2168,6 +2178,11 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 2168 | } | 2178 | } |
| 2169 | 2179 | ||
| 2170 | void GMainWindow::OnMenuLoadFile() { | 2180 | void GMainWindow::OnMenuLoadFile() { |
| 2181 | if (is_load_file_select_active) { | ||
| 2182 | return; | ||
| 2183 | } | ||
| 2184 | |||
| 2185 | is_load_file_select_active = true; | ||
| 2171 | const QString extensions = | 2186 | const QString extensions = |
| 2172 | QStringLiteral("*.") | 2187 | QStringLiteral("*.") |
| 2173 | .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) | 2188 | .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) |
| @@ -2177,6 +2192,7 @@ void GMainWindow::OnMenuLoadFile() { | |||
| 2177 | .arg(extensions); | 2192 | .arg(extensions); |
| 2178 | const QString filename = QFileDialog::getOpenFileName( | 2193 | const QString filename = QFileDialog::getOpenFileName( |
| 2179 | this, tr("Load File"), UISettings::values.roms_path, file_filter); | 2194 | this, tr("Load File"), UISettings::values.roms_path, file_filter); |
| 2195 | is_load_file_select_active = false; | ||
| 2180 | 2196 | ||
| 2181 | if (filename.isEmpty()) { | 2197 | if (filename.isEmpty()) { |
| 2182 | return; | 2198 | return; |
| @@ -2809,6 +2825,11 @@ void GMainWindow::OnTasStartStop() { | |||
| 2809 | if (!emulation_running) { | 2825 | if (!emulation_running) { |
| 2810 | return; | 2826 | return; |
| 2811 | } | 2827 | } |
| 2828 | |||
| 2829 | // Disable system buttons to prevent TAS from executing a hotkey | ||
| 2830 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 2831 | controller->ResetSystemButtons(); | ||
| 2832 | |||
| 2812 | input_subsystem->GetTas()->StartStop(); | 2833 | input_subsystem->GetTas()->StartStop(); |
| 2813 | OnTasStateChanged(); | 2834 | OnTasStateChanged(); |
| 2814 | } | 2835 | } |
| @@ -2817,12 +2838,34 @@ void GMainWindow::OnTasRecord() { | |||
| 2817 | if (!emulation_running) { | 2838 | if (!emulation_running) { |
| 2818 | return; | 2839 | return; |
| 2819 | } | 2840 | } |
| 2841 | if (is_tas_recording_dialog_active) { | ||
| 2842 | return; | ||
| 2843 | } | ||
| 2844 | |||
| 2845 | // Disable system buttons to prevent TAS from recording a hotkey | ||
| 2846 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 2847 | controller->ResetSystemButtons(); | ||
| 2848 | |||
| 2820 | const bool is_recording = input_subsystem->GetTas()->Record(); | 2849 | const bool is_recording = input_subsystem->GetTas()->Record(); |
| 2821 | if (!is_recording) { | 2850 | if (!is_recording) { |
| 2822 | const auto res = | 2851 | is_tas_recording_dialog_active = true; |
| 2823 | QMessageBox::question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), | 2852 | ControllerNavigation* controller_navigation = |
| 2824 | QMessageBox::Yes | QMessageBox::No); | 2853 | new ControllerNavigation(system->HIDCore(), this); |
| 2854 | // Use QMessageBox instead of question so we can link controller navigation | ||
| 2855 | QMessageBox* box_dialog = new QMessageBox(); | ||
| 2856 | box_dialog->setWindowTitle(tr("TAS Recording")); | ||
| 2857 | box_dialog->setText(tr("Overwrite file of player 1?")); | ||
| 2858 | box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); | ||
| 2859 | box_dialog->setDefaultButton(QMessageBox::Yes); | ||
| 2860 | connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, | ||
| 2861 | [box_dialog](Qt::Key key) { | ||
| 2862 | QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); | ||
| 2863 | QCoreApplication::postEvent(box_dialog, event); | ||
| 2864 | }); | ||
| 2865 | int res = box_dialog->exec(); | ||
| 2866 | controller_navigation->UnloadController(); | ||
| 2825 | input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); | 2867 | input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); |
| 2868 | is_tas_recording_dialog_active = false; | ||
| 2826 | } | 2869 | } |
| 2827 | OnTasStateChanged(); | 2870 | OnTasStateChanged(); |
| 2828 | } | 2871 | } |
| @@ -2871,10 +2914,15 @@ void GMainWindow::OnLoadAmiibo() { | |||
| 2871 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 2914 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 2872 | return; | 2915 | return; |
| 2873 | } | 2916 | } |
| 2917 | if (is_amiibo_file_select_active) { | ||
| 2918 | return; | ||
| 2919 | } | ||
| 2874 | 2920 | ||
| 2921 | is_amiibo_file_select_active = true; | ||
| 2875 | const QString extensions{QStringLiteral("*.bin")}; | 2922 | const QString extensions{QStringLiteral("*.bin")}; |
| 2876 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); | 2923 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); |
| 2877 | const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter); | 2924 | const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter); |
| 2925 | is_amiibo_file_select_active = false; | ||
| 2878 | 2926 | ||
| 2879 | if (filename.isEmpty()) { | 2927 | if (filename.isEmpty()) { |
| 2880 | return; | 2928 | return; |
| @@ -2934,6 +2982,10 @@ void GMainWindow::OnToggleFilterBar() { | |||
| 2934 | } | 2982 | } |
| 2935 | } | 2983 | } |
| 2936 | 2984 | ||
| 2985 | void GMainWindow::OnToggleStatusBar() { | ||
| 2986 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); | ||
| 2987 | } | ||
| 2988 | |||
| 2937 | void GMainWindow::OnCaptureScreenshot() { | 2989 | void GMainWindow::OnCaptureScreenshot() { |
| 2938 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 2990 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 2939 | return; | 2991 | return; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7870bb963..ca4ab9af5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -186,6 +186,9 @@ public slots: | |||
| 186 | void OnTasStateChanged(); | 186 | void OnTasStateChanged(); |
| 187 | 187 | ||
| 188 | private: | 188 | private: |
| 189 | /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. | ||
| 190 | void LinkActionShortcut(QAction* action, const QString& action_name); | ||
| 191 | |||
| 189 | void RegisterMetaTypes(); | 192 | void RegisterMetaTypes(); |
| 190 | 193 | ||
| 191 | void InitializeWidgets(); | 194 | void InitializeWidgets(); |
| @@ -286,6 +289,7 @@ private slots: | |||
| 286 | void OnOpenYuzuFolder(); | 289 | void OnOpenYuzuFolder(); |
| 287 | void OnAbout(); | 290 | void OnAbout(); |
| 288 | void OnToggleFilterBar(); | 291 | void OnToggleFilterBar(); |
| 292 | void OnToggleStatusBar(); | ||
| 289 | void OnDisplayTitleBars(bool); | 293 | void OnDisplayTitleBars(bool); |
| 290 | void InitializeHotkeys(); | 294 | void InitializeHotkeys(); |
| 291 | void ToggleFullscreen(); | 295 | void ToggleFullscreen(); |
| @@ -303,9 +307,6 @@ private slots: | |||
| 303 | void OnMouseActivity(); | 307 | void OnMouseActivity(); |
| 304 | 308 | ||
| 305 | private: | 309 | private: |
| 306 | /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. | ||
| 307 | void LinkActionShortcut(QAction* action, const QString& action_name); | ||
| 308 | |||
| 309 | void RemoveBaseContent(u64 program_id, const QString& entry_type); | 310 | void RemoveBaseContent(u64 program_id, const QString& entry_type); |
| 310 | void RemoveUpdateContent(u64 program_id, const QString& entry_type); | 311 | void RemoveUpdateContent(u64 program_id, const QString& entry_type); |
| 311 | void RemoveAddOnContent(u64 program_id, const QString& entry_type); | 312 | void RemoveAddOnContent(u64 program_id, const QString& entry_type); |
| @@ -400,6 +401,16 @@ private: | |||
| 400 | 401 | ||
| 401 | // Applets | 402 | // Applets |
| 402 | QtSoftwareKeyboardDialog* software_keyboard = nullptr; | 403 | QtSoftwareKeyboardDialog* software_keyboard = nullptr; |
| 404 | |||
| 405 | // True if amiibo file select is visible | ||
| 406 | bool is_amiibo_file_select_active{}; | ||
| 407 | |||
| 408 | // True if load file select is visible | ||
| 409 | bool is_load_file_select_active{}; | ||
| 410 | |||
| 411 | // True if TAS recording dialog is visible | ||
| 412 | bool is_tas_recording_dialog_active{}; | ||
| 413 | |||
| 403 | #ifdef __linux__ | 414 | #ifdef __linux__ |
| 404 | QDBusObjectPath wake_lock{}; | 415 | QDBusObjectPath wake_lock{}; |
| 405 | #endif | 416 | #endif |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index a610e7e25..402c4556d 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -17,7 +17,11 @@ | |||
| 17 | 17 | ||
| 18 | namespace UISettings { | 18 | namespace UISettings { |
| 19 | 19 | ||
| 20 | using ContextualShortcut = std::pair<QString, int>; | 20 | struct ContextualShortcut { |
| 21 | QString keyseq; | ||
| 22 | QString controller_keyseq; | ||
| 23 | int context; | ||
| 24 | }; | ||
| 21 | 25 | ||
| 22 | struct Shortcut { | 26 | struct Shortcut { |
| 23 | QString name; | 27 | QString name; |