diff options
Diffstat (limited to 'src')
202 files changed, 5010 insertions, 1649 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9182dbfd4..39d038493 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,10 @@ if (MSVC) /we4305 # 'context': truncation from 'type1' to 'type2' /we4388 # 'expression': signed/unsigned mismatch /we4389 # 'operator': signed/unsigned mismatch + /we4456 # Declaration of 'identifier' hides previous local declaration + /we4457 # Declaration of 'identifier' hides function parameter + /we4458 # Declaration of 'identifier' hides class member + /we4459 # Declaration of 'identifier' hides global declaration /we4505 # 'function': unreferenced local function has been removed /we4547 # 'operator': operator before comma has no effect; expected operator with side-effect /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'? @@ -92,6 +96,7 @@ else() -Werror=missing-declarations -Werror=missing-field-initializers -Werror=reorder + -Werror=shadow -Werror=sign-compare -Werror=switch -Werror=uninitialized diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index e553b8203..89575a53e 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -49,9 +49,6 @@ if (NOT MSVC) target_compile_options(audio_core PRIVATE -Werror=conversion -Werror=ignored-qualifiers - -Werror=shadow - -Werror=unused-parameter - -Werror=unused-variable $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index ae4efafb6..f97520820 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -129,17 +129,17 @@ s32 ToS32(float sample) { return static_cast<s32>(rescaled_sample); } -constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +constexpr std::array<u8, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; +constexpr std::array<u8, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; -constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; +constexpr std::array<u8, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; -constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; +constexpr std::array<u8, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; template <std::size_t CHANNEL_COUNT> void ApplyReverbGeneric( @@ -429,7 +429,7 @@ void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, Vo in_params.node_id); break; default: - UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); } } } @@ -1312,7 +1312,7 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::s samples_to_read - samples_read, channel, temp_mix_offset); break; default: - UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); } temp_mix_offset += samples_decoded; diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index 51059580e..79bcd1192 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -50,7 +50,7 @@ EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { effects[i] = std::make_unique<EffectBiquadFilter>(); break; default: - UNREACHABLE_MSG("Unimplemented effect {}", effect); + ASSERT_MSG(false, "Unimplemented effect {}", effect); effects[i] = std::make_unique<EffectStubbed>(); } return GetInfo(i); @@ -104,7 +104,7 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { auto& params = GetParams(); const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data()); if (!ValidChannelCountForEffect(reverb_params->max_channels)) { - UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels); + ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels); return; } diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp index 1751d0212..10646dc05 100644 --- a/src/audio_core/splitter_context.cpp +++ b/src/audio_core/splitter_context.cpp @@ -483,7 +483,7 @@ bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { // Add more work index_stack.push(j); } else if (node_state == NodeStates::State::InFound) { - UNREACHABLE_MSG("Node start marked as found"); + ASSERT_MSG(false, "Node start marked as found"); ResetState(); return false; } diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index c8e4a6caf..f58a5c754 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp @@ -114,7 +114,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, in_params.current_playstate = ServerPlayState::Play; break; default: - UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state); + ASSERT_MSG(false, "Unknown playstate {}", voice_in.play_state); break; } @@ -410,7 +410,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration( return in_params.should_depop; } default: - UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate); + ASSERT_MSG(false, "Invalid playstate {}", in_params.current_playstate); } return false; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index adf70eb8b..73bf626d4 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(common STATIC div_ceil.h dynamic_library.cpp dynamic_library.h + elf.h error.cpp error.h expected.h diff --git a/src/common/assert.cpp b/src/common/assert.cpp index b44570528..6026b7dc2 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -6,8 +6,13 @@ #include "common/settings.h" -void assert_handle_failure() { +void assert_fail_impl() { if (Settings::values.use_debug_asserts) { Crash(); } } + +[[noreturn]] void unreachable_impl() { + Crash(); + throw std::runtime_error("Unreachable code"); +} diff --git a/src/common/assert.h b/src/common/assert.h index dbfd8abaf..8c927fcc0 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -9,44 +9,43 @@ // Sometimes we want to try to continue even after hitting an assert. // However touching this file yields a global recompilation as this header is included almost // everywhere. So let's just move the handling of the failed assert to a single cpp file. -void assert_handle_failure(); -// For asserts we'd like to keep all the junk executed when an assert happens away from the -// important code in the function. One way of doing this is to put all the relevant code inside a -// lambda and force the compiler to not inline it. Unfortunately, MSVC seems to have no syntax to -// specify __declspec on lambda functions, so what we do instead is define a noinline wrapper -// template that calls the lambda. This seems to generate an extra instruction at the call-site -// compared to the ideal implementation (which wouldn't support ASSERT_MSG parameters), but is good -// enough for our purposes. -template <typename Fn> -#if defined(_MSC_VER) -[[msvc::noinline]] -#elif defined(__GNUC__) -[[gnu::cold, gnu::noinline]] +void assert_fail_impl(); +[[noreturn]] void unreachable_impl(); + +#ifdef _MSC_VER +#define YUZU_NO_INLINE __declspec(noinline) +#else +#define YUZU_NO_INLINE __attribute__((noinline)) #endif -static void -assert_noinline_call(const Fn& fn) { - fn(); - assert_handle_failure(); -} #define ASSERT(_a_) \ - do \ - if (!(_a_)) { \ - assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \ + ([&]() YUZU_NO_INLINE { \ + if (!(_a_)) [[unlikely]] { \ + LOG_CRITICAL(Debug, "Assertion Failed!"); \ + assert_fail_impl(); \ } \ - while (0) + }()) #define ASSERT_MSG(_a_, ...) \ - do \ - if (!(_a_)) { \ - assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \ + ([&]() YUZU_NO_INLINE { \ + if (!(_a_)) [[unlikely]] { \ + LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ + assert_fail_impl(); \ } \ - while (0) + }()) + +#define UNREACHABLE() \ + do { \ + LOG_CRITICAL(Debug, "Unreachable code!"); \ + unreachable_impl(); \ + } while (0) -#define UNREACHABLE() assert_noinline_call([] { LOG_CRITICAL(Debug, "Unreachable code!"); }) #define UNREACHABLE_MSG(...) \ - assert_noinline_call([&] { LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); }) + do { \ + LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \ + unreachable_impl(); \ + } while (0) #ifdef _DEBUG #define DEBUG_ASSERT(_a_) ASSERT(_a_) diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h new file mode 100644 index 000000000..e83064c7f --- /dev/null +++ b/src/common/bounded_threadsafe_queue.h @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se> +// SPDX-License-Identifier: MIT +#pragma once +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4324) +#endif + +#include <atomic> +#include <bit> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <new> +#include <stdexcept> +#include <stop_token> +#include <type_traits> +#include <utility> + +namespace Common { +namespace mpsc { +#if defined(__cpp_lib_hardware_interference_size) +constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size; +#else +constexpr size_t hardware_interference_size = 64; +#endif + +template <typename T> +using AlignedAllocator = std::allocator<T>; + +template <typename T> +struct Slot { + ~Slot() noexcept { + if (turn.test()) { + destroy(); + } + } + + template <typename... Args> + void construct(Args&&... args) noexcept { + static_assert(std::is_nothrow_constructible_v<T, Args&&...>, + "T must be nothrow constructible with Args&&..."); + std::construct_at(reinterpret_cast<T*>(&storage), std::forward<Args>(args)...); + } + + void destroy() noexcept { + static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible"); + std::destroy_at(reinterpret_cast<T*>(&storage)); + } + + T&& move() noexcept { + return reinterpret_cast<T&&>(storage); + } + + // Align to avoid false sharing between adjacent slots + alignas(hardware_interference_size) std::atomic_flag turn{}; + struct aligned_store { + struct type { + alignas(T) unsigned char data[sizeof(T)]; + }; + }; + typename aligned_store::type storage; +}; + +template <typename T, typename Allocator = AlignedAllocator<Slot<T>>> +class Queue { +public: + explicit Queue(const size_t capacity, const Allocator& allocator = Allocator()) + : allocator_(allocator) { + if (capacity < 1) { + throw std::invalid_argument("capacity < 1"); + } + // Ensure that the queue length is an integer power of 2 + // This is so that idx(i) can be a simple i & mask_ insted of i % capacity + // https://github.com/rigtorp/MPMCQueue/pull/36 + if (!std::has_single_bit(capacity)) { + throw std::invalid_argument("capacity must be an integer power of 2"); + } + + mask_ = capacity - 1; + + // Allocate one extra slot to prevent false sharing on the last slot + slots_ = allocator_.allocate(mask_ + 2); + // Allocators are not required to honor alignment for over-aligned types + // (see http://eel.is/c++draft/allocator.requirements#10) so we verify + // alignment here + if (reinterpret_cast<uintptr_t>(slots_) % alignof(Slot<T>) != 0) { + allocator_.deallocate(slots_, mask_ + 2); + throw std::bad_alloc(); + } + for (size_t i = 0; i < mask_ + 1; ++i) { + std::construct_at(&slots_[i]); + } + static_assert(alignof(Slot<T>) == hardware_interference_size, + "Slot must be aligned to cache line boundary to prevent false sharing"); + static_assert(sizeof(Slot<T>) % hardware_interference_size == 0, + "Slot size must be a multiple of cache line size to prevent " + "false sharing between adjacent slots"); + static_assert(sizeof(Queue) % hardware_interference_size == 0, + "Queue size must be a multiple of cache line size to " + "prevent false sharing between adjacent queues"); + } + + ~Queue() noexcept { + for (size_t i = 0; i < mask_ + 1; ++i) { + slots_[i].~Slot(); + } + allocator_.deallocate(slots_, mask_ + 2); + } + + // non-copyable and non-movable + Queue(const Queue&) = delete; + Queue& operator=(const Queue&) = delete; + + void Push(const T& v) noexcept { + static_assert(std::is_nothrow_copy_constructible_v<T>, + "T must be nothrow copy constructible"); + emplace(v); + } + + template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>> + void Push(P&& v) noexcept { + emplace(std::forward<P>(v)); + } + + void Pop(T& v, std::stop_token stop) noexcept { + auto const tail = tail_.fetch_add(1); + auto& slot = slots_[idx(tail)]; + if (false == slot.turn.test()) { + std::unique_lock lock{cv_mutex}; + cv.wait(lock, stop, [&slot] { return slot.turn.test(); }); + } + v = slot.move(); + slot.destroy(); + slot.turn.clear(); + slot.turn.notify_one(); + } + +private: + template <typename... Args> + void emplace(Args&&... args) noexcept { + static_assert(std::is_nothrow_constructible_v<T, Args&&...>, + "T must be nothrow constructible with Args&&..."); + auto const head = head_.fetch_add(1); + auto& slot = slots_[idx(head)]; + slot.turn.wait(true); + slot.construct(std::forward<Args>(args)...); + slot.turn.test_and_set(); + cv.notify_one(); + } + + constexpr size_t idx(size_t i) const noexcept { + return i & mask_; + } + + std::conditional_t<true, std::condition_variable_any, std::condition_variable> cv; + std::mutex cv_mutex; + size_t mask_; + Slot<T>* slots_; + [[no_unique_address]] Allocator allocator_; + + // Align to avoid false sharing between head_ and tail_ + alignas(hardware_interference_size) std::atomic<size_t> head_{0}; + alignas(hardware_interference_size) std::atomic<size_t> tail_{0}; + + static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>, + "T must be nothrow copy or move assignable"); + + static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible"); +}; +} // namespace mpsc + +template <typename T, typename Allocator = mpsc::AlignedAllocator<mpsc::Slot<T>>> +using MPSCQueue = mpsc::Queue<T, Allocator>; + +} // namespace Common + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp index c1362631e..ec31d0b88 100644 --- a/src/common/detached_tasks.cpp +++ b/src/common/detached_tasks.cpp @@ -33,9 +33,9 @@ void DetachedTasks::AddTask(std::function<void()> task) { ++instance->count; std::thread([task{std::move(task)}]() { task(); - std::unique_lock lock{instance->mutex}; + std::unique_lock thread_lock{instance->mutex}; --instance->count; - std::notify_all_at_thread_exit(instance->cv, std::move(lock)); + std::notify_all_at_thread_exit(instance->cv, std::move(thread_lock)); }).detach(); } diff --git a/src/common/elf.h b/src/common/elf.h new file mode 100644 index 000000000..14a5e9597 --- /dev/null +++ b/src/common/elf.h @@ -0,0 +1,333 @@ +// SPDX-FileCopyrightText: 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <cstddef> + +#include "common_types.h" + +namespace Common { +namespace ELF { + +/* Type for a 16-bit quantity. */ +using Elf32_Half = u16; +using Elf64_Half = u16; + +/* Types for signed and unsigned 32-bit quantities. */ +using Elf32_Word = u32; +using Elf32_Sword = s32; +using Elf64_Word = u32; +using Elf64_Sword = s32; + +/* Types for signed and unsigned 64-bit quantities. */ +using Elf32_Xword = u64; +using Elf32_Sxword = s64; +using Elf64_Xword = u64; +using Elf64_Sxword = s64; + +/* Type of addresses. */ +using Elf32_Addr = u32; +using Elf64_Addr = u64; + +/* Type of file offsets. */ +using Elf32_Off = u32; +using Elf64_Off = u64; + +/* Type for section indices, which are 16-bit quantities. */ +using Elf32_Section = u16; +using Elf64_Section = u16; + +/* Type for version symbol information. */ +using Elf32_Versym = Elf32_Half; +using Elf64_Versym = Elf64_Half; + +constexpr size_t ElfIdentSize = 16; + +/* The ELF file header. This appears at the start of every ELF file. */ + +struct Elf32_Ehdr { + std::array<u8, ElfIdentSize> e_ident; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +}; + +struct Elf64_Ehdr { + std::array<u8, ElfIdentSize> e_ident; /* Magic number and other info */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Architecture */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size in bytes */ + Elf64_Half e_phentsize; /* Program header table entry size */ + Elf64_Half e_phnum; /* Program header table entry count */ + Elf64_Half e_shentsize; /* Section header table entry size */ + Elf64_Half e_shnum; /* Section header table entry count */ + Elf64_Half e_shstrndx; /* Section header string table index */ +}; + +constexpr u8 ElfClass32 = 1; /* 32-bit objects */ +constexpr u8 ElfClass64 = 2; /* 64-bit objects */ +constexpr u8 ElfData2Lsb = 1; /* 2's complement, little endian */ +constexpr u8 ElfVersionCurrent = 1; /* EV_CURRENT */ +constexpr u8 ElfOsAbiNone = 0; /* System V ABI */ + +constexpr u16 ElfTypeNone = 0; /* No file type */ +constexpr u16 ElfTypeRel = 0; /* Relocatable file */ +constexpr u16 ElfTypeExec = 0; /* Executable file */ +constexpr u16 ElfTypeDyn = 0; /* Shared object file */ + +constexpr u16 ElfMachineArm = 40; /* ARM */ +constexpr u16 ElfMachineAArch64 = 183; /* ARM AARCH64 */ + +constexpr std::array<u8, ElfIdentSize> Elf32Ident{ + 0x7f, 'E', 'L', 'F', ElfClass32, ElfData2Lsb, ElfVersionCurrent, ElfOsAbiNone}; + +constexpr std::array<u8, ElfIdentSize> Elf64Ident{ + 0x7f, 'E', 'L', 'F', ElfClass64, ElfData2Lsb, ElfVersionCurrent, ElfOsAbiNone}; + +/* Section header. */ + +struct Elf32_Shdr { + Elf32_Word sh_name; /* Section name (string tbl index) */ + Elf32_Word sh_type; /* Section type */ + Elf32_Word sh_flags; /* Section flags */ + Elf32_Addr sh_addr; /* Section virtual addr at execution */ + Elf32_Off sh_offset; /* Section file offset */ + Elf32_Word sh_size; /* Section size in bytes */ + Elf32_Word sh_link; /* Link to another section */ + Elf32_Word sh_info; /* Additional section information */ + Elf32_Word sh_addralign; /* Section alignment */ + Elf32_Word sh_entsize; /* Entry size if section holds table */ +}; + +struct Elf64_Shdr { + Elf64_Word sh_name; /* Section name (string tbl index) */ + Elf64_Word sh_type; /* Section type */ + Elf64_Xword sh_flags; /* Section flags */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Section size in bytes */ + Elf64_Word sh_link; /* Link to another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +}; + +constexpr u32 ElfShnUndef = 0; /* Undefined section */ + +constexpr u32 ElfShtNull = 0; /* Section header table entry unused */ +constexpr u32 ElfShtProgBits = 1; /* Program data */ +constexpr u32 ElfShtSymtab = 2; /* Symbol table */ +constexpr u32 ElfShtStrtab = 3; /* String table */ +constexpr u32 ElfShtRela = 4; /* Relocation entries with addends */ +constexpr u32 ElfShtDynamic = 6; /* Dynamic linking information */ +constexpr u32 ElfShtNobits = 7; /* Program space with no data (bss) */ +constexpr u32 ElfShtRel = 9; /* Relocation entries, no addends */ +constexpr u32 ElfShtDynsym = 11; /* Dynamic linker symbol table */ + +/* Symbol table entry. */ + +struct Elf32_Sym { + Elf32_Word st_name; /* Symbol name (string tbl index) */ + Elf32_Addr st_value; /* Symbol value */ + Elf32_Word st_size; /* Symbol size */ + u8 st_info; /* Symbol type and binding */ + u8 st_other; /* Symbol visibility */ + Elf32_Section st_shndx; /* Section index */ +}; + +struct Elf64_Sym { + Elf64_Word st_name; /* Symbol name (string tbl index) */ + u8 st_info; /* Symbol type and binding */ + u8 st_other; /* Symbol visibility */ + Elf64_Section st_shndx; /* Section index */ + Elf64_Addr st_value; /* Symbol value */ + Elf64_Xword st_size; /* Symbol size */ +}; + +/* How to extract and insert information held in the st_info field. */ + +static inline u8 ElfStBind(u8 st_info) { + return st_info >> 4; +} +static inline u8 ElfStType(u8 st_info) { + return st_info & 0xf; +} +static inline u8 ElfStInfo(u8 st_bind, u8 st_type) { + return static_cast<u8>((st_bind << 4) + (st_type & 0xf)); +} + +constexpr u8 ElfBindLocal = 0; /* Local symbol */ +constexpr u8 ElfBindGlobal = 1; /* Global symbol */ +constexpr u8 ElfBindWeak = 2; /* Weak symbol */ + +constexpr u8 ElfTypeUnspec = 0; /* Symbol type is unspecified */ +constexpr u8 ElfTypeObject = 1; /* Symbol is a data object */ +constexpr u8 ElfTypeFunc = 2; /* Symbol is a code object */ + +static inline u8 ElfStVisibility(u8 st_other) { + return static_cast<u8>(st_other & 0x3); +} + +constexpr u8 ElfVisibilityDefault = 0; /* Default symbol visibility rules */ +constexpr u8 ElfVisibilityInternal = 1; /* Processor specific hidden class */ +constexpr u8 ElfVisibilityHidden = 2; /* Sym unavailable in other modules */ +constexpr u8 ElfVisibilityProtected = 3; /* Not preemptible, not exported */ + +/* Relocation table entry without addend (in section of type ShtRel). */ + +struct Elf32_Rel { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ +}; + +/* Relocation table entry with addend (in section of type ShtRela). */ + +struct Elf32_Rela { + Elf32_Addr r_offset; /* Address */ + Elf32_Word r_info; /* Relocation type and symbol index */ + Elf32_Sword r_addend; /* Addend */ +}; + +struct Elf64_Rela { + Elf64_Addr r_offset; /* Address */ + Elf64_Xword r_info; /* Relocation type and symbol index */ + Elf64_Sxword r_addend; /* Addend */ +}; + +/* How to extract and insert information held in the r_info field. */ + +static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { + return r_info >> 8; +} +static inline u8 Elf32RelType(Elf32_Word r_info) { + return static_cast<u8>(r_info & 0xff); +} +static inline Elf32_Word Elf32RelInfo(u32 sym_index, u8 type) { + return (sym_index << 8) + type; +} +static inline u32 Elf64RelSymIndex(Elf64_Xword r_info) { + return static_cast<u32>(r_info >> 32); +} +static inline u32 Elf64RelType(Elf64_Xword r_info) { + return r_info & 0xffffffff; +} +static inline Elf64_Xword Elf64RelInfo(u32 sym_index, u32 type) { + return (static_cast<Elf64_Xword>(sym_index) << 32) + type; +} + +constexpr u32 ElfArmCopy = 20; /* Copy symbol at runtime */ +constexpr u32 ElfArmGlobDat = 21; /* Create GOT entry */ +constexpr u32 ElfArmJumpSlot = 22; /* Create PLT entry */ +constexpr u32 ElfArmRelative = 23; /* Adjust by program base */ + +constexpr u32 ElfAArch64Copy = 1024; /* Copy symbol at runtime */ +constexpr u32 ElfAArch64GlobDat = 1025; /* Create GOT entry */ +constexpr u32 ElfAArch64JumpSlot = 1026; /* Create PLT entry */ +constexpr u32 ElfAArch64Relative = 1027; /* Adjust by program base */ + +/* Program segment header. */ + +struct Elf32_Phdr { + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +}; + +struct Elf64_Phdr { + Elf64_Word p_type; /* Segment type */ + Elf64_Word p_flags; /* Segment flags */ + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment */ +}; + +/* Legal values for p_type (segment type). */ + +constexpr u32 ElfPtNull = 0; /* Program header table entry unused */ +constexpr u32 ElfPtLoad = 1; /* Loadable program segment */ +constexpr u32 ElfPtDynamic = 2; /* Dynamic linking information */ +constexpr u32 ElfPtInterp = 3; /* Program interpreter */ +constexpr u32 ElfPtNote = 4; /* Auxiliary information */ +constexpr u32 ElfPtPhdr = 6; /* Entry for header table itself */ +constexpr u32 ElfPtTls = 7; /* Thread-local storage segment */ + +/* Legal values for p_flags (segment flags). */ + +constexpr u32 ElfPfExec = 0; /* Segment is executable */ +constexpr u32 ElfPfWrite = 1; /* Segment is writable */ +constexpr u32 ElfPfRead = 2; /* Segment is readable */ + +/* Dynamic section entry. */ + +struct Elf32_Dyn { + Elf32_Sword d_tag; /* Dynamic entry type */ + union { + Elf32_Word d_val; /* Integer value */ + Elf32_Addr d_ptr; /* Address value */ + } d_un; +}; + +struct Elf64_Dyn { + Elf64_Sxword d_tag; /* Dynamic entry type */ + union { + Elf64_Xword d_val; /* Integer value */ + Elf64_Addr d_ptr; /* Address value */ + } d_un; +}; + +/* Legal values for d_tag (dynamic entry type). */ + +constexpr u32 ElfDtNull = 0; /* Marks end of dynamic section */ +constexpr u32 ElfDtNeeded = 1; /* Name of needed library */ +constexpr u32 ElfDtPltRelSz = 2; /* Size in bytes of PLT relocs */ +constexpr u32 ElfDtPltGot = 3; /* Processor defined value */ +constexpr u32 ElfDtHash = 4; /* Address of symbol hash table */ +constexpr u32 ElfDtStrtab = 5; /* Address of string table */ +constexpr u32 ElfDtSymtab = 6; /* Address of symbol table */ +constexpr u32 ElfDtRela = 7; /* Address of Rela relocs */ +constexpr u32 ElfDtRelasz = 8; /* Total size of Rela relocs */ +constexpr u32 ElfDtRelaent = 9; /* Size of one Rela reloc */ +constexpr u32 ElfDtStrsz = 10; /* Size of string table */ +constexpr u32 ElfDtSyment = 11; /* Size of one symbol table entry */ +constexpr u32 ElfDtInit = 12; /* Address of init function */ +constexpr u32 ElfDtFini = 13; /* Address of termination function */ +constexpr u32 ElfDtRel = 17; /* Address of Rel relocs */ +constexpr u32 ElfDtRelsz = 18; /* Total size of Rel relocs */ +constexpr u32 ElfDtRelent = 19; /* Size of one Rel reloc */ +constexpr u32 ElfDtPltRel = 20; /* Type of reloc in PLT */ +constexpr u32 ElfDtTextRel = 22; /* Reloc might modify .text */ +constexpr u32 ElfDtJmpRel = 23; /* Address of PLT relocs */ +constexpr u32 ElfDtBindNow = 24; /* Process relocations of object */ +constexpr u32 ElfDtInitArray = 25; /* Array with addresses of init fct */ +constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */ +constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ +constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ +constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ + +} // namespace ELF +} // namespace Common diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 62318e70c..1074f2421 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -232,9 +232,7 @@ void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { fs::path GetExeDirectory() { wchar_t exe_path[MAX_PATH]; - GetModuleFileNameW(nullptr, exe_path, MAX_PATH); - - if (!exe_path) { + if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH) == 0) { LOG_ERROR(Common_Filesystem, "Failed to get the path to the executable of the current process"); } diff --git a/src/common/input.h b/src/common/input.h index 54fcb24b0..bb42aaacc 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -72,6 +72,7 @@ enum class PollingError { enum class VibrationAmplificationType { Linear, Exponential, + Test, }; // Analog properties for calibration diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 9a9c74a70..751549583 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -70,6 +70,7 @@ void LogSettings() { log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); log_setting("Debugging_ProgramArgs", values.program_args.GetValue()); + log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue()); log_setting("Input_EnableMotion", values.motion_enabled.GetValue()); log_setting("Input_EnableVibration", values.vibration_enabled.GetValue()); log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue()); @@ -146,7 +147,7 @@ void UpdateRescalingInfo() { info.down_shift = 0; break; default: - UNREACHABLE(); + ASSERT(false); info.up_scale = 1; info.down_shift = 0; } diff --git a/src/common/settings.h b/src/common/settings.h index 5b34169a8..a507744a2 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -496,7 +496,7 @@ struct Values { // Renderer RangedSetting<RendererBackend> renderer_backend{ - RendererBackend::OpenGL, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; + RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; BasicSetting<bool> renderer_debug{false, "debug"}; BasicSetting<bool> renderer_shader_feedback{false, "shader_feedback"}; BasicSetting<bool> enable_nsight_aftermath{false, "nsight_aftermath"}; @@ -601,11 +601,12 @@ struct Values { // Debugging bool record_frame_times; BasicSetting<bool> use_gdbstub{false, "use_gdbstub"}; - BasicSetting<u16> gdbstub_port{0, "gdbstub_port"}; + BasicSetting<u16> gdbstub_port{6543, "gdbstub_port"}; BasicSetting<std::string> program_args{std::string(), "program_args"}; BasicSetting<bool> dump_exefs{false, "dump_exefs"}; BasicSetting<bool> dump_nso{false, "dump_nso"}; BasicSetting<bool> dump_shaders{false, "dump_shaders"}; + BasicSetting<bool> dump_macros{false, "dump_macros"}; BasicSetting<bool> enable_fs_access_log{false, "enable_fs_access_log"}; BasicSetting<bool> reporting_services{false, "reporting_services"}; BasicSetting<bool> quest_flag{false, "quest_flag"}; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 703aa5db8..7a495bc79 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -178,6 +178,10 @@ std::wstring UTF8ToUTF16W(const std::string& input) { #endif +std::u16string U16StringFromBuffer(const u16* input, std::size_t length) { + return std::u16string(reinterpret_cast<const char16_t*>(input), length); +} + std::string StringFromFixedZeroTerminatedBuffer(std::string_view buffer, std::size_t max_len) { std::size_t len = 0; while (len < buffer.length() && len < max_len && buffer[len] != '\0') { diff --git a/src/common/string_util.h b/src/common/string_util.h index a33830aec..ce18a33cf 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -44,6 +44,8 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ #endif +[[nodiscard]] std::u16string U16StringFromBuffer(const u16* input, std::size_t length); + /** * Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string * `other` for equality. diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 62230bae0..670410e75 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -36,6 +36,13 @@ add_library(core STATIC crypto/ctr_encryption_layer.h crypto/xts_encryption_layer.cpp crypto/xts_encryption_layer.h + debugger/debugger_interface.h + debugger/debugger.cpp + debugger/debugger.h + debugger/gdbstub_arch.cpp + debugger/gdbstub_arch.h + debugger/gdbstub.cpp + debugger/gdbstub.h device_memory.cpp device_memory.h file_sys/bis_factory.cpp @@ -736,16 +743,11 @@ if (MSVC) /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data - /we4456 # Declaration of 'identifier' hides previous local declaration - /we4457 # Declaration of 'identifier' hides function parameter - /we4458 # Declaration of 'identifier' hides class member - /we4459 # Declaration of 'identifier' hides global declaration ) else() target_compile_options(core PRIVATE -Werror=conversion -Werror=ignored-qualifiers - -Werror=shadow $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess> $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> @@ -761,6 +763,9 @@ create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) +if (MINGW) + target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) +endif() if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index c347e7ea7..9a285dfc6 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -9,7 +9,9 @@ #include "core/arm/arm_interface.h" #include "core/arm/symbols.h" #include "core/core.h" +#include "core/debugger/debugger.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" #include "core/loader/loader.h" #include "core/memory.h" @@ -88,4 +90,50 @@ void ARM_Interface::LogBacktrace() const { } } +void ARM_Interface::Run() { + using Kernel::StepState; + using Kernel::SuspendType; + + while (true) { + Kernel::KThread* current_thread{system.Kernel().CurrentScheduler()->GetCurrentThread()}; + Dynarmic::HaltReason hr{}; + + // Notify the debugger and go to sleep if a step was performed + // and this thread has been scheduled again. + if (current_thread->GetStepState() == StepState::StepPerformed) { + system.GetDebugger().NotifyThreadStopped(current_thread); + current_thread->RequestSuspend(SuspendType::Debug); + break; + } + + // Otherwise, run the thread. + system.EnterDynarmicProfile(); + if (current_thread->GetStepState() == StepState::StepPending) { + hr = StepJit(); + + if (Has(hr, step_thread)) { + current_thread->SetStepState(StepState::StepPerformed); + } + } else { + hr = RunJit(); + } + system.ExitDynarmicProfile(); + + // Notify the debugger and go to sleep if a breakpoint was hit. + if (Has(hr, breakpoint)) { + system.GetDebugger().NotifyThreadStopped(current_thread); + current_thread->RequestSuspend(Kernel::SuspendType::Debug); + break; + } + + // Handle syscalls and scheduling (this may change the current thread) + if (Has(hr, svc_call)) { + Kernel::Svc::Call(system, GetSvcNumber()); + } + if (Has(hr, break_loop) || !uses_wall_clock) { + break; + } + } +} + } // namespace Core diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 8ce973a77..66f6107e9 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -6,6 +6,9 @@ #include <array> #include <vector> + +#include <dynarmic/interface/halt_reason.h> + #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hardware_properties.h" @@ -64,10 +67,7 @@ public: static_assert(sizeof(ThreadContext64) == 0x320); /// Runs the CPU until an event happens - virtual void Run() = 0; - - /// Step CPU by one instruction - virtual void Step() = 0; + void Run(); /// Clear all instruction cache virtual void ClearInstructionCache() = 0; @@ -194,6 +194,11 @@ public: void LogBacktrace() const; + static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step; + static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; + static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; + static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4; + protected: /// System context that this ARM interface is running under. System& system; @@ -201,6 +206,10 @@ protected: bool uses_wall_clock; static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out); + + virtual Dynarmic::HaltReason RunJit() = 0; + virtual Dynarmic::HaltReason StepJit() = 0; + virtual u32 GetSvcNumber() const = 0; }; } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 781a77f6f..7c82d0b96 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -17,6 +17,8 @@ #include "core/arm/dynarmic/arm_exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/debugger/debugger.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/svc.h" #include "core/memory.h" @@ -24,9 +26,6 @@ namespace Core { using namespace Common::Literals; -constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; -constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; - class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { public: explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) @@ -78,16 +77,21 @@ public: } void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { + if (parent.system.DebuggerEnabled()) { + parent.jit.load()->Regs()[15] = pc; + parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); + return; + } + parent.LogBacktrace(); LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", exception, pc, MemoryReadCode(pc), parent.IsInThumbMode()); - UNIMPLEMENTED(); } void CallSVC(u32 swi) override { parent.svc_swi = swi; - parent.jit.load()->HaltExecution(svc_call); + parent.jit.load()->HaltExecution(ARM_Interface::svc_call); } void AddTicks(u64 ticks) override { @@ -232,20 +236,16 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* return std::make_unique<Dynarmic::A32::Jit>(config); } -void ARM_Dynarmic_32::Run() { - while (true) { - const auto hr = jit.load()->Run(); - if (Has(hr, svc_call)) { - Kernel::Svc::Call(system, svc_swi); - } - if (Has(hr, break_loop) || !uses_wall_clock) { - break; - } - } +Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() { + return jit.load()->Run(); +} + +Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() { + return jit.load()->Step(); } -void ARM_Dynarmic_32::Step() { - jit.load()->Step(); +u32 ARM_Dynarmic_32::GetSvcNumber() const { + return svc_swi; } ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index abfe76644..5b1d60005 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -41,8 +41,6 @@ public: void SetVectorReg(int index, u128 value) override; u32 GetPSTATE() const override; void SetPSTATE(u32 pstate) override; - void Run() override; - void Step() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; @@ -70,6 +68,11 @@ public: std::vector<BacktraceEntry> GetBacktrace() const override; +protected: + Dynarmic::HaltReason RunJit() override; + Dynarmic::HaltReason StepJit() override; + u32 GetSvcNumber() const override; + private: std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 1b1334598..d4c67eafd 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -15,6 +15,7 @@ #include "core/arm/dynarmic/arm_exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/debugger/debugger.h" #include "core/hardware_properties.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/svc.h" @@ -25,9 +26,6 @@ namespace Core { using Vector = Dynarmic::A64::Vector; using namespace Common::Literals; -constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; -constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; - class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { public: explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) @@ -119,8 +117,13 @@ public: case Dynarmic::A64::Exception::SendEventLocal: case Dynarmic::A64::Exception::Yield: return; - case Dynarmic::A64::Exception::Breakpoint: default: + if (parent.system.DebuggerEnabled()) { + parent.jit.load()->SetPC(pc); + parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); + return; + } + parent.LogBacktrace(); ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); @@ -129,7 +132,7 @@ public: void CallSVC(u32 swi) override { parent.svc_swi = swi; - parent.jit.load()->HaltExecution(svc_call); + parent.jit.load()->HaltExecution(ARM_Interface::svc_call); } void AddTicks(u64 ticks) override { @@ -293,20 +296,16 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* return std::make_shared<Dynarmic::A64::Jit>(config); } -void ARM_Dynarmic_64::Run() { - while (true) { - const auto hr = jit.load()->Run(); - if (Has(hr, svc_call)) { - Kernel::Svc::Call(system, svc_swi); - } - if (Has(hr, break_loop) || !uses_wall_clock) { - break; - } - } +Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() { + return jit.load()->Run(); +} + +Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() { + return jit.load()->Step(); } -void ARM_Dynarmic_64::Step() { - jit.load()->Step(); +u32 ARM_Dynarmic_64::GetSvcNumber() const { + return svc_swi; } ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 01a7e4dad..abfbc3c3f 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -39,8 +39,6 @@ public: void SetVectorReg(int index, u128 value) override; u32 GetPSTATE() const override; void SetPSTATE(u32 pstate) override; - void Run() override; - void Step() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; @@ -64,6 +62,11 @@ public: std::vector<BacktraceEntry> GetBacktrace() const override; +protected: + Dynarmic::HaltReason RunJit() override; + Dynarmic::HaltReason StepJit() override; + u32 GetSvcNumber() const override; + private: std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, std::size_t address_space_bits) const; diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp index a043e6735..6aae79c48 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -20,7 +20,7 @@ struct fmt::formatter<Dynarmic::A32::CoprocReg> { } template <typename FormatContext> auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) { - return format_to(ctx.out(), "cp{}", static_cast<size_t>(reg)); + return fmt::format_to(ctx.out(), "cp{}", static_cast<size_t>(reg)); } }; diff --git a/src/core/arm/symbols.cpp b/src/core/arm/symbols.cpp index 4aa1a1ee1..0259c7ea2 100644 --- a/src/core/arm/symbols.cpp +++ b/src/core/arm/symbols.cpp @@ -3,73 +3,14 @@ #include "common/bit_field.h" #include "common/common_funcs.h" +#include "common/elf.h" #include "core/arm/symbols.h" #include "core/core.h" #include "core/memory.h" -namespace Core { -namespace { - -constexpr u64 ELF_DYNAMIC_TAG_NULL = 0; -constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5; -constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6; -constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11; - -enum class ELFSymbolType : u8 { - None = 0, - Object = 1, - Function = 2, - Section = 3, - File = 4, - Common = 5, - TLS = 6, -}; - -enum class ELFSymbolBinding : u8 { - Local = 0, - Global = 1, - Weak = 2, -}; - -enum class ELFSymbolVisibility : u8 { - Default = 0, - Internal = 1, - Hidden = 2, - Protected = 3, -}; - -struct ELF64Symbol { - u32 name_index; - union { - u8 info; - - BitField<0, 4, ELFSymbolType> type; - BitField<4, 4, ELFSymbolBinding> binding; - }; - ELFSymbolVisibility visibility; - u16 sh_index; - u64 value; - u64 size; -}; -static_assert(sizeof(ELF64Symbol) == 0x18, "ELF64Symbol has incorrect size."); - -struct ELF32Symbol { - u32 name_index; - u32 value; - u32 size; - union { - u8 info; - - BitField<0, 4, ELFSymbolType> type; - BitField<4, 4, ELFSymbolBinding> binding; - }; - ELFSymbolVisibility visibility; - u16 sh_index; -}; -static_assert(sizeof(ELF32Symbol) == 0x10, "ELF32Symbol has incorrect size."); - -} // Anonymous namespace +using namespace Common::ELF; +namespace Core { namespace Symbols { template <typename Word, typename ELFSymbol, typename ByteReader> @@ -110,15 +51,15 @@ static Symbols GetSymbols(ByteReader ReadBytes) { const Word value = ReadWord(dynamic_index + sizeof(Word)); dynamic_index += 2 * sizeof(Word); - if (tag == ELF_DYNAMIC_TAG_NULL) { + if (tag == ElfDtNull) { break; } - if (tag == ELF_DYNAMIC_TAG_STRTAB) { + if (tag == ElfDtStrtab) { string_table_offset = value; - } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) { + } else if (tag == ElfDtSymtab) { symbol_table_offset = value; - } else if (tag == ELF_DYNAMIC_TAG_SYMENT) { + } else if (tag == ElfDtSyment) { symbol_entry_size = value; } } @@ -134,14 +75,14 @@ static Symbols GetSymbols(ByteReader ReadBytes) { ELFSymbol symbol{}; ReadBytes(&symbol, symbol_index, sizeof(ELFSymbol)); - VAddr string_offset = string_table_offset + symbol.name_index; + VAddr string_offset = string_table_offset + symbol.st_name; std::string name; for (u8 c = Read8(string_offset); c != 0; c = Read8(++string_offset)) { name += static_cast<char>(c); } symbol_index += symbol_entry_size; - out[name] = std::make_pair(symbol.value, symbol.size); + out[name] = std::make_pair(symbol.st_value, symbol.st_size); } return out; @@ -152,9 +93,9 @@ Symbols GetSymbols(VAddr base, Core::Memory::Memory& memory, bool is_64) { [&](void* ptr, size_t offset, size_t size) { memory.ReadBlock(base + offset, ptr, size); }}; if (is_64) { - return GetSymbols<u64, ELF64Symbol>(ReadBytes); + return GetSymbols<u64, Elf64_Sym>(ReadBytes); } else { - return GetSymbols<u32, ELF32Symbol>(ReadBytes); + return GetSymbols<u32, Elf32_Sym>(ReadBytes); } } @@ -164,9 +105,9 @@ Symbols GetSymbols(std::span<const u8> data, bool is_64) { }}; if (is_64) { - return GetSymbols<u64, ELF64Symbol>(ReadBytes); + return GetSymbols<u64, Elf64_Sym>(ReadBytes); } else { - return GetSymbols<u32, ELF32Symbol>(ReadBytes); + return GetSymbols<u32, Elf32_Sym>(ReadBytes); } } diff --git a/src/core/core.cpp b/src/core/core.cpp index 8a887904d..954136adb 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -17,6 +17,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" +#include "core/debugger/debugger.h" #include "core/device_memory.h" #include "core/file_sys/bis_factory.h" #include "core/file_sys/mode.h" @@ -171,6 +172,10 @@ struct System::Impl { } } + void InitializeDebugger(System& system, u16 port) { + debugger = std::make_unique<Debugger>(system, port); + } + SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { LOG_DEBUG(Core, "initialized OK"); @@ -329,6 +334,7 @@ struct System::Impl { gpu_core->NotifyShutdown(); } + debugger.reset(); services.reset(); service_manager.reset(); cheat_engine.reset(); @@ -436,6 +442,9 @@ struct System::Impl { /// Network instance Network::NetworkInstance network_instance; + /// Debugger + std::unique_ptr<Core::Debugger> debugger; + SystemResultStatus status = SystemResultStatus::Success; std::string status_details = ""; @@ -472,10 +481,6 @@ SystemResultStatus System::Pause() { return impl->Pause(); } -SystemResultStatus System::SingleStep() { - return SystemResultStatus::Success; -} - void System::InvalidateCpuInstructionCaches() { impl->kernel.InvalidateAllInstructionCaches(); } @@ -488,6 +493,12 @@ void System::Shutdown() { impl->Shutdown(); } +void System::DetachDebugger() { + if (impl->debugger) { + impl->debugger->NotifyShutdown(); + } +} + std::unique_lock<std::mutex> System::StallCPU() { return impl->StallCPU(); } @@ -496,6 +507,10 @@ void System::UnstallCPU() { impl->UnstallCPU(); } +void System::InitializeDebugger() { + impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue()); +} + SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, u64 program_id, std::size_t program_index) { return impl->Load(*this, emu_window, filepath, program_id, program_index); @@ -809,6 +824,18 @@ bool System::IsMulticore() const { return impl->is_multicore; } +bool System::DebuggerEnabled() const { + return Settings::values.use_gdbstub.GetValue(); +} + +Core::Debugger& System::GetDebugger() { + return *impl->debugger; +} + +const Core::Debugger& System::GetDebugger() const { + return *impl->debugger; +} + void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { impl->execute_program_callback = std::move(callback); } diff --git a/src/core/core.h b/src/core/core.h index 4a0c7dc84..5c367349e 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -97,6 +97,7 @@ namespace Core { class ARM_Interface; class CpuManager; +class Debugger; class DeviceMemory; class ExclusiveMonitor; class SpeedLimiter; @@ -148,12 +149,6 @@ public: [[nodiscard]] SystemResultStatus Pause(); /** - * Step the CPU one instruction - * @return Result status, indicating whether or not the operation succeeded. - */ - [[nodiscard]] SystemResultStatus SingleStep(); - - /** * Invalidate the CPU instruction caches * This function should only be used by GDB Stub to support breakpoints, memory updates and * step/continue commands. @@ -165,10 +160,18 @@ public: /// Shutdown the emulated system. void Shutdown(); + /// Forcibly detach the debugger if it is running. + void DetachDebugger(); + std::unique_lock<std::mutex> StallCPU(); void UnstallCPU(); /** + * Initialize the debugger. + */ + void InitializeDebugger(); + + /** * Load an executable application. * @param emu_window Reference to the host-system window used for video output and keyboard * input. @@ -354,6 +357,9 @@ public: [[nodiscard]] Service::Time::TimeManager& GetTimeManager(); [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const; + [[nodiscard]] Core::Debugger& GetDebugger(); + [[nodiscard]] const Core::Debugger& GetDebugger() const; + void SetExitLock(bool locked); [[nodiscard]] bool GetExitLock() const; @@ -375,6 +381,9 @@ public: /// Tells if system is running on multicore. [[nodiscard]] bool IsMulticore() const; + /// Tells if the system debugger is enabled. + [[nodiscard]] bool DebuggerEnabled() const; + /// Type used for the frontend to designate a callback for System to re-launch the application /// using a specified program index. using ExecuteProgramCallback = std::function<void(std::size_t)>; diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 09d9c5163..132fe5b60 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -16,7 +16,8 @@ namespace Core { -CpuManager::CpuManager(System& system_) : system{system_} {} +CpuManager::CpuManager(System& system_) + : pause_barrier{std::make_unique<Common::Barrier>(1)}, system{system_} {} CpuManager::~CpuManager() = default; void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, @@ -30,8 +31,10 @@ void CpuManager::Initialize() { for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core); } + pause_barrier = std::make_unique<Common::Barrier>(Core::Hardware::NUM_CPU_CORES + 1); } else { core_data[0].host_thread = std::jthread(ThreadStart, std::ref(*this), 0); + pause_barrier = std::make_unique<Common::Barrier>(2); } } @@ -110,12 +113,10 @@ void CpuManager::MultiCoreRunGuestLoop() { while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - system.EnterDynarmicProfile(); while (!physical_core->IsInterrupted()) { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } - system.ExitDynarmicProfile(); { Kernel::KScopedDisableDispatch dd(kernel); physical_core->ArmInterface().ClearExclusiveState(); @@ -138,51 +139,14 @@ void CpuManager::MultiCoreRunSuspendThread() { auto core = kernel.CurrentPhysicalCoreIndex(); auto& scheduler = *kernel.CurrentScheduler(); Kernel::KThread* current_thread = scheduler.GetCurrentThread(); + current_thread->DisableDispatch(); + Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); - ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.CurrentPhysicalCoreIndex()); scheduler.RescheduleCurrentCore(); } } -void CpuManager::MultiCorePause(bool paused) { - if (!paused) { - bool all_not_barrier = false; - while (!all_not_barrier) { - all_not_barrier = true; - for (const auto& data : core_data) { - all_not_barrier &= !data.is_running.load() && data.initialized.load(); - } - } - for (auto& data : core_data) { - data.enter_barrier->Set(); - } - if (paused_state.load()) { - bool all_barrier = false; - while (!all_barrier) { - all_barrier = true; - for (const auto& data : core_data) { - all_barrier &= data.is_paused.load() && data.initialized.load(); - } - } - for (auto& data : core_data) { - data.exit_barrier->Set(); - } - } - } else { - /// Wait until all cores are paused. - bool all_barrier = false; - while (!all_barrier) { - all_barrier = true; - for (const auto& data : core_data) { - all_barrier &= data.is_paused.load() && data.initialized.load(); - } - } - /// Don't release the barrier - } - paused_state = paused; -} - /////////////////////////////////////////////////////////////////////////////// /// SingleCore /// /////////////////////////////////////////////////////////////////////////////// @@ -200,12 +164,10 @@ void CpuManager::SingleCoreRunGuestLoop() { auto& kernel = system.Kernel(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - system.EnterDynarmicProfile(); if (!physical_core->IsInterrupted()) { physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } - system.ExitDynarmicProfile(); kernel.SetIsPhantomModeForSingleCore(true); system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); @@ -235,8 +197,9 @@ void CpuManager::SingleCoreRunSuspendThread() { auto core = kernel.GetCurrentHostThreadID(); auto& scheduler = *kernel.CurrentScheduler(); Kernel::KThread* current_thread = scheduler.GetCurrentThread(); + current_thread->DisableDispatch(); + Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[0].host_context); - ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.GetCurrentHostThreadID()); scheduler.RescheduleCurrentCore(); } @@ -274,37 +237,21 @@ void CpuManager::PreemptSingleCore(bool from_running_enviroment) { } } -void CpuManager::SingleCorePause(bool paused) { - if (!paused) { - bool all_not_barrier = false; - while (!all_not_barrier) { - all_not_barrier = !core_data[0].is_running.load() && core_data[0].initialized.load(); - } - core_data[0].enter_barrier->Set(); - if (paused_state.load()) { - bool all_barrier = false; - while (!all_barrier) { - all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load(); - } - core_data[0].exit_barrier->Set(); - } - } else { - /// Wait until all cores are paused. - bool all_barrier = false; - while (!all_barrier) { - all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load(); - } - /// Don't release the barrier - } - paused_state = paused; -} - void CpuManager::Pause(bool paused) { - if (is_multicore) { - MultiCorePause(paused); - } else { - SingleCorePause(paused); + std::scoped_lock lk{pause_lock}; + + if (pause_state == paused) { + return; } + + // Set the new state + pause_state.store(paused); + + // Wake up any waiting threads + pause_state.notify_all(); + + // Wait for all threads to successfully change state before returning + pause_barrier->Sync(); } void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) { @@ -320,27 +267,29 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) { Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); auto& data = core_data[core]; - data.enter_barrier = std::make_unique<Common::Event>(); - data.exit_barrier = std::make_unique<Common::Event>(); data.host_context = Common::Fiber::ThreadToFiber(); - data.is_running = false; - data.initialized = true; const bool sc_sync = !is_async_gpu && !is_multicore; bool sc_sync_first_use = sc_sync; // Cleanup SCOPE_EXIT({ data.host_context->Exit(); - data.enter_barrier.reset(); - data.exit_barrier.reset(); - data.initialized = false; MicroProfileOnThreadExit(); }); /// Running while (running_mode) { - data.is_running = false; - data.enter_barrier->Wait(); + if (pause_state.load(std::memory_order_relaxed)) { + // Wait for caller to acknowledge pausing + pause_barrier->Sync(); + + // Wait until unpaused + pause_state.wait(true, std::memory_order_relaxed); + + // Wait for caller to acknowledge unpausing + pause_barrier->Sync(); + } + if (sc_sync_first_use) { system.GPU().ObtainContext(); sc_sync_first_use = false; @@ -352,12 +301,7 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) { } auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); - data.is_running = true; Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext()); - data.is_running = false; - data.is_paused = true; - data.exit_barrier->Wait(); - data.is_paused = false; } } diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index aee352245..ddd9691ca 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -69,13 +69,11 @@ private: void MultiCoreRunGuestLoop(); void MultiCoreRunIdleThread(); void MultiCoreRunSuspendThread(); - void MultiCorePause(bool paused); void SingleCoreRunGuestThread(); void SingleCoreRunGuestLoop(); void SingleCoreRunIdleThread(); void SingleCoreRunSuspendThread(); - void SingleCorePause(bool paused); static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); @@ -83,16 +81,13 @@ private: struct CoreData { std::shared_ptr<Common::Fiber> host_context; - std::unique_ptr<Common::Event> enter_barrier; - std::unique_ptr<Common::Event> exit_barrier; - std::atomic<bool> is_running; - std::atomic<bool> is_paused; - std::atomic<bool> initialized; std::jthread host_thread; }; std::atomic<bool> running_mode{}; - std::atomic<bool> paused_state{}; + std::atomic<bool> pause_state{}; + std::unique_ptr<Common::Barrier> pause_barrier{}; + std::mutex pause_lock{}; std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{}; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index e3c4f80eb..443323390 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -140,7 +140,6 @@ u64 GetSignatureTypeDataSize(SignatureType type) { return 0x3C; } UNREACHABLE(); - return 0; } u64 GetSignatureTypePaddingSize(SignatureType type) { @@ -155,7 +154,6 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { return 0x40; } UNREACHABLE(); - return 0; } SignatureType Ticket::GetSignatureType() const { diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp new file mode 100644 index 000000000..edf991d71 --- /dev/null +++ b/src/core/debugger/debugger.cpp @@ -0,0 +1,310 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <mutex> +#include <thread> + +#include <boost/asio.hpp> +#include <boost/process/async_pipe.hpp> + +#include "common/logging/log.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/debugger/debugger.h" +#include "core/debugger/debugger_interface.h" +#include "core/debugger/gdbstub.h" +#include "core/hle/kernel/global_scheduler_context.h" + +template <typename Readable, typename Buffer, typename Callback> +static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) { + static_assert(std::is_trivial_v<Buffer>); + auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))}; + r.async_read_some( + boost_buffer, [&, c](const boost::system::error_code& error, size_t bytes_read) { + if (!error.failed()) { + const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); + std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; + c(received_data); + } + + AsyncReceiveInto(r, buffer, c); + }); +} + +template <typename Readable, typename Buffer> +static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { + static_assert(std::is_trivial_v<Buffer>); + auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))}; + size_t bytes_read = r.read_some(boost_buffer); + const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); + std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; + return received_data; +} + +enum class SignalType { + Stopped, + ShuttingDown, +}; + +struct SignalInfo { + SignalType type; + Kernel::KThread* thread; +}; + +namespace Core { + +class DebuggerImpl : public DebuggerBackend { +public: + explicit DebuggerImpl(Core::System& system_, u16 port) + : system{system_}, signal_pipe{io_context}, client_socket{io_context} { + frontend = std::make_unique<GDBStub>(*this, system); + InitializeServer(port); + } + + ~DebuggerImpl() override { + ShutdownServer(); + } + + bool SignalDebugger(SignalInfo signal_info) { + std::scoped_lock lk{connection_lock}; + + if (stopped) { + // Do not notify the debugger about another event. + // It should be ignored. + return false; + } + + // Set up the state. + stopped = true; + info = signal_info; + + // Write a single byte into the pipe to wake up the debug interface. + boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); + return true; + } + + std::span<const u8> ReadFromClient() override { + return ReceiveInto(client_socket, client_data); + } + + void WriteToClient(std::span<const u8> data) override { + boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes())); + } + + void SetActiveThread(Kernel::KThread* thread) override { + active_thread = thread; + } + + Kernel::KThread* GetActiveThread() override { + return active_thread; + } + +private: + void InitializeServer(u16 port) { + using boost::asio::ip::tcp; + + LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port); + + // Run the connection thread. + connection_thread = std::jthread([&, port](std::stop_token stop_token) { + try { + // Initialize the listening socket and accept a new client. + tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port}; + tcp::acceptor acceptor{io_context, endpoint}; + + acceptor.async_accept(client_socket, [](const auto&) {}); + io_context.run_one(); + io_context.restart(); + + if (stop_token.stop_requested()) { + return; + } + + ThreadLoop(stop_token); + } catch (const std::exception& ex) { + LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); + } + }); + } + + void ShutdownServer() { + connection_thread.request_stop(); + io_context.stop(); + connection_thread.join(); + } + + void ThreadLoop(std::stop_token stop_token) { + Common::SetCurrentThreadName("yuzu:Debugger"); + + // Set up the client signals for new data. + AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); }); + AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); }); + + // Stop the emulated CPU. + AllCoreStop(); + + // Set the active thread. + UpdateActiveThread(); + + // Set up the frontend. + frontend->Connected(); + + // Main event loop. + while (!stop_token.stop_requested() && io_context.run()) { + } + } + + void PipeData(std::span<const u8> data) { + switch (info.type) { + case SignalType::Stopped: + // Stop emulation. + AllCoreStop(); + + // Notify the client. + active_thread = info.thread; + UpdateActiveThread(); + frontend->Stopped(active_thread); + + break; + case SignalType::ShuttingDown: + frontend->ShuttingDown(); + + // Wait for emulation to shut down gracefully now. + suspend.reset(); + signal_pipe.close(); + client_socket.shutdown(boost::asio::socket_base::shutdown_both); + LOG_INFO(Debug_GDBStub, "Shut down server"); + + break; + } + } + + void ClientData(std::span<const u8> data) { + const auto actions{frontend->ClientData(data)}; + for (const auto action : actions) { + switch (action) { + case DebuggerAction::Interrupt: { + { + std::scoped_lock lk{connection_lock}; + stopped = true; + } + AllCoreStop(); + UpdateActiveThread(); + frontend->Stopped(active_thread); + break; + } + case DebuggerAction::Continue: + active_thread->SetStepState(Kernel::StepState::NotStepping); + ResumeInactiveThreads(); + AllCoreResume(); + break; + case DebuggerAction::StepThreadUnlocked: + active_thread->SetStepState(Kernel::StepState::StepPending); + ResumeInactiveThreads(); + AllCoreResume(); + break; + case DebuggerAction::StepThreadLocked: + active_thread->SetStepState(Kernel::StepState::StepPending); + SuspendInactiveThreads(); + AllCoreResume(); + break; + case DebuggerAction::ShutdownEmulation: { + // Suspend all threads and release any locks held + active_thread->RequestSuspend(Kernel::SuspendType::Debug); + SuspendInactiveThreads(); + AllCoreResume(); + + // Spawn another thread that will exit after shutdown, + // to avoid a deadlock + Core::System* system_ref{&system}; + std::thread t([system_ref] { system_ref->Exit(); }); + t.detach(); + break; + } + } + } + } + + void AllCoreStop() { + if (!suspend) { + suspend = system.StallCPU(); + } + } + + void AllCoreResume() { + stopped = false; + system.UnstallCPU(); + suspend.reset(); + } + + void SuspendInactiveThreads() { + for (auto* thread : ThreadList()) { + if (thread != active_thread) { + thread->RequestSuspend(Kernel::SuspendType::Debug); + } + } + } + + void ResumeInactiveThreads() { + for (auto* thread : ThreadList()) { + if (thread != active_thread) { + thread->Resume(Kernel::SuspendType::Debug); + thread->SetStepState(Kernel::StepState::NotStepping); + } + } + } + + void UpdateActiveThread() { + const auto& threads{ThreadList()}; + if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { + active_thread = threads[0]; + } + active_thread->Resume(Kernel::SuspendType::Debug); + active_thread->SetStepState(Kernel::StepState::NotStepping); + } + + const std::vector<Kernel::KThread*>& ThreadList() { + return system.GlobalSchedulerContext().GetThreadList(); + } + +private: + System& system; + std::unique_ptr<DebuggerFrontend> frontend; + + std::jthread connection_thread; + std::mutex connection_lock; + boost::asio::io_context io_context; + boost::process::async_pipe signal_pipe; + boost::asio::ip::tcp::socket client_socket; + std::optional<std::unique_lock<std::mutex>> suspend; + + SignalInfo info; + Kernel::KThread* active_thread; + bool pipe_data; + bool stopped; + + std::array<u8, 4096> client_data; +}; + +Debugger::Debugger(Core::System& system, u16 port) { + try { + impl = std::make_unique<DebuggerImpl>(system, port); + } catch (const std::exception& ex) { + LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what()); + } +} + +Debugger::~Debugger() = default; + +bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { + return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread}); +} + +void Debugger::NotifyShutdown() { + if (impl) { + impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr}); + } +} + +} // namespace Core diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h new file mode 100644 index 000000000..f9738ca3d --- /dev/null +++ b/src/core/debugger/debugger.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> + +#include "common/common_types.h" + +namespace Kernel { +class KThread; +} + +namespace Core { +class System; + +class DebuggerImpl; + +class Debugger { +public: + /** + * Blocks and waits for a connection on localhost, port `server_port`. + * Does not create the debugger if the port is already in use. + */ + explicit Debugger(Core::System& system, u16 server_port); + ~Debugger(); + + /** + * Notify the debugger that the given thread is stopped + * (due to a breakpoint, or due to stopping after a successful step). + * + * The debugger will asynchronously halt emulation after the notification has + * occurred. If another thread attempts to notify before emulation has stopped, + * it is ignored and this method will return false. Otherwise it will return true. + */ + bool NotifyThreadStopped(Kernel::KThread* thread); + + /** + * Notify the debugger that a shutdown is being performed now and disconnect. + */ + void NotifyShutdown(); + +private: + std::unique_ptr<DebuggerImpl> impl; +}; +} // namespace Core diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h new file mode 100644 index 000000000..c0bb4ecaf --- /dev/null +++ b/src/core/debugger/debugger_interface.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <functional> +#include <span> +#include <vector> + +#include "common/common_types.h" + +namespace Kernel { +class KThread; +} + +namespace Core { + +enum class DebuggerAction { + Interrupt, ///< Stop emulation as soon as possible. + Continue, ///< Resume emulation. + StepThreadLocked, ///< Step the currently-active thread without resuming others. + StepThreadUnlocked, ///< Step the currently-active thread and resume others. + ShutdownEmulation, ///< Shut down the emulator. +}; + +class DebuggerBackend { +public: + virtual ~DebuggerBackend() = default; + + /** + * Can be invoked from a callback to synchronously wait for more data. + * Will return as soon as least one byte is received. Reads up to 4096 bytes. + */ + virtual std::span<const u8> ReadFromClient() = 0; + + /** + * Can be invoked from a callback to write data to the client. + * Returns immediately after the data is sent. + */ + virtual void WriteToClient(std::span<const u8> data) = 0; + + /** + * Gets the currently active thread when the debugger is stopped. + */ + virtual Kernel::KThread* GetActiveThread() = 0; + + /** + * Sets the currently active thread when the debugger is stopped. + */ + virtual void SetActiveThread(Kernel::KThread* thread) = 0; +}; + +class DebuggerFrontend { +public: + explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {} + + virtual ~DebuggerFrontend() = default; + + /** + * Called after the client has successfully connected to the port. + */ + virtual void Connected() = 0; + + /** + * Called when emulation has stopped. + */ + virtual void Stopped(Kernel::KThread* thread) = 0; + + /** + * Called when emulation is shutting down. + */ + virtual void ShuttingDown() = 0; + + /** + * Called when new data is asynchronously received on the client socket. + * A list of actions to perform is returned. + */ + [[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0; + +protected: + DebuggerBackend& backend; +}; + +} // namespace Core diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp new file mode 100644 index 000000000..52e76f659 --- /dev/null +++ b/src/core/debugger/gdbstub.cpp @@ -0,0 +1,620 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <atomic> +#include <numeric> +#include <optional> +#include <thread> + +#include <boost/algorithm/string.hpp> + +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/scope_exit.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/debugger/gdbstub.h" +#include "core/debugger/gdbstub_arch.h" +#include "core/hle/kernel/k_page_table.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/loader/loader.h" +#include "core/memory.h" + +namespace Core { + +constexpr char GDB_STUB_START = '$'; +constexpr char GDB_STUB_END = '#'; +constexpr char GDB_STUB_ACK = '+'; +constexpr char GDB_STUB_NACK = '-'; +constexpr char GDB_STUB_INT3 = 0x03; +constexpr int GDB_STUB_SIGTRAP = 5; + +constexpr char GDB_STUB_REPLY_ERR[] = "E01"; +constexpr char GDB_STUB_REPLY_OK[] = "OK"; +constexpr char GDB_STUB_REPLY_EMPTY[] = ""; + +static u8 CalculateChecksum(std::string_view data) { + return std::accumulate(data.begin(), data.end(), u8{0}, + [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); }); +} + +static std::string EscapeGDB(std::string_view data) { + std::string escaped; + escaped.reserve(data.size()); + + for (char c : data) { + switch (c) { + case '#': + escaped += "}\x03"; + break; + case '$': + escaped += "}\x04"; + break; + case '*': + escaped += "}\x0a"; + break; + case '}': + escaped += "}\x5d"; + break; + default: + escaped += c; + break; + } + } + + return escaped; +} + +static std::string EscapeXML(std::string_view data) { + std::string escaped; + escaped.reserve(data.size()); + + for (char c : data) { + switch (c) { + case '&': + escaped += "&"; + break; + case '"': + escaped += """; + break; + case '<': + escaped += "<"; + break; + case '>': + escaped += ">"; + break; + default: + escaped += c; + break; + } + } + + return escaped; +} + +GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) + : DebuggerFrontend(backend_), system{system_} { + if (system.CurrentProcess()->Is64BitProcess()) { + arch = std::make_unique<GDBStubA64>(); + } else { + arch = std::make_unique<GDBStubA32>(); + } +} + +GDBStub::~GDBStub() = default; + +void GDBStub::Connected() {} + +void GDBStub::ShuttingDown() {} + +void GDBStub::Stopped(Kernel::KThread* thread) { + SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); +} + +std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) { + std::vector<DebuggerAction> actions; + current_command.insert(current_command.end(), data.begin(), data.end()); + + while (current_command.size() != 0) { + ProcessData(actions); + } + + return actions; +} + +void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) { + const char c{current_command[0]}; + + // Acknowledgement + if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) { + current_command.erase(current_command.begin()); + return; + } + + // Interrupt + if (c == GDB_STUB_INT3) { + LOG_INFO(Debug_GDBStub, "Received interrupt"); + current_command.erase(current_command.begin()); + actions.push_back(DebuggerAction::Interrupt); + SendStatus(GDB_STUB_ACK); + return; + } + + // Otherwise, require the data to be the start of a command + if (c != GDB_STUB_START) { + LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data()); + current_command.clear(); + SendStatus(GDB_STUB_NACK); + return; + } + + // Continue reading until command is complete + while (CommandEnd() == current_command.end()) { + const auto new_data{backend.ReadFromClient()}; + current_command.insert(current_command.end(), new_data.begin(), new_data.end()); + } + + // Execute and respond to GDB + const auto command{DetachCommand()}; + + if (command) { + SendStatus(GDB_STUB_ACK); + ExecuteCommand(*command, actions); + } else { + SendStatus(GDB_STUB_NACK); + } +} + +void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) { + LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet); + + if (packet.length() == 0) { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + if (packet.starts_with("vCont")) { + HandleVCont(packet.substr(5), actions); + return; + } + + std::string_view command{packet.substr(1, packet.size())}; + + switch (packet[0]) { + case 'H': { + Kernel::KThread* thread{nullptr}; + s64 thread_id{strtoll(command.data() + 1, nullptr, 16)}; + if (thread_id >= 1) { + thread = GetThreadByID(thread_id); + } else { + thread = backend.GetActiveThread(); + } + + if (thread) { + SendReply(GDB_STUB_REPLY_OK); + backend.SetActiveThread(thread); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + case 'T': { + s64 thread_id{strtoll(command.data(), nullptr, 16)}; + if (GetThreadByID(thread_id)) { + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + case 'Q': + case 'q': + HandleQuery(command); + break; + case '?': + SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP)); + break; + case 'k': + LOG_INFO(Debug_GDBStub, "Shutting down emulation"); + actions.push_back(DebuggerAction::ShutdownEmulation); + break; + case 'g': + SendReply(arch->ReadRegisters(backend.GetActiveThread())); + break; + case 'G': + arch->WriteRegisters(backend.GetActiveThread(), command); + SendReply(GDB_STUB_REPLY_OK); + break; + case 'p': { + const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; + SendReply(arch->RegRead(backend.GetActiveThread(), reg)); + break; + } + case 'P': { + const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1}; + const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; + arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep)); + break; + } + case 'm': { + const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; + const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))}; + + if (system.Memory().IsValidVirtualAddressRange(addr, size)) { + std::vector<u8> mem(size); + system.Memory().ReadBlock(addr, mem.data(), size); + + SendReply(Common::HexToString(mem)); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + case 'M': { + const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1}; + + const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; + const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; + + const auto mem_substr{std::string_view(command).substr(mem_sep)}; + const auto mem{Common::HexStringToVector(mem_substr, false)}; + + if (system.Memory().IsValidVirtualAddressRange(addr, size)) { + system.Memory().WriteBlock(addr, mem.data(), size); + system.InvalidateCpuInstructionCacheRange(addr, size); + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + case 's': + actions.push_back(DebuggerAction::StepThreadLocked); + break; + case 'C': + case 'c': + actions.push_back(DebuggerAction::Continue); + break; + case 'Z': { + const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; + + if (system.Memory().IsValidVirtualAddress(addr)) { + replaced_instructions[addr] = system.Memory().Read32(addr); + system.Memory().Write32(addr, arch->BreakpointInstruction()); + system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); + + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + case 'z': { + const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; + + const auto orig_insn{replaced_instructions.find(addr)}; + if (system.Memory().IsValidVirtualAddress(addr) && + orig_insn != replaced_instructions.end()) { + system.Memory().Write32(addr, orig_insn->second); + system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); + replaced_instructions.erase(addr); + + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); + } + break; + } + default: + SendReply(GDB_STUB_REPLY_EMPTY); + break; + } +} + +// Structure offsets are from Atmosphere +// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp + +static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, + const Kernel::KThread* thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)}; + const VAddr argument_thread_type{thread->GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x26)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read32(tls_thread_type + 0xe4); + } else { + name_pointer = memory.Read32(tls_thread_type + 0xe8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, + const Kernel::KThread* thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)}; + const VAddr argument_thread_type{thread->GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x46)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read64(tls_thread_type + 0x1a0); + } else { + name_pointer = memory.Read64(tls_thread_type + 0x1a8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +static std::optional<std::string> GetThreadName(Core::System& system, + const Kernel::KThread* thread) { + if (system.CurrentProcess()->Is64BitProcess()) { + return GetNameFromThreadType64(system.Memory(), thread); + } else { + return GetNameFromThreadType32(system.Memory(), thread); + } +} + +static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { + switch (thread->GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::Sleep: + return "Sleep"; + case Kernel::ThreadWaitReasonForDebugging::IPC: + return "IPC"; + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + return "Synchronization"; + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + return "ConditionVar"; + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + return "Arbitration"; + case Kernel::ThreadWaitReasonForDebugging::Suspended: + return "Suspended"; + default: + return "Unknown"; + } +} + +static std::string GetThreadState(const Kernel::KThread* thread) { + switch (thread->GetState()) { + case Kernel::ThreadState::Initialized: + return "Initialized"; + case Kernel::ThreadState::Waiting: + return fmt::format("Waiting ({})", GetThreadWaitReason(thread)); + case Kernel::ThreadState::Runnable: + return "Runnable"; + case Kernel::ThreadState::Terminated: + return "Terminated"; + default: + return "Unknown"; + } +} + +static std::string PaginateBuffer(std::string_view buffer, std::string_view request) { + const auto amount{request.substr(request.find(',') + 1)}; + const auto offset_val{static_cast<u64>(strtoll(request.data(), nullptr, 16))}; + const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))}; + + if (offset_val + amount_val > buffer.size()) { + return fmt::format("l{}", buffer.substr(offset_val)); + } else { + return fmt::format("m{}", buffer.substr(offset_val, amount_val)); + } +} + +void GDBStub::HandleQuery(std::string_view command) { + if (command.starts_with("TStatus")) { + // no tracepoint support + SendReply("T0"); + } else if (command.starts_with("Supported")) { + SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;" + "vContSupported+;QStartNoAckMode+"); + } else if (command.starts_with("Xfer:features:read:target.xml:")) { + const auto target_xml{arch->GetTargetXML()}; + SendReply(PaginateBuffer(target_xml, command.substr(30))); + } else if (command.starts_with("Offsets")) { + Loader::AppLoader::Modules modules; + system.GetAppLoader().ReadNSOModules(modules); + + const auto main = std::find_if(modules.begin(), modules.end(), + [](const auto& key) { return key.second == "main"; }); + if (main != modules.end()) { + SendReply(fmt::format("TextSeg={:x}", main->first)); + } else { + SendReply(fmt::format("TextSeg={:x}", + system.CurrentProcess()->PageTable().GetCodeRegionStart())); + } + } else if (command.starts_with("Xfer:libraries:read::")) { + Loader::AppLoader::Modules modules; + system.GetAppLoader().ReadNSOModules(modules); + + std::string buffer; + buffer += R"(<?xml version="1.0"?>)"; + buffer += "<library-list>"; + for (const auto& [base, name] : modules) { + buffer += fmt::format(R"(<library name="{}"><segment address="{:#x}"/></library>)", + EscapeXML(name), base); + } + buffer += "</library-list>"; + + SendReply(PaginateBuffer(buffer, command.substr(21))); + } else if (command.starts_with("fThreadInfo")) { + // beginning of list + const auto& threads = system.GlobalSchedulerContext().GetThreadList(); + std::vector<std::string> thread_ids; + for (const auto& thread : threads) { + thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID())); + } + SendReply(fmt::format("m{}", fmt::join(thread_ids, ","))); + } else if (command.starts_with("sThreadInfo")) { + // end of list + SendReply("l"); + } else if (command.starts_with("Xfer:threads:read::")) { + std::string buffer; + buffer += R"(<?xml version="1.0"?>)"; + buffer += "<threads>"; + + const auto& threads = system.GlobalSchedulerContext().GetThreadList(); + for (const auto* thread : threads) { + auto thread_name{GetThreadName(system, thread)}; + if (!thread_name) { + thread_name = fmt::format("Thread {:d}", thread->GetThreadID()); + } + + buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)", + thread->GetThreadID(), thread->GetActiveCore(), + EscapeXML(*thread_name), GetThreadState(thread)); + } + + buffer += "</threads>"; + + SendReply(PaginateBuffer(buffer, command.substr(19))); + } else if (command.starts_with("Attached")) { + SendReply("0"); + } else if (command.starts_with("StartNoAckMode")) { + no_ack = true; + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_EMPTY); + } +} + +void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) { + if (command == "?") { + // Continuing and stepping are supported + // (signal is ignored, but required for GDB to use vCont) + SendReply("vCont;c;C;s;S"); + return; + } + + Kernel::KThread* stepped_thread{nullptr}; + bool lock_execution{true}; + + std::vector<std::string> entries; + boost::split(entries, command.substr(1), boost::is_any_of(";")); + for (const auto& thread_action : entries) { + std::vector<std::string> parts; + boost::split(parts, thread_action, boost::is_any_of(":")); + + if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) { + lock_execution = false; + } + if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) { + stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16)); + } + } + + if (stepped_thread) { + backend.SetActiveThread(stepped_thread); + actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked + : DebuggerAction::StepThreadUnlocked); + } else { + actions.push_back(DebuggerAction::Continue); + } +} + +Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { + const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; + for (auto* thread : threads) { + if (thread->GetThreadID() == thread_id) { + return thread; + } + } + + return nullptr; +} + +std::vector<char>::const_iterator GDBStub::CommandEnd() const { + // Find the end marker + const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)}; + + // Require the checksum to be present + return std::min(end + 2, current_command.end()); +} + +std::optional<std::string> GDBStub::DetachCommand() { + // Slice the string part from the beginning to the end marker + const auto end{CommandEnd()}; + + // Extract possible command data + std::string data(current_command.data(), end - current_command.begin() + 1); + + // Shift over the remaining contents + current_command.erase(current_command.begin(), end + 1); + + // Validate received command + if (data[0] != GDB_STUB_START) { + LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]); + return std::nullopt; + } + + u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4)); + u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16)); + + // Verify checksum + if (calculated != received) { + LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}", + calculated, received); + return std::nullopt; + } + + return data.substr(1, data.size() - 4); +} + +void GDBStub::SendReply(std::string_view data) { + const auto escaped{EscapeGDB(data)}; + const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END, + CalculateChecksum(escaped))}; + LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); + + // C++ string support is complete rubbish + const u8* output_begin = reinterpret_cast<const u8*>(output.data()); + const u8* output_end = output_begin + output.size(); + backend.WriteToClient(std::span<const u8>(output_begin, output_end)); +} + +void GDBStub::SendStatus(char status) { + if (no_ack) { + return; + } + + std::array<u8, 1> buf = {static_cast<u8>(status)}; + LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); + backend.WriteToClient(buf); +} + +} // namespace Core diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h new file mode 100644 index 000000000..ec934c77e --- /dev/null +++ b/src/core/debugger/gdbstub.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <map> +#include <memory> +#include <optional> +#include <string_view> +#include <vector> + +#include "core/debugger/debugger_interface.h" +#include "core/debugger/gdbstub_arch.h" + +namespace Core { + +class System; + +class GDBStub : public DebuggerFrontend { +public: + explicit GDBStub(DebuggerBackend& backend, Core::System& system); + ~GDBStub() override; + + void Connected() override; + void Stopped(Kernel::KThread* thread) override; + void ShuttingDown() override; + std::vector<DebuggerAction> ClientData(std::span<const u8> data) override; + +private: + void ProcessData(std::vector<DebuggerAction>& actions); + void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); + void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); + void HandleQuery(std::string_view command); + std::vector<char>::const_iterator CommandEnd() const; + std::optional<std::string> DetachCommand(); + Kernel::KThread* GetThreadByID(u64 thread_id); + + void SendReply(std::string_view data); + void SendStatus(char status); + +private: + Core::System& system; + std::unique_ptr<GDBStubArch> arch; + std::vector<char> current_command; + std::map<VAddr, u32> replaced_instructions; + bool no_ack{}; +}; + +} // namespace Core diff --git a/src/core/debugger/gdbstub_arch.cpp b/src/core/debugger/gdbstub_arch.cpp new file mode 100644 index 000000000..750c353b9 --- /dev/null +++ b/src/core/debugger/gdbstub_arch.cpp @@ -0,0 +1,483 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/hex_util.h" +#include "core/debugger/gdbstub_arch.h" +#include "core/hle/kernel/k_thread.h" + +namespace Core { + +template <typename T> +static T HexToValue(std::string_view hex) { + static_assert(std::is_trivially_copyable_v<T>); + T value{}; + const auto mem{Common::HexStringToVector(hex, false)}; + std::memcpy(&value, mem.data(), std::min(mem.size(), sizeof(T))); + return value; +} + +template <typename T> +static std::string ValueToHex(const T value) { + static_assert(std::is_trivially_copyable_v<T>); + std::array<u8, sizeof(T)> mem{}; + std::memcpy(mem.data(), &value, sizeof(T)); + return Common::HexToString(mem); +} + +template <typename T> +static T GetSIMDRegister(const std::array<u32, 64>& simd_regs, size_t offset) { + static_assert(std::is_trivially_copyable_v<T>); + T value{}; + std::memcpy(&value, reinterpret_cast<const u8*>(simd_regs.data()) + sizeof(T) * offset, + sizeof(T)); + return value; +} + +template <typename T> +static void PutSIMDRegister(std::array<u32, 64>& simd_regs, size_t offset, const T value) { + static_assert(std::is_trivially_copyable_v<T>); + std::memcpy(reinterpret_cast<u8*>(simd_regs.data()) + sizeof(T) * offset, &value, sizeof(T)); +} + +// For sample XML files see the GDB source /gdb/features +// This XML defines what the registers are for this specific ARM device +std::string GDBStubA64::GetTargetXML() const { + constexpr const char* target_xml = + R"(<?xml version="1.0"?> +<!DOCTYPE target SYSTEM "gdb-target.dtd"> +<target version="1.0"> + <architecture>aarch64</architecture> + <feature name="org.gnu.gdb.aarch64.core"> + <reg name="x0" bitsize="64"/> + <reg name="x1" bitsize="64"/> + <reg name="x2" bitsize="64"/> + <reg name="x3" bitsize="64"/> + <reg name="x4" bitsize="64"/> + <reg name="x5" bitsize="64"/> + <reg name="x6" bitsize="64"/> + <reg name="x7" bitsize="64"/> + <reg name="x8" bitsize="64"/> + <reg name="x9" bitsize="64"/> + <reg name="x10" bitsize="64"/> + <reg name="x11" bitsize="64"/> + <reg name="x12" bitsize="64"/> + <reg name="x13" bitsize="64"/> + <reg name="x14" bitsize="64"/> + <reg name="x15" bitsize="64"/> + <reg name="x16" bitsize="64"/> + <reg name="x17" bitsize="64"/> + <reg name="x18" bitsize="64"/> + <reg name="x19" bitsize="64"/> + <reg name="x20" bitsize="64"/> + <reg name="x21" bitsize="64"/> + <reg name="x22" bitsize="64"/> + <reg name="x23" bitsize="64"/> + <reg name="x24" bitsize="64"/> + <reg name="x25" bitsize="64"/> + <reg name="x26" bitsize="64"/> + <reg name="x27" bitsize="64"/> + <reg name="x28" bitsize="64"/> + <reg name="x29" bitsize="64"/> + <reg name="x30" bitsize="64"/> + <reg name="sp" bitsize="64" type="data_ptr"/> + <reg name="pc" bitsize="64" type="code_ptr"/> + <flags id="cpsr_flags" size="4"> + <field name="SP" start="0" end="0"/> + <field name="" start="1" end="1"/> + <field name="EL" start="2" end="3"/> + <field name="nRW" start="4" end="4"/> + <field name="" start="5" end="5"/> + <field name="F" start="6" end="6"/> + <field name="I" start="7" end="7"/> + <field name="A" start="8" end="8"/> + <field name="D" start="9" end="9"/> + <field name="IL" start="20" end="20"/> + <field name="SS" start="21" end="21"/> + <field name="V" start="28" end="28"/> + <field name="C" start="29" end="29"/> + <field name="Z" start="30" end="30"/> + <field name="N" start="31" end="31"/> + </flags> + <reg name="cpsr" bitsize="32" type="cpsr_flags"/> + </feature> + <feature name="org.gnu.gdb.aarch64.fpu"> + <vector id="v2d" type="ieee_double" count="2"/> + <vector id="v2u" type="uint64" count="2"/> + <vector id="v2i" type="int64" count="2"/> + <vector id="v4f" type="ieee_single" count="4"/> + <vector id="v4u" type="uint32" count="4"/> + <vector id="v4i" type="int32" count="4"/> + <vector id="v8u" type="uint16" count="8"/> + <vector id="v8i" type="int16" count="8"/> + <vector id="v16u" type="uint8" count="16"/> + <vector id="v16i" type="int8" count="16"/> + <vector id="v1u" type="uint128" count="1"/> + <vector id="v1i" type="int128" count="1"/> + <union id="vnd"> + <field name="f" type="v2d"/> + <field name="u" type="v2u"/> + <field name="s" type="v2i"/> + </union> + <union id="vns"> + <field name="f" type="v4f"/> + <field name="u" type="v4u"/> + <field name="s" type="v4i"/> + </union> + <union id="vnh"> + <field name="u" type="v8u"/> + <field name="s" type="v8i"/> + </union> + <union id="vnb"> + <field name="u" type="v16u"/> + <field name="s" type="v16i"/> + </union> + <union id="vnq"> + <field name="u" type="v1u"/> + <field name="s" type="v1i"/> + </union> + <union id="aarch64v"> + <field name="d" type="vnd"/> + <field name="s" type="vns"/> + <field name="h" type="vnh"/> + <field name="b" type="vnb"/> + <field name="q" type="vnq"/> + </union> + <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/> + <reg name="v1" bitsize="128" type="aarch64v" /> + <reg name="v2" bitsize="128" type="aarch64v" /> + <reg name="v3" bitsize="128" type="aarch64v" /> + <reg name="v4" bitsize="128" type="aarch64v" /> + <reg name="v5" bitsize="128" type="aarch64v" /> + <reg name="v6" bitsize="128" type="aarch64v" /> + <reg name="v7" bitsize="128" type="aarch64v" /> + <reg name="v8" bitsize="128" type="aarch64v" /> + <reg name="v9" bitsize="128" type="aarch64v" /> + <reg name="v10" bitsize="128" type="aarch64v"/> + <reg name="v11" bitsize="128" type="aarch64v"/> + <reg name="v12" bitsize="128" type="aarch64v"/> + <reg name="v13" bitsize="128" type="aarch64v"/> + <reg name="v14" bitsize="128" type="aarch64v"/> + <reg name="v15" bitsize="128" type="aarch64v"/> + <reg name="v16" bitsize="128" type="aarch64v"/> + <reg name="v17" bitsize="128" type="aarch64v"/> + <reg name="v18" bitsize="128" type="aarch64v"/> + <reg name="v19" bitsize="128" type="aarch64v"/> + <reg name="v20" bitsize="128" type="aarch64v"/> + <reg name="v21" bitsize="128" type="aarch64v"/> + <reg name="v22" bitsize="128" type="aarch64v"/> + <reg name="v23" bitsize="128" type="aarch64v"/> + <reg name="v24" bitsize="128" type="aarch64v"/> + <reg name="v25" bitsize="128" type="aarch64v"/> + <reg name="v26" bitsize="128" type="aarch64v"/> + <reg name="v27" bitsize="128" type="aarch64v"/> + <reg name="v28" bitsize="128" type="aarch64v"/> + <reg name="v29" bitsize="128" type="aarch64v"/> + <reg name="v30" bitsize="128" type="aarch64v"/> + <reg name="v31" bitsize="128" type="aarch64v"/> + <reg name="fpsr" bitsize="32"/> + <reg name="fpcr" bitsize="32"/> + </feature> +</target>)"; + + return target_xml; +} + +std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const { + if (!thread) { + return ""; + } + + const auto& context{thread->GetContext64()}; + const auto& gprs{context.cpu_registers}; + const auto& fprs{context.vector_registers}; + + if (id <= SP_REGISTER) { + return ValueToHex(gprs[id]); + } else if (id == PC_REGISTER) { + return ValueToHex(context.pc); + } else if (id == PSTATE_REGISTER) { + return ValueToHex(context.pstate); + } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) { + return ValueToHex(fprs[id - Q0_REGISTER]); + } else if (id == FPSR_REGISTER) { + return ValueToHex(context.fpsr); + } else if (id == FPCR_REGISTER) { + return ValueToHex(context.fpcr); + } else { + return ""; + } +} + +void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const { + if (!thread) { + return; + } + + auto& context{thread->GetContext64()}; + + if (id <= SP_REGISTER) { + context.cpu_registers[id] = HexToValue<u64>(value); + } else if (id == PC_REGISTER) { + context.pc = HexToValue<u64>(value); + } else if (id == PSTATE_REGISTER) { + context.pstate = HexToValue<u32>(value); + } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) { + context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value); + } else if (id == FPSR_REGISTER) { + context.fpsr = HexToValue<u32>(value); + } else if (id == FPCR_REGISTER) { + context.fpcr = HexToValue<u32>(value); + } +} + +std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const { + std::string output; + + for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) { + output += RegRead(thread, reg); + } + + return output; +} + +void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const { + for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) { + if (reg <= SP_REGISTER || reg == PC_REGISTER) { + RegWrite(thread, reg, register_data.substr(i, 16)); + i += 16; + } else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) { + RegWrite(thread, reg, register_data.substr(i, 8)); + i += 8; + } else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) { + RegWrite(thread, reg, register_data.substr(i, 32)); + i += 32; + } + } +} + +std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const { + return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER, + RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER), + LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID()); +} + +u32 GDBStubA64::BreakpointInstruction() const { + // A64: brk #0 + return 0xd4200000; +} + +std::string GDBStubA32::GetTargetXML() const { + constexpr const char* target_xml = + R"(<?xml version="1.0"?> +<!DOCTYPE target SYSTEM "gdb-target.dtd"> +<target version="1.0"> + <architecture>arm</architecture> + <feature name="org.gnu.gdb.arm.core"> + <reg name="r0" bitsize="32" type="uint32"/> + <reg name="r1" bitsize="32" type="uint32"/> + <reg name="r2" bitsize="32" type="uint32"/> + <reg name="r3" bitsize="32" type="uint32"/> + <reg name="r4" bitsize="32" type="uint32"/> + <reg name="r5" bitsize="32" type="uint32"/> + <reg name="r6" bitsize="32" type="uint32"/> + <reg name="r7" bitsize="32" type="uint32"/> + <reg name="r8" bitsize="32" type="uint32"/> + <reg name="r9" bitsize="32" type="uint32"/> + <reg name="r10" bitsize="32" type="uint32"/> + <reg name="r11" bitsize="32" type="uint32"/> + <reg name="r12" bitsize="32" type="uint32"/> + <reg name="sp" bitsize="32" type="data_ptr"/> + <reg name="lr" bitsize="32" type="code_ptr"/> + <reg name="pc" bitsize="32" type="code_ptr"/> + <!-- The CPSR is register 25, rather than register 16, because + the FPA registers historically were placed between the PC + and the CPSR in the "g" packet. --> + <reg name="cpsr" bitsize="32" regnum="25"/> + </feature> + <feature name="org.gnu.gdb.arm.vfp"> + <vector id="neon_uint8x8" type="uint8" count="8"/> + <vector id="neon_uint16x4" type="uint16" count="4"/> + <vector id="neon_uint32x2" type="uint32" count="2"/> + <vector id="neon_float32x2" type="ieee_single" count="2"/> + <union id="neon_d"> + <field name="u8" type="neon_uint8x8"/> + <field name="u16" type="neon_uint16x4"/> + <field name="u32" type="neon_uint32x2"/> + <field name="u64" type="uint64"/> + <field name="f32" type="neon_float32x2"/> + <field name="f64" type="ieee_double"/> + </union> + <vector id="neon_uint8x16" type="uint8" count="16"/> + <vector id="neon_uint16x8" type="uint16" count="8"/> + <vector id="neon_uint32x4" type="uint32" count="4"/> + <vector id="neon_uint64x2" type="uint64" count="2"/> + <vector id="neon_float32x4" type="ieee_single" count="4"/> + <vector id="neon_float64x2" type="ieee_double" count="2"/> + <union id="neon_q"> + <field name="u8" type="neon_uint8x16"/> + <field name="u16" type="neon_uint16x8"/> + <field name="u32" type="neon_uint32x4"/> + <field name="u64" type="neon_uint64x2"/> + <field name="f32" type="neon_float32x4"/> + <field name="f64" type="neon_float64x2"/> + </union> + <reg name="d0" bitsize="64" type="neon_d" regnum="32"/> + <reg name="d1" bitsize="64" type="neon_d"/> + <reg name="d2" bitsize="64" type="neon_d"/> + <reg name="d3" bitsize="64" type="neon_d"/> + <reg name="d4" bitsize="64" type="neon_d"/> + <reg name="d5" bitsize="64" type="neon_d"/> + <reg name="d6" bitsize="64" type="neon_d"/> + <reg name="d7" bitsize="64" type="neon_d"/> + <reg name="d8" bitsize="64" type="neon_d"/> + <reg name="d9" bitsize="64" type="neon_d"/> + <reg name="d10" bitsize="64" type="neon_d"/> + <reg name="d11" bitsize="64" type="neon_d"/> + <reg name="d12" bitsize="64" type="neon_d"/> + <reg name="d13" bitsize="64" type="neon_d"/> + <reg name="d14" bitsize="64" type="neon_d"/> + <reg name="d15" bitsize="64" type="neon_d"/> + <reg name="d16" bitsize="64" type="neon_d"/> + <reg name="d17" bitsize="64" type="neon_d"/> + <reg name="d18" bitsize="64" type="neon_d"/> + <reg name="d19" bitsize="64" type="neon_d"/> + <reg name="d20" bitsize="64" type="neon_d"/> + <reg name="d21" bitsize="64" type="neon_d"/> + <reg name="d22" bitsize="64" type="neon_d"/> + <reg name="d23" bitsize="64" type="neon_d"/> + <reg name="d24" bitsize="64" type="neon_d"/> + <reg name="d25" bitsize="64" type="neon_d"/> + <reg name="d26" bitsize="64" type="neon_d"/> + <reg name="d27" bitsize="64" type="neon_d"/> + <reg name="d28" bitsize="64" type="neon_d"/> + <reg name="d29" bitsize="64" type="neon_d"/> + <reg name="d30" bitsize="64" type="neon_d"/> + <reg name="d31" bitsize="64" type="neon_d"/> + + <reg name="q0" bitsize="128" type="neon_q" regnum="64"/> + <reg name="q1" bitsize="128" type="neon_q"/> + <reg name="q2" bitsize="128" type="neon_q"/> + <reg name="q3" bitsize="128" type="neon_q"/> + <reg name="q4" bitsize="128" type="neon_q"/> + <reg name="q5" bitsize="128" type="neon_q"/> + <reg name="q6" bitsize="128" type="neon_q"/> + <reg name="q7" bitsize="128" type="neon_q"/> + <reg name="q8" bitsize="128" type="neon_q"/> + <reg name="q9" bitsize="128" type="neon_q"/> + <reg name="q10" bitsize="128" type="neon_q"/> + <reg name="q10" bitsize="128" type="neon_q"/> + <reg name="q12" bitsize="128" type="neon_q"/> + <reg name="q13" bitsize="128" type="neon_q"/> + <reg name="q14" bitsize="128" type="neon_q"/> + <reg name="q15" bitsize="128" type="neon_q"/> + + <reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/> + </feature> +</target>)"; + + return target_xml; +} + +std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const { + if (!thread) { + return ""; + } + + const auto& context{thread->GetContext32()}; + const auto& gprs{context.cpu_registers}; + const auto& fprs{context.extension_registers}; + + if (id <= PC_REGISTER) { + return ValueToHex(gprs[id]); + } else if (id == CPSR_REGISTER) { + return ValueToHex(context.cpsr); + } else if (id >= D0_REGISTER && id < Q0_REGISTER) { + const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)}; + return ValueToHex(dN); + } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) { + const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)}; + return ValueToHex(qN); + } else if (id == FPSCR_REGISTER) { + return ValueToHex(context.fpscr); + } else { + return ""; + } +} + +void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const { + if (!thread) { + return; + } + + auto& context{thread->GetContext32()}; + auto& fprs{context.extension_registers}; + + if (id <= PC_REGISTER) { + context.cpu_registers[id] = HexToValue<u32>(value); + } else if (id == CPSR_REGISTER) { + context.cpsr = HexToValue<u32>(value); + } else if (id >= D0_REGISTER && id < Q0_REGISTER) { + PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value)); + } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) { + PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value)); + } else if (id == FPSCR_REGISTER) { + context.fpscr = HexToValue<u32>(value); + } +} + +std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const { + std::string output; + + for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) { + const bool gpr{reg <= PC_REGISTER}; + const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER}; + const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER}; + + if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) { + continue; + } + + output += RegRead(thread, reg); + } + + return output; +} + +void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const { + for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) { + const bool gpr{reg <= PC_REGISTER}; + const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER}; + const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER}; + + if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) { + RegWrite(thread, reg, register_data.substr(i, 8)); + i += 8; + } else if (dfpr) { + RegWrite(thread, reg, register_data.substr(i, 16)); + i += 16; + } else if (qfpr) { + RegWrite(thread, reg, register_data.substr(i, 32)); + i += 32; + } + + if (reg == PC_REGISTER) { + reg = CPSR_REGISTER - 1; + } else if (reg == CPSR_REGISTER) { + reg = D0_REGISTER - 1; + } + } +} + +std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const { + return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER, + RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER), + LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID()); +} + +u32 GDBStubA32::BreakpointInstruction() const { + // A32: trap + // T32: trap + b #4 + return 0xe7ffdefe; +} + +} // namespace Core diff --git a/src/core/debugger/gdbstub_arch.h b/src/core/debugger/gdbstub_arch.h new file mode 100644 index 000000000..2540d6456 --- /dev/null +++ b/src/core/debugger/gdbstub_arch.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "common/common_types.h" + +namespace Kernel { +class KThread; +} + +namespace Core { + +class GDBStubArch { +public: + virtual ~GDBStubArch() = default; + virtual std::string GetTargetXML() const = 0; + virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0; + virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0; + virtual std::string ReadRegisters(const Kernel::KThread* thread) const = 0; + virtual void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const = 0; + virtual std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const = 0; + virtual u32 BreakpointInstruction() const = 0; +}; + +class GDBStubA64 final : public GDBStubArch { +public: + std::string GetTargetXML() const override; + std::string RegRead(const Kernel::KThread* thread, size_t id) const override; + void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override; + std::string ReadRegisters(const Kernel::KThread* thread) const override; + void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override; + std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override; + u32 BreakpointInstruction() const override; + +private: + static constexpr u32 LR_REGISTER = 30; + static constexpr u32 SP_REGISTER = 31; + static constexpr u32 PC_REGISTER = 32; + static constexpr u32 PSTATE_REGISTER = 33; + static constexpr u32 Q0_REGISTER = 34; + static constexpr u32 FPSR_REGISTER = 66; + static constexpr u32 FPCR_REGISTER = 67; +}; + +class GDBStubA32 final : public GDBStubArch { +public: + std::string GetTargetXML() const override; + std::string RegRead(const Kernel::KThread* thread, size_t id) const override; + void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override; + std::string ReadRegisters(const Kernel::KThread* thread) const override; + void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override; + std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override; + u32 BreakpointInstruction() const override; + +private: + static constexpr u32 SP_REGISTER = 13; + static constexpr u32 LR_REGISTER = 14; + static constexpr u32 PC_REGISTER = 15; + static constexpr u32 CPSR_REGISTER = 25; + static constexpr u32 D0_REGISTER = 32; + static constexpr u32 Q0_REGISTER = 64; + static constexpr u32 FPSCR_REGISTER = 80; +}; + +} // namespace Core diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 93f784418..78e56bbbd 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -419,7 +419,7 @@ std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type Core::Crypto::Mode::ECB); cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); - Core::Crypto::Key128 out; + Core::Crypto::Key128 out{}; if (type == NCASectionCryptoType::XTS) { std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp index d4c0a974a..2735d053b 100644 --- a/src/core/file_sys/nca_patch.cpp +++ b/src/core/file_sys/nca_patch.cpp @@ -50,7 +50,7 @@ std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockTyp low = mid + 1; } } - UNREACHABLE_MSG("Offset could not be found in BKTR block."); + ASSERT_MSG(false, "Offset could not be found in BKTR block."); return {0, 0}; } } // Anonymous namespace diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 2eaac73ef..878d832c2 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -108,7 +108,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. return ContentRecordType::HtmlDocument; default: - UNREACHABLE_MSG("Invalid NCAContentType={:02X}", type); + ASSERT_MSG(false, "Invalid NCAContentType={:02X}", type); return ContentRecordType{}; } } diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index e42d7c9f6..cc0076238 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -144,7 +144,7 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); } } else { - UNREACHABLE(); + ASSERT(false); return nullptr; } diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 0ff2a338e..6c230f619 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -65,7 +65,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); controller->Connect(true); } else { - UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!"); + ASSERT_MSG(false, "Unable to add a new controller based on the given parameters!"); } } diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp index fd220ccb5..aac45907d 100644 --- a/src/core/hid/emulated_console.cpp +++ b/src/core/hid/emulated_console.cpp @@ -27,12 +27,19 @@ void EmulatedConsole::SetTouchParams() { // We can't use mouse as touch if native mouse is enabled touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"}; } - touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"}; - touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"}; + + touch_params[index++] = + Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0,touch_id:0"}; + touch_params[index++] = + Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1,touch_id:1"}; + touch_params[index++] = + Common::ParamPackage{"engine:touch,axis_x:4,axis_y:5,button:2,touch_id:2"}; + touch_params[index++] = + Common::ParamPackage{"engine:touch,axis_x:6,axis_y:7,button:3,touch_id:3"}; touch_params[index++] = - Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"}; + Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536,touch_id:0"}; touch_params[index++] = - Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"}; + Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072,touch_id:1"}; const auto button_index = static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue()); diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index ba1dcd171..bd2384515 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -884,18 +884,42 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v } bool EmulatedController::TestVibration(std::size_t device_index) { - static constexpr VibrationValue test_vibration = { + if (device_index >= output_devices.size()) { + return false; + } + if (!output_devices[device_index]) { + return false; + } + + const auto player_index = NpadIdTypeToIndex(npad_id_type); + const auto& player = Settings::values.players.GetValue()[player_index]; + + if (!player.vibration_enabled) { + return false; + } + + const Common::Input::VibrationStatus test_vibration = { .low_amplitude = 0.001f, - .low_frequency = 160.0f, + .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency, .high_amplitude = 0.001f, - .high_frequency = 320.0f, + .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency, + .type = Common::Input::VibrationAmplificationType::Test, + }; + + const Common::Input::VibrationStatus zero_vibration = { + .low_amplitude = DEFAULT_VIBRATION_VALUE.low_amplitude, + .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency, + .high_amplitude = DEFAULT_VIBRATION_VALUE.high_amplitude, + .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency, + .type = Common::Input::VibrationAmplificationType::Test, }; // Send a slight vibration to test for rumble support - SetVibration(device_index, test_vibration); + output_devices[device_index]->SetVibration(test_vibration); // Stop any vibration and return the result - return SetVibration(device_index, DEFAULT_VIBRATION_VALUE); + return output_devices[device_index]->SetVibration(zero_vibration) == + Common::Input::VibrationError::None; } bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp index 7eed52593..7d6373414 100644 --- a/src/core/hid/hid_core.cpp +++ b/src/core/hid/hid_core.cpp @@ -48,7 +48,7 @@ EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) { return handheld.get(); case NpadIdType::Invalid: default: - UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type); + ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type); return nullptr; } } @@ -77,7 +77,7 @@ const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type return handheld.get(); case NpadIdType::Invalid: default: - UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type); + ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type); return nullptr; } } diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 26ec1091b..9f76f9bcb 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h @@ -498,6 +498,49 @@ struct SixAxisSensorFusionParameters { static_assert(sizeof(SixAxisSensorFusionParameters) == 8, "SixAxisSensorFusionParameters is an invalid size"); +// This is nn::hid::server::SixAxisSensorProperties +struct SixAxisSensorProperties { + union { + u8 raw{}; + BitField<0, 1, u8> is_newly_assigned; + BitField<1, 1, u8> is_firmware_update_available; + }; +}; +static_assert(sizeof(SixAxisSensorProperties) == 1, "SixAxisSensorProperties is an invalid size"); + +// This is nn::hid::SixAxisSensorCalibrationParameter +struct SixAxisSensorCalibrationParameter { + std::array<u8, 0x744> unknown_data{}; +}; +static_assert(sizeof(SixAxisSensorCalibrationParameter) == 0x744, + "SixAxisSensorCalibrationParameter is an invalid size"); + +// This is nn::hid::SixAxisSensorIcInformation +struct SixAxisSensorIcInformation { + f32 angular_rate{2000.0f}; // dps + std::array<f32, 6> unknown_gyro_data1{ + -10.0f, -10.0f, -10.0f, 10.0f, 10.0f, 10.0f, + }; // dps + std::array<f32, 9> unknown_gyro_data2{ + 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, + }; + std::array<f32, 9> unknown_gyro_data3{ + 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, + }; + f32 acceleration_range{8.0f}; // g force + std::array<f32, 6> unknown_accel_data1{ + -0.0612f, -0.0612f, -0.0612f, 0.0612f, 0.0612f, 0.0612f, + }; // g force + std::array<f32, 9> unknown_accel_data2{ + 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f, + }; + std::array<f32, 9> unknown_accel_data3{ + 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f, + }; +}; +static_assert(sizeof(SixAxisSensorIcInformation) == 0xC8, + "SixAxisSensorIcInformation is an invalid size"); + // This is nn::hid::VibrationDeviceHandle struct VibrationDeviceHandle { NpadStyleIndex npad_type{NpadStyleIndex::None}; diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 3c26260f3..18d9f042d 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> #include <random> #include "common/input.h" @@ -196,6 +197,9 @@ Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& x = std::clamp(x, 0.0f, 1.0f); y = std::clamp(y, 0.0f, 1.0f); + // Limit id to maximum number of fingers + status.id = std::clamp(status.id, 0, 16); + if (status.pressed.inverted) { status.pressed.value = !status.pressed.value; } diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index a427cbc93..0ddc8df9e 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -141,7 +141,7 @@ public: if (index < DomainHandlerCount()) { domain_handlers[index] = nullptr; } else { - UNREACHABLE_MSG("Unexpected handler index {}", index); + ASSERT_MSG(false, "Unexpected handler index {}", index); } } diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index 34a8be052..9b6b284d0 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -244,7 +244,7 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) { FOREACH_SLAB_TYPE(INITIALIZE_SLAB_HEAP) // If we somehow get an invalid type, abort. default: - UNREACHABLE_MSG("Unknown slab type: {}", slab_types[i]); + ASSERT_MSG(false, "Unknown slab type: {}", slab_types[i]); } // If we've hit the end of a gap, free it. diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h index e46e0d848..5fa19d386 100644 --- a/src/core/hle/kernel/k_address_arbiter.h +++ b/src/core/hle/kernel/k_address_arbiter.h @@ -35,7 +35,7 @@ public: case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: return SignalAndModifyByWaitingCountIfEqual(addr, value, count); } - UNREACHABLE(); + ASSERT(false); return ResultUnknown; } @@ -49,7 +49,7 @@ public: case Svc::ArbitrationType::WaitIfEqual: return WaitIfEqual(addr, value, timeout); } - UNREACHABLE(); + ASSERT(false); return ResultUnknown; } diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp index bc37cadda..3e612a207 100644 --- a/src/core/hle/kernel/k_address_space_info.cpp +++ b/src/core/hle/kernel/k_address_space_info.cpp @@ -84,7 +84,7 @@ u64 KAddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) { ASSERT(IsAllowedIndexForAddress(AddressSpaceIndices39Bit[index])); return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address; } - UNREACHABLE(); + ASSERT(false); return 0; } @@ -101,7 +101,7 @@ std::size_t KAddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) ASSERT(IsAllowed39BitType(type)); return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size; } - UNREACHABLE(); + ASSERT(false); return 0; } diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index ea47fc600..2827763d5 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -18,7 +18,7 @@ namespace Kernel { class KernelCore; class KProcess; -#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \ +#define KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, ATTRIBUTE) \ \ private: \ friend class ::Kernel::KClassTokenGenerator; \ @@ -40,16 +40,19 @@ public: static constexpr const char* GetStaticTypeName() { \ return TypeName; \ } \ - virtual TypeObj GetTypeObj() const { \ + virtual TypeObj GetTypeObj() ATTRIBUTE { \ return GetStaticTypeObj(); \ } \ - virtual const char* GetTypeName() const { \ + virtual const char* GetTypeName() ATTRIBUTE { \ return GetStaticTypeName(); \ } \ \ private: \ constexpr bool operator!=(const TypeObj& rhs) +#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \ + KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, const override) + class KAutoObject { protected: class TypeObj { @@ -82,7 +85,7 @@ protected: }; private: - KERNEL_AUTOOBJECT_TRAITS(KAutoObject, KAutoObject); + KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const); public: explicit KAutoObject(KernelCore& kernel_) : kernel(kernel_) { diff --git a/src/core/hle/kernel/k_class_token.h b/src/core/hle/kernel/k_class_token.h index be9e3c357..c9001ae3d 100644 --- a/src/core/hle/kernel/k_class_token.h +++ b/src/core/hle/kernel/k_class_token.h @@ -49,6 +49,7 @@ private: } } } + UNREACHABLE(); }(); template <typename T> diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index fd3cbfd94..4ae40ec8e 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp @@ -27,23 +27,18 @@ ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr auto& page_table = m_owner->PageTable(); // Construct the page group. - m_page_group = - KPageLinkedList(page_table.GetPhysicalAddr(addr), Common::DivideUp(size, PageSize)); + m_page_group = {}; // Lock the memory. - R_TRY(page_table.LockForCodeMemory(addr, size)) + R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size)) // Clear the memory. - // - // FIXME: this ends up clobbering address ranges outside the scope of the mapping within - // guest memory, and is not specifically required if the guest program is correctly - // written, so disable until this is further investigated. - // - // for (const auto& block : m_page_group.Nodes()) { - // std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); - // } + for (const auto& block : m_page_group.Nodes()) { + std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); + } // Set remaining tracking members. + m_owner->Open(); m_address = addr; m_is_initialized = true; m_is_owner_mapped = false; @@ -57,8 +52,14 @@ void KCodeMemory::Finalize() { // Unlock. if (!m_is_mapped && !m_is_owner_mapped) { const size_t size = m_page_group.GetNumPages() * PageSize; - m_owner->PageTable().UnlockForCodeMemory(m_address, size); + m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group); } + + // Close the page group. + m_page_group = {}; + + // Close our reference to our owner. + m_owner->Close(); } ResultCode KCodeMemory::Map(VAddr address, size_t size) { @@ -118,7 +119,8 @@ ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermis k_perm = KMemoryPermission::UserReadExecute; break; default: - break; + // Already validated by ControlCodeMemory svc + UNREACHABLE(); } // Map the memory. diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index a55db3088..58e540f31 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -29,7 +29,7 @@ constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) { } else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) { return KMemoryManager::Pool::SystemNonSecure; } else { - UNREACHABLE_MSG("InvalidMemoryRegionType for conversion to Pool"); + ASSERT_MSG(false, "InvalidMemoryRegionType for conversion to Pool"); return {}; } } diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index b38ef333b..504e22cb9 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -35,7 +35,7 @@ constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceT case FileSys::ProgramAddressSpaceType::Is39Bit: return 39; default: - UNREACHABLE(); + ASSERT(false); return {}; } } @@ -128,7 +128,7 @@ ResultCode KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_ const std::size_t needed_size{ (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)}; if (alloc_size < needed_size) { - UNREACHABLE(); + ASSERT(false); return ResultOutOfMemory; } @@ -542,6 +542,95 @@ ResultCode KPageTable::MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num return ResultSuccess; } +bool KPageTable::IsValidPageGroup(const KPageLinkedList& pg_ll, VAddr addr, size_t num_pages) { + ASSERT(this->IsLockedByCurrentThread()); + + const size_t size = num_pages * PageSize; + const auto& pg = pg_ll.Nodes(); + const auto& memory_layout = system.Kernel().MemoryLayout(); + + // Empty groups are necessarily invalid. + if (pg.empty()) { + return false; + } + + // We're going to validate that the group we'd expect is the group we see. + auto cur_it = pg.begin(); + PAddr cur_block_address = cur_it->GetAddress(); + size_t cur_block_pages = cur_it->GetNumPages(); + + auto UpdateCurrentIterator = [&]() { + if (cur_block_pages == 0) { + if ((++cur_it) == pg.end()) { + return false; + } + + cur_block_address = cur_it->GetAddress(); + cur_block_pages = cur_it->GetNumPages(); + } + return true; + }; + + // Begin traversal. + Common::PageTable::TraversalContext context; + Common::PageTable::TraversalEntry next_entry; + if (!page_table_impl.BeginTraversal(next_entry, context, addr)) { + return false; + } + + // Prepare tracking variables. + PAddr cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (cur_addr & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + // Iterate, comparing expected to actual. + while (tot_size < size) { + if (!page_table_impl.ContinueTraversal(next_entry, context)) { + return false; + } + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + const size_t cur_pages = cur_size / PageSize; + + if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) { + return false; + } + + if (!UpdateCurrentIterator()) { + return false; + } + + if (cur_block_address != cur_addr || cur_block_pages < cur_pages) { + return false; + } + + cur_block_address += cur_size; + cur_block_pages -= cur_pages; + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + // Ensure we compare the right amount for the last block. + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) { + return false; + } + + if (!UpdateCurrentIterator()) { + return false; + } + + return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize); +} + ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table, VAddr src_addr) { KScopedLightLock lk(general_lock); @@ -1341,7 +1430,7 @@ ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, new_state = KMemoryState::AliasCodeData; break; default: - UNREACHABLE(); + ASSERT(false); } } @@ -1687,22 +1776,22 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) return ResultSuccess; } -ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) { +ResultCode KPageTable::LockForCodeMemory(KPageLinkedList* out, VAddr addr, std::size_t size) { return this->LockMemoryAndOpen( - nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, - KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite, - KMemoryAttribute::All, KMemoryAttribute::None, + out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, + KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, + KMemoryAttribute::None, static_cast<KMemoryPermission>(KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite), KMemoryAttribute::Locked); } -ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) { - return this->UnlockMemory(addr, size, KMemoryState::FlagCanCodeMemory, - KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, - KMemoryPermission::None, KMemoryAttribute::All, - KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, - KMemoryAttribute::Locked, nullptr); +ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size, + const KPageLinkedList& pg) { + return this->UnlockMemory( + addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, + KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, + KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg); } ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) { @@ -1734,9 +1823,7 @@ void KPageTable::AddRegionToPages(VAddr start, std::size_t num_pages, VAddr addr{start}; while (addr < start + (num_pages * PageSize)) { const PAddr paddr{GetPhysicalAddr(addr)}; - if (!paddr) { - UNREACHABLE(); - } + ASSERT(paddr != 0); page_linked_list.AddBlock(paddr, 1); addr += PageSize; } @@ -1767,7 +1854,7 @@ ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageLin system.Memory().MapMemoryRegion(page_table_impl, addr, size, node.GetAddress()); break; default: - UNREACHABLE(); + ASSERT(false); } addr += size; @@ -1798,7 +1885,7 @@ ResultCode KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermiss case OperationType::ChangePermissionsAndRefresh: break; default: - UNREACHABLE(); + ASSERT(false); } return ResultSuccess; } @@ -1835,7 +1922,6 @@ VAddr KPageTable::GetRegionAddress(KMemoryState state) const { return code_region_start; default: UNREACHABLE(); - return {}; } } @@ -1871,7 +1957,6 @@ std::size_t KPageTable::GetRegionSize(KMemoryState state) const { return code_region_end - code_region_start; default: UNREACHABLE(); - return {}; } } @@ -2125,7 +2210,7 @@ ResultCode KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_ // Check the page group. if (pg != nullptr) { - UNIMPLEMENTED_MSG("PageGroup support is unimplemented!"); + R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), ResultInvalidMemoryRegion); } // Decide on new perm and attr. diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 52a93ce86..6312eb682 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -72,8 +72,8 @@ public: KMemoryPermission perm, PAddr map_addr = 0); ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size); ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size); - ResultCode LockForCodeMemory(VAddr addr, std::size_t size); - ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size); + ResultCode LockForCodeMemory(KPageLinkedList* out, VAddr addr, std::size_t size); + ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageLinkedList& pg); ResultCode MakeAndOpenPageGroup(KPageLinkedList* out, VAddr address, size_t num_pages, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, @@ -178,6 +178,7 @@ private: const KPageLinkedList* pg); ResultCode MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num_pages); + bool IsValidPageGroup(const KPageLinkedList& pg, VAddr addr, size_t num_pages); bool IsLockedByCurrentThread() const { return general_lock.IsLockedByCurrentThread(); diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp index a31861cdb..51c2cd1ef 100644 --- a/src/core/hle/kernel/k_port.cpp +++ b/src/core/hle/kernel/k_port.cpp @@ -60,7 +60,7 @@ ResultCode KPort::EnqueueSession(KServerSession* session) { if (auto session_ptr = server.GetSessionRequestHandler().lock()) { session_ptr->ClientConnected(server.AcceptSession()); } else { - UNREACHABLE(); + ASSERT(false); } return ResultSuccess; diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 490e31fc7..8c79b4f0f 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -64,6 +64,10 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority { KScopedSchedulerLock lock{kernel}; thread->SetState(ThreadState::Runnable); + + if (system.DebuggerEnabled()) { + thread->RequestSuspend(SuspendType::Debug); + } } } } // Anonymous namespace @@ -346,7 +350,7 @@ ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, break; default: - UNREACHABLE(); + ASSERT(false); } // Create TLS region diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index 7e39f6d50..60f8ed470 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp @@ -97,13 +97,13 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co "object_id {} is too big! This probably means a recent service call " "to {} needed to return a new interface!", object_id, name); - UNREACHABLE(); + ASSERT(false); return ResultSuccess; // Ignore error if asserts are off } if (auto strong_ptr = manager->DomainHandler(object_id - 1).lock()) { return strong_ptr->HandleSyncRequest(*this, context); } else { - UNREACHABLE(); + ASSERT(false); return ResultSuccess; } diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index ab9ce6a86..ea2160099 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -133,7 +133,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s UNIMPLEMENTED(); break; default: - UNREACHABLE_MSG("KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type)); + ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type)); break; } thread_type = type; @@ -198,6 +198,10 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s resource_limit_release_hint = false; cpu_time = 0; + // Set debug context. + stack_top = user_stack_top; + argument = arg; + // Clear our stack parameters. std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0, sizeof(StackParameters)); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index b55a922ab..f4d83f99a 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -100,6 +100,12 @@ enum class ThreadWaitReasonForDebugging : u32 { Suspended, ///< Thread is waiting due to process suspension }; +enum class StepState : u32 { + NotStepping, ///< Thread is not currently stepping + StepPending, ///< Thread will step when next scheduled + StepPerformed, ///< Thread has stepped, waiting to be scheduled again +}; + [[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel); [[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel); [[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel); @@ -267,6 +273,14 @@ public: void SetState(ThreadState state); + [[nodiscard]] StepState GetStepState() const { + return step_state; + } + + void SetStepState(StepState state) { + step_state = state; + } + [[nodiscard]] s64 GetLastScheduledTick() const { return last_scheduled_tick; } @@ -646,6 +660,14 @@ public: void IfDummyThreadTryWait(); void IfDummyThreadEndWait(); + [[nodiscard]] uintptr_t GetArgument() const { + return argument; + } + + [[nodiscard]] VAddr GetUserStackTop() const { + return stack_top; + } + private: static constexpr size_t PriorityInheritanceCountMax = 10; union SyncObjectBuffer { @@ -769,6 +791,7 @@ private: std::shared_ptr<Common::Fiber> host_context{}; bool is_single_core{}; ThreadType thread_type{}; + StepState step_state{}; std::mutex dummy_wait_lock; std::condition_variable dummy_wait_cv; @@ -776,6 +799,8 @@ private: std::vector<KSynchronizationObject*> wait_objects_for_debugging; VAddr mutex_wait_address_for_debugging{}; ThreadWaitReasonForDebugging wait_reason_for_debugging{}; + uintptr_t argument; + VAddr stack_top; public: using ConditionVariableThreadTreeType = ConditionVariableThreadTree; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 92f6d8c49..b2c4f12b4 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -212,7 +212,9 @@ struct KernelCore::Impl { system_resource_limit = KResourceLimit::Create(system.Kernel()); system_resource_limit->Initialize(&core_timing); - const auto [total_size, kernel_size] = memory_layout->GetTotalAndKernelMemorySizes(); + const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; + const auto total_size{sizes.first}; + const auto kernel_size{sizes.second}; // If setting the default system values fails, then something seriously wrong has occurred. ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, total_size) @@ -252,6 +254,7 @@ struct KernelCore::Impl { core_id) .IsSuccess()); suspend_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id)); + suspend_threads[core_id]->DisableDispatch(); } } @@ -1073,9 +1076,6 @@ void KernelCore::Suspend(bool in_suspention) { impl->suspend_threads[core_id]->SetState(state); impl->suspend_threads[core_id]->SetWaitReasonForDebugging( ThreadWaitReasonForDebugging::Suspended); - if (!should_suspend) { - impl->suspend_threads[core_id]->DisableDispatch(); - } } } } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 66e0ce2d0..4f0a44363 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -15,6 +15,7 @@ #include "common/scope_exit.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/debugger/debugger.h" #include "core/hle/kernel/k_client_port.h" #include "core/hle/kernel/k_client_session.h" #include "core/hle/kernel/k_code_memory.h" @@ -627,6 +628,12 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { const auto thread_processor_id = current_thread->GetActiveCore(); system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); } + + if (system.DebuggerEnabled()) { + auto* thread = system.Kernel().GetCurrentEmuThread(); + system.GetDebugger().NotifyThreadStopped(thread); + thread->RequestSuspend(Kernel::SuspendType::Debug); + } } static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { @@ -1876,7 +1883,7 @@ static void SleepThread(Core::System& system, s64 nanoseconds) { KScheduler::YieldToAnyThread(kernel); } else { // Nintendo does nothing at all if an otherwise invalid value is passed. - UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); + ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } @@ -2982,7 +2989,6 @@ static const FunctionDef* GetSVCInfo64(u32 func_num) { } void Call(Core::System& system, u32 immediate) { - system.ExitDynarmicProfile(); auto& kernel = system.Kernel(); kernel.EnterSVCProfile(); @@ -3007,8 +3013,6 @@ void Call(Core::System& system, u32 immediate) { auto* host_context = thread->GetHostContext().get(); host_context->Rewind(); } - - system.EnterDynarmicProfile(); } } // namespace Kernel::Svc diff --git a/src/core/hle/service/am/applets/applet_controller.cpp b/src/core/hle/service/am/applets/applet_controller.cpp index 655f2e936..0a5603d18 100644 --- a/src/core/hle/service/am/applets/applet_controller.cpp +++ b/src/core/hle/service/am/applets/applet_controller.cpp @@ -178,7 +178,7 @@ ResultCode Controller::GetStatus() const { } void Controller::ExecuteInteractive() { - UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); + ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet."); } void Controller::Execute() { diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp index 911b2c229..0b87c60b9 100644 --- a/src/core/hle/service/am/applets/applet_error.cpp +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -156,7 +156,7 @@ ResultCode Error::GetStatus() const { } void Error::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data!"); + ASSERT_MSG(false, "Unexpected interactive applet data!"); } void Error::Execute() { diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp index 3fe1a390a..41c002ef2 100644 --- a/src/core/hle/service/am/applets/applet_general_backend.cpp +++ b/src/core/hle/service/am/applets/applet_general_backend.cpp @@ -76,7 +76,7 @@ ResultCode Auth::GetStatus() const { } void Auth::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data."); + ASSERT_MSG(false, "Unexpected interactive applet data."); } void Auth::Execute() { @@ -175,7 +175,7 @@ ResultCode PhotoViewer::GetStatus() const { } void PhotoViewer::ExecuteInteractive() { - UNREACHABLE_MSG("Unexpected interactive applet data."); + ASSERT_MSG(false, "Unexpected interactive applet data."); } void PhotoViewer::Execute() { diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index 3acde1630..8d847c3f6 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp @@ -67,7 +67,7 @@ ResultCode MiiEdit::GetStatus() const { } void MiiEdit::ExecuteInteractive() { - UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); + ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet."); } void MiiEdit::Execute() { diff --git a/src/core/hle/service/am/applets/applet_profile_select.cpp b/src/core/hle/service/am/applets/applet_profile_select.cpp index fd16f2e49..02049fd9f 100644 --- a/src/core/hle/service/am/applets/applet_profile_select.cpp +++ b/src/core/hle/service/am/applets/applet_profile_select.cpp @@ -44,7 +44,7 @@ ResultCode ProfileSelect::GetStatus() const { } void ProfileSelect::ExecuteInteractive() { - UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); + ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet."); } void ProfileSelect::Execute() { diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp index 7c21365e4..4116fbaa7 100644 --- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp +++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp @@ -71,7 +71,7 @@ void SoftwareKeyboard::Initialize() { InitializeBackground(applet_mode); break; default: - UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode); + ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode); break; } } diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2aa4a00ad..7b3f77a51 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -279,7 +279,7 @@ void WebBrowser::Initialize() { InitializeLobby(); break; default: - UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); break; } } @@ -320,7 +320,7 @@ void WebBrowser::Execute() { ExecuteLobby(); break; default: - UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); WebBrowserExit(WebExitReason::EndButtonPressed); break; } diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index ddfcba0f1..fae6e5aff 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -899,7 +899,7 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { case FileSys::SaveDataSpaceId::TemporaryStorage: case FileSys::SaveDataSpaceId::ProperSystem: case FileSys::SaveDataSpaceId::SafeMode: - UNREACHABLE(); + ASSERT(false); } auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()), diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 3eae1ae35..32e0708ba 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -61,6 +61,7 @@ void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) { } last_update_timestamp = shared_memory->gesture_lifo.timestamp; + UpdateGestureSharedMemory(gesture, time_difference); } void Controller_Gesture::ReadTouchInput() { @@ -94,8 +95,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture, return false; } -void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size, - GestureProperties& gesture, +void Controller_Gesture::UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference) { GestureType type = GestureType::Idle; GestureAttribute attributes{}; diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index c62a341bf..0d6099ea0 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -107,8 +107,7 @@ private: bool ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference); // Updates the shared memory to the next state - void UpdateGestureSharedMemory(u8* data, std::size_t size, GestureProperties& gesture, - f32 time_difference); + void UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference); // Initializes new gesture void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 17f71beaf..ac5c38cc6 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -3,7 +3,9 @@ #include <algorithm> #include <array> +#include <chrono> #include <cstring> + #include "common/assert.h" #include "common/bit_field.h" #include "common/common_types.h" @@ -54,11 +56,22 @@ bool Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle return npad_id && npad_type && device_index; } -bool Controller_NPad::IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle) { +ResultCode Controller_NPad::VerifyValidSixAxisSensorHandle( + const Core::HID::SixAxisSensorHandle& device_handle) { const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)); - const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType; + if (!npad_id) { + return InvalidNpadId; + } const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex; - return npad_id && npad_type && device_index; + if (!device_index) { + return NpadDeviceIndexOutOfRange; + } + // This doesn't get validated on nnsdk + const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType; + if (!npad_type) { + return NpadInvalidHandle; + } + return ResultSuccess; } Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_, @@ -147,7 +160,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.raw = 0; switch (controller_type) { case Core::HID::NpadStyleIndex::None: - UNREACHABLE(); + ASSERT(false); break; case Core::HID::NpadStyleIndex::ProController: shared_memory->style_tag.fullkey.Assign(1); @@ -156,6 +169,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_plus.Assign(1); shared_memory->system_properties.use_minus.Assign(1); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; + shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::Handheld: shared_memory->style_tag.handheld.Assign(1); @@ -168,16 +182,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; + shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconDual: shared_memory->style_tag.joycon_dual.Assign(1); if (controller.is_dual_left_connected) { shared_memory->device_type.joycon_left.Assign(1); shared_memory->system_properties.use_minus.Assign(1); + shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1); } if (controller.is_dual_right_connected) { shared_memory->device_type.joycon_right.Assign(1); shared_memory->system_properties.use_plus.Assign(1); + shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1); } shared_memory->system_properties.use_directional_buttons.Assign(1); shared_memory->system_properties.is_vertical.Assign(1); @@ -196,6 +213,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.is_horizontal.Assign(1); shared_memory->system_properties.use_minus.Assign(1); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; + shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconRight: shared_memory->style_tag.joycon_right.Assign(1); @@ -203,6 +221,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.is_horizontal.Assign(1); shared_memory->system_properties.use_plus.Assign(1); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; + shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::GameCube: shared_memory->style_tag.gamecube.Assign(1); @@ -213,6 +232,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { case Core::HID::NpadStyleIndex::Pokeball: shared_memory->style_tag.palma.Assign(1); shared_memory->device_type.palma.Assign(1); + shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::NES: shared_memory->style_tag.lark.Assign(1); @@ -402,7 +422,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { libnx_state.connection_status.is_connected.Assign(1); switch (controller_type) { case Core::HID::NpadStyleIndex::None: - UNREACHABLE(); + ASSERT(false); break; case Core::HID::NpadStyleIndex::ProController: case Core::HID::NpadStyleIndex::NES: @@ -529,6 +549,14 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing auto& sixaxis_left_lifo_state = controller.sixaxis_left_lifo_state; auto& sixaxis_right_lifo_state = controller.sixaxis_right_lifo_state; + // Clear previous state + sixaxis_fullkey_state = {}; + sixaxis_handheld_state = {}; + sixaxis_dual_left_state = {}; + sixaxis_dual_right_state = {}; + sixaxis_left_lifo_state = {}; + sixaxis_right_lifo_state = {}; + if (controller.sixaxis_sensor_enabled && Settings::values.motion_enabled.GetValue()) { controller.sixaxis_at_rest = true; for (std::size_t e = 0; e < motion_state.size(); ++e) { @@ -537,69 +565,56 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing } } + const auto set_motion_state = [&](SixAxisSensorState& state, + const Core::HID::ControllerMotion& hid_state) { + using namespace std::literals::chrono_literals; + static constexpr SixAxisSensorState default_motion_state = { + .delta_time = std::chrono::nanoseconds(5ms).count(), + .accel = {0, 0, -1.0f}, + .orientation = + { + Common::Vec3f{1.0f, 0, 0}, + Common::Vec3f{0, 1.0f, 0}, + Common::Vec3f{0, 0, 1.0f}, + }, + .attribute = {1}, + }; + if (!controller.sixaxis_sensor_enabled) { + state = default_motion_state; + return; + } + if (!Settings::values.motion_enabled.GetValue()) { + state = default_motion_state; + return; + } + state.attribute.is_connected.Assign(1); + state.delta_time = std::chrono::nanoseconds(5ms).count(); + state.accel = hid_state.accel; + state.gyro = hid_state.gyro; + state.rotation = hid_state.rotation; + state.orientation = hid_state.orientation; + }; + switch (controller_type) { case Core::HID::NpadStyleIndex::None: - UNREACHABLE(); + ASSERT(false); break; case Core::HID::NpadStyleIndex::ProController: - sixaxis_fullkey_state.attribute.raw = 0; - if (controller.sixaxis_sensor_enabled) { - sixaxis_fullkey_state.attribute.is_connected.Assign(1); - sixaxis_fullkey_state.accel = motion_state[0].accel; - sixaxis_fullkey_state.gyro = motion_state[0].gyro; - sixaxis_fullkey_state.rotation = motion_state[0].rotation; - sixaxis_fullkey_state.orientation = motion_state[0].orientation; - } + case Core::HID::NpadStyleIndex::Pokeball: + set_motion_state(sixaxis_fullkey_state, motion_state[0]); break; case Core::HID::NpadStyleIndex::Handheld: - sixaxis_handheld_state.attribute.raw = 0; - if (controller.sixaxis_sensor_enabled) { - sixaxis_handheld_state.attribute.is_connected.Assign(1); - sixaxis_handheld_state.accel = motion_state[0].accel; - sixaxis_handheld_state.gyro = motion_state[0].gyro; - sixaxis_handheld_state.rotation = motion_state[0].rotation; - sixaxis_handheld_state.orientation = motion_state[0].orientation; - } + set_motion_state(sixaxis_handheld_state, motion_state[0]); break; case Core::HID::NpadStyleIndex::JoyconDual: - sixaxis_dual_left_state.attribute.raw = 0; - sixaxis_dual_right_state.attribute.raw = 0; - if (controller.sixaxis_sensor_enabled) { - // Set motion for the left joycon - sixaxis_dual_left_state.attribute.is_connected.Assign(1); - sixaxis_dual_left_state.accel = motion_state[0].accel; - sixaxis_dual_left_state.gyro = motion_state[0].gyro; - sixaxis_dual_left_state.rotation = motion_state[0].rotation; - sixaxis_dual_left_state.orientation = motion_state[0].orientation; - } - if (controller.sixaxis_sensor_enabled) { - // Set motion for the right joycon - sixaxis_dual_right_state.attribute.is_connected.Assign(1); - sixaxis_dual_right_state.accel = motion_state[1].accel; - sixaxis_dual_right_state.gyro = motion_state[1].gyro; - sixaxis_dual_right_state.rotation = motion_state[1].rotation; - sixaxis_dual_right_state.orientation = motion_state[1].orientation; - } + set_motion_state(sixaxis_dual_left_state, motion_state[0]); + set_motion_state(sixaxis_dual_right_state, motion_state[1]); break; case Core::HID::NpadStyleIndex::JoyconLeft: - sixaxis_left_lifo_state.attribute.raw = 0; - if (controller.sixaxis_sensor_enabled) { - sixaxis_left_lifo_state.attribute.is_connected.Assign(1); - sixaxis_left_lifo_state.accel = motion_state[0].accel; - sixaxis_left_lifo_state.gyro = motion_state[0].gyro; - sixaxis_left_lifo_state.rotation = motion_state[0].rotation; - sixaxis_left_lifo_state.orientation = motion_state[0].orientation; - } + set_motion_state(sixaxis_left_lifo_state, motion_state[0]); break; case Core::HID::NpadStyleIndex::JoyconRight: - sixaxis_right_lifo_state.attribute.raw = 0; - if (controller.sixaxis_sensor_enabled) { - sixaxis_right_lifo_state.attribute.is_connected.Assign(1); - sixaxis_right_lifo_state.accel = motion_state[1].accel; - sixaxis_right_lifo_state.gyro = motion_state[1].gyro; - sixaxis_right_lifo_state.rotation = motion_state[1].rotation; - sixaxis_right_lifo_state.orientation = motion_state[1].orientation; - } + set_motion_state(sixaxis_right_lifo_state, motion_state[1]); break; default: break; @@ -676,6 +691,12 @@ std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const { } void Controller_NPad::SetHoldType(NpadJoyHoldType joy_hold_type) { + if (joy_hold_type != NpadJoyHoldType::Horizontal && + joy_hold_type != NpadJoyHoldType::Vertical) { + LOG_ERROR(Service_HID, "Npad joy hold type needs to be valid, joy_hold_type={}", + joy_hold_type); + return; + } hold_type = joy_hold_type; } @@ -699,11 +720,12 @@ Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode return communication_mode; } -void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type, - NpadJoyAssignmentMode assignment_mode) { +ResultCode Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, + NpadJoyDeviceType npad_device_type, + NpadJoyAssignmentMode assignment_mode) { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); - return; + return InvalidNpadId; } auto& controller = GetControllerFromNpadIdType(npad_id); @@ -712,7 +734,7 @@ void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceTy } if (!controller.device->IsConnected()) { - return; + return ResultSuccess; } if (assignment_mode == NpadJoyAssignmentMode::Dual) { @@ -721,34 +743,34 @@ void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceTy controller.is_dual_left_connected = true; controller.is_dual_right_connected = false; UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); - return; + return ResultSuccess; } if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight) { DisconnectNpad(npad_id); controller.is_dual_left_connected = false; controller.is_dual_right_connected = true; UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); - return; + return ResultSuccess; } - return; + return ResultSuccess; } // This is for NpadJoyAssignmentMode::Single // Only JoyconDual get affected by this function if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::JoyconDual) { - return; + return ResultSuccess; } if (controller.is_dual_left_connected && !controller.is_dual_right_connected) { DisconnectNpad(npad_id); UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); - return; + return ResultSuccess; } if (!controller.is_dual_left_connected && controller.is_dual_right_connected) { DisconnectNpad(npad_id); UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); - return; + return ResultSuccess; } // We have two controllers connected to the same npad_id we need to split them @@ -766,6 +788,7 @@ void Controller_NPad::SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceTy controller_2.is_dual_right_connected = false; UpdateControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_2, true); } + return ResultSuccess; } bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, @@ -833,7 +856,7 @@ void Controller_NPad::VibrateController( } if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) { - UNREACHABLE_MSG("DeviceIndex should never be None!"); + ASSERT_MSG(false, "DeviceIndex should never be None!"); return; } @@ -961,10 +984,10 @@ void Controller_NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type, InitNewlyAddedController(npad_id); } -void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { +ResultCode Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); - return; + return InvalidNpadId; } LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); @@ -981,6 +1004,12 @@ void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { shared_memory->device_type.raw = 0; shared_memory->system_properties.raw = 0; shared_memory->button_properties.raw = 0; + shared_memory->sixaxis_fullkey_properties.raw = 0; + shared_memory->sixaxis_handheld_properties.raw = 0; + shared_memory->sixaxis_dual_left_properties.raw = 0; + shared_memory->sixaxis_dual_right_properties.raw = 0; + shared_memory->sixaxis_left_properties.raw = 0; + shared_memory->sixaxis_right_properties.raw = 0; shared_memory->battery_level_dual = 0; shared_memory->battery_level_left = 0; shared_memory->battery_level_right = 0; @@ -1001,346 +1030,268 @@ void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { controller.device->Disconnect(); SignalStyleSetChangedEvent(npad_id); WriteEmptyEntry(shared_memory); + return ResultSuccess; } - -ResultCode Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, - GyroscopeZeroDriftMode drift_mode) { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; +ResultCode Controller_NPad::SetGyroscopeZeroDriftMode( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, GyroscopeZeroDriftMode drift_mode) { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - controller.sixaxis_fullkey.gyroscope_zero_drift_mode = drift_mode; - break; - case Core::HID::NpadStyleIndex::Handheld: - controller.sixaxis_handheld.gyroscope_zero_drift_mode = drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - controller.sixaxis_dual_left.gyroscope_zero_drift_mode = drift_mode; - break; - } - controller.sixaxis_dual_right.gyroscope_zero_drift_mode = drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - controller.sixaxis_left.gyroscope_zero_drift_mode = drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - controller.sixaxis_right.gyroscope_zero_drift_mode = drift_mode; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + auto& sixaxis = GetSixaxisState(sixaxis_handle); + sixaxis.gyroscope_zero_drift_mode = drift_mode; return ResultSuccess; } -ResultCode Controller_NPad::GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, - GyroscopeZeroDriftMode& drift_mode) const { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; +ResultCode Controller_NPad::GetGyroscopeZeroDriftMode( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, + GyroscopeZeroDriftMode& drift_mode) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - drift_mode = controller.sixaxis_fullkey.gyroscope_zero_drift_mode; - break; - case Core::HID::NpadStyleIndex::Handheld: - drift_mode = controller.sixaxis_handheld.gyroscope_zero_drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - drift_mode = controller.sixaxis_dual_left.gyroscope_zero_drift_mode; - break; - } - drift_mode = controller.sixaxis_dual_right.gyroscope_zero_drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - drift_mode = controller.sixaxis_left.gyroscope_zero_drift_mode; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - drift_mode = controller.sixaxis_right.gyroscope_zero_drift_mode; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + drift_mode = sixaxis.gyroscope_zero_drift_mode; return ResultSuccess; } -ResultCode Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle, - bool& is_at_rest) const { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; +ResultCode Controller_NPad::IsSixAxisSensorAtRest( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_at_rest) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } + const auto& controller = GetControllerFromHandle(sixaxis_handle); is_at_rest = controller.sixaxis_at_rest; return ResultSuccess; } ResultCode Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor( - Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - // We don't support joycon firmware updates - is_firmware_available = false; + const auto& sixaxis_properties = GetSixaxisProperties(sixaxis_handle); + is_firmware_available = sixaxis_properties.is_firmware_update_available != 0; return ResultSuccess; } -ResultCode Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, +ResultCode Controller_NPad::EnableSixAxisSensorUnalteredPassthrough( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled) { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + auto& sixaxis = GetSixaxisState(sixaxis_handle); + sixaxis.unaltered_passtrough = is_enabled; + return ResultSuccess; +} + +ResultCode Controller_NPad::IsSixAxisSensorUnalteredPassthroughEnabled( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + is_enabled = sixaxis.unaltered_passtrough; + return ResultSuccess; +} + +ResultCode Controller_NPad::LoadSixAxisSensorCalibrationParameter( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, + Core::HID::SixAxisSensorCalibrationParameter& calibration) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + // TODO: Request this data to the controller. On error return 0xd8ca + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + calibration = sixaxis.calibration; + return ResultSuccess; +} + +ResultCode Controller_NPad::GetSixAxisSensorIcInformation( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, + Core::HID::SixAxisSensorIcInformation& ic_information) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + // TODO: Request this data to the controller. On error return 0xd8ca + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + ic_information = sixaxis.ic_information; + return ResultSuccess; +} + +ResultCode Controller_NPad::ResetIsSixAxisSensorDeviceNewlyAssigned( + const Core::HID::SixAxisSensorHandle& sixaxis_handle) { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; + } + + auto& sixaxis_properties = GetSixaxisProperties(sixaxis_handle); + sixaxis_properties.is_newly_assigned.Assign(0); + + return ResultSuccess; +} + +ResultCode Controller_NPad::SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool sixaxis_status) { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } + auto& controller = GetControllerFromHandle(sixaxis_handle); controller.sixaxis_sensor_enabled = sixaxis_status; return ResultSuccess; } ResultCode Controller_NPad::IsSixAxisSensorFusionEnabled( - Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_fusion_enabled) const { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_fusion_enabled) const { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - is_fusion_enabled = controller.sixaxis_fullkey.is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::Handheld: - is_fusion_enabled = controller.sixaxis_handheld.is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - is_fusion_enabled = controller.sixaxis_dual_left.is_fusion_enabled; - break; - } - is_fusion_enabled = controller.sixaxis_dual_right.is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - is_fusion_enabled = controller.sixaxis_left.is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - is_fusion_enabled = controller.sixaxis_right.is_fusion_enabled; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + is_fusion_enabled = sixaxis.is_fusion_enabled; return ResultSuccess; } -ResultCode Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, - bool is_fusion_enabled) { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; +ResultCode Controller_NPad::SetSixAxisFusionEnabled( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_fusion_enabled) { + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - controller.sixaxis_fullkey.is_fusion_enabled = is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::Handheld: - controller.sixaxis_handheld.is_fusion_enabled = is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - controller.sixaxis_dual_left.is_fusion_enabled = is_fusion_enabled; - break; - } - controller.sixaxis_dual_right.is_fusion_enabled = is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - controller.sixaxis_left.is_fusion_enabled = is_fusion_enabled; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - controller.sixaxis_right.is_fusion_enabled = is_fusion_enabled; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + auto& sixaxis = GetSixaxisState(sixaxis_handle); + sixaxis.is_fusion_enabled = is_fusion_enabled; return ResultSuccess; } ResultCode Controller_NPad::SetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle, + const Core::HID::SixAxisSensorHandle& sixaxis_handle, Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } + const auto param1 = sixaxis_fusion_parameters.parameter1; if (param1 < 0.0f || param1 > 1.0f) { return InvalidSixAxisFusionRange; } - auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - controller.sixaxis_fullkey.fusion = sixaxis_fusion_parameters; - break; - case Core::HID::NpadStyleIndex::Handheld: - controller.sixaxis_handheld.fusion = sixaxis_fusion_parameters; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - controller.sixaxis_dual_left.fusion = sixaxis_fusion_parameters; - break; - } - controller.sixaxis_dual_right.fusion = sixaxis_fusion_parameters; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - controller.sixaxis_left.fusion = sixaxis_fusion_parameters; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - controller.sixaxis_right.fusion = sixaxis_fusion_parameters; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + auto& sixaxis = GetSixaxisState(sixaxis_handle); + sixaxis.fusion = sixaxis_fusion_parameters; return ResultSuccess; } ResultCode Controller_NPad::GetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle, + const Core::HID::SixAxisSensorHandle& sixaxis_handle, Core::HID::SixAxisSensorFusionParameters& parameters) const { - if (!IsDeviceHandleValid(sixaxis_handle)) { - LOG_ERROR(Service_HID, "Invalid handle"); - return NpadInvalidHandle; + const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle); + if (is_valid.IsError()) { + LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw); + return is_valid; } - const auto& controller = GetControllerFromHandle(sixaxis_handle); - switch (sixaxis_handle.npad_type) { - case Core::HID::NpadStyleIndex::ProController: - parameters = controller.sixaxis_fullkey.fusion; - break; - case Core::HID::NpadStyleIndex::Handheld: - parameters = controller.sixaxis_handheld.fusion; - break; - case Core::HID::NpadStyleIndex::JoyconDual: - case Core::HID::NpadStyleIndex::GameCube: - case Core::HID::NpadStyleIndex::Pokeball: - if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { - parameters = controller.sixaxis_dual_left.fusion; - break; - } - parameters = controller.sixaxis_dual_right.fusion; - break; - case Core::HID::NpadStyleIndex::JoyconLeft: - parameters = controller.sixaxis_left.fusion; - break; - case Core::HID::NpadStyleIndex::JoyconRight: - parameters = controller.sixaxis_right.fusion; - break; - default: - LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type); - return NpadInvalidHandle; - } + const auto& sixaxis = GetSixaxisState(sixaxis_handle); + parameters = sixaxis.fusion; return ResultSuccess; } -void Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2) { +ResultCode Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, npad_id_2); - return; + return InvalidNpadId; } auto& controller_1 = GetControllerFromNpadIdType(npad_id_1); auto& controller_2 = GetControllerFromNpadIdType(npad_id_2); - const auto controller_style_1 = controller_1.device->GetNpadStyleIndex(); - const auto controller_style_2 = controller_2.device->GetNpadStyleIndex(); - bool merge_controllers = false; + auto controller_style_1 = controller_1.device->GetNpadStyleIndex(); + auto controller_style_2 = controller_2.device->GetNpadStyleIndex(); - // If the controllers at both npad indices form a pair of left and right joycons, merge them. - // Otherwise, do nothing. + // Simplify this code by converting dualjoycon with only a side connected to single joycons + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected) { + controller_style_1 = Core::HID::NpadStyleIndex::JoyconLeft; + } + if (!controller_1.is_dual_left_connected && controller_1.is_dual_right_connected) { + controller_style_1 = Core::HID::NpadStyleIndex::JoyconRight; + } + } + if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) { + controller_style_2 = Core::HID::NpadStyleIndex::JoyconLeft; + } + if (!controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) { + controller_style_2 = Core::HID::NpadStyleIndex::JoyconRight; + } + } + + // Invalid merge errors + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual || + controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual) { + return NpadIsDualJoycon; + } if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft) { + return NpadIsSameType; + } + if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight && controller_style_2 == Core::HID::NpadStyleIndex::JoyconRight) { - merge_controllers = true; - } - if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft && - controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight) { - merge_controllers = true; - } - if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_2 == Core::HID::NpadStyleIndex::JoyconRight && - controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected) { - merge_controllers = true; - } - if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_2 == Core::HID::NpadStyleIndex::JoyconLeft && - !controller_1.is_dual_left_connected && controller_1.is_dual_right_connected) { - merge_controllers = true; - } - if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_1 == Core::HID::NpadStyleIndex::JoyconRight && - controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) { - merge_controllers = true; - } - if (controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_1 == Core::HID::NpadStyleIndex::JoyconLeft && - !controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) { - merge_controllers = true; - } - if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual && - controller_1.is_dual_left_connected && !controller_1.is_dual_right_connected && - !controller_2.is_dual_left_connected && controller_2.is_dual_right_connected) { - merge_controllers = true; - } - if (controller_style_1 == Core::HID::NpadStyleIndex::JoyconDual && - controller_style_2 == Core::HID::NpadStyleIndex::JoyconDual && - !controller_1.is_dual_left_connected && controller_1.is_dual_right_connected && - controller_2.is_dual_left_connected && !controller_2.is_dual_right_connected) { - merge_controllers = true; - } - - if (merge_controllers) { - // Disconnect the joycon at the second id and connect the dual joycon at the first index. - DisconnectNpad(npad_id_2); - controller_1.is_dual_left_connected = true; - controller_1.is_dual_right_connected = true; - AddNewControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); - return; + return NpadIsSameType; + } + + // These exceptions are handled as if they where dual joycon + if (controller_style_1 != Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_1 != Core::HID::NpadStyleIndex::JoyconRight) { + return NpadIsDualJoycon; + } + if (controller_style_2 != Core::HID::NpadStyleIndex::JoyconLeft && + controller_style_2 != Core::HID::NpadStyleIndex::JoyconRight) { + return NpadIsDualJoycon; } - LOG_WARNING(Service_HID, - "Controllers can't be merged npad_id_1:{}, npad_id_2:{}, type_1:{}, type_2:{}, " - "dual_1(left/right):{}/{}, dual_2(left/right):{}/{}", - npad_id_1, npad_id_2, controller_1.device->GetNpadStyleIndex(), - controller_2.device->GetNpadStyleIndex(), controller_1.is_dual_left_connected, - controller_1.is_dual_right_connected, controller_2.is_dual_left_connected, - controller_2.is_dual_right_connected); + + // Disconnect the joycon at the second id and connect the dual joycon at the first index. + DisconnectNpad(npad_id_2); + controller_1.is_dual_left_connected = true; + controller_1.is_dual_right_connected = true; + AddNewControllerAt(Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); + return ResultSuccess; } void Controller_NPad::StartLRAssignmentMode() { @@ -1353,17 +1304,17 @@ void Controller_NPad::StopLRAssignmentMode() { is_in_lr_assignment_mode = false; } -bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2) { +ResultCode Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2) { if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, npad_id_2); - return false; + return InvalidNpadId; } if (npad_id_1 == Core::HID::NpadIdType::Handheld || npad_id_2 == Core::HID::NpadIdType::Handheld || npad_id_1 == Core::HID::NpadIdType::Other || npad_id_2 == Core::HID::NpadIdType::Other) { - return true; + return ResultSuccess; } const auto& controller_1 = GetControllerFromNpadIdType(npad_id_1).device; const auto& controller_2 = GetControllerFromNpadIdType(npad_id_2).device; @@ -1373,46 +1324,49 @@ bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, const auto is_connected_2 = controller_2->IsConnected(); if (!IsControllerSupported(type_index_1) && is_connected_1) { - return false; + return NpadNotConnected; } if (!IsControllerSupported(type_index_2) && is_connected_2) { - return false; + return NpadNotConnected; } UpdateControllerAt(type_index_2, npad_id_1, is_connected_2); UpdateControllerAt(type_index_1, npad_id_2, is_connected_1); - return true; + return ResultSuccess; } -Core::HID::LedPattern Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id) { +ResultCode Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id, + Core::HID::LedPattern& pattern) const { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); - return Core::HID::LedPattern{0, 0, 0, 0}; + return InvalidNpadId; } const auto& controller = GetControllerFromNpadIdType(npad_id).device; - return controller->GetLedPattern(); + pattern = controller->GetLedPattern(); + return ResultSuccess; } -bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled( - Core::HID::NpadIdType npad_id) const { +ResultCode Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled( + Core::HID::NpadIdType npad_id, bool& is_valid) const { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); - // Return the default value - return false; + return InvalidNpadId; } const auto& controller = GetControllerFromNpadIdType(npad_id); - return controller.unintended_home_button_input_protection; + is_valid = controller.unintended_home_button_input_protection; + return ResultSuccess; } -void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, - Core::HID::NpadIdType npad_id) { +ResultCode Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled( + bool is_protection_enabled, Core::HID::NpadIdType npad_id) { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); - return; + return InvalidNpadId; } auto& controller = GetControllerFromNpadIdType(npad_id); controller.unintended_home_button_input_protection = is_protection_enabled; + return ResultSuccess; } void Controller_NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) { @@ -1550,4 +1504,96 @@ const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpa return controller_data[npad_index]; } +Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties( + const Core::HID::SixAxisSensorHandle& sixaxis_handle) { + auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.shared_memory->sixaxis_fullkey_properties; + case Core::HID::NpadStyleIndex::Handheld: + return controller.shared_memory->sixaxis_handheld_properties; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.shared_memory->sixaxis_dual_left_properties; + } + return controller.shared_memory->sixaxis_dual_right_properties; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.shared_memory->sixaxis_left_properties; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.shared_memory->sixaxis_right_properties; + default: + return controller.shared_memory->sixaxis_fullkey_properties; + } +} + +const Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties( + const Core::HID::SixAxisSensorHandle& sixaxis_handle) const { + const auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.shared_memory->sixaxis_fullkey_properties; + case Core::HID::NpadStyleIndex::Handheld: + return controller.shared_memory->sixaxis_handheld_properties; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.shared_memory->sixaxis_dual_left_properties; + } + return controller.shared_memory->sixaxis_dual_right_properties; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.shared_memory->sixaxis_left_properties; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.shared_memory->sixaxis_right_properties; + default: + return controller.shared_memory->sixaxis_fullkey_properties; + } +} + +Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState( + const Core::HID::SixAxisSensorHandle& sixaxis_handle) { + auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.sixaxis_fullkey; + case Core::HID::NpadStyleIndex::Handheld: + return controller.sixaxis_handheld; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.sixaxis_dual_left; + } + return controller.sixaxis_dual_right; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.sixaxis_left; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.sixaxis_right; + default: + return controller.sixaxis_unknown; + } +} + +const Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState( + const Core::HID::SixAxisSensorHandle& sixaxis_handle) const { + const auto& controller = GetControllerFromHandle(sixaxis_handle); + switch (sixaxis_handle.npad_type) { + case Core::HID::NpadStyleIndex::ProController: + case Core::HID::NpadStyleIndex::Pokeball: + return controller.sixaxis_fullkey; + case Core::HID::NpadStyleIndex::Handheld: + return controller.sixaxis_handheld; + case Core::HID::NpadStyleIndex::JoyconDual: + if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) { + return controller.sixaxis_dual_left; + } + return controller.sixaxis_dual_right; + case Core::HID::NpadStyleIndex::JoyconLeft: + return controller.sixaxis_left; + case Core::HID::NpadStyleIndex::JoyconRight: + return controller.sixaxis_right; + default: + return controller.sixaxis_unknown; + } +} + } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 0a96825a5..0b662b7f8 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -107,8 +107,8 @@ public: void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_); NpadCommunicationMode GetNpadCommunicationMode() const; - void SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type, - NpadJoyAssignmentMode assignment_mode); + ResultCode SetNpadMode(Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type, + NpadJoyAssignmentMode assignment_mode); bool VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index, const Core::HID::VibrationValue& vibration_value); @@ -141,50 +141,65 @@ public: void UpdateControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id, bool connected); - void DisconnectNpad(Core::HID::NpadIdType npad_id); + ResultCode DisconnectNpad(Core::HID::NpadIdType npad_id); - ResultCode SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + ResultCode SetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle, GyroscopeZeroDriftMode drift_mode); - ResultCode GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle, + ResultCode GetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle, GyroscopeZeroDriftMode& drift_mode) const; - ResultCode IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle, + ResultCode IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_at_rest) const; ResultCode IsFirmwareUpdateAvailableForSixAxisSensor( - Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const; - ResultCode SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const; + ResultCode EnableSixAxisSensorUnalteredPassthrough( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled); + ResultCode IsSixAxisSensorUnalteredPassthroughEnabled( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const; + ResultCode LoadSixAxisSensorCalibrationParameter( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, + Core::HID::SixAxisSensorCalibrationParameter& calibration) const; + ResultCode GetSixAxisSensorIcInformation( + const Core::HID::SixAxisSensorHandle& sixaxis_handle, + Core::HID::SixAxisSensorIcInformation& ic_information) const; + ResultCode ResetIsSixAxisSensorDeviceNewlyAssigned( + const Core::HID::SixAxisSensorHandle& sixaxis_handle); + ResultCode SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool sixaxis_status); - ResultCode IsSixAxisSensorFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + ResultCode IsSixAxisSensorFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_fusion_enabled) const; - ResultCode SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, + ResultCode SetSixAxisFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_fusion_enabled); ResultCode SetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle, + const Core::HID::SixAxisSensorHandle& sixaxis_handle, Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters); ResultCode GetSixAxisFusionParameters( - Core::HID::SixAxisSensorHandle sixaxis_handle, + const Core::HID::SixAxisSensorHandle& sixaxis_handle, Core::HID::SixAxisSensorFusionParameters& parameters) const; - Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id); - bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const; - void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, - Core::HID::NpadIdType npad_id); + ResultCode GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const; + ResultCode IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id, + bool& is_enabled) const; + ResultCode SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, + Core::HID::NpadIdType npad_id); void SetAnalogStickUseCenterClamp(bool use_center_clamp); void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); void ClearAllControllers(); - void MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); + ResultCode MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, + Core::HID::NpadIdType npad_id_2); void StartLRAssignmentMode(); void StopLRAssignmentMode(); - bool SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); + ResultCode SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); // Logical OR for all buttons presses on all controllers // Specifically for cheat engine and other features. Core::HID::NpadButton GetAndResetPressState(); static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); - static bool IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle); static bool IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); + static ResultCode VerifyValidSixAxisSensorHandle( + const Core::HID::SixAxisSensorHandle& device_handle); private: static constexpr std::size_t NPAD_COUNT = 10; @@ -451,9 +466,13 @@ private: NpadLuciaType lucia_type{}; NpadLagonType lagon_type{}; NpadLagerType lager_type{}; - // FW 13.x Investigate there is some sort of bitflag related to joycons - INSERT_PADDING_BYTES(0x4); - INSERT_PADDING_BYTES(0xc08); // Unknown + Core::HID::SixAxisSensorProperties sixaxis_fullkey_properties; + Core::HID::SixAxisSensorProperties sixaxis_handheld_properties; + Core::HID::SixAxisSensorProperties sixaxis_dual_left_properties; + Core::HID::SixAxisSensorProperties sixaxis_dual_right_properties; + Core::HID::SixAxisSensorProperties sixaxis_left_properties; + Core::HID::SixAxisSensorProperties sixaxis_right_properties; + INSERT_PADDING_BYTES(0xc06); // Unknown }; static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size"); @@ -465,7 +484,10 @@ private: struct SixaxisParameters { bool is_fusion_enabled{true}; + bool unaltered_passtrough{false}; Core::HID::SixAxisSensorFusionParameters fusion{}; + Core::HID::SixAxisSensorCalibrationParameter calibration{}; + Core::HID::SixAxisSensorIcInformation ic_information{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; }; @@ -491,6 +513,7 @@ private: SixaxisParameters sixaxis_dual_right{}; SixaxisParameters sixaxis_left{}; SixaxisParameters sixaxis_right{}; + SixaxisParameters sixaxis_unknown{}; // Current pad state NPadGenericState npad_pad_state{}; @@ -522,6 +545,14 @@ private: NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id); const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const; + Core::HID::SixAxisSensorProperties& GetSixaxisProperties( + const Core::HID::SixAxisSensorHandle& device_handle); + const Core::HID::SixAxisSensorProperties& GetSixaxisProperties( + const Core::HID::SixAxisSensorHandle& device_handle) const; + SixaxisParameters& GetSixaxisState(const Core::HID::SixAxisSensorHandle& device_handle); + const SixaxisParameters& GetSixaxisState( + const Core::HID::SixAxisSensorHandle& device_handle) const; + std::atomic<u64> press_state{}; std::array<NpadControllerData, NPAD_COUNT> controller_data{}; diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 108ce5a41..1da8d3eb0 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -44,7 +44,6 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin for (std::size_t id = 0; id < MAX_FINGERS; id++) { const auto& current_touch = touch_status[id]; auto& finger = fingers[id]; - finger.position = current_touch.position; finger.id = current_touch.id; if (finger.attribute.start_touch) { @@ -61,13 +60,18 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin if (!finger.pressed && current_touch.pressed) { finger.attribute.start_touch.Assign(1); finger.pressed = true; + finger.position = current_touch.position; continue; } if (finger.pressed && !current_touch.pressed) { finger.attribute.raw = 0; finger.attribute.end_touch.Assign(1); + continue; } + + // Only update position if touch is not on a special frame + finger.position = current_touch.position; } std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers; diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h index b31834074..6c8ad04af 100644 --- a/src/core/hle/service/hid/errors.h +++ b/src/core/hle/service/hid/errors.h @@ -8,7 +8,11 @@ namespace Service::HID { constexpr ResultCode NpadInvalidHandle{ErrorModule::HID, 100}; +constexpr ResultCode NpadDeviceIndexOutOfRange{ErrorModule::HID, 107}; constexpr ResultCode InvalidSixAxisFusionRange{ErrorModule::HID, 423}; +constexpr ResultCode NpadIsDualJoycon{ErrorModule::HID, 601}; +constexpr ResultCode NpadIsSameType{ErrorModule::HID, 602}; +constexpr ResultCode InvalidNpadId{ErrorModule::HID, 709}; constexpr ResultCode NpadNotConnected{ErrorModule::HID, 710}; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index eba44eda8..dc5d0366d 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -37,8 +37,7 @@ namespace Service::HID { // Period time is obtained by measuring the number of samples in a second on HW using a homebrew constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) -// TODO: Correct update rate for motion is 5ms. Check why some games don't behave at that speed -constexpr auto motion_update_ns = std::chrono::nanoseconds{10 * 1000 * 1000}; // (10ms, 100Hz) +constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) IAppletResource::IAppletResource(Core::System& system_, KernelHelpers::ServiceContext& service_context_) @@ -258,12 +257,12 @@ Hid::Hid(Core::System& system_) {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"}, {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, {83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"}, - {84, nullptr, "EnableSixAxisSensorUnalteredPassthrough"}, - {85, nullptr, "IsSixAxisSensorUnalteredPassthroughEnabled"}, + {84, &Hid::EnableSixAxisSensorUnalteredPassthrough, "EnableSixAxisSensorUnalteredPassthrough"}, + {85, &Hid::IsSixAxisSensorUnalteredPassthroughEnabled, "IsSixAxisSensorUnalteredPassthroughEnabled"}, {86, nullptr, "StoreSixAxisSensorCalibrationParameter"}, - {87, nullptr, "LoadSixAxisSensorCalibrationParameter"}, - {88, nullptr, "GetSixAxisSensorIcInformation"}, - {89, nullptr, "ResetIsSixAxisSensorDeviceNewlyAssigned"}, + {87, &Hid::LoadSixAxisSensorCalibrationParameter, "LoadSixAxisSensorCalibrationParameter"}, + {88, &Hid::GetSixAxisSensorIcInformation, "GetSixAxisSensorIcInformation"}, + {89, &Hid::ResetIsSixAxisSensorDeviceNewlyAssigned, "ResetIsSixAxisSensorDeviceNewlyAssigned"}, {91, &Hid::ActivateGesture, "ActivateGesture"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, @@ -695,11 +694,7 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) { rb.Push(result1); return; } - if (result2.IsError()) { - rb.Push(result2); - return; - } - rb.Push(ResultSuccess); + rb.Push(result2); } void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { @@ -822,6 +817,144 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c rb.Push(is_firmware_available); } +void Hid::EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool enabled; + Core::HID::SixAxisSensorHandle sixaxis_handle; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.EnableSixAxisSensorUnalteredPassthrough( + parameters.sixaxis_handle, parameters.enabled); + + LOG_WARNING(Service_HID, + "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, " + "applet_resource_user_id={}", + parameters.enabled, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void Hid::IsSixAxisSensorUnalteredPassthroughEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + bool is_unaltered_sisxaxis_enabled{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled( + parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(is_unaltered_sisxaxis_enabled); +} + +void Hid::LoadSixAxisSensorCalibrationParameter(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + Core::HID::SixAxisSensorCalibrationParameter calibration{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.LoadSixAxisSensorCalibrationParameter(parameters.sixaxis_handle, calibration); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + if (result.IsSuccess()) { + ctx.WriteBuffer(calibration); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void Hid::GetSixAxisSensorIcInformation(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + Core::HID::SixAxisSensorIcInformation ic_information{}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.GetSixAxisSensorIcInformation(parameters.sixaxis_handle, ic_information); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + if (result.IsSuccess()) { + ctx.WriteBuffer(ic_information); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void Hid::ResetIsSixAxisSensorDeviceNewlyAssigned(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::SixAxisSensorHandle sixaxis_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.ResetIsSixAxisSensorDeviceNewlyAssigned(parameters.sixaxis_handle); + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { @@ -949,27 +1082,29 @@ void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .DisconnectNpad(parameters.npad_id); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.DisconnectNpad(parameters.npad_id); LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()}; + Core::HID::LedPattern pattern{0, 0, 0, 0}; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.GetLedPattern(npad_id, pattern); + LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetLedPattern(npad_id) - .raw); + rb.Push(result); + rb.Push(pattern.raw); } void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { @@ -1029,15 +1164,16 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyDeviceType::Left, - Controller_NPad::NpadJoyAssignmentMode::Single); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyDeviceType::Left, + Controller_NPad::NpadJoyAssignmentMode::Single); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { @@ -1052,16 +1188,16 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetNpadMode(parameters.npad_id, parameters.npad_joy_device_type, - Controller_NPad::NpadJoyAssignmentMode::Single); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetNpadMode(parameters.npad_id, parameters.npad_joy_device_type, + Controller_NPad::NpadJoyAssignmentMode::Single); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", parameters.npad_id, parameters.applet_resource_user_id, parameters.npad_joy_device_type); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { @@ -1075,14 +1211,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetNpadMode(parameters.npad_id, {}, Controller_NPad::NpadJoyAssignmentMode::Dual); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetNpadMode(parameters.npad_id, {}, + Controller_NPad::NpadJoyAssignmentMode::Dual); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { @@ -1091,14 +1228,14 @@ void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", npad_id_1, npad_id_2, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) { @@ -1158,19 +1295,14 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SwapNpadAssignment(npad_id_1, npad_id_2); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SwapNpadAssignment(npad_id_1, npad_id_2); LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", npad_id_1, npad_id_2, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - if (res) { - rb.Push(ResultSuccess); - } else { - LOG_ERROR(Service_HID, "Npads are not connected!"); - rb.Push(NpadNotConnected); - } + rb.Push(result); } void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { @@ -1184,13 +1316,17 @@ void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext const auto parameters{rp.PopRaw<Parameters>()}; + bool is_enabled = false; + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = + controller.IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id, is_enabled); + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id)); + rb.Push(result); + rb.Push(is_enabled); } void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) { @@ -1205,9 +1341,9 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c const auto parameters{rp.PopRaw<Parameters>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetUnintendedHomeButtonInputProtectionEnabled( - parameters.unintended_home_button_input_protection, parameters.npad_id); + auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad); + const auto result = controller.SetUnintendedHomeButtonInputProtectionEnabled( + parameters.unintended_home_button_input_protection, parameters.npad_id); LOG_WARNING(Service_HID, "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={}," @@ -1216,7 +1352,7 @@ void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& c parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void Hid::SetNpadAnalogStickUseCenterClamp(Kernel::HLERequestContext& ctx) { @@ -1305,7 +1441,7 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { break; case Core::HID::DeviceIndex::None: default: - UNREACHABLE_MSG("DeviceIndex should never be None!"); + ASSERT_MSG(false, "DeviceIndex should never be None!"); vibration_device_info.position = Core::HID::VibrationDevicePosition::None; break; } @@ -1378,6 +1514,8 @@ void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_vibrate{rp.Pop<bool>()}; + // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value + // by converting it to a bool Settings::values.vibration_enabled.SetValue(can_vibrate); LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); @@ -1389,9 +1527,12 @@ void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called"); + // nnSDK checks if a float is greater than zero. We return the bool we stored earlier + const auto is_enabled = Settings::values.vibration_enabled.GetValue(); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(Settings::values.vibration_enabled.GetValue()); + rb.Push(is_enabled); } void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 1be04c22b..ac4333022 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -113,6 +113,11 @@ private: void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); void IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx); + void EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx); + void IsSixAxisSensorUnalteredPassthroughEnabled(Kernel::HLERequestContext& ctx); + void LoadSixAxisSensorCalibrationParameter(Kernel::HLERequestContext& ctx); + void GetSixAxisSensorIcInformation(Kernel::HLERequestContext& ctx); + void ResetIsSixAxisSensorDeviceNewlyAssigned(Kernel::HLERequestContext& ctx); void ActivateGesture(Kernel::HLERequestContext& ctx); void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 9e32f3e60..d2a91d913 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -5,7 +5,9 @@ #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/irs.h" namespace Service::HID { @@ -38,21 +40,32 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { } void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_IRS, "called"); + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_IRS, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); @@ -60,35 +73,109 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { } void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + PackedMomentProcessorConfig processor_config; + }; + static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + PackedClusteringProcessorConfig processor_config; + }; + static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + PackedImageTransferProcessorConfig processor_config; + u32 transfer_memory_size; + }; + static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto t_mem_handle{ctx.GetCopyHandle(0)}; + + auto t_mem = + system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); @@ -97,71 +184,195 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { } void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<PackedTeraPluginProcessorConfig>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " + "applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, processor_config.mode, + processor_config.required_mcu_version.major, + processor_config.required_mcu_version.minor, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()}; + + if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid && + npad_id != Core::HID::NpadIdType::Handheld) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(InvalidNpadId); + return; + } + + IrCameraHandle camera_handle{ + .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)), + .npad_type = Core::HID::NpadStyleIndex::None, + }; + + LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}", + npad_id, camera_handle.npad_id, camera_handle.npad_type); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushRaw<u32>(device_handle); + rb.PushRaw(camera_handle); } void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<PackedPointingProcessorConfig>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, mcu_version={}.{}, applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major, + processor_config.required_mcu_version.minor, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; + const auto mcu_version{rp.PopRaw<PackedMcuVersion>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}, mcu_version={}.{}", + camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major, + mcu_version.minor); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + PackedFunctionLevel function_level; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + PackedImageTransferProcessorExConfig processor_config; + u64 transfer_memory_size; + }; + static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto t_mem_handle{ctx.GetCopyHandle(0)}; + + auto t_mem = + system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; + const auto processor_config{rp.PopRaw<PackedIrLedProcessorConfig>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, light_target={}, mcu_version={}.{} " + "applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, processor_config.light_target, + processor_config.required_mcu_version.major, + processor_config.required_mcu_version.minor, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + IrCameraHandle camera_handle; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + struct Parameters { + PackedFunctionLevel function_level; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_IRS, "(STUBBED) called, function_level={}, applet_resource_user_id={}", + parameters.function_level.function_level, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index efb29d3fd..361dc2213 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -3,6 +3,7 @@ #pragma once +#include "core/hid/hid_types.h" #include "core/hle/service/service.h" namespace Core { @@ -17,6 +18,235 @@ public: ~IRS() override; private: + // This is nn::irsensor::IrCameraStatus + enum IrCameraStatus : u32 { + Available, + Unsupported, + Unconnected, + }; + + // This is nn::irsensor::IrCameraInternalStatus + enum IrCameraInternalStatus : u32 { + Stopped, + FirmwareUpdateNeeded, + Unkown2, + Unkown3, + Unkown4, + FirmwareVersionRequested, + FirmwareVersionIsInvalid, + Ready, + Setting, + }; + + // This is nn::irsensor::detail::StatusManager::IrSensorMode + enum IrSensorMode : u64 { + None, + MomentProcessor, + ClusteringProcessor, + ImageTransferProcessor, + PointingProcessorMarker, + TeraPluginProcessor, + IrLedProcessor, + }; + + // This is nn::irsensor::ImageProcessorStatus + enum ImageProcessorStatus : u8 { + stopped, + running, + }; + + // This is nn::irsensor::ImageTransferProcessorFormat + enum ImageTransferProcessorFormat : u8 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, + }; + + // This is nn::irsensor::AdaptiveClusteringMode + enum AdaptiveClusteringMode : u8 { + StaticFov, + DynamicFov, + }; + + // This is nn::irsensor::AdaptiveClusteringTargetDistance + enum AdaptiveClusteringTargetDistance : u8 { + Near, + Middle, + Far, + }; + + // This is nn::irsensor::IrsHandAnalysisMode + enum IrsHandAnalysisMode : u8 { + Silhouette, + Image, + SilhoueteAndImage, + SilhuetteOnly, + }; + + // This is nn::irsensor::IrSensorFunctionLevel + enum IrSensorFunctionLevel : u8 { + unknown0, + unknown1, + unknown2, + unknown3, + unknown4, + }; + + // This is nn::irsensor::IrCameraHandle + struct IrCameraHandle { + u8 npad_id{}; + Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; + INSERT_PADDING_BYTES(2); + }; + static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); + + struct IrsRect { + s16 x; + s16 y; + s16 width; + s16 height; + }; + + // This is nn::irsensor::PackedMcuVersion + struct PackedMcuVersion { + u16 major; + u16 minor; + }; + static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); + + // This is nn::irsensor::MomentProcessorConfig + struct MomentProcessorConfig { + u64 exposire_time; + u8 light_target; + u8 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(7); + IrsRect window_of_interest; + u8 preprocess; + u8 preprocess_intensity_threshold; + INSERT_PADDING_BYTES(5); + }; + static_assert(sizeof(MomentProcessorConfig) == 0x28, + "MomentProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedMomentProcessorConfig + struct PackedMomentProcessorConfig { + u64 exposire_time; + u8 light_target; + u8 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(5); + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + u8 preprocess; + u8 preprocess_intensity_threshold; + INSERT_PADDING_BYTES(2); + }; + static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, + "PackedMomentProcessorConfig is an invalid size"); + + // This is nn::irsensor::ClusteringProcessorConfig + struct ClusteringProcessorConfig { + u64 exposire_time; + u32 light_target; + u32 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(7); + IrsRect window_of_interest; + u32 pixel_count_min; + u32 pixel_count_max; + u32 object_intensity_min; + u8 is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ClusteringProcessorConfig) == 0x30, + "ClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedClusteringProcessorConfig + struct PackedClusteringProcessorConfig { + u64 exposire_time; + u8 light_target; + u8 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(5); + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + u32 pixel_count_min; + u32 pixel_count_max; + u32 object_intensity_min; + u8 is_external_light_filter_enabled; + INSERT_PADDING_BYTES(2); + }; + static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30, + "PackedClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedImageTransferProcessorConfig + struct PackedImageTransferProcessorConfig { + u64 exposire_time; + u8 light_target; + u8 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(5); + PackedMcuVersion required_mcu_version; + u8 format; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, + "PackedImageTransferProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedTeraPluginProcessorConfig + struct PackedTeraPluginProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 mode; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, + "PackedTeraPluginProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedPointingProcessorConfig + struct PackedPointingProcessorConfig { + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + }; + static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, + "PackedPointingProcessorConfig is an invalid size"); + + // This is nn::irsensor::PackedFunctionLevel + struct PackedFunctionLevel { + IrSensorFunctionLevel function_level; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); + + // This is nn::irsensor::PackedImageTransferProcessorExConfig + struct PackedImageTransferProcessorExConfig { + u64 exposire_time; + u8 light_target; + u8 gain; + u8 is_negative_used; + INSERT_PADDING_BYTES(5); + PackedMcuVersion required_mcu_version; + ImageTransferProcessorFormat origin_format; + ImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + u8 is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, + "PackedImageTransferProcessorExConfig is an invalid size"); + + // This is nn::irsensor::PackedIrLedProcessorConfig + struct PackedIrLedProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 light_target; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, + "PackedIrLedProcessorConfig is an invalid size"); + void ActivateIrsensor(Kernel::HLERequestContext& ctx); void DeactivateIrsensor(Kernel::HLERequestContext& ctx); void GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx); @@ -35,8 +265,6 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); - - const u32 device_handle{0xABCD}; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp index 19bd85b6c..4ed3f02e2 100644 --- a/src/core/hle/service/jit/jit_context.cpp +++ b/src/core/hle/service/jit/jit_context.cpp @@ -11,10 +11,13 @@ #include "common/alignment.h" #include "common/common_funcs.h" #include "common/div_ceil.h" +#include "common/elf.h" #include "common/logging/log.h" #include "core/hle/service/jit/jit_context.h" #include "core/memory.h" +using namespace Common::ELF; + namespace Service::JIT { constexpr std::array<u8, 8> SVC0_ARM64 = { @@ -26,25 +29,6 @@ constexpr std::array HELPER_FUNCTIONS{ "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset", }; -struct Elf64_Dyn { - u64 d_tag; - u64 d_un; -}; - -struct Elf64_Rela { - u64 r_offset; - u64 r_info; - s64 r_addend; -}; - -static constexpr u32 Elf64_RelaType(const Elf64_Rela* rela) { - return static_cast<u32>(rela->r_info); -} - -constexpr int DT_RELA = 7; /* Address of Rela relocs */ -constexpr int DT_RELASZ = 8; /* Total size of Rela relocs */ -constexpr int R_AARCH64_RELATIVE = 1027; /* Adjust by program base. */ - constexpr size_t STACK_ALIGN = 16; class JITContextImpl; @@ -206,17 +190,17 @@ public: if (!dyn.d_tag) { break; } - if (dyn.d_tag == DT_RELA) { - rela_dyn = dyn.d_un; + if (dyn.d_tag == ElfDtRela) { + rela_dyn = dyn.d_un.d_ptr; } - if (dyn.d_tag == DT_RELASZ) { - num_rela = dyn.d_un / sizeof(Elf64_Rela); + if (dyn.d_tag == ElfDtRelasz) { + num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); } } for (size_t i = 0; i < num_rela; i++) { const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))}; - if (Elf64_RelaType(&rela) != R_AARCH64_RELATIVE) { + if (Elf64RelType(rela.r_info) != ElfAArch64Relative) { continue; } const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index fa72fcba9..72e4902cb 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -347,7 +347,7 @@ public: } if (!succeeded) { - UNREACHABLE_MSG("Out of address space!"); + ASSERT_MSG(false, "Out of address space!"); return Kernel::ResultOutOfMemory; } diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 4964539f9..08300a1a6 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -290,7 +290,7 @@ MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Commo u8 glasses_type{}; while (glasses_type_start < glasses_type_info.values[glasses_type]) { if (++glasses_type >= glasses_type_info.values_count) { - UNREACHABLE(); + ASSERT(false); break; } } diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp index f77f0df27..a6fa943e8 100644 --- a/src/core/hle/service/nvdrv/syncpoint_manager.cpp +++ b/src/core/hle/service/nvdrv/syncpoint_manager.cpp @@ -23,7 +23,7 @@ u32 SyncpointManager::AllocateSyncpoint() { return syncpoint_id; } } - UNREACHABLE_MSG("No more available syncpoints!"); + ASSERT_MSG(false, "No more available syncpoints!"); return {}; } diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp index d7db77aff..4b3d5efd6 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp @@ -89,14 +89,6 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot); - // If the front buffer is still being tracked, update its slot state - if (core->StillTracking(*front)) { - slots[slot].acquire_called = true; - slots[slot].needs_cleanup_on_release = false; - slots[slot].buffer_state = BufferState::Acquired; - slots[slot].fence = Fence::NoFence(); - } - // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to // avoid unnecessarily remapping this buffer on the consumer side. if (out_buffer->acquire_called) { @@ -139,26 +131,11 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc ++current; } - if (slots[slot].buffer_state == BufferState::Acquired) { - slots[slot].fence = release_fence; - slots[slot].buffer_state = BufferState::Free; - - listener = core->connected_producer_listener; - - LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); - } else if (slots[slot].needs_cleanup_on_release) { - LOG_DEBUG(Service_NVFlinger, "releasing a stale buffer slot {} (state = {})", slot, - slots[slot].buffer_state); + slots[slot].buffer_state = BufferState::Free; - slots[slot].needs_cleanup_on_release = false; + listener = core->connected_producer_listener; - return Status::StaleBufferSlot; - } else { - LOG_ERROR(Service_NVFlinger, "attempted to release buffer slot {} but its state was {}", - slot, slots[slot].buffer_state); - - return Status::BadValue; - } + LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); core->SignalDequeueCondition(); } diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.cpp b/src/core/hle/service/nvflinger/buffer_queue_core.cpp index d4e8b44d0..ea4a14ea4 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp @@ -84,10 +84,6 @@ void BufferQueueCore::FreeBufferLocked(s32 slot) { slots[slot].graphic_buffer.reset(); - if (slots[slot].buffer_state == BufferState::Acquired) { - slots[slot].needs_cleanup_on_release = true; - } - slots[slot].buffer_state = BufferState::Free; slots[slot].frame_number = UINT32_MAX; slots[slot].acquire_called = false; diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp index fe95d1b73..337431488 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp @@ -659,7 +659,7 @@ Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) { value = core->consumer_usage_bit; break; default: - UNREACHABLE(); + ASSERT(false); return Status::BadValue; } diff --git a/src/core/hle/service/nvflinger/buffer_slot.h b/src/core/hle/service/nvflinger/buffer_slot.h index 6b3e87446..0cd0e9964 100644 --- a/src/core/hle/service/nvflinger/buffer_slot.h +++ b/src/core/hle/service/nvflinger/buffer_slot.h @@ -31,7 +31,6 @@ struct BufferSlot final { u64 frame_number{}; Fence fence; bool acquire_called{}; - bool needs_cleanup_on_release{}; bool attached_by_consumer{}; bool is_preallocated{}; }; diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp index f0cc9a155..508091dc2 100644 --- a/src/core/hle/service/time/standard_user_system_clock_core.cpp +++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp @@ -48,12 +48,12 @@ ResultCode StandardUserSystemClockCore::GetClockContext(Core::System& system, } ResultCode StandardUserSystemClockCore::Flush(const SystemClockContext&) { - UNREACHABLE(); + UNIMPLEMENTED(); return ERROR_NOT_IMPLEMENTED; } ResultCode StandardUserSystemClockCore::SetClockContext(const SystemClockContext&) { - UNREACHABLE(); + UNIMPLEMENTED(); return ERROR_NOT_IMPLEMENTED; } diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp index acc038dbf..28667710e 100644 --- a/src/core/hle/service/time/time_manager.cpp +++ b/src/core/hle/service/time/time_manager.cpp @@ -111,7 +111,7 @@ struct TimeManager::Impl final { FileSys::VirtualFile& vfs_file) { if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( location_name, vfs_file) != ResultSuccess) { - UNREACHABLE(); + ASSERT(false); return; } @@ -155,7 +155,7 @@ struct TimeManager::Impl final { } else { if (standard_local_system_clock_core.SetCurrentTime(system_, posix_time) != ResultSuccess) { - UNREACHABLE(); + ASSERT(false); return; } } @@ -170,7 +170,7 @@ struct TimeManager::Impl final { if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != ResultSuccess) { - UNREACHABLE(); + ASSERT(false); return; } @@ -183,7 +183,7 @@ struct TimeManager::Impl final { Clock::SteadyClockTimePoint steady_clock_time_point) { if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled( system_, is_automatic_correction_enabled) != ResultSuccess) { - UNREACHABLE(); + ASSERT(false); return; } @@ -203,7 +203,7 @@ struct TimeManager::Impl final { if (GetStandardLocalSystemClockCore() .SetCurrentTime(system_, timespan.ToSeconds()) .IsError()) { - UNREACHABLE(); + ASSERT(false); return; } } diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index 449a5ac96..fee05ec7a 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -110,10 +110,9 @@ static constexpr s64 GetLeapDaysFromYear(s64 year) { } } -static constexpr int GetMonthLength(bool is_leap_year, int month) { - constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31}; +static constexpr s8 GetMonthLength(bool is_leap_year, int month) { + constexpr std::array<s8, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr std::array<s8, 12> month_lengths_leap{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return is_leap_year ? month_lengths_leap[month] : month_lengths[month]; } @@ -280,7 +279,7 @@ static constexpr int TransitionTime(int year, Rule rule, int offset) { break; } default: - UNREACHABLE(); + ASSERT(false); } return value + rule.transition_time + offset; } diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index cf5933699..dfb10c34f 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -6,6 +6,7 @@ #include <memory> #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/elf.h" #include "common/logging/log.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_page_table.h" @@ -13,159 +14,7 @@ #include "core/loader/elf.h" #include "core/memory.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ELF Header Constants - -// File type -enum ElfType { - ET_NONE = 0, - ET_REL = 1, - ET_EXEC = 2, - ET_DYN = 3, - ET_CORE = 4, - ET_LOPROC = 0xFF00, - ET_HIPROC = 0xFFFF, -}; - -// Machine/Architecture -enum ElfMachine { - EM_NONE = 0, - EM_M32 = 1, - EM_SPARC = 2, - EM_386 = 3, - EM_68K = 4, - EM_88K = 5, - EM_860 = 7, - EM_MIPS = 8 -}; - -// File version -#define EV_NONE 0 -#define EV_CURRENT 1 - -// Identification index -#define EI_MAG0 0 -#define EI_MAG1 1 -#define EI_MAG2 2 -#define EI_MAG3 3 -#define EI_CLASS 4 -#define EI_DATA 5 -#define EI_VERSION 6 -#define EI_PAD 7 -#define EI_NIDENT 16 - -// Sections constants - -// Section types -#define SHT_NULL 0 -#define SHT_PROGBITS 1 -#define SHT_SYMTAB 2 -#define SHT_STRTAB 3 -#define SHT_RELA 4 -#define SHT_HASH 5 -#define SHT_DYNAMIC 6 -#define SHT_NOTE 7 -#define SHT_NOBITS 8 -#define SHT_REL 9 -#define SHT_SHLIB 10 -#define SHT_DYNSYM 11 -#define SHT_LOPROC 0x70000000 -#define SHT_HIPROC 0x7FFFFFFF -#define SHT_LOUSER 0x80000000 -#define SHT_HIUSER 0xFFFFFFFF - -// Section flags -enum ElfSectionFlags { - SHF_WRITE = 0x1, - SHF_ALLOC = 0x2, - SHF_EXECINSTR = 0x4, - SHF_MASKPROC = 0xF0000000, -}; - -// Segment types -#define PT_NULL 0 -#define PT_LOAD 1 -#define PT_DYNAMIC 2 -#define PT_INTERP 3 -#define PT_NOTE 4 -#define PT_SHLIB 5 -#define PT_PHDR 6 -#define PT_LOPROC 0x70000000 -#define PT_HIPROC 0x7FFFFFFF - -// Segment flags -#define PF_X 0x1 -#define PF_W 0x2 -#define PF_R 0x4 -#define PF_MASKPROC 0xF0000000 - -typedef unsigned int Elf32_Addr; -typedef unsigned short Elf32_Half; -typedef unsigned int Elf32_Off; -typedef signed int Elf32_Sword; -typedef unsigned int Elf32_Word; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ELF file header - -struct Elf32_Ehdr { - unsigned char e_ident[EI_NIDENT]; - Elf32_Half e_type; - Elf32_Half e_machine; - Elf32_Word e_version; - Elf32_Addr e_entry; - Elf32_Off e_phoff; - Elf32_Off e_shoff; - Elf32_Word e_flags; - Elf32_Half e_ehsize; - Elf32_Half e_phentsize; - Elf32_Half e_phnum; - Elf32_Half e_shentsize; - Elf32_Half e_shnum; - Elf32_Half e_shstrndx; -}; - -// Section header -struct Elf32_Shdr { - Elf32_Word sh_name; - Elf32_Word sh_type; - Elf32_Word sh_flags; - Elf32_Addr sh_addr; - Elf32_Off sh_offset; - Elf32_Word sh_size; - Elf32_Word sh_link; - Elf32_Word sh_info; - Elf32_Word sh_addralign; - Elf32_Word sh_entsize; -}; - -// Segment header -struct Elf32_Phdr { - Elf32_Word p_type; - Elf32_Off p_offset; - Elf32_Addr p_vaddr; - Elf32_Addr p_paddr; - Elf32_Word p_filesz; - Elf32_Word p_memsz; - Elf32_Word p_flags; - Elf32_Word p_align; -}; - -// Symbol table entry -struct Elf32_Sym { - Elf32_Word st_name; - Elf32_Addr st_value; - Elf32_Word st_size; - unsigned char st_info; - unsigned char st_other; - Elf32_Half st_shndx; -}; - -// Relocation entries -struct Elf32_Rel { - Elf32_Addr r_offset; - Elf32_Word r_info; -}; +using namespace Common::ELF; //////////////////////////////////////////////////////////////////////////////////////////////////// // ElfReader class @@ -193,11 +42,11 @@ public: } // Quick accessors - ElfType GetType() const { - return (ElfType)(header->e_type); + u16 GetType() const { + return header->e_type; } - ElfMachine GetMachine() const { - return (ElfMachine)(header->e_machine); + u16 GetMachine() const { + return header->e_machine; } VAddr GetEntryPoint() const { return entryPoint; @@ -220,13 +69,13 @@ public: const u8* GetSectionDataPtr(int section) const { if (section < 0 || section >= header->e_shnum) return nullptr; - if (sections[section].sh_type != SHT_NOBITS) + if (sections[section].sh_type != ElfShtNobits) return GetPtr(sections[section].sh_offset); else return nullptr; } bool IsCodeSection(int section) const { - return sections[section].sh_type == SHT_PROGBITS; + return sections[section].sh_type == ElfShtProgBits; } const u8* GetSegmentPtr(int segment) { return GetPtr(segments[segment].p_offset); @@ -256,7 +105,7 @@ ElfReader::ElfReader(void* ptr) { } const char* ElfReader::GetSectionName(int section) const { - if (sections[section].sh_type == SHT_NULL) + if (sections[section].sh_type == ElfShtNull) return nullptr; int name_offset = sections[section].sh_name; @@ -272,7 +121,7 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx); // Should we relocate? - relocate = (header->e_type != ET_EXEC); + relocate = (header->e_type != ElfTypeExec); if (relocate) { LOG_DEBUG(Loader, "Relocatable module"); @@ -288,7 +137,7 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { u64 total_image_size = 0; for (unsigned int i = 0; i < header->e_phnum; ++i) { const Elf32_Phdr* p = &segments[i]; - if (p->p_type == PT_LOAD) { + if (p->p_type == ElfPtLoad) { total_image_size += (p->p_memsz + 0xFFF) & ~0xFFF; } } @@ -303,14 +152,14 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { LOG_DEBUG(Loader, "Type: {} Vaddr: {:08X} Filesz: {:08X} Memsz: {:08X} ", p->p_type, p->p_vaddr, p->p_filesz, p->p_memsz); - if (p->p_type == PT_LOAD) { + if (p->p_type == ElfPtLoad) { Kernel::CodeSet::Segment* codeset_segment; - u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X); - if (permission_flags == (PF_R | PF_X)) { + u32 permission_flags = p->p_flags & (ElfPfRead | ElfPfWrite | ElfPfExec); + if (permission_flags == (ElfPfRead | ElfPfExec)) { codeset_segment = &codeset.CodeSegment(); - } else if (permission_flags == (PF_R)) { + } else if (permission_flags == (ElfPfRead)) { codeset_segment = &codeset.RODataSegment(); - } else if (permission_flags == (PF_R | PF_W)) { + } else if (permission_flags == (ElfPfRead | ElfPfWrite)) { codeset_segment = &codeset.DataSegment(); } else { LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i, diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 8a938aa83..8dd956fc6 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -128,11 +128,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // Apply patches if necessary if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { - std::vector<u8> pi_header; - pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), - reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); - pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.data(), - program_image.data() + program_image.size()); + std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); + std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); + std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), + program_image.size()); pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 28d30eee2..7534de01e 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -594,6 +594,19 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; } +bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const { + VAddr end = base + size; + VAddr page = Common::AlignDown(base, PAGE_SIZE); + + for (; page < end; page += PAGE_SIZE) { + if (!IsValidVirtualAddress(page)) { + return false; + } + } + + return true; +} + u8* Memory::GetPointer(VAddr vaddr) { return impl->GetPointer(vaddr); } diff --git a/src/core/memory.h b/src/core/memory.h index b5721b740..58cc27b29 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -96,6 +96,17 @@ public: [[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const; /** + * Checks whether or not the supplied range of addresses are all valid + * virtual addresses for the current process. + * + * @param base The address to begin checking. + * @param size The amount of bytes to check. + * + * @returns True if all bytes in the given range are valid, false otherwise. + */ + [[nodiscard]] bool IsValidVirtualAddressRange(VAddr base, u64 size) const; + + /** * Gets a pointer to the given address. * * @param vaddr Virtual address to retrieve a pointer to. diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp index 7a0b73eca..5cc99fbe4 100644 --- a/src/core/tools/freezer.cpp +++ b/src/core/tools/freezer.cpp @@ -25,7 +25,6 @@ u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { return memory.Read64(addr); default: UNREACHABLE(); - return 0; } } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index d4fa69a77..48e799cf5 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -44,7 +44,6 @@ else() -Werror -Werror=conversion -Werror=ignored-qualifiers - -Werror=shadow $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> -Werror=unused-variable diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index a5c63e74a..446c027d3 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -13,11 +13,11 @@ namespace InputCommon { namespace { -std::string GetGUID(SDL_Joystick* joystick) { +Common::UUID GetGUID(SDL_Joystick* joystick) { const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; + std::array<u8, 16> data{}; + std::memcpy(data.data(), guid.data, sizeof(data)); + return Common::UUID{data}; } } // Anonymous namespace @@ -31,9 +31,9 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) { class SDLJoystick { public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, SDL_GameController* game_controller) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, sdl_controller{game_controller, &SDL_GameControllerClose} { EnableMotion(); } @@ -120,7 +120,7 @@ public: */ const PadIdentifier GetPadIdentifier() const { return { - .guid = Common::UUID{guid}, + .guid = guid, .port = static_cast<std::size_t>(port), .pad = 0, }; @@ -129,7 +129,7 @@ public: /** * The guid of the joystick */ - const std::string& GetGUID() const { + const Common::UUID& GetGUID() const { return guid; } @@ -228,7 +228,7 @@ public: } private: - std::string guid; + Common::UUID guid; int port; std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; @@ -240,7 +240,7 @@ private: BasicMotion motion; }; -std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { +std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const Common::UUID& guid, int port) { std::scoped_lock lock{joystick_map_mutex}; const auto it = joystick_map.find(guid); @@ -259,9 +259,13 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& return joystick_map[guid].emplace_back(std::move(joystick)); } +std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { + return GetSDLJoystickByGUID(Common::UUID{guid}, port); +} + std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); + const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; const auto map_it = joystick_map.find(guid); @@ -295,7 +299,7 @@ void SDLDriver::InitJoystick(int joystick_index) { return; } - const std::string guid = GetGUID(sdl_joystick); + const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; if (joystick_map.find(guid) == joystick_map.end()) { @@ -324,7 +328,7 @@ void SDLDriver::InitJoystick(int joystick_index) { } void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { - const std::string guid = GetGUID(sdl_joystick); + const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; // This call to guid is safe since the joystick is guaranteed to be in the map @@ -434,6 +438,7 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); + SendVibrations(); std::this_thread::sleep_for(1ms); } }); @@ -469,7 +474,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { devices.emplace_back(Common::ParamPackage{ {"engine", GetEngineName()}, {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, + {"guid", joystick->GetGUID().RawString()}, {"port", std::to_string(joystick->GetPort())}, }); if (joystick->IsJoyconLeft()) { @@ -492,8 +497,8 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { devices.emplace_back(Common::ParamPackage{ {"engine", GetEngineName()}, {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, - {"guid2", joystick2->GetGUID()}, + {"guid", joystick->GetGUID().RawString()}, + {"guid2", joystick2->GetGUID().RawString()}, {"port", std::to_string(joystick->GetPort())}, }); } @@ -531,57 +536,75 @@ Common::Input::VibrationError SDLDriver::SetRumble( .type = Common::Input::VibrationAmplificationType::Exponential, }; - if (!joystick->RumblePlay(new_vibration)) { - return Common::Input::VibrationError::Unknown; + if (vibration.type == Common::Input::VibrationAmplificationType::Test) { + if (!joystick->RumblePlay(new_vibration)) { + return Common::Input::VibrationError::Unknown; + } + return Common::Input::VibrationError::None; } + vibration_queue.Push(VibrationRequest{ + .identifier = identifier, + .vibration = new_vibration, + }); + return Common::Input::VibrationError::None; } -Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, +void SDLDriver::SendVibrations() { + while (!vibration_queue.Empty()) { + VibrationRequest request; + vibration_queue.Pop(request); + const auto joystick = GetSDLJoystickByGUID(request.identifier.guid.RawString(), + static_cast<int>(request.identifier.port)); + joystick->RumblePlay(request.vibration); + } +} + +Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, s32 axis, float value) const { Common::ParamPackage params{}; params.Set("engine", GetEngineName()); params.Set("port", port); - params.Set("guid", std::move(guid)); + params.Set("guid", guid.RawString()); params.Set("axis", axis); params.Set("threshold", "0.5"); params.Set("invert", value < 0 ? "-" : "+"); return params; } -Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid, +Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, const Common::UUID& guid, s32 button) const { Common::ParamPackage params{}; params.Set("engine", GetEngineName()); params.Set("port", port); - params.Set("guid", std::move(guid)); + params.Set("guid", guid.RawString()); params.Set("button", button); return params; } -Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat, - u8 value) const { +Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, const Common::UUID& guid, + s32 hat, u8 value) const { Common::ParamPackage params{}; params.Set("engine", GetEngineName()); params.Set("port", port); - params.Set("guid", std::move(guid)); + params.Set("guid", guid.RawString()); params.Set("hat", hat); params.Set("direction", GetHatButtonName(value)); return params; } -Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const { +Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& guid) const { Common::ParamPackage params{}; params.Set("engine", GetEngineName()); params.Set("motion", 0); params.Set("port", port); - params.Set("guid", std::move(guid)); + params.Set("guid", guid.RawString()); return params; } Common::ParamPackage SDLDriver::BuildParamPackageForBinding( - int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const { + int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const { switch (binding.bindType) { case SDL_CONTROLLER_BINDTYPE_NONE: break; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index dcd0d1e64..0846fbb50 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -12,6 +12,7 @@ #include <SDL.h> #include "common/common_types.h" +#include "common/threadsafe_queue.h" #include "input_common/input_engine.h" union SDL_Event; @@ -46,6 +47,7 @@ public: * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so * tie it to a SDLJoystick with the same guid and that port */ + std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port); std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); std::vector<Common::ParamPackage> GetInputDevices() const override; @@ -64,24 +66,32 @@ public: const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; private: + struct VibrationRequest { + PadIdentifier identifier; + Common::Input::VibrationStatus vibration; + }; + void InitJoystick(int joystick_index); void CloseJoystick(SDL_Joystick* sdl_joystick); /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); - Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, - float value = 0.1f) const; - Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, + /// Takes all vibrations from the queue and sends the command to the controller + void SendVibrations(); + + Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, + s32 axis, float value = 0.1f) const; + Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid, s32 button) const; - Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, + Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat, u8 value) const; - Common::ParamPackage BuildMotionParam(int port, std::string guid) const; + Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const; Common::ParamPackage BuildParamPackageForBinding( - int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const; + int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const; Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, int axis_y, float offset_x, @@ -107,8 +117,11 @@ private: /// Returns true if the button is on the left joycon bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; + /// Queue of vibration request to controllers + Common::SPSCQueue<VibrationRequest> vibration_queue; + /// Map of GUID of a list of corresponding virtual Joysticks - std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; + std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::mutex joystick_map_mutex; bool start_thread = false; diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp index 8acbe4584..1753e0893 100644 --- a/src/input_common/drivers/touch_screen.cpp +++ b/src/input_common/drivers/touch_screen.cpp @@ -14,38 +14,93 @@ constexpr PadIdentifier identifier = { TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) { PreSetController(identifier); + ReleaseAllTouch(); } -void TouchScreen::TouchMoved(float x, float y, std::size_t finger) { - if (finger >= 16) { +void TouchScreen::TouchMoved(float x, float y, std::size_t finger_id) { + const auto index = GetIndexFromFingerId(finger_id); + if (!index) { + // Touch doesn't exist handle it as a new one + TouchPressed(x, y, finger_id); return; } - TouchPressed(x, y, finger); + const auto i = index.value(); + fingers[i].is_active = true; + SetButton(identifier, static_cast<int>(i), true); + SetAxis(identifier, static_cast<int>(i * 2), x); + SetAxis(identifier, static_cast<int>(i * 2 + 1), y); } -void TouchScreen::TouchPressed(float x, float y, std::size_t finger) { - if (finger >= 16) { +void TouchScreen::TouchPressed(float x, float y, std::size_t finger_id) { + if (GetIndexFromFingerId(finger_id)) { + // Touch already exist. Just update the data + TouchMoved(x, y, finger_id); return; } - SetButton(identifier, static_cast<int>(finger), true); - SetAxis(identifier, static_cast<int>(finger * 2), x); - SetAxis(identifier, static_cast<int>(finger * 2 + 1), y); + const auto index = GetNextFreeIndex(); + if (!index) { + // No free entries. Ignore input + return; + } + const auto i = index.value(); + fingers[i].is_enabled = true; + fingers[i].finger_id = finger_id; + TouchMoved(x, y, finger_id); } -void TouchScreen::TouchReleased(std::size_t finger) { - if (finger >= 16) { +void TouchScreen::TouchReleased(std::size_t finger_id) { + const auto index = GetIndexFromFingerId(finger_id); + if (!index) { return; } - SetButton(identifier, static_cast<int>(finger), false); - SetAxis(identifier, static_cast<int>(finger * 2), 0.0f); - SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f); + const auto i = index.value(); + fingers[i].is_enabled = false; + SetButton(identifier, static_cast<int>(i), false); + SetAxis(identifier, static_cast<int>(i * 2), 0.0f); + SetAxis(identifier, static_cast<int>(i * 2 + 1), 0.0f); +} + +std::optional<std::size_t> TouchScreen::GetIndexFromFingerId(std::size_t finger_id) const { + for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) { + const auto& finger = fingers[index]; + if (!finger.is_enabled) { + continue; + } + if (finger.finger_id == finger_id) { + return index; + } + } + return std::nullopt; +} + +std::optional<std::size_t> TouchScreen::GetNextFreeIndex() const { + for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) { + if (!fingers[index].is_enabled) { + return index; + } + } + return std::nullopt; +} + +void TouchScreen::ClearActiveFlag() { + for (auto& finger : fingers) { + finger.is_active = false; + } +} + +void TouchScreen::ReleaseInactiveTouch() { + for (const auto& finger : fingers) { + if (!finger.is_active) { + TouchReleased(finger.finger_id); + } + } } void TouchScreen::ReleaseAllTouch() { - for (int index = 0; index < 16; ++index) { - SetButton(identifier, index, false); - SetAxis(identifier, index * 2, 0.0f); - SetAxis(identifier, index * 2 + 1, 0.0f); + for (const auto& finger : fingers) { + if (finger.is_enabled) { + TouchReleased(finger.finger_id); + } } } diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h index 193478ead..f46036ffd 100644 --- a/src/input_common/drivers/touch_screen.h +++ b/src/input_common/drivers/touch_screen.h @@ -3,41 +3,65 @@ #pragma once +#include <optional> + #include "input_common/input_engine.h" namespace InputCommon { /** - * A button device factory representing a keyboard. It receives keyboard events and forward them - * to all button devices it created. + * A touch device factory representing a touch screen. It receives touch events and forward them + * to all touch devices it created. */ class TouchScreen final : public InputEngine { public: explicit TouchScreen(std::string input_engine_); /** - * Signals that mouse has moved. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - * @param center_x the x-coordinate of the middle of the screen - * @param center_y the y-coordinate of the middle of the screen + * Signals that touch has moved and marks this touch point as active + * @param x new horizontal position + * @param y new vertical position + * @param finger_id of the touch point to be updated */ - void TouchMoved(float x, float y, std::size_t finger); + void TouchMoved(float x, float y, std::size_t finger_id); /** - * Sets the status of all buttons bound with the key to pressed - * @param key_code the code of the key to press + * Signals and creates a new touch point with this finger id + * @param x starting horizontal position + * @param y starting vertical position + * @param finger_id to be assigned to the new touch point */ - void TouchPressed(float x, float y, std::size_t finger); + void TouchPressed(float x, float y, std::size_t finger_id); /** - * Sets the status of all buttons bound with the key to released - * @param key_code the code of the key to release + * Signals and resets the touch point related to the this finger id + * @param finger_id to be released */ - void TouchReleased(std::size_t finger); + void TouchReleased(std::size_t finger_id); + + /// Resets the active flag for each touch point + void ClearActiveFlag(); + + /// Releases all touch that haven't been marked as active + void ReleaseInactiveTouch(); /// Resets all inputs to their initial value void ReleaseAllTouch(); + +private: + static constexpr std::size_t MAX_FINGER_COUNT = 16; + + struct TouchStatus { + std::size_t finger_id{}; + bool is_enabled{}; + bool is_active{}; + }; + + std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const; + + std::optional<std::size_t> GetNextFreeIndex() const; + + std::array<TouchStatus, MAX_FINGER_COUNT> fingers{}; }; } // namespace InputCommon diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 4c76ce1ea..ae1dbe619 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -253,9 +253,6 @@ else() -Werror -Werror=conversion -Werror=ignored-qualifiers - -Werror=implicit-fallthrough - -Werror=shadow - -Werror=sign-compare $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> -Werror=unused-variable diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h index d17dc0376..752879a18 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.h +++ b/src/shader_recompiler/frontend/ir/opcodes.h @@ -103,6 +103,6 @@ struct fmt::formatter<Shader::IR::Opcode> { } template <typename FormatContext> auto format(const Shader::IR::Opcode& op, FormatContext& ctx) { - return format_to(ctx.out(), "{}", Shader::IR::NameOf(op)); + return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(op)); } }; diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.h b/src/shader_recompiler/frontend/maxwell/control_flow.h index 2487b9b0b..1ce45b3a5 100644 --- a/src/shader_recompiler/frontend/maxwell/control_flow.h +++ b/src/shader_recompiler/frontend/maxwell/control_flow.h @@ -58,7 +58,7 @@ public: [[nodiscard]] Stack Remove(Token token) const; private: - boost::container::small_vector<StackEntry, 3> entries; + std::vector<StackEntry> entries; }; struct IndirectBranch { diff --git a/src/shader_recompiler/frontend/maxwell/opcodes.h b/src/shader_recompiler/frontend/maxwell/opcodes.h index 83093fca0..72dd143c2 100644 --- a/src/shader_recompiler/frontend/maxwell/opcodes.h +++ b/src/shader_recompiler/frontend/maxwell/opcodes.h @@ -24,6 +24,6 @@ struct fmt::formatter<Shader::Maxwell::Opcode> { } template <typename FormatContext> auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) { - return format_to(ctx.out(), "{}", NameOf(opcode)); + return fmt::format_to(ctx.out(), "{}", NameOf(opcode)); } }; diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp index 3dc7c9a11..578bc8c1b 100644 --- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp @@ -975,13 +975,7 @@ private: Environment& env; IR::AbstractSyntaxList& syntax_list; bool uses_demote_to_helper{}; - -// TODO: C++20 Remove this when all compilers support constexpr std::vector -#if __cpp_lib_constexpr_vector >= 201907 - static constexpr Flow::Block dummy_flow_block; -#else const Flow::Block dummy_flow_block; -#endif }; } // Anonymous namespace diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6a6325e38..14de7bc89 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -258,10 +258,6 @@ if (MSVC) target_compile_options(video_core PRIVATE /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data - /we4456 # Declaration of 'identifier' hides previous local declaration - /we4457 # Declaration of 'identifier' hides function parameter - /we4458 # Declaration of 'identifier' hides class member - /we4459 # Declaration of 'identifier' hides global declaration ) else() target_compile_options(video_core PRIVATE @@ -269,7 +265,6 @@ else() -Wno-error=sign-conversion -Werror=pessimizing-move -Werror=redundant-move - -Werror=shadow -Werror=type-limits $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess> @@ -277,3 +272,7 @@ else() $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> ) endif() + +if (ARCHITECTURE_x86_64) + target_link_libraries(video_core PRIVATE dynarmic) +endif() diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp index 83b2e0fc4..a5eb97b7f 100644 --- a/src/video_core/command_classes/codecs/codec.cpp +++ b/src/video_core/command_classes/codecs/codec.cpp @@ -224,7 +224,7 @@ void Codec::Decode() { vp9_hidden_frame = vp9_decoder->WasFrameHidden(); return vp9_decoder->GetFrameBytes(); default: - UNREACHABLE(); + ASSERT(false); return std::vector<u8>{}; } }(); diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp index a95618913..c01431441 100644 --- a/src/video_core/command_classes/codecs/vp9.cpp +++ b/src/video_core/command_classes/codecs/vp9.cpp @@ -153,7 +153,7 @@ constexpr Vp9EntropyProbs default_probs{ .high_precision{128, 128}, }; -constexpr std::array<s32, 256> norm_lut{ +constexpr std::array<u8, 256> norm_lut{ 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -164,7 +164,7 @@ constexpr std::array<s32, 256> norm_lut{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -constexpr std::array<s32, 254> map_lut{ +constexpr std::array<u8, 254> map_lut{ 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, @@ -232,7 +232,7 @@ constexpr std::array<s32, 254> map_lut{ std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1)); } - return map_lut[index]; + return static_cast<s32>(map_lut[index]); } } // Anonymous namespace @@ -819,7 +819,7 @@ void VpxRangeEncoder::Write(bool bit, s32 probability) { local_range = range - split; } - s32 shift = norm_lut[local_range]; + s32 shift = static_cast<s32>(norm_lut[local_range]); local_range <<= shift; count += shift; diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp index bef321b6e..7c17df353 100644 --- a/src/video_core/command_classes/vic.cpp +++ b/src/video_core/command_classes/vic.cpp @@ -228,7 +228,7 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) { break; } default: - UNREACHABLE(); + ASSERT(false); break; } gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(), diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index d4652b167..3a4646289 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -173,6 +173,8 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume case MAXWELL3D_REG_INDEX(shadow_ram_control): shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument); return; + case MAXWELL3D_REG_INDEX(macros.upload_address): + return macro_engine->ClearCode(regs.macros.upload_address); case MAXWELL3D_REG_INDEX(macros.data): return macro_engine->AddCode(regs.macros.upload_address, argument); case MAXWELL3D_REG_INDEX(macros.bind): @@ -593,8 +595,8 @@ void Maxwell3D::DrawArrays() { std::optional<u64> Maxwell3D::GetQueryResult() { switch (regs.query.query_get.select) { - case Regs::QuerySelect::Zero: - return 0; + case Regs::QuerySelect::Payload: + return regs.query.query_sequence; case Regs::QuerySelect::SamplesPassed: // Deferred. rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed, diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index c0c2c7d96..5f9eb208c 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -93,7 +93,7 @@ public: }; enum class QuerySelect : u32 { - Zero = 0, + Payload = 0, TimeElapsed = 2, TransformFeedbackPrimitivesGenerated = 11, PrimitivesGenerated = 18, @@ -202,7 +202,7 @@ public: case Size::Size_11_11_10: return 3; default: - UNREACHABLE(); + ASSERT(false); return 1; } } @@ -238,7 +238,7 @@ public: case Size::Size_11_11_10: return 4; default: - UNREACHABLE(); + ASSERT(false); return 1; } } @@ -274,7 +274,7 @@ public: case Size::Size_11_11_10: return "11_11_10"; default: - UNREACHABLE(); + ASSERT(false); return {}; } } @@ -296,7 +296,7 @@ public: case Type::Float: return "FLOAT"; } - UNREACHABLE(); + ASSERT(false); return {}; } @@ -336,7 +336,7 @@ public: case 3: return {x3, y3}; default: - UNREACHABLE(); + ASSERT(false); return {0, 0}; } } @@ -1193,7 +1193,7 @@ public: case IndexFormat::UnsignedInt: return 4; } - UNREACHABLE(); + ASSERT(false); return 1; } diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 76e8bc656..0efe58282 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -62,7 +62,7 @@ void MaxwellDMA::Launch() { if (!is_src_pitch && !is_dst_pitch) { // If both the source and the destination are in block layout, assert. - UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented"); + UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented"); return; } @@ -134,7 +134,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() { // Deswizzle the input and copy it over. UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0); - const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in; + const u32 bytes_per_pixel = + regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1; const Parameters& src_params = regs.src_params; const u32 width = src_params.width; const u32 height = src_params.height; @@ -166,7 +167,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() { UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0); const auto& dst_params = regs.dst_params; - const u32 bytes_per_pixel = regs.pitch_in / regs.line_length_in; + const u32 bytes_per_pixel = + regs.launch_dma.remap_enable ? regs.pitch_in / regs.line_length_in : 1; const u32 width = dst_params.width; const u32 height = dst_params.height; const u32 depth = dst_params.depth; @@ -210,7 +212,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() { } void MaxwellDMA::FastCopyBlockLinearToPitch() { - const u32 bytes_per_pixel = regs.pitch_out / regs.line_length_in; + const u32 bytes_per_pixel = + regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1; const size_t src_size = GOB_SIZE; const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count; u32 pos_x = regs.src_params.origin.x; @@ -257,7 +260,7 @@ void MaxwellDMA::ReleaseSemaphore() { memory_manager.Write<u64>(address + 8, system.GPU().GetTicks()); break; default: - UNREACHABLE_MSG("Unknown semaphore type: {}", static_cast<u32>(type.Value())); + ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value())); } } diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index b79a73132..b0ce9f000 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -31,7 +31,8 @@ static void RunThread(std::stop_token stop_token, Core::System& system, VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer(); while (!stop_token.stop_requested()) { - CommandDataContainer next = state.queue.PopWait(stop_token); + CommandDataContainer next; + state.queue.Pop(next, stop_token); if (stop_token.stop_requested()) { break; } @@ -49,7 +50,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system, } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { rasterizer->OnCPUWrite(invalidate->addr, invalidate->size); } else { - UNREACHABLE(); + ASSERT(false); } state.signaled_fence.store(next.fence); if (next.block) { diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 71cd35756..ad9fd5eff 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -10,7 +10,7 @@ #include <thread> #include <variant> -#include "common/threadsafe_queue.h" +#include "common/bounded_threadsafe_queue.h" #include "video_core/framebuffer_config.h" namespace Tegra { @@ -96,9 +96,9 @@ struct CommandDataContainer { /// Struct used to synchronize the GPU thread struct SynchState final { - using CommandQueue = Common::SPSCQueue<CommandDataContainer, true>; + using CommandQueue = Common::MPSCQueue<CommandDataContainer>; std::mutex write_lock; - CommandQueue queue; + CommandQueue queue{512}; // size must be 2^n u64 last_fence{}; std::atomic<u64> signaled_fence{}; std::condition_variable_any cv; diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp index a033d03be..43f8b5904 100644 --- a/src/video_core/macro/macro.cpp +++ b/src/video_core/macro/macro.cpp @@ -2,11 +2,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <cstring> +#include <fstream> #include <optional> +#include <span> #include <boost/container_hash/hash.hpp> #include "common/assert.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "video_core/macro/macro.h" #include "video_core/macro/macro_hle.h" @@ -15,6 +19,23 @@ namespace Tegra { +static void Dump(u64 hash, std::span<const u32> code) { + const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)}; + const auto macro_dir{base_dir / "macros"}; + if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { + LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); + return; + } + const auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; + std::fstream macro_file(name, std::ios::out | std::ios::binary); + if (!macro_file) { + LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", + Common::FS::PathToUTF8String(name)); + return; + } + macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes()); +} + MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d) : hle_macros{std::make_unique<Tegra::HLEMacro>(maxwell3d)} {} @@ -24,6 +45,11 @@ void MacroEngine::AddCode(u32 method, u32 data) { uploaded_macro_code[method].push_back(data); } +void MacroEngine::ClearCode(u32 method) { + macro_cache.erase(method); + uploaded_macro_code.erase(method); +} + void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { auto compiled_macro = macro_cache.find(method); if (compiled_macro != macro_cache.end()) { @@ -45,7 +71,7 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { } } if (!mid_method.has_value()) { - UNREACHABLE_MSG("Macro 0x{0:x} was not uploaded", method); + ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method); return; } } @@ -54,6 +80,9 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { if (!mid_method.has_value()) { cache_info.lle_program = Compile(macro_code->second); cache_info.hash = boost::hash_value(macro_code->second); + if (Settings::values.dump_macros) { + Dump(cache_info.hash, macro_code->second); + } } else { const auto& macro_cached = uploaded_macro_code[mid_method.value()]; const auto rebased_method = method - mid_method.value(); @@ -63,6 +92,9 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { code.size() * sizeof(u32)); cache_info.hash = boost::hash_value(code); cache_info.lle_program = Compile(code); + if (Settings::values.dump_macros) { + Dump(cache_info.hash, code); + } } if (auto hle_program = hle_macros->GetHLEProgram(cache_info.hash)) { diff --git a/src/video_core/macro/macro.h b/src/video_core/macro/macro.h index 7e12c16dc..07d97ba39 100644 --- a/src/video_core/macro/macro.h +++ b/src/video_core/macro/macro.h @@ -117,6 +117,9 @@ public: // Store the uploaded macro code to compile them when they're called. void AddCode(u32 method, u32 data); + // Clear the code associated with a method. + void ClearCode(u32 method); + // Compiles the macro if its not in the cache, and executes the compiled macro void Execute(u32 method, const std::vector<u32>& parameters); diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp index 87d2e8721..f670b1bca 100644 --- a/src/video_core/macro/macro_interpreter.cpp +++ b/src/video_core/macro/macro_interpreter.cpp @@ -308,7 +308,6 @@ bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, return value != 0; } UNREACHABLE(); - return true; } Macro::Opcode MacroInterpreterImpl::GetOpcode() const { diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index dc2b490d4..aca25d902 100644 --- a/src/video_core/macro/macro_jit_x64.cpp +++ b/src/video_core/macro/macro_jit_x64.cpp @@ -23,7 +23,8 @@ MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255 namespace Tegra { namespace { constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx; -constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp; +constexpr Xbyak::Reg32 RESULT = Xbyak::util::r10d; +constexpr Xbyak::Reg64 MAX_PARAMETER = Xbyak::util::r11; constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12; constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d; constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; @@ -31,6 +32,7 @@ constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15; constexpr std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({ STATE, RESULT, + MAX_PARAMETER, PARAMETERS, METHOD_ADDRESS, BRANCH_HOLDER, @@ -80,7 +82,7 @@ private: u32 carry_flag{}; }; static_assert(offsetof(JITState, maxwell3d) == 0, "Maxwell3D is not at 0x0"); - using ProgramType = void (*)(JITState*, const u32*); + using ProgramType = void (*)(JITState*, const u32*, const u32*); struct OptimizerState { bool can_skip_carry{}; @@ -112,7 +114,7 @@ void MacroJITx64Impl::Execute(const std::vector<u32>& parameters, u32 method) { JITState state{}; state.maxwell3d = &maxwell3d; state.registers = {}; - program(&state, parameters.data()); + program(&state, parameters.data(), parameters.data() + parameters.size()); } void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { @@ -409,7 +411,7 @@ void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) { Xbyak::Label end; auto value = Compile_GetRegister(opcode.src_a, eax); - test(value, value); + cmp(value, 0); // test(value, value); if (optimizer.has_delayed_pc) { switch (opcode.branch_condition) { case Macro::BranchCondition::Zero: @@ -488,6 +490,7 @@ void MacroJITx64Impl::Compile() { // JIT state mov(STATE, Common::X64::ABI_PARAM1); mov(PARAMETERS, Common::X64::ABI_PARAM2); + mov(MAX_PARAMETER, Common::X64::ABI_PARAM3); xor_(RESULT, RESULT); xor_(METHOD_ADDRESS, METHOD_ADDRESS); xor_(BRANCH_HOLDER, BRANCH_HOLDER); @@ -598,7 +601,22 @@ bool MacroJITx64Impl::Compile_NextInstruction() { return true; } +static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { + LOG_CRITICAL(HW_GPU, + "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", + parameter, max_parameter - sizeof(u32)); +} + Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { + Xbyak::Label parameter_ok{}; + cmp(PARAMETERS, MAX_PARAMETER); + jb(parameter_ok, T_NEAR); + Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + mov(Common::X64::ABI_PARAM1, PARAMETERS); + mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); + Common::X64::CallFarFunction(*this, &WarnInvalidParameter); + Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + L(parameter_ok); mov(eax, dword[PARAMETERS]); add(PARAMETERS, sizeof(u32)); return eax; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index c8d99fdb5..d373be0ba 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -67,7 +67,7 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { ASSERT(it->first == gpu_addr); map_ranges.erase(it); } else { - UNREACHABLE_MSG("Unmapping non-existent GPU address=0x{:x}", gpu_addr); + ASSERT_MSG(false, "Unmapping non-existent GPU address=0x{:x}", gpu_addr); } const auto submapped_ranges = GetSubmappedRange(gpu_addr, size); @@ -206,7 +206,7 @@ T MemoryManager::Read(GPUVAddr addr) const { return value; } - UNREACHABLE(); + ASSERT(false); return {}; } @@ -219,7 +219,7 @@ void MemoryManager::Write(GPUVAddr addr, T data) { return; } - UNREACHABLE(); + ASSERT(false); } template u8 MemoryManager::Read<u8>(GPUVAddr addr) const; diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index 35f42f2f8..67eae369d 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -48,7 +48,7 @@ GLenum Stage(size_t stage_index) { case 4: return GL_FRAGMENT_SHADER; } - UNREACHABLE_MSG("{}", stage_index); + ASSERT_MSG(false, "{}", stage_index); return GL_NONE; } @@ -65,7 +65,7 @@ GLenum AssemblyStage(size_t stage_index) { case 4: return GL_FRAGMENT_PROGRAM_NV; } - UNREACHABLE_MSG("{}", stage_index); + ASSERT_MSG(false, "{}", stage_index); return GL_NONE; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8ef79753f..159b71161 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -563,12 +563,11 @@ void RasterizerOpenGL::SyncViewport() { flags[Dirty::FrontFace] = false; GLenum mode = MaxwellToGL::FrontFace(regs.front_face); - bool flip_faces = false; - if (regs.screen_y_control.triangle_rast_flip != 0 && - regs.viewport_transform[0].scale_y < 0.0f) { + bool flip_faces = true; + if (regs.screen_y_control.triangle_rast_flip != 0) { flip_faces = !flip_faces; } - if (regs.viewport_transform[0].scale_z < 0.0f) { + if (regs.viewport_transform[0].scale_y < 0.0f) { flip_faces = !flip_faces; } if (flip_faces) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index cd48fef26..07d4b7cf0 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -85,7 +85,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, case Maxwell::TessellationPrimitive::Quads: return Shader::TessPrimitive::Quads; } - UNREACHABLE(); + ASSERT(false); return Shader::TessPrimitive::Triangles; }(); info.tess_spacing = [&] { @@ -97,7 +97,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, case Maxwell::TessellationSpacing::FractionalEven: return Shader::TessSpacing::FractionalEven; } - UNREACHABLE(); + ASSERT(false); return Shader::TessSpacing::Equal; }(); break; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 29ff736fb..8c0fffc67 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -83,7 +83,7 @@ GLenum ImageTarget(const VideoCommon::ImageInfo& info) { case ImageType::Buffer: return GL_TEXTURE_BUFFER; } - UNREACHABLE_MSG("Invalid image type={}", info.type); + ASSERT_MSG(false, "Invalid image type={}", info.type); return GL_NONE; } @@ -107,7 +107,7 @@ GLenum ImageTarget(Shader::TextureType type, int num_samples = 1) { case Shader::TextureType::Buffer: return GL_TEXTURE_BUFFER; } - UNREACHABLE_MSG("Invalid image view type={}", type); + ASSERT_MSG(false, "Invalid image view type={}", type); return GL_NONE; } @@ -119,7 +119,7 @@ GLenum TextureMode(PixelFormat format, bool is_first) { case PixelFormat::S8_UINT_D24_UNORM: return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT; default: - UNREACHABLE(); + ASSERT(false); return GL_DEPTH_COMPONENT; } } @@ -140,7 +140,7 @@ GLint Swizzle(SwizzleSource source) { case SwizzleSource::OneFloat: return GL_ONE; } - UNREACHABLE_MSG("Invalid swizzle source={}", source); + ASSERT_MSG(false, "Invalid swizzle source={}", source); return GL_NONE; } @@ -197,7 +197,7 @@ GLint ConvertA5B5G5R1_UNORM(SwizzleSource source) { case SwizzleSource::OneFloat: return GL_ONE; } - UNREACHABLE_MSG("Invalid swizzle source={}", source); + ASSERT_MSG(false, "Invalid swizzle source={}", source); return GL_NONE; } @@ -381,10 +381,10 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form glTextureStorage3D(handle, gl_num_levels, gl_internal_format, width, height, depth); break; case GL_TEXTURE_BUFFER: - UNREACHABLE(); + ASSERT(false); break; default: - UNREACHABLE_MSG("Invalid target=0x{:x}", target); + ASSERT_MSG(false, "Invalid target=0x{:x}", target); break; } return texture; @@ -420,7 +420,7 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form case Shader::ImageFormat::R32G32B32A32_UINT: return GL_RGBA32UI; } - UNREACHABLE_MSG("Invalid image format={}", format); + ASSERT_MSG(false, "Invalid image format={}", format); return GL_R32UI; } @@ -579,7 +579,7 @@ void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src, } else if (IsPixelFormatBGR(dst.info.format) || IsPixelFormatBGR(src.info.format)) { format_conversion_pass.ConvertImage(dst, src, copies); } else { - UNREACHABLE(); + ASSERT(false); } } @@ -620,7 +620,7 @@ void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferM case ImageType::Linear: return util_shaders.PitchUpload(image, map, swizzles); default: - UNREACHABLE(); + ASSERT(false); break; } } @@ -639,7 +639,7 @@ FormatProperties TextureCacheRuntime::FormatInfo(ImageType type, GLenum internal case ImageType::e3D: return format_properties[2].at(internal_format); default: - UNREACHABLE(); + ASSERT(false); return FormatProperties{}; } } @@ -888,7 +888,7 @@ void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t b } break; default: - UNREACHABLE(); + ASSERT(false); } } @@ -924,7 +924,7 @@ void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t b depth = copy.image_extent.depth; break; default: - UNREACHABLE(); + ASSERT(false); } // Compressed formats don't have a pixel format or type const bool is_compressed = gl_format == GL_NONE; @@ -950,7 +950,7 @@ void Image::Scale(bool up_scale) { case SurfaceType::DepthStencil: return GL_DEPTH_STENCIL_ATTACHMENT; default: - UNREACHABLE(); + ASSERT(false); return GL_COLOR_ATTACHMENT0; } }(); @@ -965,7 +965,7 @@ void Image::Scale(bool up_scale) { case SurfaceType::DepthStencil: return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; default: - UNREACHABLE(); + ASSERT(false); return GL_COLOR_BUFFER_BIT; } }(); @@ -980,7 +980,7 @@ void Image::Scale(bool up_scale) { case SurfaceType::DepthStencil: return 3; default: - UNREACHABLE(); + ASSERT(false); return 0; } }(); @@ -1045,7 +1045,7 @@ bool Image::ScaleUp(bool ignore) { return false; } if (info.type == ImageType::Linear) { - UNREACHABLE(); + ASSERT(false); return false; } flags |= ImageFlagBits::Rescaled; @@ -1139,7 +1139,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI UNIMPLEMENTED(); break; case ImageViewType::Buffer: - UNREACHABLE(); + ASSERT(false); break; } switch (info.type) { @@ -1319,7 +1319,7 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM buffer_bits |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; break; default: - UNREACHABLE(); + ASSERT(false); buffer_bits |= GL_DEPTH_BUFFER_BIT; break; } diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index c2a6da5a7..644b60d73 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -206,7 +206,7 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) { case Maxwell::IndexFormat::UnsignedInt: return GL_UNSIGNED_INT; } - UNREACHABLE_MSG("Invalid index_format={}", index_format); + ASSERT_MSG(false, "Invalid index_format={}", index_format); return {}; } @@ -243,7 +243,7 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { case Maxwell::PrimitiveTopology::Patches: return GL_PATCHES; } - UNREACHABLE_MSG("Invalid topology={}", topology); + ASSERT_MSG(false, "Invalid topology={}", topology); return GL_POINTS; } @@ -271,8 +271,8 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, } break; } - UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}", filter_mode, - mipmap_filter_mode); + ASSERT_MSG(false, "Invalid texture filter mode={} and mipmap filter mode={}", filter_mode, + mipmap_filter_mode); return GL_NEAREST; } @@ -550,7 +550,7 @@ inline GLenum PolygonMode(Maxwell::PolygonMode polygon_mode) { case Maxwell::PolygonMode::Fill: return GL_FILL; } - UNREACHABLE_MSG("Invalid polygon mode={}", polygon_mode); + ASSERT_MSG(false, "Invalid polygon mode={}", polygon_mode); return GL_FILL; } @@ -563,7 +563,7 @@ inline GLenum ReductionFilter(Tegra::Texture::SamplerReduction filter) { case Tegra::Texture::SamplerReduction::Max: return GL_MAX; } - UNREACHABLE_MSG("Invalid reduction filter={}", static_cast<int>(filter)); + ASSERT_MSG(false, "Invalid reduction filter={}", static_cast<int>(filter)); return GL_WEIGHTED_AVERAGE_ARB; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 3a3c213bb..9a9243544 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -79,7 +79,7 @@ const char* GetSource(GLenum source) { case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: - UNREACHABLE(); + ASSERT(false); return "Unknown source"; } } @@ -101,7 +101,7 @@ const char* GetType(GLenum type) { case GL_DEBUG_TYPE_MARKER: return "MARKER"; default: - UNREACHABLE(); + ASSERT(false); return "Unknown type"; } } diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 837825737..404def62e 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -282,7 +282,7 @@ GLenum StoreFormat(u32 bytes_per_block) { case 16: return GL_RGBA32UI; } - UNREACHABLE(); + ASSERT(false); return GL_R8UI; } diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index ea360f339..193cbe15e 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -25,7 +25,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter) { case Tegra::Texture::TextureFilter::Linear: return VK_FILTER_LINEAR; } - UNREACHABLE_MSG("Invalid sampler filter={}", filter); + ASSERT_MSG(false, "Invalid sampler filter={}", filter); return {}; } @@ -42,7 +42,7 @@ VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter case Tegra::Texture::TextureMipmapFilter::Linear: return VK_SAMPLER_MIPMAP_MODE_LINEAR; } - UNREACHABLE_MSG("Invalid sampler mipmap mode={}", mipmap_filter); + ASSERT_MSG(false, "Invalid sampler mipmap mode={}", mipmap_filter); return {}; } @@ -70,7 +70,7 @@ VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wra case Tegra::Texture::TextureFilter::Linear: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; } - UNREACHABLE(); + ASSERT(false); return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; case Tegra::Texture::WrapMode::MirrorOnceClampToEdge: return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; @@ -744,7 +744,7 @@ VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) case Maxwell::ViewportSwizzle::NegativeW: return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV; } - UNREACHABLE_MSG("Invalid swizzle={}", swizzle); + ASSERT_MSG(false, "Invalid swizzle={}", swizzle); return {}; } @@ -757,7 +757,7 @@ VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reducti case Tegra::Texture::SamplerReduction::Max: return VK_SAMPLER_REDUCTION_MODE_MAX_EXT; } - UNREACHABLE_MSG("Invalid sampler mode={}", static_cast<int>(reduction)); + ASSERT_MSG(false, "Invalid sampler mode={}", static_cast<int>(reduction)); return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; } @@ -780,7 +780,7 @@ VkSampleCountFlagBits MsaaMode(Tegra::Texture::MsaaMode msaa_mode) { case Tegra::Texture::MsaaMode::Msaa4x4: return VK_SAMPLE_COUNT_16_BIT; default: - UNREACHABLE_MSG("Invalid msaa_mode={}", static_cast<int>(msaa_mode)); + ASSERT_MSG(false, "Invalid msaa_mode={}", static_cast<int>(msaa_mode)); return VK_SAMPLE_COUNT_1_BIT; } } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 0aeb37538..450905197 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -46,7 +46,7 @@ size_t BytesPerIndex(VkIndexType index_type) { case VK_INDEX_TYPE_UINT32: return 4; default: - UNREACHABLE_MSG("Invalid index type={}", index_type); + ASSERT_MSG(false, "Invalid index type={}", index_type); return 1; } } @@ -366,7 +366,7 @@ void BufferCacheRuntime::ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle std::memcpy(staging_data, MakeQuadIndices<u32>(quad, first).data(), quad_size); break; default: - UNREACHABLE(); + ASSERT(false); break; } staging_data += quad_size; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 29481a102..4cba777e6 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -265,7 +265,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble( case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedInt: return 2; } - UNREACHABLE(); + ASSERT(false); return 2; }(); const u32 input_size = num_vertices << index_shift; @@ -328,31 +328,32 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, const VkImageAspectFlags aspect_mask = image.AspectMask(); const VkImage vk_image = image.Handle(); const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record( - [vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = is_initialized ? VK_ACCESS_SHADER_WRITE_BIT : VkAccessFlags{}, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT - : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); - }); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, + is_initialized](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT + : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); + }); for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { const size_t input_offset = swizzle.buffer_offset + map.offset; const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 8U); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 5196bdcf2..978e827f5 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -174,7 +174,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program case Maxwell::TessellationPrimitive::Quads: return Shader::TessPrimitive::Quads; } - UNREACHABLE(); + ASSERT(false); return Shader::TessPrimitive::Triangles; }(); info.tess_spacing = [&] { @@ -187,7 +187,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program case Maxwell::TessellationSpacing::FractionalEven: return Shader::TessSpacing::FractionalEven; } - UNREACHABLE(); + ASSERT(false); return Shader::TessSpacing::Equal; }(); break; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index fd27581ce..ce6c853c1 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -784,8 +784,8 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) }); } else { // Front face defines both faces - scheduler.Record([ref = regs.stencil_back_func_ref, write_mask = regs.stencil_back_mask, - test_mask = regs.stencil_back_func_mask](vk::CommandBuffer cmdbuf) { + scheduler.Record([ref = regs.stencil_front_func_ref, write_mask = regs.stencil_front_mask, + test_mask = regs.stencil_front_func_mask](vk::CommandBuffer cmdbuf) { cmdbuf.SetStencilReference(VK_STENCIL_FACE_FRONT_AND_BACK, ref); cmdbuf.SetStencilWriteMask(VK_STENCIL_FACE_FRONT_AND_BACK, write_mask); cmdbuf.SetStencilCompareMask(VK_STENCIL_FACE_FRONT_AND_BACK, test_mask); diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 31ce2f815..9a6afaca6 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -263,7 +263,7 @@ StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(MemoryUsage case MemoryUsage::Download: return download_cache; default: - UNREACHABLE_MSG("Invalid memory usage={}", usage); + ASSERT_MSG(false, "Invalid memory usage={}", usage); return upload_cache; } } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 353594293..43ecb9647 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -70,7 +70,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { case ImageType::Buffer: break; } - UNREACHABLE_MSG("Invalid image type={}", type); + ASSERT_MSG(false, "Invalid image type={}", type); return {}; } @@ -87,7 +87,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { case 16: return VK_SAMPLE_COUNT_16_BIT; default: - UNREACHABLE_MSG("Invalid number of samples={}", num_samples); + ASSERT_MSG(false, "Invalid number of samples={}", num_samples); return VK_SAMPLE_COUNT_1_BIT; } } @@ -107,7 +107,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; default: - UNREACHABLE_MSG("Invalid surface type"); + ASSERT_MSG(false, "Invalid surface type"); } } if (info.storage) { @@ -179,7 +179,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { case VideoCore::Surface::SurfaceType::DepthStencil: return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; default: - UNREACHABLE_MSG("Invalid surface type"); + ASSERT_MSG(false, "Invalid surface type"); return VkImageAspectFlags{}; } } @@ -221,7 +221,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { case SwizzleSource::OneInt: return VK_COMPONENT_SWIZZLE_ONE; } - UNREACHABLE_MSG("Invalid swizzle={}", swizzle); + ASSERT_MSG(false, "Invalid swizzle={}", swizzle); return VK_COMPONENT_SWIZZLE_ZERO; } @@ -242,10 +242,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { case Shader::TextureType::ColorArrayCube: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; case Shader::TextureType::Buffer: - UNREACHABLE_MSG("Texture buffers can't be image views"); + ASSERT_MSG(false, "Texture buffers can't be image views"); return VK_IMAGE_VIEW_TYPE_1D; } - UNREACHABLE_MSG("Invalid image view type={}", type); + ASSERT_MSG(false, "Invalid image view type={}", type); return VK_IMAGE_VIEW_TYPE_2D; } @@ -269,10 +269,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { UNIMPLEMENTED_MSG("Rect image view"); return VK_IMAGE_VIEW_TYPE_2D; case VideoCommon::ImageViewType::Buffer: - UNREACHABLE_MSG("Texture buffers can't be image views"); + ASSERT_MSG(false, "Texture buffers can't be image views"); return VK_IMAGE_VIEW_TYPE_1D; } - UNREACHABLE_MSG("Invalid image view type={}", type); + ASSERT_MSG(false, "Invalid image view type={}", type); return VK_IMAGE_VIEW_TYPE_2D; } @@ -644,7 +644,7 @@ struct RangedBarrierRange { case Shader::ImageFormat::R32G32B32A32_UINT: return VK_FORMAT_R32G32B32A32_UINT; } - UNREACHABLE_MSG("Invalid image format={}", format); + ASSERT_MSG(false, "Invalid image format={}", format); return VK_FORMAT_R32_UINT; } @@ -1596,7 +1596,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI UNIMPLEMENTED(); break; case VideoCommon::ImageViewType::Buffer: - UNREACHABLE(); + ASSERT(false); break; } } @@ -1822,7 +1822,7 @@ void TextureCacheRuntime::AccelerateImageUpload( if (IsPixelFormatASTC(image.info.format)) { return astc_decoder_pass.Assemble(image, map, swizzles); } - UNREACHABLE(); + ASSERT(false); } } // namespace Vulkan diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index d469964f6..c4e923bbf 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -280,7 +280,7 @@ GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_, stage_index = 4; break; default: - UNREACHABLE_MSG("Invalid program={}", program); + ASSERT_MSG(false, "Invalid program={}", program); break; } const u64 local_size{sph.LocalMemorySize()}; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 5f428d35d..69c1b1e6d 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -29,7 +29,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t return SurfaceTarget::Texture2DArray; default: LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", texture_type); - UNREACHABLE(); + ASSERT(false); return SurfaceTarget::Texture2D; } } @@ -48,7 +48,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) { return true; default: LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target); - UNREACHABLE(); + ASSERT(false); return false; } } @@ -67,7 +67,7 @@ bool SurfaceTargetIsArray(SurfaceTarget target) { return true; default: LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target); - UNREACHABLE(); + ASSERT(false); return false; } } diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 86fea61ae..75e055592 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -147,7 +147,7 @@ enum class SurfaceTarget { TextureCubeArray, }; -constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ +constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ 1, // A8B8G8R8_UNORM 1, // A8B8G8R8_SNORM 1, // A8B8G8R8_SINT @@ -249,7 +249,7 @@ constexpr u32 DefaultBlockWidth(PixelFormat format) { return BLOCK_WIDTH_TABLE[static_cast<std::size_t>(format)]; } -constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ +constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ 1, // A8B8G8R8_UNORM 1, // A8B8G8R8_SNORM 1, // A8B8G8R8_SINT @@ -351,7 +351,7 @@ constexpr u32 DefaultBlockHeight(PixelFormat format) { return BLOCK_HEIGHT_TABLE[static_cast<std::size_t>(format)]; } -constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ +constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ 32, // A8B8G8R8_UNORM 32, // A8B8G8R8_SNORM 32, // A8B8G8R8_SINT diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 802939f6c..6c073ee57 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -94,7 +94,7 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept { resources.layers = 1; break; default: - UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); + ASSERT_MSG(false, "Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); break; } if (type != ImageType::Linear) { diff --git a/src/video_core/texture_cache/image_view_info.cpp b/src/video_core/texture_cache/image_view_info.cpp index 0cee5e45f..f47885147 100644 --- a/src/video_core/texture_cache/image_view_info.cpp +++ b/src/video_core/texture_cache/image_view_info.cpp @@ -71,7 +71,7 @@ ImageViewInfo::ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept range.extent.layers = config.Depth() * 6; break; default: - UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); + ASSERT_MSG(false, "Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); break; } } diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h index 91fec60bd..d552bccf0 100644 --- a/src/video_core/texture_cache/samples_helper.h +++ b/src/video_core/texture_cache/samples_helper.h @@ -23,7 +23,7 @@ namespace VideoCommon { case 16: return {2, 2}; } - UNREACHABLE_MSG("Invalid number of samples={}", num_samples); + ASSERT_MSG(false, "Invalid number of samples={}", num_samples); return {1, 1}; } @@ -47,7 +47,7 @@ namespace VideoCommon { case MsaaMode::Msaa4x4: return 16; } - UNREACHABLE_MSG("Invalid MSAA mode={}", static_cast<int>(msaa_mode)); + ASSERT_MSG(false, "Invalid MSAA mode={}", static_cast<int>(msaa_mode)); return 1; } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 6622d7818..cf3ca06a6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1485,14 +1485,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) { const auto page_it = selected_page_table.find(page); if (page_it == selected_page_table.end()) { - UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); return; } std::vector<ImageId>& image_ids = page_it->second; const auto vector_it = std::ranges::find(image_ids, image_id); if (vector_it == image_ids.end()) { - UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", - page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", + page << PAGE_BITS); return; } image_ids.erase(vector_it); @@ -1504,14 +1504,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); return; } std::vector<ImageMapId>& image_map_ids = page_it->second; const auto vector_it = std::ranges::find(image_map_ids, map_id); if (vector_it == image_map_ids.end()) { - UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", - page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", + page << PAGE_BITS); return; } image_map_ids.erase(vector_it); @@ -1532,7 +1532,7 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); return; } std::vector<ImageMapId>& image_map_ids = page_it->second; @@ -1616,15 +1616,15 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) { const GPUVAddr gpu_addr = image.gpu_addr; const auto alloc_it = image_allocs_table.find(gpu_addr); if (alloc_it == image_allocs_table.end()) { - UNREACHABLE_MSG("Trying to delete an image alloc that does not exist in address 0x{:x}", - gpu_addr); + ASSERT_MSG(false, "Trying to delete an image alloc that does not exist in address 0x{:x}", + gpu_addr); return; } const ImageAllocId alloc_id = alloc_it->second; std::vector<ImageId>& alloc_images = slot_image_allocs[alloc_id].images; const auto alloc_image_it = std::ranges::find(alloc_images, image_id); if (alloc_image_it == alloc_images.end()) { - UNREACHABLE_MSG("Trying to delete an image that does not exist"); + ASSERT_MSG(false, "Trying to delete an image that does not exist"); return; } ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked"); diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index c81343850..9b6b8527b 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -87,7 +87,7 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe BPP_CASE(16) #undef BPP_CASE default: - UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); } } @@ -209,7 +209,7 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 BPP_CASE(16) #undef BPP_CASE default: - UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); } } @@ -230,7 +230,7 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, BPP_CASE(16) #undef BPP_CASE default: - UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); } } @@ -253,7 +253,7 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt BPP_CASE(16) #undef BPP_CASE default: - UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); } } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7b2ca8046..11ce865a7 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -566,7 +566,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout; - if (khr_workgroup_memory_explicit_layout) { + if (khr_workgroup_memory_explicit_layout && is_shader_int16_supported) { workgroup_layout = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR, @@ -577,6 +577,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR .workgroupMemoryExplicitLayout16BitAccess = VK_TRUE, }; SetNext(next, workgroup_layout); + } else if (khr_workgroup_memory_explicit_layout) { + // TODO(lat9nq): Find a proper fix for this + LOG_WARNING(Render_Vulkan, "Disabling VK_KHR_workgroup_memory_explicit_layout due to a " + "yuzu bug when host driver does not support 16-bit integers"); + khr_workgroup_memory_explicit_layout = false; } VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR executable_properties; @@ -664,6 +669,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR const bool is_amd = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE; if (is_amd) { + // TODO(lat9nq): Add an upper bound when AMD fixes their VK_KHR_push_descriptor + const bool has_broken_push_descriptor = VK_VERSION_MAJOR(properties.driverVersion) == 2 && + VK_VERSION_MINOR(properties.driverVersion) == 0 && + VK_VERSION_PATCH(properties.driverVersion) >= 226; + if (khr_push_descriptor && has_broken_push_descriptor) { + LOG_WARNING( + Render_Vulkan, + "Disabling AMD driver 2.0.226 and later from broken VK_KHR_push_descriptor"); + khr_push_descriptor = false; + } + // AMD drivers need a higher amount of Sets per Pool in certain circunstances like in XC2. sets_per_pool = 96; // Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken. @@ -722,9 +738,10 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags // The wanted format is not supported by hardware, search for alternatives const VkFormat* alternatives = GetFormatAlternatives(wanted_format); if (alternatives == nullptr) { - UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host " - "hardware does not support it", - wanted_format, wanted_usage, format_type); + ASSERT_MSG(false, + "Format={} with usage={} and type={} has no defined alternatives and host " + "hardware does not support it", + wanted_format, wanted_usage, format_type); return wanted_format; } @@ -740,9 +757,10 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags } // No alternatives found, panic - UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and " - "doesn't support any of the alternatives", - wanted_format, wanted_usage, format_type); + ASSERT_MSG(false, + "Format={} with usage={} and type={} is not supported by the host hardware and " + "doesn't support any of the alternatives", + wanted_format, wanted_usage, format_type); return wanted_format; } diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp index a5dd33fb2..4eb3913ee 100644 --- a/src/video_core/vulkan_common/vulkan_library.cpp +++ b/src/video_core/vulkan_common/vulkan_library.cpp @@ -5,11 +5,13 @@ #include "common/dynamic_library.h" #include "common/fs/path_util.h" +#include "common/logging/log.h" #include "video_core/vulkan_common/vulkan_library.h" namespace Vulkan { Common::DynamicLibrary OpenLibrary() { + LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library"); Common::DynamicLibrary library; #ifdef __APPLE__ // Check if a path to a specific Vulkan library has been specified. @@ -22,9 +24,11 @@ Common::DynamicLibrary OpenLibrary() { } #else std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); + LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename); if (!library.Open(filename.c_str())) { // Android devices may not have libvulkan.so.1, only libvulkan.so. filename = Common::DynamicLibrary::GetVersionedFilename("vulkan"); + LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename); void(library.Open(filename.c_str())); } #endif diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index caae6dfdc..6442898bd 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -49,7 +49,7 @@ struct Range { return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } - UNREACHABLE_MSG("Invalid memory usage={}", usage); + ASSERT_MSG(false, "Invalid memory usage={}", usage); return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } @@ -325,7 +325,7 @@ VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, // Remove device local, if it's not supported by the requested resource return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } - UNREACHABLE_MSG("No compatible memory types found"); + ASSERT_MSG(false, "No compatible memory types found"); return 0; } @@ -349,7 +349,7 @@ bool IsHostVisible(MemoryUsage usage) noexcept { case MemoryUsage::Download: return true; } - UNREACHABLE_MSG("Invalid memory usage={}", usage); + ASSERT_MSG(false, "Invalid memory usage={}", usage); return false; } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index b1ea6075a..2ad98dcfe 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -325,6 +325,8 @@ const char* ToString(VkResult result) noexcept { return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; case VkResult::VK_RESULT_MAX_ENUM: return "VK_RESULT_MAX_ENUM"; + case VkResult::VK_ERROR_COMPRESSION_EXHAUSTED_EXT: + return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; } return "Unknown"; } diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 6215c914f..46faddb61 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -13,8 +13,8 @@ namespace WebService { namespace Telemetry = Common::Telemetry; struct TelemetryJson::Impl { - Impl(std::string host, std::string username, std::string token) - : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {} + Impl(std::string host_, std::string username_, std::string token_) + : host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {} nlohmann::json& TopSection() { return sections[static_cast<u8>(Telemetry::FieldType::None)]; diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 58b0c2f10..dce9772fe 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -30,10 +30,10 @@ constexpr std::array<const char, 1> API_VERSION{'1'}; constexpr std::size_t TIMEOUT_SECONDS = 30; struct Client::Impl { - Impl(std::string host, std::string username, std::string token) - : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} { + Impl(std::string host_, std::string username_, std::string token_) + : host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} { std::scoped_lock lock{jwt_cache.mutex}; - if (this->username == jwt_cache.username && this->token == jwt_cache.token) { + if (username == jwt_cache.username && token == jwt_cache.token) { jwt = jwt_cache.jwt; } } @@ -69,8 +69,8 @@ struct Client::Impl { */ WebResult GenericRequest(const std::string& method, const std::string& path, const std::string& data, const std::string& accept, - const std::string& jwt = "", const std::string& username = "", - const std::string& token = "") { + const std::string& jwt_ = "", const std::string& username_ = "", + const std::string& token_ = "") { if (cli == nullptr) { cli = std::make_unique<httplib::Client>(host.c_str()); } @@ -85,14 +85,14 @@ struct Client::Impl { cli->set_write_timeout(TIMEOUT_SECONDS); httplib::Headers params; - if (!jwt.empty()) { + if (!jwt_.empty()) { params = { - {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, + {std::string("Authorization"), fmt::format("Bearer {}", jwt_)}, }; - } else if (!username.empty()) { + } else if (!username_.empty()) { params = { - {std::string("x-username"), username}, - {std::string("x-token"), token}, + {std::string("x-username"), username_}, + {std::string("x-token"), token_}, }; } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 2ee21f751..242867a4f 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -30,6 +30,8 @@ add_executable(yuzu applets/qt_web_browser_scripts.h bootmanager.cpp bootmanager.h + check_vulkan.cpp + check_vulkan.h compatdb.ui compatibility_list.cpp compatibility_list.h @@ -187,7 +189,7 @@ if (ENABLE_QT_TRANSLATION) # Update source TS file if enabled if (GENERATE_QT_TRANSLATION) get_target_property(SRCS yuzu SOURCES) - qt5_create_translation(QM_FILES + qt_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts @@ -203,7 +205,7 @@ if (ENABLE_QT_TRANSLATION) list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts) # Compile TS files to QM files - qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) # Build a QRC file from the QM file list set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) @@ -215,7 +217,7 @@ if (ENABLE_QT_TRANSLATION) file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>") # Add the QRC file to package in all QM files - qt5_add_resources(LANGUAGES ${LANGUAGES_QRC}) + qt_add_resources(LANGUAGES ${LANGUAGES_QRC}) else() set(LANGUAGES) endif() @@ -236,18 +238,23 @@ if (APPLE) set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) elseif(WIN32) # compile as a win32 gui application instead of a console application - target_link_libraries(yuzu PRIVATE Qt5::WinMain) + if (QT_VERSION VERSION_GREATER 6) + target_link_libraries(yuzu PRIVATE Qt6::EntryPointPrivate) + else() + target_link_libraries(yuzu PRIVATE Qt5::WinMain) + endif() if(MSVC) + target_link_libraries(yuzu PRIVATE version.lib) set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") elseif(MINGW) - set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-mwindows") + set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") endif() endif() create_target_directory_groups(yuzu) target_link_libraries(yuzu PRIVATE common core input_common video_core) -target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets) +target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) @@ -255,7 +262,7 @@ if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - target_link_libraries(yuzu PRIVATE Qt5::DBus) + target_link_libraries(yuzu PRIVATE Qt::DBus) endif() target_compile_definitions(yuzu PRIVATE @@ -291,7 +298,7 @@ if (USE_DISCORD_PRESENCE) endif() if (YUZU_USE_QT_WEB_ENGINE) - target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets) + target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets) target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) endif () @@ -319,3 +326,7 @@ endif() if (NOT APPLE) target_compile_definitions(yuzu PRIVATE HAS_OPENGL) endif() + +if (ARCHITECTURE_x86_64) + target_link_libraries(yuzu PRIVATE dynarmic) +endif() diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index cbcef7b45..eeff54359 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -19,7 +19,11 @@ AboutDialog::AboutDialog(QWidget* parent) const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; ui->setupUi(this); - ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200)); + // Try and request the icon from Qt theme (Linux?) + const QIcon yuzu_logo = QIcon::fromTheme(QStringLiteral("org.yuzu_emu.yuzu")); + if (!yuzu_logo.isNull()) { + ui->labelLogo->setPixmap(yuzu_logo.pixmap(200)); + } ui->labelBuildInfo->setText( ui->labelBuildInfo->text().arg(QString::fromStdString(yuzu_build_version), QString::fromUtf8(Common::g_build_date).left(10))); diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index 2f7ddc7f3..1dd7b74bf 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -26,8 +26,20 @@ <verstretch>0</verstretch> </sizepolicy> </property> + <property name="maximumSize"> + <size> + <width>200</width> + <height>200</height> + </size> + </property> <property name="text"> - <string><html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html></string> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../dist/qt_themes/default/default.qrc">:/icons/default/256x256/yuzu.png</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> </property> </widget> </item> @@ -152,7 +164,7 @@ p, li { white-space: pre-wrap; } </layout> </widget> <resources> - <include location="../../dist/icons/icons.qrc"/> + <include location="../../dist/qt_themes_default/default/default.qrc"/> </resources> <connections> <connection> diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index c924cb0cb..8be311fcb 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -631,7 +631,7 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { switch (max_supported_players) { case 0: default: - UNREACHABLE(); + ASSERT(false); return; case 1: ui->widgetSpacer->hide(); diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index d3cf0b43b..e8b217d90 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -411,11 +411,11 @@ void QtSoftwareKeyboardDialog::ShowTextCheckDialog( break; } - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); + const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText() + : ui->line_edit_osk->text(); + auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size()); - emit SubmitNormalText(SwkbdResult::Ok, std::move(text), true); + emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str), true); break; } } @@ -562,7 +562,7 @@ void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { return; } - InlineTextInsertString(entered_text.toStdU16String()); + InlineTextInsertString(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size())); } void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { @@ -1119,11 +1119,11 @@ void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) } if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); + const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText() + : ui->line_edit_osk->text(); + auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size()); - emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); + emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str)); return; } @@ -1189,7 +1189,8 @@ void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) return; } - InlineTextInsertString(button->text().toStdU16String()); + const auto button_text = button->text(); + InlineTextInsertString(Common::U16StringFromBuffer(button_text.utf16(), button_text.size())); // Revert the keyboard to lowercase if the shift key is active. if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { @@ -1282,11 +1283,11 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button if (is_inline) { emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); } else { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); + const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText() + : ui->line_edit_osk->text(); + auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size()); - emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); + emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_str)); } break; case Core::HID::NpadButton::Y: diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index a1b819ae0..cbe4e2daa 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -50,6 +50,7 @@ void EmuThread::run() { auto& gpu = system.GPU(); auto stop_token = stop_source.get_token(); + bool debugger_should_start = system.DebuggerEnabled(); system.RegisterHostThread(); @@ -89,6 +90,12 @@ void EmuThread::run() { this->SetRunning(false); emit ErrorThrown(result, system.GetStatusDetails()); } + + if (debugger_should_start) { + system.InitializeDebugger(); + debugger_should_start = false; + } + running_wait.Wait(); result = system.Pause(); if (result != Core::SystemResultStatus::Success) { @@ -102,11 +109,9 @@ void EmuThread::run() { was_active = true; emit DebugModeEntered(); } - } else if (exec_step) { - UNIMPLEMENTED(); } else { std::unique_lock lock{running_mutex}; - running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; }); + running_cv.wait(lock, stop_token, [this] { return IsRunning(); }); } } @@ -122,7 +127,7 @@ void EmuThread::run() { class OpenGLSharedContext : public Core::Frontend::GraphicsContext { public: /// Create the original context that should be shared from - explicit OpenGLSharedContext(QSurface* surface) : surface(surface) { + explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { QSurfaceFormat format; format.setVersion(4, 6); format.setProfile(QSurfaceFormat::CompatibilityProfile); @@ -359,9 +364,9 @@ void GRenderWindow::RestoreGeometry() { QWidget::restoreGeometry(geometry); } -void GRenderWindow::restoreGeometry(const QByteArray& geometry) { +void GRenderWindow::restoreGeometry(const QByteArray& geometry_) { // Make sure users of this class don't need to deal with backing up the geometry themselves - QWidget::restoreGeometry(geometry); + QWidget::restoreGeometry(geometry_); BackupGeometry(); } @@ -747,7 +752,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y); if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { - QCursor::setPos(mapToGlobal({center_x, center_y})); + QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); } emit MouseActivity(); @@ -772,65 +777,25 @@ void GRenderWindow::wheelEvent(QWheelEvent* event) { void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints(); for (const auto& touch_point : touch_points) { - if (!TouchUpdate(touch_point)) { - TouchStart(touch_point); - } + const auto [x, y] = ScaleTouch(touch_point.pos()); + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, touch_point.id()); } } void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints(); + input_subsystem->GetTouchScreen()->ClearActiveFlag(); for (const auto& touch_point : touch_points) { - if (!TouchUpdate(touch_point)) { - TouchStart(touch_point); - } - } - // Release all inactive points - for (std::size_t id = 0; id < touch_ids.size(); ++id) { - if (!TouchExist(touch_ids[id], touch_points)) { - touch_ids[id] = 0; - input_subsystem->GetTouchScreen()->TouchReleased(id); - } + const auto [x, y] = ScaleTouch(touch_point.pos()); + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, touch_point.id()); } + input_subsystem->GetTouchScreen()->ReleaseInactiveTouch(); } void GRenderWindow::TouchEndEvent() { - for (std::size_t id = 0; id < touch_ids.size(); ++id) { - if (touch_ids[id] != 0) { - touch_ids[id] = 0; - input_subsystem->GetTouchScreen()->TouchReleased(id); - } - } -} - -void GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) { - for (std::size_t id = 0; id < touch_ids.size(); ++id) { - if (touch_ids[id] == 0) { - touch_ids[id] = touch_point.id() + 1; - const auto [x, y] = ScaleTouch(touch_point.pos()); - const auto [touch_x, touch_y] = MapToTouchScreen(x, y); - input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id); - } - } -} - -bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) { - for (std::size_t id = 0; id < touch_ids.size(); ++id) { - if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) { - const auto [x, y] = ScaleTouch(touch_point.pos()); - const auto [touch_x, touch_y] = MapToTouchScreen(x, y); - input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id); - return true; - } - } - return false; -} - -bool GRenderWindow::TouchExist(std::size_t id, - const QList<QTouchEvent::TouchPoint>& touch_points) const { - return std::any_of(touch_points.begin(), touch_points.end(), [id](const auto& point) { - return id == static_cast<std::size_t>(point.id() + 1); - }); + input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } bool GRenderWindow::event(QEvent* event) { @@ -1049,8 +1014,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { return unsupported_ext; } -void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { - this->emu_thread = emu_thread; +void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) { + emu_thread = emu_thread_; } void GRenderWindow::OnEmulationStopping() { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 4b0ce0293..81fe52c0e 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -10,6 +10,7 @@ #include <mutex> #include <QImage> +#include <QStringList> #include <QThread> #include <QTouchEvent> #include <QWidget> @@ -20,7 +21,6 @@ class GRenderWindow; class GMainWindow; class QKeyEvent; -class QStringList; namespace Core { enum class SystemResultStatus : u32; @@ -55,22 +55,13 @@ public: void run() override; /** - * Steps the emulation thread by a single CPU instruction (if the CPU is not already running) - * @note This function is thread-safe - */ - void ExecStep() { - exec_step = true; - running_cv.notify_all(); - } - - /** * Sets whether the emulation thread is running or not - * @param running Boolean value, set the emulation thread to running if true + * @param running_ Boolean value, set the emulation thread to running if true * @note This function is thread-safe */ - void SetRunning(bool running) { + void SetRunning(bool running_) { std::unique_lock lock{running_mutex}; - this->running = running; + running = running_; lock.unlock(); running_cv.notify_all(); if (!running) { @@ -99,7 +90,6 @@ public: } private: - bool exec_step = false; bool running = false; std::stop_source stop_source; std::mutex running_mutex; @@ -148,8 +138,8 @@ public: void BackupGeometry(); void RestoreGeometry(); - void restoreGeometry(const QByteArray& geometry); // overridden - QByteArray saveGeometry(); // overridden + void restoreGeometry(const QByteArray& geometry_); // overridden + QByteArray saveGeometry(); // overridden qreal windowPixelRatio() const; @@ -199,7 +189,7 @@ public: void Exit(); public slots: - void OnEmulationStarting(EmuThread* emu_thread); + void OnEmulationStarting(EmuThread* emu_thread_); void OnEmulationStopping(); void OnFramebufferSizeChanged(); @@ -217,10 +207,6 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); - void TouchStart(const QTouchEvent::TouchPoint& touch_point); - bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point); - bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const; - void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; bool InitializeOpenGL(); @@ -246,8 +232,6 @@ private: bool first_frame = false; InputCommon::TasInput::TasState last_tas_state; - std::array<std::size_t, 16> touch_ids{}; - Core::System& system; protected: diff --git a/src/yuzu/check_vulkan.cpp b/src/yuzu/check_vulkan.cpp new file mode 100644 index 000000000..e6d66ab34 --- /dev/null +++ b/src/yuzu/check_vulkan.cpp @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/vulkan_common/vulkan_wrapper.h" + +#include <filesystem> +#include <fstream> +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_library.h" +#include "yuzu/check_vulkan.h" +#include "yuzu/uisettings.h" + +constexpr char TEMP_FILE_NAME[] = "vulkan_check"; + +bool CheckVulkan() { + if (UISettings::values.has_broken_vulkan) { + return true; + } + + LOG_DEBUG(Frontend, "Checking presence of Vulkan"); + + const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir); + const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME; + + if (std::filesystem::exists(temp_file_loc)) { + LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization"); + + UISettings::values.has_broken_vulkan = true; + std::filesystem::remove(temp_file_loc); + return false; + } + + std::ofstream temp_file_handle(temp_file_loc); + temp_file_handle.close(); + + try { + Vulkan::vk::InstanceDispatch dld; + const Common::DynamicLibrary library = Vulkan::OpenLibrary(); + const Vulkan::vk::Instance instance = + Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0); + + } catch (const Vulkan::vk::Exception& exception) { + LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what()); + // Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the + // application, not when we can handle it. + } + + std::filesystem::remove(temp_file_loc); + return true; +} diff --git a/src/yuzu/check_vulkan.h b/src/yuzu/check_vulkan.h new file mode 100644 index 000000000..e4ea93582 --- /dev/null +++ b/src/yuzu/check_vulkan.h @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +bool CheckVulkan(); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index ac26b885b..9df4752be 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -71,28 +71,28 @@ const std::array<int, 2> Config::default_ringcon_analogs{{ // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ - {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, - {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, - {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, - {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Change Adapting Filter"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, - {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, - {QStringLiteral("Change GPU Accuracy"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}}, - {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, - {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, - {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, - {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Load/Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, - {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, }}; // clang-format on @@ -525,6 +525,9 @@ void Config::ReadDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini Settings::values.record_frame_times = qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); + + ReadBasicSetting(Settings::values.use_gdbstub); + ReadBasicSetting(Settings::values.gdbstub_port); ReadBasicSetting(Settings::values.program_args); ReadBasicSetting(Settings::values.dump_exefs); ReadBasicSetting(Settings::values.dump_nso); @@ -679,6 +682,12 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); + if (!global && UISettings::values.has_broken_vulkan && + Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan && + !Settings::values.renderer_backend.UsingGlobal()) { + Settings::values.renderer_backend.SetGlobal(true); + } + if (global) { ReadBasicSetting(Settings::values.renderer_debug); ReadBasicSetting(Settings::values.renderer_shader_feedback); @@ -798,6 +807,7 @@ void Config::ReadUIValues() { ReadBasicSetting(UISettings::values.pause_when_in_background); ReadBasicSetting(UISettings::values.mute_when_in_background); ReadBasicSetting(UISettings::values.hide_mouse); + ReadBasicSetting(UISettings::values.has_broken_vulkan); ReadBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); @@ -1095,6 +1105,8 @@ void Config::SaveDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); + WriteBasicSetting(Settings::values.use_gdbstub); + WriteBasicSetting(Settings::values.gdbstub_port); WriteBasicSetting(Settings::values.program_args); WriteBasicSetting(Settings::values.dump_exefs); WriteBasicSetting(Settings::values.dump_nso); @@ -1343,6 +1355,7 @@ void Config::SaveUIValues() { WriteBasicSetting(UISettings::values.pause_when_in_background); WriteBasicSetting(UISettings::values.mute_when_in_background); WriteBasicSetting(UISettings::values.hide_mouse); + WriteBasicSetting(UISettings::values.has_broken_vulkan); WriteBasicSetting(UISettings::values.disable_web_applet); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index bd50f7a68..343d2aee1 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -24,13 +24,18 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); + + connect(ui->toggle_gdbstub, &QCheckBox::toggled, + [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); } ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::SetConfiguration() { const bool runtime_lock = !system.IsPoweredOn(); - + ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue()); + ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue()); + ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue()); ui->toggle_console->setEnabled(runtime_lock); ui->toggle_console->setChecked(UISettings::values.show_console.GetValue()); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue())); @@ -53,6 +58,8 @@ void ConfigureDebug::SetConfiguration() { ui->enable_nsight_aftermath->setChecked(Settings::values.enable_nsight_aftermath.GetValue()); ui->dump_shaders->setEnabled(runtime_lock); ui->dump_shaders->setChecked(Settings::values.dump_shaders.GetValue()); + ui->dump_macros->setEnabled(runtime_lock); + ui->dump_macros->setChecked(Settings::values.dump_macros.GetValue()); ui->disable_macro_jit->setEnabled(runtime_lock); ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit.GetValue()); ui->disable_loop_safety_checks->setEnabled(runtime_lock); @@ -69,6 +76,8 @@ void ConfigureDebug::SetConfiguration() { } void ConfigureDebug::ApplyConfiguration() { + Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); + Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); @@ -83,6 +92,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); Settings::values.dump_shaders = ui->dump_shaders->isChecked(); + Settings::values.dump_macros = ui->dump_macros->isChecked(); Settings::values.disable_shader_loop_safety_checks = ui->disable_loop_safety_checks->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index c1d90d588..1152fa6c6 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -3,6 +3,60 @@ <class>ConfigureDebug</class> <widget class="QWidget" name="ConfigureDebug"> <layout class="QVBoxLayout" name="verticalLayout_1"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Debugger</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <item> + <widget class="QCheckBox" name="toggle_gdbstub"> + <property name="text"> + <string>Enable GDB Stub</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="gdbport_spinbox"> + <property name="minimum"> + <number>1024</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> @@ -118,6 +172,19 @@ </property> </widget> </item> + <item row="0" column="2"> + <widget class="QCheckBox" name="dump_macros"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string>When checked, it will dump all the macro programs of the GPU</string> + </property> + <property name="text"> + <string>Dump Maxwell Macros</string> + </property> + </widget> + </item> <item row="0" column="1"> <widget class="QCheckBox" name="disable_macro_jit"> <property name="enabled"> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index b415a1cc4..e99657bd6 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -27,12 +27,11 @@ #include "yuzu/hotkeys.h" #include "yuzu/uisettings.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, Core::System& system_) - : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, - registry(registry), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, - this)}, + : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_}, + system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, this)}, cpu_tab{std::make_unique<ConfigureCpu>(system_, this)}, debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)}, filesystem_tab{std::make_unique<ConfigureFilesystem>(this)}, diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 32ddfd4e0..12cf25daf 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -40,7 +40,7 @@ class ConfigureDialog : public QDialog { Q_OBJECT public: - explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, + explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, Core::System& system_); ~ConfigureDialog() override; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 2f1435b10..85f34dc35 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -17,6 +17,7 @@ #include "video_core/vulkan_common/vulkan_library.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" +#include "yuzu/uisettings.h" ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent) : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} { @@ -57,6 +58,24 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren UpdateBackgroundColorButton(new_bg_color); }); + connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] { + UISettings::values.has_broken_vulkan = false; + + if (RetrieveVulkanDevices()) { + ui->api->setEnabled(true); + ui->button_check_vulkan->hide(); + + for (const auto& device : vulkan_devices) { + ui->device->addItem(device); + } + } else { + UISettings::values.has_broken_vulkan = true; + } + }); + + ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue()); + ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue()); + ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); } @@ -296,7 +315,7 @@ void ConfigureGraphics::UpdateAPILayout() { vulkan_device = Settings::values.vulkan_device.GetValue(true); shader_backend = Settings::values.shader_backend.GetValue(true); ui->device_widget->setEnabled(false); - ui->backend_widget->setEnabled(false); + ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue()); } else { vulkan_device = Settings::values.vulkan_device.GetValue(); shader_backend = Settings::values.shader_backend.GetValue(); @@ -318,7 +337,11 @@ void ConfigureGraphics::UpdateAPILayout() { } } -void ConfigureGraphics::RetrieveVulkanDevices() try { +bool ConfigureGraphics::RetrieveVulkanDevices() try { + if (UISettings::values.has_broken_vulkan) { + return false; + } + using namespace Vulkan; vk::InstanceDispatch dld; @@ -333,8 +356,10 @@ void ConfigureGraphics::RetrieveVulkanDevices() try { vulkan_devices.push_back(QString::fromStdString(name)); } + return true; } catch (const Vulkan::vk::Exception& exception) { LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); + return false; } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { @@ -415,4 +440,11 @@ void ConfigureGraphics::SetupPerGameUI() { ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); + + if (UISettings::values.has_broken_vulkan) { + ui->backend_widget->setEnabled(true); + ConfigurationShared::SetColoredComboBox( + ui->backend, ui->backend_widget, + static_cast<int>(Settings::values.shader_backend.GetValue(true))); + } } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 1b101c940..8438f0187 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -41,7 +41,7 @@ private: void UpdateDeviceSelection(int device); void UpdateShaderBackendSelection(int backend); - void RetrieveVulkanDevices(); + bool RetrieveVulkanDevices(); void SetupPerGameUI(); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 74f0e0b79..2f94c94bc 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>437</width> - <height>482</height> + <width>471</width> + <height>759</height> </rect> </property> <property name="windowTitle"> @@ -171,11 +171,11 @@ </widget> </item> <item> - <widget class="QCheckBox" name="accelerate_astc"> - <property name="text"> - <string>Accelerate ASTC texture decoding</string> - </property> - </widget> + <widget class="QCheckBox" name="accelerate_astc"> + <property name="text"> + <string>Accelerate ASTC texture decoding</string> + </property> + </widget> </item> <item> <widget class="QWidget" name="nvdec_emulation_widget" native="true"> @@ -438,43 +438,43 @@ </widget> </item> <item> - <widget class="QWidget" name="anti_aliasing_layout" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> + <widget class="QWidget" name="anti_aliasing_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="anti_aliasing_label"> + <property name="text"> + <string>Anti-Aliasing Method:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="anti_aliasing_combobox"> + <item> + <property name="text"> + <string>None</string> </property> - <property name="bottomMargin"> - <number>0</number> + </item> + <item> + <property name="text"> + <string>FXAA</string> </property> - <item> - <widget class="QLabel" name="anti_aliasing_label"> - <property name="text"> - <string>Anti-Aliasing Method:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="anti_aliasing_combobox"> - <item> - <property name="text"> - <string>None</string> - </property> - </item> - <item> - <property name="text"> - <string>FXAA</string> - </property> - </item> - </widget> - </item> - </layout> - </widget> + </item> + </widget> + </item> + </layout> + </widget> </item> <item> <widget class="QWidget" name="bg_layout" native="true"> @@ -574,6 +574,13 @@ </property> </spacer> </item> + <item> + <widget class="QPushButton" name="button_check_vulkan"> + <property name="text"> + <string>Check for Working Vulkan</string> + </property> + </widget> + </item> </layout> </widget> <resources/> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 6679e9c53..edf0893c4 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -61,14 +61,18 @@ ConfigureHotkeys::~ConfigureHotkeys() = default; void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { for (const auto& group : registry.hotkey_groups) { - auto* parent_item = new QStandardItem(group.first); + auto* parent_item = + new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(group.first))); parent_item->setEditable(false); + parent_item->setData(group.first); for (const auto& hotkey : group.second) { - auto* action = new QStandardItem(hotkey.first); + auto* action = + new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(hotkey.first))); auto* keyseq = new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq); action->setEditable(false); + action->setData(hotkey.first); keyseq->setEditable(false); controller_keyseq->setEditable(false); parent_item->appendRow({action, keyseq, controller_keyseq}); @@ -93,6 +97,16 @@ void ConfigureHotkeys::RetranslateUI() { ui->retranslateUi(this); model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")}); + for (int key_id = 0; key_id < model->rowCount(); key_id++) { + QStandardItem* parent = model->item(key_id, 0); + parent->setText( + QCoreApplication::translate("Hotkeys", qPrintable(parent->data().toString()))); + for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { + QStandardItem* action = parent->child(key_column_id, name_column); + action->setText( + QCoreApplication::translate("Hotkeys", qPrintable(action->data().toString()))); + } + } } void ConfigureHotkeys::Configure(QModelIndex index) { @@ -273,10 +287,10 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { const QStandardItem* controller_keyseq = parent->child(key_column_id, controller_column); for (auto& [group, sub_actions] : registry.hotkey_groups) { - if (group != parent->text()) + if (group != parent->data()) continue; for (auto& [action_name, hotkey] : sub_actions) { - if (action_name != action->text()) + if (action_name != action->data()) continue; hotkey.keyseq = QKeySequence(keyseq->text()); hotkey.controller_keyseq = controller_keyseq->text(); diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 1c05dd0f3..f3be9a374 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -264,15 +264,16 @@ QString ConfigureInputPlayer::AnalogToText(const Common::ParamPackage& param, return QObject::tr("[unknown]"); } -ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, - QWidget* bottom_row, +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index_, + QWidget* bottom_row_, InputCommon::InputSubsystem* input_subsystem_, InputProfiles* profiles_, Core::HID::HIDCore& hid_core_, - bool is_powered_on_, bool debug) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), - debug(debug), is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, - profiles(profiles_), timeout_timer(std::make_unique<QTimer>()), - poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row), hid_core{hid_core_} { + bool is_powered_on_, bool debug_) + : QWidget(parent), + ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index{player_index_}, debug{debug_}, + is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique<QTimer>()), + poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} { if (player_index == 0) { auto* emulated_controller_p1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); @@ -696,39 +697,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i UpdateControllerEnabledButtons(); UpdateControllerButtonNames(); UpdateMotionButtons(); - connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), - [this, player_index](int) { - UpdateControllerAvailableButtons(); - UpdateControllerEnabledButtons(); - UpdateControllerButtonNames(); - UpdateMotionButtons(); - const Core::HID::NpadStyleIndex type = - GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); - - if (player_index == 0) { - auto* emulated_controller_p1 = - hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); - auto* emulated_controller_handheld = - hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - bool is_connected = emulated_controller->IsConnected(true); - - emulated_controller_p1->SetNpadStyleIndex(type); - emulated_controller_handheld->SetNpadStyleIndex(type); - if (is_connected) { - if (type == Core::HID::NpadStyleIndex::Handheld) { - emulated_controller_p1->Disconnect(); - emulated_controller_handheld->Connect(true); - emulated_controller = emulated_controller_handheld; - } else { - emulated_controller_handheld->Disconnect(); - emulated_controller_p1->Connect(true); - emulated_controller = emulated_controller_p1; - } - } - ui->controllerFrame->SetController(emulated_controller); + connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { + UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); + const Core::HID::NpadStyleIndex type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + + if (player_index == 0) { + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_handheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + bool is_connected = emulated_controller->IsConnected(true); + + emulated_controller_p1->SetNpadStyleIndex(type); + emulated_controller_handheld->SetNpadStyleIndex(type); + if (is_connected) { + if (type == Core::HID::NpadStyleIndex::Handheld) { + emulated_controller_p1->Disconnect(); + emulated_controller_handheld->Connect(true); + emulated_controller = emulated_controller_handheld; + } else { + emulated_controller_handheld->Disconnect(); + emulated_controller_p1->Connect(true); + emulated_controller = emulated_controller_p1; } - emulated_controller->SetNpadStyleIndex(type); - }); + } + ui->controllerFrame->SetController(emulated_controller); + } + emulated_controller->SetNpadStyleIndex(type); + }); connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this, &ConfigureInputPlayer::UpdateMappingWithDefaults); diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index 27559c37b..c313b0919 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -151,6 +151,8 @@ void ConfigureMotionTouch::ConnectEvents() { &ConfigureMotionTouch::OnConfigureTouchCalibration); connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, &ConfigureMotionTouch::OnConfigureTouchFromButton); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &ConfigureMotionTouch::ApplyConfiguration); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { if (CanCloseDialog()) { reject(); diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui index c75a84ae4..0237fae54 100644 --- a/src/yuzu/configuration/configure_motion_touch.ui +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -293,22 +293,5 @@ </layout> </widget> <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ConfigureMotionTouch</receiver> - <slot>ApplyConfiguration()</slot> - <hints> - <hint type="sourcelabel"> - <x>20</x> - <y>20</y> - </hint> - <hint type="destinationlabel"> - <x>20</x> - <y>20</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 54b3fe150..af8343b2e 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -35,10 +35,10 @@ #include "yuzu/uisettings.h" #include "yuzu/util/util.h" -ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id, const std::string& file_name, +ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, Core::System& system_) - : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), - title_id(title_id), system{system_} { + : QDialog(parent), + ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} { const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); @@ -116,8 +116,8 @@ void ConfigurePerGame::HandleApplyButtonClicked() { ApplyConfiguration(); } -void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) { - this->file = std::move(file); +void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) { + file = std::move(file_); LoadConfiguration(); } diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index e6dc05546..17a98a0f3 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -39,14 +39,14 @@ class ConfigurePerGame : public QDialog { public: // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 - explicit ConfigurePerGame(QWidget* parent, u64 title_id, const std::string& file_name, + explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, Core::System& system_); ~ConfigurePerGame() override; /// Save all button configurations to settings file void ApplyConfiguration(); - void LoadFromFile(FileSys::VirtualFile file); + void LoadFromFile(FileSys::VirtualFile file_); private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 7893a85bb..4906997ab 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -89,8 +89,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { Settings::values.disabled_addons[title_id] = disabled_addons; } -void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) { - this->file = std::move(file); +void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) { + file = std::move(file_); LoadConfiguration(); } diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 24b017494..14690fba8 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -35,7 +35,7 @@ public: /// Save all button configurations to settings file void ApplyConfiguration(); - void LoadFromFile(FileSys::VirtualFile file); + void LoadFromFile(FileSys::VirtualFile file_); void SetTitleId(u64 id); diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index 4fcc22b7a..688c2dd38 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -165,10 +165,10 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, const std::string invert_str = invert_value ? "+" : "-"; param.Set("invert_x", invert_str); emulated_device->SetRingParam(param); - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; - ++sub_button_id) { - analog_map_buttons[sub_button_id]->setText( - AnalogToText(param, analog_sub_buttons[sub_button_id])); + for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id2) { + analog_map_buttons[sub_button_id2]->setText( + AnalogToText(param, analog_sub_buttons[sub_button_id2])); } }); context_menu.exec( diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 19aa589f9..ecebb0fb7 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -130,8 +130,7 @@ void ConfigureSystem::ApplyConfiguration() { // Guard if during game and set to game-specific value if (Settings::values.rng_seed.UsingGlobal()) { if (ui->rng_seed_checkbox->isChecked()) { - Settings::values.rng_seed.SetValue( - ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16)); } else { Settings::values.rng_seed.SetValue(std::nullopt); } @@ -142,8 +141,7 @@ void ConfigureSystem::ApplyConfiguration() { case ConfigurationShared::CheckState::Off: Settings::values.rng_seed.SetGlobal(false); if (ui->rng_seed_checkbox->isChecked()) { - Settings::values.rng_seed.SetValue( - ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16)); } else { Settings::values.rng_seed.SetValue(std::nullopt); } diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp index c17da6fd1..06cc452c3 100644 --- a/src/yuzu/configuration/configure_touch_from_button.cpp +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -68,10 +68,10 @@ static QString ButtonToText(const Common::ParamPackage& param) { } ConfigureTouchFromButton::ConfigureTouchFromButton( - QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps, + QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps_, InputCommon::InputSubsystem* input_subsystem_, const int default_index) : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), - touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index), + touch_maps{touch_maps_}, input_subsystem{input_subsystem_}, selected_index{default_index}, timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { ui->setupUi(this); binding_list_model = new QStandardItemModel(0, 3, this); diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h index e1400481a..b8c55db66 100644 --- a/src/yuzu/configuration/configure_touch_from_button.h +++ b/src/yuzu/configuration/configure_touch_from_button.h @@ -37,7 +37,7 @@ class ConfigureTouchFromButton : public QDialog { public: explicit ConfigureTouchFromButton(QWidget* parent, - const std::vector<Settings::TouchFromButtonMap>& touch_maps, + const std::vector<Settings::TouchFromButtonMap>& touch_maps_, InputCommon::InputSubsystem* input_subsystem_, int default_index = 0); ~ConfigureTouchFromButton() override; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 8f486a131..0ea31cd33 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -113,9 +113,9 @@ QString WaitTreeText::GetText() const { return text; } -WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::KHandleTable& handle_table, +WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table, Core::System& system_) - : mutex_address(mutex_address), system{system_} { + : mutex_address{mutex_address_}, system{system_} { mutex_value = system.Memory().Read32(mutex_address); owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Svc::HandleWaitMask); owner = handle_table.GetObject<Kernel::KThread>(owner_handle).GetPointerUnsafe(); @@ -140,8 +140,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() cons return list; } -WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread, Core::System& system_) - : thread(thread), system{system_} {} +WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_) + : thread{thread_}, system{system_} {} WaitTreeCallstack::~WaitTreeCallstack() = default; QString WaitTreeCallstack::GetText() const { @@ -171,8 +171,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons } WaitTreeSynchronizationObject::WaitTreeSynchronizationObject( - const Kernel::KSynchronizationObject& o, Core::System& system_) - : object(o), system{system_} {} + const Kernel::KSynchronizationObject& object_, Core::System& system_) + : object{object_}, system{system_} {} WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default; WaitTreeExpandableItem::WaitTreeExpandableItem() = default; @@ -380,8 +380,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { return list; } -WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object, Core::System& system_) - : WaitTreeSynchronizationObject(object, system_) {} +WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_) + : WaitTreeSynchronizationObject(object_, system_) {} WaitTreeEvent::~WaitTreeEvent() = default; WaitTreeThreadList::WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_) diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 4a36dfc48..f21b9f467 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -78,7 +78,7 @@ public: class WaitTreeMutexInfo : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeMutexInfo(VAddr mutex_address, const Kernel::KHandleTable& handle_table, + explicit WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table, Core::System& system_); ~WaitTreeMutexInfo() override; @@ -97,7 +97,7 @@ private: class WaitTreeCallstack : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeCallstack(const Kernel::KThread& thread, Core::System& system_); + explicit WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_); ~WaitTreeCallstack() override; QString GetText() const override; @@ -112,7 +112,7 @@ private: class WaitTreeSynchronizationObject : public WaitTreeExpandableItem { Q_OBJECT public: - explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object, + explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object_, Core::System& system_); ~WaitTreeSynchronizationObject() override; @@ -162,7 +162,7 @@ private: class WaitTreeEvent : public WaitTreeSynchronizationObject { Q_OBJECT public: - explicit WaitTreeEvent(const Kernel::KReadableEvent& object, Core::System& system_); + explicit WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_); ~WaitTreeEvent() override; }; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 4a6d74a7e..05d309827 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -28,8 +28,8 @@ #include "yuzu/uisettings.h" #include "yuzu/util/controller_navigation.h" -GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent) - : QObject(parent), gamelist{gamelist} {} +GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) + : QObject(parent), gamelist{gamelist_} {} // EventFilter in order to process systemkeys while editing the searchfield bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { @@ -80,9 +80,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve return QObject::eventFilter(obj, event); } -void GameListSearchField::setFilterResult(int visible, int total) { - this->visible = visible; - this->total = total; +void GameListSearchField::setFilterResult(int visible_, int total_) { + visible = visible_; + total = total_; label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } @@ -309,9 +309,9 @@ void GameList::OnFilterCloseClicked() { main_window->filterBarSetChecked(false); } -GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider, +GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, Core::System& system_, GMainWindow* parent) - : QWidget{parent}, vfs(std::move(vfs)), provider(provider), system{system_} { + : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -483,7 +483,7 @@ void GameList::DonePopulating(const QStringList& watch_list) { // Also artificially caps the watcher to a certain number of directories constexpr int LIMIT_WATCH_DIRECTORIES = 5000; constexpr int SLICE_SIZE = 25; - int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); + int len = std::min(static_cast<int>(watch_list.size()), LIMIT_WATCH_DIRECTORIES); for (int i = 0; i < len; i += SLICE_SIZE) { watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); QCoreApplication::processEvents(); @@ -870,7 +870,7 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} layout->setAlignment(Qt::AlignCenter); image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); - text->setText(tr("Double-click to add a new folder to the game list")); + RetranslateUI(); QFont font = text->font(); font.setPointSize(20); text->setFont(font); @@ -891,3 +891,15 @@ void GameListPlaceholder::onUpdateThemedIcons() { void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { emit GameListPlaceholder::AddDirectory(); } + +void GameListPlaceholder::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameListPlaceholder::RetranslateUI() { + text->setText(tr("Double-click to add a new folder to the game list")); +} diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index d19dbe4b0..bc36d015a 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -67,8 +67,8 @@ public: COLUMN_COUNT, // Number of columns }; - explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, - FileSys::ManualContentProvider* provider, Core::System& system_, + explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, + FileSys::ManualContentProvider* provider_, Core::System& system_, GMainWindow* parent = nullptr); ~GameList() override; @@ -166,6 +166,9 @@ protected: void mouseDoubleClickEvent(QMouseEvent* event) override; private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + QVBoxLayout* layout = nullptr; QLabel* image = nullptr; QLabel* text = nullptr; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index f2a986ed8..cd7d63536 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -225,8 +225,8 @@ public: static constexpr int GameDirRole = Qt::UserRole + 2; explicit GameListDir(UISettings::GameDir& directory, - GameListItemType dir_type = GameListItemType::CustomDir) - : dir_type{dir_type} { + GameListItemType dir_type_ = GameListItemType::CustomDir) + : dir_type{dir_type_} { setData(type(), TypeRole); UISettings::GameDir* game_dir = &directory; @@ -348,7 +348,7 @@ public: explicit GameListSearchField(GameList* parent = nullptr); QString filterText() const; - void setFilterResult(int visible, int total); + void setFilterResult(int visible_, int total_); void clear(); void setFocus(); @@ -356,7 +356,7 @@ public: private: class KeyReleaseEater : public QObject { public: - explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr); + explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); private: GameList* gamelist = nullptr; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index ca1899b5c..63326968b 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -223,12 +223,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri } } // Anonymous namespace -GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, - FileSys::ManualContentProvider* provider, - QVector<UISettings::GameDir>& game_dirs, - const CompatibilityList& compatibility_list, Core::System& system_) - : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs), - compatibility_list(compatibility_list), system{system_} {} +GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, + FileSys::ManualContentProvider* provider_, + QVector<UISettings::GameDir>& game_dirs_, + const CompatibilityList& compatibility_list_, Core::System& system_) + : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, + compatibility_list{compatibility_list_}, system{system_} {} GameListWorker::~GameListWorker() = default; diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 622d241fb..24a4e92c3 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -33,10 +33,10 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, - FileSys::ManualContentProvider* provider, - QVector<UISettings::GameDir>& game_dirs, - const CompatibilityList& compatibility_list, Core::System& system_); + explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, + FileSys::ManualContentProvider* provider_, + QVector<UISettings::GameDir>& game_dirs_, + const CompatibilityList& compatibility_list_, Core::System& system_); ~GameListWorker() override; /// Starts the processing of directory tree information. diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index edfb946a8..e273744fd 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -183,7 +183,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size void LoadingScreen::paintEvent(QPaintEvent* event) { QStyleOption opt; - opt.init(this); + opt.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); QWidget::paintEvent(event); diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h index 7c960ee72..17045595d 100644 --- a/src/yuzu/loading_screen.h +++ b/src/yuzu/loading_screen.h @@ -7,6 +7,7 @@ #include <memory> #include <QString> #include <QWidget> +#include <QtGlobal> #if !QT_CONFIG(movie) #define YUZU_QT_MOVIE_MISSING 1 @@ -88,4 +89,6 @@ private: std::size_t slow_shader_first_value = 0; }; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage); +#endif diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f607f464a..b460020b1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -52,7 +52,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #define QT_NO_OPENGL #include <QClipboard> #include <QDesktopServices> -#include <QDesktopWidget> #include <QFile> #include <QFileDialog> #include <QInputDialog> @@ -60,6 +59,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QProgressBar> #include <QProgressDialog> #include <QPushButton> +#include <QScreen> #include <QShortcut> #include <QStatusBar> #include <QString> @@ -115,6 +115,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "video_core/shader_notify.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" +#include "yuzu/check_vulkan.h" #include "yuzu/compatdb.h" #include "yuzu/compatibility_list.h" #include "yuzu/configuration/config.h" @@ -198,6 +199,59 @@ static void RemoveCachedContents() { Common::FS::RemoveDirRecursively(offline_system_data); } +static void LogRuntimes() { +#ifdef _MSC_VER + // It is possible that the name of the dll will change. + // vcruntime140.dll is for 2015 and onwards + constexpr char runtime_dll_name[] = "vcruntime140.dll"; + UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); + bool runtime_version_inspection_worked = false; + if (sz > 0) { + std::vector<u8> buf(sz); + if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) { + VS_FIXEDFILEINFO* pvi; + sz = sizeof(VS_FIXEDFILEINFO); + if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) { + if (pvi->dwSignature == VS_FFI_SIGNATURE) { + runtime_version_inspection_worked = true; + LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER, + pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF, + pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF); + } + } + } + } + if (!runtime_version_inspection_worked) { + LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name); + } +#endif +} + +static QString PrettyProductName() { +#ifdef _WIN32 + // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 + // With that notation change they changed the registry key used to denote the current version + QSettings windows_registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); + if (release_id == QStringLiteral("2009")) { + const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); + const QString display_version = + windows_registry.value(QStringLiteral("DisplayVersion")).toString(); + const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); + u32 version = 10; + if (current_build >= 22000) { + version = 11; + } + return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") + .arg(QString::number(version), display_version, QString::number(current_build), + QString::number(ubr)); + } +#endif + return QSysInfo::prettyProductName(); +} + GMainWindow::GMainWindow() : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, @@ -243,6 +297,7 @@ GMainWindow::GMainWindow() const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version); + LogRuntimes(); #ifdef ARCHITECTURE_x86_64 const auto& caps = Common::GetCPUCaps(); std::string cpu_string = caps.cpu_string; @@ -259,7 +314,7 @@ GMainWindow::GMainWindow() } LOG_INFO(Frontend, "Host CPU: {}", cpu_string); #endif - LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString()); + LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); @@ -297,6 +352,23 @@ GMainWindow::GMainWindow() MigrateConfigFiles(); + if (!CheckVulkan()) { + config->Save(); + + QMessageBox::warning( + this, tr("Broken Vulkan Installation Detected"), + tr("Vulkan initialization failed on the previous boot.<br><br>Click <a " + "href='https://yuzu-emu.org/wiki/faq/" + "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for " + "instructions to fix the issue</a>.")); + } + if (UISettings::values.has_broken_vulkan) { + Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; + + renderer_status_button->setDisabled(true); + renderer_status_button->setChecked(false); + } + #if defined(HAVE_SDL2) && !defined(_WIN32) SDL_InitSubSystem(SDL_INIT_VIDEO); // SDL disables the screen saver by default, and setting the hint @@ -827,12 +899,11 @@ void GMainWindow::InitializeWidgets() { // Setup Dock button dock_status_button = new QPushButton(); - dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); + dock_status_button->setObjectName(QStringLiteral("DockingStatusBarButton")); dock_status_button->setFocusPolicy(Qt::NoFocus); connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode); - dock_status_button->setText(tr("DOCK")); dock_status_button->setCheckable(true); - dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); + UpdateDockedButton(); statusBar()->insertPermanentWidget(0, dock_status_button); gpu_accuracy_button = new QPushButton(); @@ -863,8 +934,7 @@ void GMainWindow::InitializeWidgets() { Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan); } else { Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL); - const auto filter = Settings::values.scaling_filter.GetValue(); - if (filter == Settings::ScalingFilter::Fsr) { + if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor); UpdateFilterText(); } @@ -1002,7 +1072,7 @@ void GMainWindow::InitializeHotkeys() { void GMainWindow::SetDefaultUIGeometry() { // geometry: 53% of the window contents are in the upper screen half, 47% in the lower half - const QRect screenRect = QApplication::desktop()->screenGeometry(this); + const QRect screenRect = QGuiApplication::primaryScreen()->geometry(); const int w = screenRect.width() * 2 / 3; const int h = screenRect.height() * 2 / 3; @@ -1015,6 +1085,10 @@ void GMainWindow::SetDefaultUIGeometry() { void GMainWindow::RestoreUIState() { setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint); restoreGeometry(UISettings::values.geometry); + // Work-around because the games list isn't supposed to be full screen + if (isFullScreen()) { + showNormal(); + } restoreState(UISettings::values.state); render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint); render_window->restoreGeometry(UISettings::values.renderwindow_geometry); @@ -1367,7 +1441,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p } return false; } - game_path = filename; + current_game_path = filename; system->TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt"); return true; @@ -1401,7 +1475,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && type == StartGameType::Normal) { // Load per game settings - const auto file_path = std::filesystem::path{filename.toStdU16String()}; + const auto file_path = + std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}; const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); @@ -1432,7 +1507,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t // Register an ExecuteProgram callback such that Core can execute a sub-program system->RegisterExecuteProgramCallback( - [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); }); + [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); // Register an Exit callback such that Core can exit the currently running application. system->RegisterExitCallback([this]() { render_window->Exit(); }); @@ -1482,7 +1557,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t } if (res != Loader::ResultStatus::Success || title_name.empty()) { title_name = Common::FS::PathToUTF8String( - std::filesystem::path{filename.toStdU16String()}.filename()); + std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} + .filename()); } const bool is_64bit = system->Kernel().CurrentProcess()->Is64BitProcess(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); @@ -1514,6 +1590,7 @@ void GMainWindow::ShutdownGame() { AllowOSSleep(); + system->DetachDebugger(); discord_rpc->Pause(); emu_thread->RequestStop(); @@ -1561,9 +1638,9 @@ void GMainWindow::ShutdownGame() { emu_speed_label->setVisible(false); game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); - renderer_status_button->setEnabled(true); + renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan); - game_path.clear(); + current_game_path.clear(); // When closing the game, destroy the GLWindow to clear the context after the game is closed render_window->ReleaseRenderTarget(); @@ -1581,7 +1658,7 @@ void GMainWindow::StoreRecentFile(const QString& filename) { void GMainWindow::UpdateRecentFiles() { const int num_recent_files = - std::min(UISettings::values.recent_files.size(), max_recent_files_item); + std::min(static_cast<int>(UISettings::values.recent_files.size()), max_recent_files_item); for (int i = 0; i < num_recent_files; i++) { const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg( @@ -2482,7 +2559,7 @@ void GMainWindow::OnRestartGame() { return; } // Make a copy since BootGame edits game_path - BootGame(QString(game_path)); + BootGame(QString(current_game_path)); } void GMainWindow::OnPauseGame() { @@ -2579,6 +2656,18 @@ void GMainWindow::ToggleFullscreen() { } } +// We're going to return the screen that the given window has the most pixels on +static QScreen* GuessCurrentScreen(QWidget* window) { + const QList<QScreen*> screens = QGuiApplication::screens(); + return *std::max_element( + screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) { + const QSize left_size = left->geometry().intersected(window->geometry()).size(); + const QSize right_size = right->geometry().intersected(window->geometry()).size(); + return (left_size.height() * left_size.width()) < + (right_size.height() * right_size.width()); + }); +} + void GMainWindow::ShowFullscreen() { const auto show_fullscreen = [](QWidget* window) { if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) { @@ -2587,7 +2676,7 @@ void GMainWindow::ShowFullscreen() { } window->hide(); window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint); - const auto screen_geometry = QApplication::desktop()->screenGeometry(window); + const auto screen_geometry = GuessCurrentScreen(window)->geometry(); window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(), screen_geometry.height() + 1); window->raise(); @@ -2771,6 +2860,10 @@ void GMainWindow::OnConfigure() { mouse_hide_timer.start(); } + if (!UISettings::values.has_broken_vulkan) { + renderer_status_button->setEnabled(!emulation_running); + } + UpdateStatusButtons(); controller_dialog->refreshConfiguration(); } @@ -2856,7 +2949,7 @@ void GMainWindow::OnToggleDockedMode() { } Settings::values.use_docked_mode.SetValue(!is_docked); - dock_status_button->setChecked(!is_docked); + UpdateDockedButton(); OnDockedModeChanged(is_docked, !is_docked, *system); } @@ -2895,7 +2988,7 @@ void GMainWindow::OnToggleAdaptingFilter() { void GMainWindow::OnConfigurePerGame() { const u64 title_id = system->GetCurrentProcessProgramID(); - OpenPerGameConfiguration(title_id, game_path.toStdString()); + OpenPerGameConfiguration(title_id, current_game_path.toStdString()); } void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { @@ -3150,7 +3243,7 @@ void GMainWindow::OnTasStateChanged() { } void GMainWindow::UpdateStatusBar() { - if (emu_thread == nullptr) { + if (emu_thread == nullptr || !system->IsPoweredOn()) { status_bar_update_timer.stop(); return; } @@ -3222,6 +3315,12 @@ void GMainWindow::UpdateGPUAccuracyButton() { } } +void GMainWindow::UpdateDockedButton() { + const bool is_docked = Settings::values.use_docked_mode.GetValue(); + dock_status_button->setChecked(is_docked); + dock_status_button->setText(is_docked ? tr("DOCKED") : tr("HANDHELD")); +} + void GMainWindow::UpdateFilterText() { const auto filter = Settings::values.scaling_filter.GetValue(); switch (filter) { @@ -3265,10 +3364,10 @@ void GMainWindow::UpdateAAText() { } void GMainWindow::UpdateStatusButtons() { - dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); UpdateGPUAccuracyButton(); + UpdateDockedButton(); UpdateFilterText(); UpdateAAText(); } @@ -3319,7 +3418,7 @@ void GMainWindow::CenterMouseCursor() { const int center_x = render_window->width() / 2; const int center_y = render_window->height() / 2; - QCursor::setPos(mapToGlobal({center_x, center_y})); + QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); } void GMainWindow::OnMouseActivity() { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index b399e9b01..8cf224c9c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -320,6 +320,7 @@ private: void MigrateConfigFiles(); void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, std::string_view gpu_vendor = {}); + void UpdateDockedButton(); void UpdateFilterText(); void UpdateAAText(); void UpdateStatusBar(); @@ -368,7 +369,7 @@ private: bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; // The path to the game currently running - QString game_path; + QString current_game_path; bool auto_paused = false; bool auto_muted = false; diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 15ba9ea17..c64d87ace 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -77,6 +77,8 @@ struct Values { Settings::BasicSetting<bool> pause_when_in_background{false, "pauseWhenInBackground"}; Settings::BasicSetting<bool> mute_when_in_background{false, "muteWhenInBackground"}; Settings::BasicSetting<bool> hide_mouse{true, "hideInactiveMouse"}; + // Set when Vulkan is known to crash the application + Settings::BasicSetting<bool> has_broken_vulkan{false, "has_broken_vulkan"}; Settings::BasicSetting<bool> select_user_on_boot{false, "select_user_on_boot"}; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index fc16f0f0c..fc4744fb0 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -344,6 +344,8 @@ void Config::ReadValues() { ReadSetting("Debugging", Settings::values.use_debug_asserts); ReadSetting("Debugging", Settings::values.use_auto_stub); ReadSetting("Debugging", Settings::values.disable_macro_jit); + ReadSetting("Debugging", Settings::values.use_gdbstub); + ReadSetting("Debugging", Settings::values.gdbstub_port); const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); std::stringstream ss(title_list); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index f34d6b728..a3b8432f5 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -218,7 +218,7 @@ cpuopt_unsafe_ignore_global_monitor = [Renderer] # Which backend API to use. -# 0 (default): OpenGL, 1: Vulkan +# 0: OpenGL, 1 (default): Vulkan backend = # Enable graphics API debugging mode. @@ -437,6 +437,11 @@ disable_macro_jit=false # Presents guest frames as they become available. Experimental. # false: Disabled (default), true: Enabled disable_fps_limit=false +# Determines whether to enable the GDB stub and wait for the debugger to attach before running. +# false: Disabled (default), true: Enabled +use_gdbstub=false +# The port to use for the GDB server, if it is enabled. +gdbstub_port=6543 [WebService] # Whether or not to enable telemetry diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index ae2e62dc5..8e38724db 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -93,7 +93,7 @@ void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { } void EmuWindow_SDL2::OnFingerUp() { - input_subsystem->GetTouchScreen()->TouchReleased(0); + input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { @@ -162,7 +162,15 @@ void EmuWindow_SDL2::WaitEvent() { SDL_Event event; if (!SDL_WaitEvent(&event)) { - LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", SDL_GetError()); + const char* error = SDL_GetError(); + if (!error || strcmp(error, "") == 0) { + // https://github.com/libsdl-org/SDL/issues/5780 + // Sometimes SDL will return without actually having hit an error condition; + // just ignore it in this case. + return; + } + + LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error); exit(1); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 9746585f5..58b885465 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -20,7 +20,7 @@ enum class MouseButton; class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem, Core::System& system_); + explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); ~EmuWindow_SDL2(); /// Whether the window is still open, and a close request hasn't yet been sent diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 8075c9082..9b660c13c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -73,9 +73,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, +EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem, system_} { + : EmuWindow_SDL2{input_subsystem_, system_} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h index d159166fd..39346e704 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h @@ -17,7 +17,7 @@ class InputSubsystem; class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, Core::System& system_, + explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen); ~EmuWindow_SDL2_GL(); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp index d5fe35aa0..65455c86e 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -21,9 +21,9 @@ #include <SDL.h> #include <SDL_syswm.h> -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, +EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem, system_} { + : EmuWindow_SDL2{input_subsystem_, system_} { const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); render_window = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h index d92e3aaab..e39ad754d 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h @@ -18,7 +18,7 @@ class InputSubsystem; class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem, Core::System& system, + explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, bool fullscreen); ~EmuWindow_SDL2_VK() override; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index ab12dd15d..0dce5e274 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -217,10 +217,19 @@ int main(int argc, char** argv) { [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); } + system.RegisterExitCallback([&] { + // Just exit right away. + exit(0); + }); + void(system.Run()); + if (system.DebuggerEnabled()) { + system.InitializeDebugger(); + } while (emu_window->IsOpen()) { emu_window->WaitEvent(); } + system.DetachDebugger(); void(system.Pause()); system.Shutdown(); |