diff options
153 files changed, 3718 insertions, 808 deletions
| diff --git a/.github/ISSUE_TEMPLATE/blank_issue_template.yml b/.github/ISSUE_TEMPLATE/blank_issue_template.yml new file mode 100644 index 000000000..49b7f3822 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/blank_issue_template.yml @@ -0,0 +1,10 @@ +name: New Issue (Developers Only) +description: A blank issue template for developers only. If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template. +body: +  - type: markdown +    attributes: +      value: | +        **If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.** +  - type: textarea +    attributes: +      label: "Issue" diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md deleted file mode 100644 index 808613237..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug Report / Feature Request -about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better. -title: '' -labels: '' -assignees: '' - ---- - -<!--- -Please keep in mind yuzu is EXPERIMENTAL SOFTWARE. - -Please read the FAQ: -https://yuzu-emu.org/wiki/faq/ - -THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO: -https://community.citra-emu.org/ - -If the FAQ does not answer your question, please go to: -https://community.citra-emu.org/ - -When submitting an issue, please check the following: - -- You have read the above. -- You have provided the version (commit hash) of yuzu you are using. -- You have provided sufficient detail for the issue to be reproduced. -- You have provided system specs (if relevant). -- Please also provide: -  - For any issues, a log file -  - For crashes, a backtrace. -  - For graphical issues, comparison screenshots with real hardware. -  - For emulation inaccuracies, a test-case (if able). - ---> - - - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..1405ccce8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug Report +description: File a bug report +body: +  - type: markdown +    attributes: +      value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu. +  - type: checkboxes +    attributes: +      label: Is there an existing issue for this? +      description: Please search to see if an issue already exists for the bug you encountered. +      options: +      - label: I have searched the existing issues +        required: true +  - type: input +    attributes: +      label: Affected Build(s) +      description: List the affected build(s) that this issue applies to. +      placeholder: Mainline 1234 / Early Access 1234 +    validations: +      required: true +  - type: textarea +    id: issue-desc +    attributes: +      label: Description of Issue +      description: A brief description of the issue encountered along with any images and/or videos. +    validations: +      required: true +  - type: textarea +    id: expected-behavior +    attributes: +      label: Expected Behavior +      description: A brief description of how it is expected to work along with any images and/or videos. +    validations: +      required: true +  - type: textarea +    id: reproduction-steps +    attributes: +      label: Reproduction Steps +      description: A brief explanation of how to reproduce this issue. If possible, provide a save file to aid in reproducing the issue. +    validations: +      required: true +  - type: textarea +    id: log +    attributes: +      label: Log File +      description: A log file will help our developers to better diagnose and fix the issue. +    validations: +      required: true +  - type: textarea +    id: system-config +    attributes: +      label: System Configuration +      placeholder: | +        CPU: Intel i5-10400 / AMD Ryzen 5 3600 +        GPU/Driver: NVIDIA GeForce GTX 1060 (Driver 512.95) +        RAM: 16GB DDR4-3200 +        OS: Windows 11 22H2 (Build 22621.819) +      value: | +        CPU: +        GPU/Driver: +        RAM: +        OS: +    validations: +      required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..766da2c87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +name: Feature Request +description: File a feature request +labels: "request" +body: +  - type: markdown +    attributes: +      value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better. +  - type: checkboxes +    attributes: +      label: Is there an existing issue for this? +      description: Please search to see if an issue already exists for the feature you are requesting. +      options: +      - label: I have searched the existing issues +        required: true +  - type: textarea +    id: what-feature +    attributes: +      label: What feature are you suggesting? +      description: A brief description of the requested feature. +    validations: +      required: true +  - type: textarea +    id: why-feature +    attributes: +      label: Why would this feature be useful? +      description: A brief description of why this feature would make yuzu better. +    validations: +      required: true diff --git a/.reuse/dep5 b/.reuse/dep5 index 9a90f9eb6..3810f2c41 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -132,10 +132,6 @@ Files: vcpkg.json  Copyright: 2022 yuzu Emulator Project  License: GPL-3.0-or-later -Files: .github/ISSUE_TEMPLATE/config.yml -Copyright: 2020 tgsm <doodrabbit@hotmail.com> -License: GPL-2.0-or-later - -Files: .github/ISSUE_TEMPLATE/bug-report-feature-request.md -Copyright: 2016 MerryMage +Files: .github/ISSUE_TEMPLATE/* +Copyright: 2022 yuzu Emulator Project  License: GPL-2.0-or-later diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fc5dd9e..1d13dc74e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,13 +133,13 @@ if (NOT ENABLE_GENERIC)      if (MSVC)          detect_architecture("_M_AMD64" x86_64)          detect_architecture("_M_IX86" x86) -        detect_architecture("_M_ARM" ARM) -        detect_architecture("_M_ARM64" ARM64) +        detect_architecture("_M_ARM" arm) +        detect_architecture("_M_ARM64" arm64)      else()          detect_architecture("__x86_64__" x86_64)          detect_architecture("__i386__" x86) -        detect_architecture("__arm__" ARM) -        detect_architecture("__aarch64__" ARM64) +        detect_architecture("__arm__" arm) +        detect_architecture("__aarch64__" arm64)      endif()  endif() diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e80fd124e..7f0a6d069 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -7,15 +7,14 @@ include(DownloadExternals)  # xbyak  if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) -    add_library(xbyak INTERFACE) -    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) -    file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) -    target_include_directories(xbyak SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include) -    target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES) +    add_subdirectory(xbyak)  endif()  # Dynarmic -if (ARCHITECTURE_x86_64) +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) +    if (ARCHITECTURE_arm64) +       set(DYNARMIC_FRONTENDS "A32") +    endif()      set(DYNARMIC_NO_BUNDLED_FMT ON)      set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE)      add_subdirectory(dynarmic) diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 2d4602a6516c67d547000d4c80bcc5f74976abd +Subproject 07c614f91b0af5335e1f9c0653c2d75e7b5f53b diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h index 1357a08fd..ca9fe7063 100644 --- a/externals/microprofile/microprofileui.h +++ b/externals/microprofile/microprofileui.h @@ -845,8 +845,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int      MicroProfile& S = *MicroProfileGet();      MP_DEBUG_DUMP_RANGE();      int nY = nBaseY - UI.nOffsetY; -    int64_t nNumBoxes = 0; -    int64_t nNumLines = 0;      uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;      MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent]; @@ -1149,7 +1147,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int                                  }                              }  #endif -                            ++nNumBoxes;                          }                          else                          { @@ -1165,7 +1162,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int                                  }                                  nLinesDrawn[nStackPos] = nLineX;                                  MicroProfileDrawLineVertical(nLineX, fYStart + 0.5f, fYEnd + 0.5f, nColor|UI.nOpacityForeground); -                                ++nNumLines;                              }                          }                          nStackPos--; diff --git a/externals/xbyak b/externals/xbyak -Subproject c306b8e5786eeeb87b8925a8af5c3bf057ff5a9 +Subproject 348e3e548ebac06d243e5881caec8440e249f65 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 0a1f3bf18..8e3a8f5a8 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -217,7 +217,7 @@ else()  endif()  target_link_libraries(audio_core PUBLIC common core) -if (ARCHITECTURE_x86_64) +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)      target_link_libraries(audio_core PRIVATE dynarmic)  endif() diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp index fd5873e1e..8aa0f5ed0 100644 --- a/src/audio_core/renderer/performance/performance_manager.cpp +++ b/src/audio_core/renderer/performance/performance_manager.cpp @@ -26,6 +26,7 @@ void PerformanceManager::CreateImpl(const size_t version) {          impl = std::make_unique<              PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,                                     PerformanceEntryVersion1, PerformanceDetailVersion1>>(); +        break;      }  } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c0555f840..b7c15c191 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -34,6 +34,8 @@ add_library(common STATIC      bit_util.h      cityhash.cpp      cityhash.h +    cache_management.cpp +    cache_management.h      common_funcs.h      common_types.h      concepts.h diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h index bef5015c1..aef3b66a4 100644 --- a/src/common/atomic_helpers.h +++ b/src/common/atomic_helpers.h @@ -156,6 +156,7 @@ AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {          break;      default:          assert(false); +        break;      }  } diff --git a/src/common/cache_management.cpp b/src/common/cache_management.cpp new file mode 100644 index 000000000..57810b76a --- /dev/null +++ b/src/common/cache_management.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cstring> + +#include "alignment.h" +#include "cache_management.h" +#include "common_types.h" + +namespace Common { + +#if defined(ARCHITECTURE_x86_64) + +// Most cache operations are no-ops on x86 + +void DataCacheLineCleanByVAToPoU(void* start, size_t size) {} +void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size) {} +void DataCacheLineCleanByVAToPoC(void* start, size_t size) {} +void DataCacheZeroByVA(void* start, size_t size) { +    std::memset(start, 0, size); +} + +#elif defined(ARCHITECTURE_arm64) + +// BS/DminLine is log2(cache size in words), we want size in bytes +#define EXTRACT_DMINLINE(ctr_el0) (1 << ((((ctr_el0) >> 16) & 0xf) + 2)) +#define EXTRACT_BS(dczid_el0) (1 << (((dczid_el0)&0xf) + 2)) + +#define DEFINE_DC_OP(op_name, function_name)                                                       \ +    void function_name(void* start, size_t size) {                                                 \ +        size_t ctr_el0;                                                                            \ +        asm volatile("mrs %[ctr_el0], ctr_el0\n\t" : [ctr_el0] "=r"(ctr_el0));                     \ +        size_t cacheline_size = EXTRACT_DMINLINE(ctr_el0);                                         \ +        uintptr_t va_start = reinterpret_cast<uintptr_t>(start);                                   \ +        uintptr_t va_end = va_start + size;                                                        \ +        for (uintptr_t va = va_start; va < va_end; va += cacheline_size) {                         \ +            asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory");                \ +        }                                                                                          \ +    } + +#define DEFINE_DC_OP_DCZID(op_name, function_name)                                                 \ +    void function_name(void* start, size_t size) {                                                 \ +        size_t dczid_el0;                                                                          \ +        asm volatile("mrs %[dczid_el0], dczid_el0\n\t" : [dczid_el0] "=r"(dczid_el0));             \ +        size_t cacheline_size = EXTRACT_BS(dczid_el0);                                             \ +        uintptr_t va_start = reinterpret_cast<uintptr_t>(start);                                   \ +        uintptr_t va_end = va_start + size;                                                        \ +        for (uintptr_t va = va_start; va < va_end; va += cacheline_size) {                         \ +            asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory");                \ +        }                                                                                          \ +    } + +DEFINE_DC_OP(cvau, DataCacheLineCleanByVAToPoU); +DEFINE_DC_OP(civac, DataCacheLineCleanAndInvalidateByVAToPoC); +DEFINE_DC_OP(cvac, DataCacheLineCleanByVAToPoC); +DEFINE_DC_OP_DCZID(zva, DataCacheZeroByVA); + +#endif + +} // namespace Common diff --git a/src/common/cache_management.h b/src/common/cache_management.h new file mode 100644 index 000000000..e467b87e4 --- /dev/null +++ b/src/common/cache_management.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "stdlib.h" + +namespace Common { + +// Data cache instructions enabled at EL0 by SCTLR_EL1.UCI. +// VA = virtual address +// PoC = point of coherency +// PoU = point of unification + +// dc cvau +void DataCacheLineCleanByVAToPoU(void* start, size_t size); + +// dc civac +void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size); + +// dc cvac +void DataCacheLineCleanByVAToPoC(void* start, size_t size); + +// dc zva +void DataCacheZeroByVA(void* start, size_t size); + +} // namespace Common diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index e1e2a90fc..0dad9338a 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -31,8 +31,10 @@  #ifndef _MSC_VER -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64)  #define Crash() __asm__ __volatile__("int $3") +#elif defined(ARCHITECTURE_arm64) +#define Crash() __asm__ __volatile__("brk #0")  #else  #define Crash() exit(1)  #endif diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 7f9659612..909f6cf3f 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -359,6 +359,12 @@ public:              }          }); +        long page_size = sysconf(_SC_PAGESIZE); +        if (page_size != 0x1000) { +            LOG_CRITICAL(HW_Memory, "page size {:#x} is incompatible with 4K paging", page_size); +            throw std::bad_alloc{}; +        } +          // Backing memory initialization  #if defined(__FreeBSD__) && __FreeBSD__ < 13          // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 0a560ebb7..8173462cb 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -151,6 +151,7 @@ void UpdateRescalingInfo() {          ASSERT(false);          info.up_scale = 1;          info.down_shift = 0; +        break;      }      info.up_factor = static_cast<f32>(info.up_scale) / (1U << info.down_shift);      info.down_factor = static_cast<f32>(1U << info.down_shift) / info.up_scale; diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 1a27532d4..e54383a4a 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -4,14 +4,27 @@  #include <array>  #include <cstring> +#include <fstream>  #include <iterator> +#include <optional>  #include <string_view> +#include <thread> +#include <vector>  #include "common/bit_util.h"  #include "common/common_types.h" +#include "common/logging/log.h"  #include "common/x64/cpu_detect.h" +#ifdef _WIN32 +#include <windows.h> +#endif +  #ifdef _MSC_VER  #include <intrin.h> + +static inline u64 xgetbv(u32 index) { +    return _xgetbv(index); +}  #else  #if defined(__DragonFly__) || defined(__FreeBSD__) @@ -39,12 +52,11 @@ static inline void __cpuid(int info[4], u32 function_id) {  }  #define _XCR_XFEATURE_ENABLED_MASK 0 -static inline u64 _xgetbv(u32 index) { +static inline u64 xgetbv(u32 index) {      u32 eax, edx;      __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));      return ((u64)edx << 32) | eax;  } -  #endif // _MSC_VER  namespace Common { @@ -107,7 +119,7 @@ static CPUCaps Detect() {          //  - Is the XSAVE bit set in CPUID?          //  - XGETBV result has the XCR bit set.          if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { -            if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { +            if ((xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) {                  caps.avx = true;                  if (Common::Bit<12>(cpu_id[2]))                      caps.fma = true; @@ -192,4 +204,45 @@ const CPUCaps& GetCPUCaps() {      return caps;  } +std::optional<int> GetProcessorCount() { +#if defined(_WIN32) +    // Get the buffer length. +    DWORD length = 0; +    GetLogicalProcessorInformation(nullptr, &length); +    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { +        LOG_ERROR(Frontend, "Failed to query core count."); +        return std::nullopt; +    } +    std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer( +        length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); +    // Now query the core count. +    if (!GetLogicalProcessorInformation(buffer.data(), &length)) { +        LOG_ERROR(Frontend, "Failed to query core count."); +        return std::nullopt; +    } +    return static_cast<int>( +        std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) { +            return proc_info.Relationship == RelationProcessorCore; +        })); +#elif defined(__unix__) +    const int thread_count = std::thread::hardware_concurrency(); +    std::ifstream smt("/sys/devices/system/cpu/smt/active"); +    char state = '0'; +    if (smt) { +        smt.read(&state, sizeof(state)); +    } +    switch (state) { +    case '0': +        return thread_count; +    case '1': +        return thread_count / 2; +    default: +        return std::nullopt; +    } +#else +    // Shame on you +    return std::nullopt; +#endif +} +  } // namespace Common diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index 6830f3795..ca8db19d6 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h @@ -4,6 +4,7 @@  #pragma once +#include <optional>  #include <string_view>  #include "common/common_types.h" @@ -74,4 +75,7 @@ struct CPUCaps {   */  const CPUCaps& GetCPUCaps(); +/// Detects CPU core count +std::optional<int> GetProcessorCount(); +  } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f6e082c36..740c5b0fd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(core STATIC      file_sys/vfs_vector.h      file_sys/xts_archive.cpp      file_sys/xts_archive.h +    frontend/applets/cabinet.cpp +    frontend/applets/cabinet.h      frontend/applets/controller.cpp      frontend/applets/controller.h      frontend/applets/error.cpp @@ -312,6 +314,8 @@ add_library(core STATIC      hle/service/am/applet_ae.h      hle/service/am/applet_oe.cpp      hle/service/am/applet_oe.h +    hle/service/am/applets/applet_cabinet.cpp +    hle/service/am/applets/applet_cabinet.h      hle/service/am/applets/applet_controller.cpp      hle/service/am/applets/applet_controller.h      hle/service/am/applets/applet_error.cpp @@ -497,10 +501,6 @@ add_library(core STATIC      hle/service/hid/irsensor/processor_base.h      hle/service/hid/irsensor/tera_plugin_processor.cpp      hle/service/hid/irsensor/tera_plugin_processor.h -    hle/service/jit/jit_context.cpp -    hle/service/jit/jit_context.h -    hle/service/jit/jit.cpp -    hle/service/jit/jit.h      hle/service/lbl/lbl.cpp      hle/service/lbl/lbl.h      hle/service/ldn/lan_discovery.cpp @@ -805,14 +805,18 @@ if (ENABLE_WEB_SERVICE)      target_link_libraries(core PRIVATE web_service)  endif() -if (ARCHITECTURE_x86_64) +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)      target_sources(core PRIVATE -        arm/dynarmic/arm_dynarmic_32.cpp -        arm/dynarmic/arm_dynarmic_32.h          arm/dynarmic/arm_dynarmic_64.cpp          arm/dynarmic/arm_dynarmic_64.h +        arm/dynarmic/arm_dynarmic_32.cpp +        arm/dynarmic/arm_dynarmic_32.h          arm/dynarmic/arm_dynarmic_cp15.cpp          arm/dynarmic/arm_dynarmic_cp15.h +        hle/service/jit/jit_context.cpp +        hle/service/jit/jit_context.h +        hle/service/jit/jit.cpp +        hle/service/jit/jit.h      )      target_link_libraries(core PRIVATE dynarmic)  endif() diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 287ba102e..227e06ea1 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -301,6 +301,11 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*          }      } +#ifdef ARCHITECTURE_arm64 +    // TODO: remove when fixed in dynarmic +    config.optimizations &= ~Dynarmic::OptimizationFlag::BlockLinking; +#endif +      return std::make_unique<Dynarmic::A32::Jit>(config);  } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index afb7fb3a0..cb53d64ba 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -348,7 +348,6 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*          if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {              config.unsafe_optimizations = true;              config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; -            config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;              config.fastmem_address_space_bits = 64;              config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;          } diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp index 200efe4db..5a4eba3eb 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -52,12 +52,16 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1          case 4:              // CP15_DATA_SYNC_BARRIER              return Callback{ -                [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t { -#ifdef _MSC_VER +                [](void*, std::uint32_t, std::uint32_t) -> std::uint64_t { +#if defined(_MSC_VER) && defined(ARCHITECTURE_x86_64)                      _mm_mfence();                      _mm_lfence(); -#else +#elif defined(ARCHITECTURE_x86_64)                      asm volatile("mfence\n\tlfence\n\t" : : : "memory"); +#elif defined(ARCHITECTURE_arm64) +                    asm volatile("dsb sy\n\t" : : : "memory"); +#else +#error Unsupported architecture  #endif                      return 0;                  }, @@ -66,11 +70,15 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1          case 5:              // CP15_DATA_MEMORY_BARRIER              return Callback{ -                [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t { -#ifdef _MSC_VER +                [](void*, std::uint32_t, std::uint32_t) -> std::uint64_t { +#if defined(_MSC_VER) && defined(ARCHITECTURE_x86_64)                      _mm_mfence(); -#else +#elif defined(ARCHITECTURE_x86_64)                      asm volatile("mfence\n\t" : : : "memory"); +#elif defined(ARCHITECTURE_arm64) +                    asm volatile("dmb sy\n\t" : : : "memory"); +#else +#error Unsupported architecture  #endif                      return 0;                  }, @@ -115,7 +123,7 @@ CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1,  CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {      if (!two && opc == 0 && CRm == CoprocReg::C14) {          // CNTPCT -        const auto callback = [](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 { +        const auto callback = [](void* arg, u32, u32) -> u64 {              const auto& parent_arg = *static_cast<ARM_Dynarmic_32*>(arg);              return parent_arg.system.CoreTiming().GetClockTicks();          }; diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp index 2db0b035d..20550faeb 100644 --- a/src/core/arm/exclusive_monitor.cpp +++ b/src/core/arm/exclusive_monitor.cpp @@ -1,7 +1,7 @@  // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project  // SPDX-License-Identifier: GPL-2.0-or-later -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)  #include "core/arm/dynarmic/arm_exclusive_monitor.h"  #endif  #include "core/arm/exclusive_monitor.h" @@ -13,7 +13,7 @@ ExclusiveMonitor::~ExclusiveMonitor() = default;  std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,                                                               std::size_t num_cores) { -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)      return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);  #else      // TODO(merry): Passthrough exclusive monitor diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index 339f971e6..1a8e02e6a 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {                  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);              } - -            AsyncReceiveInto(r, buffer, c);          });  } +template <typename Callback> +static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) { +    acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) { +        if (!error.failed()) { +            c(peer_socket); +            AsyncAccept(acceptor, c); +        } +    }); +} +  template <typename Readable, typename Buffer>  static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {      static_assert(std::is_trivial_v<Buffer>); @@ -59,9 +68,7 @@ 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); +    explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} {          InitializeServer(port);      } @@ -70,39 +77,42 @@ public:      }      bool SignalDebugger(SignalInfo signal_info) { -        { -            std::scoped_lock lk{connection_lock}; +        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; +        if (stopped || !state) { +            // Do not notify the debugger about another event. +            // It should be ignored. +            return false;          } +        // Set up the state. +        stopped = true; +        state->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))); +        boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); +          return true;      } +    // These functions are callbacks from the frontend, and the lock will be held. +    // There is no need to relock it. +      std::span<const u8> ReadFromClient() override { -        return ReceiveInto(client_socket, client_data); +        return ReceiveInto(state->client_socket, state->client_data);      }      void WriteToClient(std::span<const u8> data) override { -        boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes())); +        boost::asio::write(state->client_socket, +                           boost::asio::buffer(data.data(), data.size_bytes()));      }      void SetActiveThread(Kernel::KThread* thread) override { -        active_thread = thread; +        state->active_thread = thread;      }      Kernel::KThread* GetActiveThread() override { -        return active_thread; +        return state->active_thread;      }  private: @@ -113,65 +123,78 @@ private:          // Run the connection thread.          connection_thread = std::jthread([&, port](std::stop_token stop_token) { +            Common::SetCurrentThreadName("Debugger"); +              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(); +                AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); }); -                if (stop_token.stop_requested()) { -                    return; +                while (!stop_token.stop_requested() && io_context.run()) {                  } - -                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 AcceptConnection(boost::asio::ip::tcp::socket&& peer) { +        LOG_INFO(Debug_GDBStub, "Accepting new peer connection"); -    void ThreadLoop(std::stop_token stop_token) { -        Common::SetCurrentThreadName("Debugger"); +        std::scoped_lock lk{connection_lock}; + +        // Ensure everything is stopped. +        PauseEmulation(); + +        // Set up the new frontend. +        frontend = std::make_unique<GDBStub>(*this, system); + +        // Set the new state. This will tear down any existing state. +        state = ConnectionState{ +            .client_socket{std::move(peer)}, +            .signal_pipe{io_context}, +            .info{}, +            .active_thread{}, +            .client_data{}, +            .pipe_data{}, +        };          // 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); }); +        AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); }); +        AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); });          // Set the active thread.          UpdateActiveThread();          // Set up the frontend.          frontend->Connected(); +    } -        // Main event loop. -        while (!stop_token.stop_requested() && io_context.run()) { -        } +    void ShutdownServer() { +        connection_thread.request_stop(); +        io_context.stop(); +        connection_thread.join();      }      void PipeData(std::span<const u8> data) { -        switch (info.type) { +        std::scoped_lock lk{connection_lock}; + +        switch (state->info.type) {          case SignalType::Stopped:          case SignalType::Watchpoint:              // Stop emulation.              PauseEmulation();              // Notify the client. -            active_thread = info.thread; +            state->active_thread = state->info.thread;              UpdateActiveThread(); -            if (info.type == SignalType::Watchpoint) { -                frontend->Watchpoint(active_thread, *info.watchpoint); +            if (state->info.type == SignalType::Watchpoint) { +                frontend->Watchpoint(state->active_thread, *state->info.watchpoint);              } else { -                frontend->Stopped(active_thread); +                frontend->Stopped(state->active_thread);              }              break; @@ -179,8 +202,8 @@ private:              frontend->ShuttingDown();              // Wait for emulation to shut down gracefully now. -            signal_pipe.close(); -            client_socket.shutdown(boost::asio::socket_base::shutdown_both); +            state->signal_pipe.close(); +            state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);              LOG_INFO(Debug_GDBStub, "Shut down server");              break; @@ -188,17 +211,16 @@ private:      }      void ClientData(std::span<const u8> data) { +        std::scoped_lock lk{connection_lock}; +          const auto actions{frontend->ClientData(data)};          for (const auto action : actions) {              switch (action) {              case DebuggerAction::Interrupt: { -                { -                    std::scoped_lock lk{connection_lock}; -                    stopped = true; -                } +                stopped = true;                  PauseEmulation();                  UpdateActiveThread(); -                frontend->Stopped(active_thread); +                frontend->Stopped(state->active_thread);                  break;              }              case DebuggerAction::Continue: @@ -206,15 +228,15 @@ private:                  break;              case DebuggerAction::StepThreadUnlocked:                  MarkResumed([&] { -                    active_thread->SetStepState(Kernel::StepState::StepPending); -                    active_thread->Resume(Kernel::SuspendType::Debug); -                    ResumeEmulation(active_thread); +                    state->active_thread->SetStepState(Kernel::StepState::StepPending); +                    state->active_thread->Resume(Kernel::SuspendType::Debug); +                    ResumeEmulation(state->active_thread);                  });                  break;              case DebuggerAction::StepThreadLocked: {                  MarkResumed([&] { -                    active_thread->SetStepState(Kernel::StepState::StepPending); -                    active_thread->Resume(Kernel::SuspendType::Debug); +                    state->active_thread->SetStepState(Kernel::StepState::StepPending); +                    state->active_thread->Resume(Kernel::SuspendType::Debug);                  });                  break;              } @@ -254,15 +276,14 @@ private:      template <typename Callback>      void MarkResumed(Callback&& cb) {          Kernel::KScopedSchedulerLock sl{system.Kernel()}; -        std::scoped_lock cl{connection_lock};          stopped = false;          cb();      }      void UpdateActiveThread() {          const auto& threads{ThreadList()}; -        if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { -            active_thread = threads[0]; +        if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) { +            state->active_thread = threads[0];          }      } @@ -274,18 +295,22 @@ private:      System& system;      std::unique_ptr<DebuggerFrontend> frontend; +    boost::asio::io_context io_context;      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; -    SignalInfo info; -    Kernel::KThread* active_thread; -    bool pipe_data; -    bool stopped; +    struct ConnectionState { +        boost::asio::ip::tcp::socket client_socket; +        boost::process::async_pipe signal_pipe; + +        SignalInfo info; +        Kernel::KThread* active_thread; +        std::array<u8, 4096> client_data; +        bool pipe_data; +    }; -    std::array<u8, 4096> client_data; +    std::optional<ConnectionState> state{}; +    bool stopped{};  };  Debugger::Debugger(Core::System& system, u16 port) { diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 884229c77..a64a9ac64 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -606,6 +606,8 @@ void GDBStub::HandleQuery(std::string_view command) {      } else if (command.starts_with("StartNoAckMode")) {          no_ack = true;          SendReply(GDB_STUB_REPLY_OK); +    } else if (command.starts_with("Rcmd,")) { +        HandleRcmd(Common::HexStringToVector(command.substr(5), false));      } else {          SendReply(GDB_STUB_REPLY_EMPTY);      } @@ -645,6 +647,155 @@ void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>&      }  } +constexpr std::array<std::pair<const char*, Kernel::Svc::MemoryState>, 22> MemoryStateNames{{ +    {"----- Free -----", Kernel::Svc::MemoryState::Free}, +    {"Io              ", Kernel::Svc::MemoryState::Io}, +    {"Static          ", Kernel::Svc::MemoryState::Static}, +    {"Code            ", Kernel::Svc::MemoryState::Code}, +    {"CodeData        ", Kernel::Svc::MemoryState::CodeData}, +    {"Normal          ", Kernel::Svc::MemoryState::Normal}, +    {"Shared          ", Kernel::Svc::MemoryState::Shared}, +    {"AliasCode       ", Kernel::Svc::MemoryState::AliasCode}, +    {"AliasCodeData   ", Kernel::Svc::MemoryState::AliasCodeData}, +    {"Ipc             ", Kernel::Svc::MemoryState::Ipc}, +    {"Stack           ", Kernel::Svc::MemoryState::Stack}, +    {"ThreadLocal     ", Kernel::Svc::MemoryState::ThreadLocal}, +    {"Transfered      ", Kernel::Svc::MemoryState::Transfered}, +    {"SharedTransfered", Kernel::Svc::MemoryState::SharedTransfered}, +    {"SharedCode      ", Kernel::Svc::MemoryState::SharedCode}, +    {"Inaccessible    ", Kernel::Svc::MemoryState::Inaccessible}, +    {"NonSecureIpc    ", Kernel::Svc::MemoryState::NonSecureIpc}, +    {"NonDeviceIpc    ", Kernel::Svc::MemoryState::NonDeviceIpc}, +    {"Kernel          ", Kernel::Svc::MemoryState::Kernel}, +    {"GeneratedCode   ", Kernel::Svc::MemoryState::GeneratedCode}, +    {"CodeOut         ", Kernel::Svc::MemoryState::CodeOut}, +    {"Coverage        ", Kernel::Svc::MemoryState::Coverage}, +}}; + +static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) { +    for (size_t i = 0; i < MemoryStateNames.size(); i++) { +        if (std::get<1>(MemoryStateNames[i]) == state) { +            return std::get<0>(MemoryStateNames[i]); +        } +    } +    return "Unknown         "; +} + +static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::MemoryInfo& info) { +    if (info.state == Kernel::Svc::MemoryState::Free) { +        return "   "; +    } + +    switch (info.permission) { +    case Kernel::Svc::MemoryPermission::ReadExecute: +        return "r-x"; +    case Kernel::Svc::MemoryPermission::Read: +        return "r--"; +    case Kernel::Svc::MemoryPermission::ReadWrite: +        return "rw-"; +    default: +        return "---"; +    } +} + +static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) { +    Kernel::Svc::MemoryInfo mem_info; +    VAddr cur_addr{base}; + +    // Expect: r-x Code (.text) +    mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); +    cur_addr = mem_info.base_address + mem_info.size; +    if (mem_info.state != Kernel::Svc::MemoryState::Code || +        mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) { +        return cur_addr - 1; +    } + +    // Expect: r-- Code (.rodata) +    mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); +    cur_addr = mem_info.base_address + mem_info.size; +    if (mem_info.state != Kernel::Svc::MemoryState::Code || +        mem_info.permission != Kernel::Svc::MemoryPermission::Read) { +        return cur_addr - 1; +    } + +    // Expect: rw- CodeData (.data) +    mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); +    cur_addr = mem_info.base_address + mem_info.size; +    return cur_addr - 1; +} + +void GDBStub::HandleRcmd(const std::vector<u8>& command) { +    std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()}; +    std::string reply; + +    auto* process = system.CurrentProcess(); +    auto& page_table = process->PageTable(); + +    if (command_str == "get info") { +        Loader::AppLoader::Modules modules; +        system.GetAppLoader().ReadNSOModules(modules); + +        reply = fmt::format("Process:     {:#x} ({})\n" +                            "Program Id:  {:#018x}\n", +                            process->GetProcessID(), process->GetName(), process->GetProgramID()); +        reply += +            fmt::format("Layout:\n" +                        "  Alias: {:#012x} - {:#012x}\n" +                        "  Heap:  {:#012x} - {:#012x}\n" +                        "  Aslr:  {:#012x} - {:#012x}\n" +                        "  Stack: {:#012x} - {:#012x}\n" +                        "Modules:\n", +                        page_table.GetAliasRegionStart(), page_table.GetAliasRegionEnd(), +                        page_table.GetHeapRegionStart(), page_table.GetHeapRegionEnd(), +                        page_table.GetAliasCodeRegionStart(), page_table.GetAliasCodeRegionEnd(), +                        page_table.GetStackRegionStart(), page_table.GetStackRegionEnd()); + +        for (const auto& [vaddr, name] : modules) { +            reply += fmt::format("  {:#012x} - {:#012x} {}\n", vaddr, +                                 GetModuleEnd(page_table, vaddr), name); +        } +    } else if (command_str == "get mappings") { +        reply = "Mappings:\n"; +        VAddr cur_addr = 0; + +        while (true) { +            using MemoryAttribute = Kernel::Svc::MemoryAttribute; + +            auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); + +            if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible || +                mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) { +                const char* state = GetMemoryStateName(mem_info.state); +                const char* perm = GetMemoryPermissionString(mem_info); + +                const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-'; +                const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-'; +                const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-'; +                const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-'; + +                reply += +                    fmt::format("  {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n", +                                mem_info.base_address, mem_info.base_address + mem_info.size - 1, +                                perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); +            } + +            const uintptr_t next_address = mem_info.base_address + mem_info.size; +            if (next_address <= cur_addr) { +                break; +            } + +            cur_addr = next_address; +        } +    } else if (command_str == "help") { +        reply = "Commands:\n  get info\n  get mappings\n"; +    } else { +        reply = "Unknown command.\nCommands:\n  get info\n  get mappings\n"; +    } + +    std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()}; +    SendReply(Common::HexToString(reply_span, false)); +} +  Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {      const auto& threads{system.GlobalSchedulerContext().GetThreadList()};      for (auto* thread : threads) { diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index 0b0f56e4b..368197920 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h @@ -32,6 +32,7 @@ private:      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); +    void HandleRcmd(const std::vector<u8>& command);      void HandleBreakpointInsert(std::string_view command);      void HandleBreakpointRemove(std::string_view command);      std::vector<char>::const_iterator CommandEnd() const; diff --git a/src/core/frontend/applets/cabinet.cpp b/src/core/frontend/applets/cabinet.cpp new file mode 100644 index 000000000..26c7fefe3 --- /dev/null +++ b/src/core/frontend/applets/cabinet.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/frontend/applets/cabinet.h" + +#include <thread> + +namespace Core::Frontend { + +CabinetApplet::~CabinetApplet() = default; + +void DefaultCabinetApplet::ShowCabinetApplet( +    const CabinetCallback& callback, const CabinetParameters& parameters, +    std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const { +    LOG_WARNING(Service_AM, "(STUBBED) called"); +    callback(false, {}); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/cabinet.h b/src/core/frontend/applets/cabinet.h new file mode 100644 index 000000000..c28a235c1 --- /dev/null +++ b/src/core/frontend/applets/cabinet.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <functional> +#include "core/hle/service/nfp/nfp_types.h" + +namespace Service::NFP { +class NfpDevice; +} // namespace Service::NFP + +namespace Core::Frontend { + +struct CabinetParameters { +    Service::NFP::TagInfo tag_info; +    Service::NFP::RegisterInfo register_info; +    Service::NFP::CabinetMode mode; +}; + +using CabinetCallback = std::function<void(bool, const std::string&)>; + +class CabinetApplet { +public: +    virtual ~CabinetApplet(); +    virtual void ShowCabinetApplet(const CabinetCallback& callback, +                                   const CabinetParameters& parameters, +                                   std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const = 0; +}; + +class DefaultCabinetApplet final : public CabinetApplet { +public: +    void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters, +                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp index aac45907d..fb7e5802a 100644 --- a/src/core/hid/emulated_console.cpp +++ b/src/core/hid/emulated_console.cpp @@ -19,27 +19,26 @@ void EmulatedConsole::ReloadFromSettings() {  }  void EmulatedConsole::SetTouchParams() { -    // TODO(german77): Support any number of fingers      std::size_t index = 0; -    // Hardcode mouse, touchscreen and cemuhook parameters +    // We can't use mouse as touch if native mouse is enabled      if (!Settings::values.mouse_enabled) { -        // 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_id:0"}; +        Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};      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,touch_id:0"}; -    touch_params[index++] = -        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072,touch_id:1"}; +        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"}; + +    for (int i = 0; i < static_cast<int>(MaxActiveTouchInputs); i++) { +        Common::ParamPackage touchscreen_param{}; +        touchscreen_param.Set("engine", "touch"); +        touchscreen_param.Set("axis_x", i * 2); +        touchscreen_param.Set("axis_y", (i * 2) + 1); +        touchscreen_param.Set("button", i); +        touch_params[index++] = touchscreen_param; +    }      const auto button_index =          static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue()); @@ -47,7 +46,7 @@ void EmulatedConsole::SetTouchParams() {      // Map the rest of the fingers from touch from button configuration      for (const auto& config_entry : touch_buttons) { -        if (index >= touch_params.size()) { +        if (index >= MaxTouchDevices) {              continue;          }          Common::ParamPackage params{config_entry}; @@ -60,7 +59,6 @@ void EmulatedConsole::SetTouchParams() {          touch_button_params.Set("button", params.Serialize());          touch_button_params.Set("x", x);          touch_button_params.Set("y", y); -        touch_button_params.Set("touch_id", static_cast<int>(index));          touch_params[index] = touch_button_params;          index++;      } @@ -178,12 +176,38 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {  }  void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) { -    if (index >= console.touch_values.size()) { +    if (index >= MaxTouchDevices) {          return;      }      std::unique_lock lock{mutex}; -    console.touch_values[index] = TransformToTouch(callback); +    const auto touch_input = TransformToTouch(callback); +    auto touch_index = GetIndexFromFingerId(index); +    bool is_new_input = false; + +    if (!touch_index.has_value() && touch_input.pressed.value) { +        touch_index = GetNextFreeIndex(); +        is_new_input = true; +    } + +    // No free entries or invalid state. Ignore input +    if (!touch_index.has_value()) { +        return; +    } + +    auto& touch_value = console.touch_values[touch_index.value()]; + +    if (is_new_input) { +        touch_value.pressed.value = true; +        touch_value.id = static_cast<u32>(index); +    } + +    touch_value.x = touch_input.x; +    touch_value.y = touch_input.y; + +    if (!touch_input.pressed.value) { +        touch_value.pressed.value = false; +    }      if (is_configuring) {          lock.unlock(); @@ -191,11 +215,15 @@ void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, st          return;      } -    // TODO(german77): Remap touch id in sequential order -    console.touch_state[index] = { -        .position = {console.touch_values[index].x.value, console.touch_values[index].y.value}, -        .id = static_cast<u32>(console.touch_values[index].id), -        .pressed = console.touch_values[index].pressed.value, +    // Touch outside allowed range. Ignore input +    if (touch_index.value() >= MaxActiveTouchInputs) { +        return; +    } + +    console.touch_state[touch_index.value()] = { +        .position = {touch_value.x.value, touch_value.y.value}, +        .id = static_cast<u32>(touch_index.value()), +        .pressed = touch_input.pressed.value,      };      lock.unlock(); @@ -222,6 +250,28 @@ TouchFingerState EmulatedConsole::GetTouch() const {      return console.touch_state;  } +std::optional<std::size_t> EmulatedConsole::GetIndexFromFingerId(std::size_t finger_id) const { +    for (std::size_t index = 0; index < MaxTouchDevices; ++index) { +        const auto& finger = console.touch_values[index]; +        if (!finger.pressed.value) { +            continue; +        } +        if (finger.id == static_cast<int>(finger_id)) { +            return index; +        } +    } +    return std::nullopt; +} + +std::optional<std::size_t> EmulatedConsole::GetNextFreeIndex() const { +    for (std::size_t index = 0; index < MaxTouchDevices; ++index) { +        if (!console.touch_values[index].pressed.value) { +            return index; +        } +    } +    return std::nullopt; +} +  void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {      std::scoped_lock lock{callback_mutex};      for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_console.h b/src/core/hid/emulated_console.h index 1c510cd19..697ecd2d6 100644 --- a/src/core/hid/emulated_console.h +++ b/src/core/hid/emulated_console.h @@ -7,6 +7,7 @@  #include <functional>  #include <memory>  #include <mutex> +#include <optional>  #include <unordered_map>  #include "common/common_funcs.h" @@ -20,6 +21,8 @@  #include "core/hid/motion_input.h"  namespace Core::HID { +static constexpr std::size_t MaxTouchDevices = 32; +static constexpr std::size_t MaxActiveTouchInputs = 16;  struct ConsoleMotionInfo {      Common::Input::MotionStatus raw_status{}; @@ -27,13 +30,13 @@ struct ConsoleMotionInfo {  };  using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>; -using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>; +using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>;  using ConsoleMotionParams = Common::ParamPackage; -using TouchParams = std::array<Common::ParamPackage, 16>; +using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;  using ConsoleMotionValues = ConsoleMotionInfo; -using TouchValues = std::array<Common::Input::TouchStatus, 16>; +using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;  struct TouchFinger {      u64 last_touch{}; @@ -55,7 +58,7 @@ struct ConsoleMotion {      bool is_at_rest{};  }; -using TouchFingerState = std::array<TouchFinger, 16>; +using TouchFingerState = std::array<TouchFinger, MaxActiveTouchInputs>;  struct ConsoleStatus {      // Data from input_common @@ -166,6 +169,10 @@ private:       */      void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index); +    std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const; + +    std::optional<std::size_t> GetNextFreeIndex() const; +      /**       * Triggers a callback that something has changed on the console status       * @param type Input type of the event to trigger diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 5d8b75b50..502692875 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -200,9 +200,6 @@ 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/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 3bb111748..a86bec252 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -149,7 +149,7 @@ public:              context->AddDomainObject(std::move(iface));          } else {              kernel.CurrentProcess()->GetResourceLimit()->Reserve( -                Kernel::LimitableResource::Sessions, 1); +                Kernel::LimitableResource::SessionCountMax, 1);              auto* session = Kernel::KSession::Create(kernel);              session->Initialize(nullptr, iface->GetServiceName()); diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index aa2dddcc6..7b363eb1e 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -243,6 +243,7 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {              // If we somehow get an invalid type, abort.          default:              ASSERT_MSG(false, "Unknown slab type: {}", slab_types[i]); +            break;          }          // If we've hit the end of a gap, free it. @@ -265,7 +266,8 @@ void KPageBufferSlabHeap::Initialize(Core::System& system) {      const size_t slab_size = num_pages * PageSize;      // Reserve memory from the system resource limit. -    ASSERT(kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemory, slab_size)); +    ASSERT( +        kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemoryMax, slab_size));      // Allocate memory for the slab.      constexpr auto AllocateOption = KMemoryManager::EncodeOption( diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp index eaa2e094c..2ec623a58 100644 --- a/src/core/hle/kernel/k_client_port.cpp +++ b/src/core/hle/kernel/k_client_port.cpp @@ -61,7 +61,7 @@ bool KClientPort::IsSignaled() const {  Result KClientPort::CreateSession(KClientSession** out) {      // Reserve a new session from the resource limit.      KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), -                                                   LimitableResource::Sessions); +                                                   LimitableResource::SessionCountMax);      R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);      // Update the session counts. diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp index 78ca59463..d973853ab 100644 --- a/src/core/hle/kernel/k_event.cpp +++ b/src/core/hle/kernel/k_event.cpp @@ -20,8 +20,12 @@ void KEvent::Initialize(KProcess* owner) {      m_readable_event.Initialize(this);      // Set our owner process. -    m_owner = owner; -    m_owner->Open(); +    // HACK: this should never be nullptr, but service threads don't have a +    // proper parent process yet. +    if (owner != nullptr) { +        m_owner = owner; +        m_owner->Open(); +    }      // Mark initialized.      m_initialized = true; @@ -50,8 +54,11 @@ Result KEvent::Clear() {  void KEvent::PostDestroy(uintptr_t arg) {      // Release the event count resource the owner process holds.      KProcess* owner = reinterpret_cast<KProcess*>(arg); -    owner->GetResourceLimit()->Release(LimitableResource::Events, 1); -    owner->Close(); + +    if (owner != nullptr) { +        owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); +        owner->Close(); +    }  }  } // namespace Kernel diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h index 6f845d675..3b6e7baff 100644 --- a/src/core/hle/kernel/k_memory_block.h +++ b/src/core/hle/kernel/k_memory_block.h @@ -216,13 +216,15 @@ struct KMemoryInfo {      constexpr Svc::MemoryInfo GetSvcMemoryInfo() const {          return { -            .addr = m_address, +            .base_address = m_address,              .size = m_size,              .state = static_cast<Svc::MemoryState>(m_state & KMemoryState::Mask), -            .attr = static_cast<Svc::MemoryAttribute>(m_attribute & KMemoryAttribute::UserMask), -            .perm = static_cast<Svc::MemoryPermission>(m_permission & KMemoryPermission::UserMask), -            .ipc_refcount = m_ipc_lock_count, -            .device_refcount = m_device_use_count, +            .attribute = +                static_cast<Svc::MemoryAttribute>(m_attribute & KMemoryAttribute::UserMask), +            .permission = +                static_cast<Svc::MemoryPermission>(m_permission & KMemoryPermission::UserMask), +            .ipc_count = m_ipc_lock_count, +            .device_count = m_device_use_count,              .padding = {},          };      } diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index fab55a057..612fc76fa 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -920,8 +920,8 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add      // Reserve space for any partial pages we allocate.      const size_t unmapped_size = aligned_src_size - mapping_src_size; -    KScopedResourceReservation memory_reservation(m_resource_limit, -                                                  LimitableResource::PhysicalMemory, unmapped_size); +    KScopedResourceReservation memory_reservation( +        m_resource_limit, LimitableResource::PhysicalMemoryMax, unmapped_size);      R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);      // Ensure that we manage page references correctly. @@ -1227,7 +1227,7 @@ Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState      const VAddr mapping_start = Common::AlignUp((address), PageSize);      const VAddr mapping_end = Common::AlignDown((address) + size, PageSize);      const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0; -    m_resource_limit->Release(LimitableResource::PhysicalMemory, aligned_size - mapping_size); +    m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, aligned_size - mapping_size);      R_SUCCEED();  } @@ -1568,7 +1568,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {          {              // Reserve the memory from the process resource limit.              KScopedResourceReservation memory_reservation( -                m_resource_limit, LimitableResource::PhysicalMemory, size - mapped_size); +                m_resource_limit, LimitableResource::PhysicalMemoryMax, size - mapped_size);              R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);              // Allocate pages for the new memory. @@ -1908,7 +1908,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {      // Release the memory resource.      m_mapped_physical_memory_size -= mapped_size; -    m_resource_limit->Release(LimitableResource::PhysicalMemory, mapped_size); +    m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, mapped_size);      // Update memory blocks.      m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, @@ -2301,6 +2301,7 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size,              break;          default:              ASSERT(false); +            break;          }      } @@ -2492,7 +2493,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {                            OperationType::Unmap));              // Release the memory from the resource limit. -            m_resource_limit->Release(LimitableResource::PhysicalMemory, num_pages * PageSize); +            m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, num_pages * PageSize);              // Apply the memory block update.              m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size, @@ -2522,7 +2523,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {      // Reserve memory for the heap extension.      KScopedResourceReservation memory_reservation( -        m_resource_limit, LimitableResource::PhysicalMemory, allocation_size); +        m_resource_limit, LimitableResource::PhysicalMemoryMax, allocation_size);      R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);      // Allocate pages for the heap extension. @@ -2803,6 +2804,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_              break;          default:              ASSERT(false); +            break;          }          addr += size; @@ -2838,6 +2840,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,          break;      default:          ASSERT(false); +        break;      }      R_SUCCEED();  } diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 950850291..f1ca785d7 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -320,6 +320,9 @@ public:      constexpr VAddr GetAliasCodeRegionStart() const {          return m_alias_code_region_start;      } +    constexpr VAddr GetAliasCodeRegionEnd() const { +        return m_alias_code_region_end; +    }      constexpr VAddr GetAliasCodeRegionSize() const {          return m_alias_code_region_end - m_alias_code_region_start;      } diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 4ddeea73b..d1dc62401 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -38,7 +38,7 @@ namespace {   */  void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, VAddr stack_top) {      const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart(); -    ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::Threads, 1)); +    ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));      KThread* thread = KThread::Create(system.Kernel());      SCOPE_EXIT({ thread->Close(); }); @@ -124,7 +124,7 @@ void KProcess::DecrementRunningThreadCount() {  }  u64 KProcess::GetTotalPhysicalMemoryAvailable() { -    const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) + +    const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) +                         page_table.GetNormalMemorySize() + GetSystemResourceSize() + image_size +                         main_thread_stack_size};      if (const auto pool_size = kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application); @@ -349,8 +349,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:      // We currently do not support process-specific system resource      UNIMPLEMENTED_IF(system_resource_size != 0); -    KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory, -                                                  code_size + system_resource_size); +    KScopedResourceReservation memory_reservation( +        resource_limit, LimitableResource::PhysicalMemoryMax, code_size + system_resource_size);      if (!memory_reservation.Succeeded()) {          LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",                    code_size + system_resource_size); @@ -395,6 +395,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:      default:          ASSERT(false); +        break;      }      // Create TLS region @@ -406,8 +407,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:  void KProcess::Run(s32 main_thread_priority, u64 stack_size) {      AllocateMainThreadStack(stack_size); -    resource_limit->Reserve(LimitableResource::Threads, 1); -    resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size); +    resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); +    resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, main_thread_stack_size);      const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)};      ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError()); @@ -442,7 +443,7 @@ void KProcess::PrepareForTermination() {      plr_address = 0;      if (resource_limit) { -        resource_limit->Release(LimitableResource::PhysicalMemory, +        resource_limit->Release(LimitableResource::PhysicalMemoryMax,                                  main_thread_stack_size + image_size);      } diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index 010dcf99e..b9d22b414 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -159,12 +159,13 @@ KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical      // TODO(bunnei): These values are the system defaults, the limits for service processes are      // lower. These should use the correct limit values. -    ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, physical_memory_size) +    ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, physical_memory_size)                 .IsSuccess()); -    ASSERT(resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess()); -    ASSERT(resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess()); -    ASSERT(resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200).IsSuccess()); -    ASSERT(resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess()); +    ASSERT(resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800).IsSuccess()); +    ASSERT(resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900).IsSuccess()); +    ASSERT( +        resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200).IsSuccess()); +    ASSERT(resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133).IsSuccess());      return resource_limit;  } diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index 65c98c979..2573d1b7c 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h @@ -16,15 +16,8 @@ class CoreTiming;  namespace Kernel {  class KernelCore; -enum class LimitableResource : u32 { -    PhysicalMemory = 0, -    Threads = 1, -    Events = 2, -    TransferMemory = 3, -    Sessions = 4, - -    Count, -}; + +using LimitableResource = Svc::LimitableResource;  constexpr bool IsValidResourceType(LimitableResource type) {      return type < LimitableResource::Count; diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index b1cabbca0..d6676904b 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -384,7 +384,8 @@ void KScheduler::SwitchThread(KThread* next_thread) {  void KScheduler::ScheduleImpl() {      // First, clear the needs scheduling bool. -    m_state.needs_scheduling.store(false, std::memory_order_seq_cst); +    m_state.needs_scheduling.store(false, std::memory_order_relaxed); +    std::atomic_thread_fence(std::memory_order_seq_cst);      // Load the appropriate thread pointers for scheduling.      KThread* const cur_thread{GetCurrentThreadPointer(kernel)}; @@ -400,7 +401,8 @@ void KScheduler::ScheduleImpl() {      // If there aren't, we want to check if the highest priority thread is the same as the current      // thread.      if (highest_priority_thread == cur_thread) { -        // If they're the same, then we can just return. +        // If they're the same, then we can just issue a memory barrier and return. +        std::atomic_thread_fence(std::memory_order_seq_cst);          return;      } @@ -476,7 +478,8 @@ void KScheduler::ScheduleImplFiber() {          // We failed to successfully do the context switch, and need to retry.          // Clear needs_scheduling. -        m_state.needs_scheduling.store(false, std::memory_order_seq_cst); +        m_state.needs_scheduling.store(false, std::memory_order_relaxed); +        std::atomic_thread_fence(std::memory_order_seq_cst);          // Refresh the highest priority thread.          highest_priority_thread = m_state.highest_priority_thread; diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h index 73314b45e..129d60472 100644 --- a/src/core/hle/kernel/k_scheduler_lock.h +++ b/src/core/hle/kernel/k_scheduler_lock.h @@ -60,6 +60,9 @@ public:          // Release an instance of the lock.          if ((--lock_count) == 0) { +            // Perform a memory barrier here. +            std::atomic_thread_fence(std::memory_order_seq_cst); +              // We're no longer going to hold the lock. Take note of what cores need scheduling.              const u64 cores_needing_scheduling =                  SchedulerType::UpdateHighestPriorityThreads(kernel); diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp index 7a6534ac3..b6f6fe9d9 100644 --- a/src/core/hle/kernel/k_session.cpp +++ b/src/core/hle/kernel/k_session.cpp @@ -76,7 +76,7 @@ void KSession::OnClientClosed() {  void KSession::PostDestroy(uintptr_t arg) {      // Release the session count resource the owner process holds.      KProcess* owner = reinterpret_cast<KProcess*>(arg); -    owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1); +    owner->GetResourceLimit()->Release(LimitableResource::SessionCountMax, 1);      owner->Close();  } diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index a039cc591..10cd4c43d 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -14,7 +14,7 @@ namespace Kernel {  KSharedMemory::KSharedMemory(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}  KSharedMemory::~KSharedMemory() { -    kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemory, size); +    kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemoryMax, size);  }  Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_, @@ -35,7 +35,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o      KResourceLimit* reslimit = kernel.GetSystemResourceLimit();      // Reserve memory for ourselves. -    KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemory, +    KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemoryMax,                                                    size_);      R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); @@ -57,7 +57,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o  void KSharedMemory::Finalize() {      // Release the memory reservation. -    resource_limit->Release(LimitableResource::PhysicalMemory, size); +    resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);      resource_limit->Close();      // Perform inherited finalization. diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index cc88d08f0..21207fe99 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -263,9 +263,9 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_      R_SUCCEED();  } -Result KThread::InitializeDummyThread(KThread* thread) { +Result KThread::InitializeDummyThread(KThread* thread, KProcess* owner) {      // Initialize the thread. -    R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy)); +    R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy));      // Initialize emulation parameters.      thread->stack_parameters.disable_count = 0; @@ -303,7 +303,7 @@ void KThread::PostDestroy(uintptr_t arg) {      const bool resource_limit_release_hint = (arg & 1);      const s64 hint_value = (resource_limit_release_hint ? 0 : 1);      if (owner != nullptr) { -        owner->GetResourceLimit()->Release(LimitableResource::Threads, 1, hint_value); +        owner->GetResourceLimit()->Release(LimitableResource::ThreadCountMax, 1, hint_value);          owner->Close();      }  } @@ -1054,7 +1054,7 @@ void KThread::Exit() {      // Release the thread resource hint, running thread count from parent.      if (parent != nullptr) { -        parent->GetResourceLimit()->Release(Kernel::LimitableResource::Threads, 0, 1); +        parent->GetResourceLimit()->Release(Kernel::LimitableResource::ThreadCountMax, 0, 1);          resource_limit_release_hint = true;          parent->DecrementRunningThreadCount();      } diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 30aa10c9a..f38c92bff 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -415,7 +415,7 @@ public:      static void PostDestroy(uintptr_t arg); -    [[nodiscard]] static Result InitializeDummyThread(KThread* thread); +    [[nodiscard]] static Result InitializeDummyThread(KThread* thread, KProcess* owner);      [[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread,                                                       s32 virt_core); diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp index b0320eb73..9f34c2d46 100644 --- a/src/core/hle/kernel/k_transfer_memory.cpp +++ b/src/core/hle/kernel/k_transfer_memory.cpp @@ -37,7 +37,7 @@ void KTransferMemory::Finalize() {  void KTransferMemory::PostDestroy(uintptr_t arg) {      KProcess* owner = reinterpret_cast<KProcess*>(arg); -    owner->GetResourceLimit()->Release(LimitableResource::TransferMemory, 1); +    owner->GetResourceLimit()->Release(LimitableResource::TransferMemoryCountMax, 1);      owner->Close();  } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index abff14079..b77723503 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -91,7 +91,7 @@ struct KernelCore::Impl {                                         pt_heap_region.GetSize());          } -        RegisterHostThread(); +        RegisterHostThread(nullptr);          default_service_thread = &CreateServiceThread(kernel, "DefaultServiceThread");      } @@ -229,18 +229,22 @@ struct KernelCore::Impl {          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) +        ASSERT( +            system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, total_size) +                .IsSuccess()); +        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800)                     .IsSuccess()); -        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess()); -        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess()); -        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200) +        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900)                     .IsSuccess()); -        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess()); -        system_resource_limit->Reserve(LimitableResource::PhysicalMemory, kernel_size); +        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200) +                   .IsSuccess()); +        ASSERT(system_resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133) +                   .IsSuccess()); +        system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, kernel_size);          // Reserve secure applet memory, introduced in firmware 5.0.0          constexpr u64 secure_applet_memory_size{4_MiB}; -        ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory, +        ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax,                                                secure_applet_memory_size));      } @@ -373,15 +377,18 @@ struct KernelCore::Impl {      }      // Gets the dummy KThread for the caller, allocating a new one if this is the first time -    KThread* GetHostDummyThread() { +    KThread* GetHostDummyThread(KThread* existing_thread) {          auto initialize = [this](KThread* thread) { -            ASSERT(KThread::InitializeDummyThread(thread).IsSuccess()); +            ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());              thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));              return thread;          }; -        thread_local auto raw_thread = KThread(system.Kernel()); -        thread_local auto thread = initialize(&raw_thread); +        thread_local KThread raw_thread{system.Kernel()}; +        thread_local KThread* thread = nullptr; +        if (thread == nullptr) { +            thread = (existing_thread == nullptr) ? initialize(&raw_thread) : existing_thread; +        }          return thread;      } @@ -396,9 +403,9 @@ struct KernelCore::Impl {      }      /// Registers a new host thread by allocating a host thread ID for it -    void RegisterHostThread() { +    void RegisterHostThread(KThread* existing_thread) {          [[maybe_unused]] const auto this_id = GetHostThreadId(); -        [[maybe_unused]] const auto dummy_thread = GetHostDummyThread(); +        [[maybe_unused]] const auto dummy_thread = GetHostDummyThread(existing_thread);      }      [[nodiscard]] u32 GetCurrentHostThreadID() { @@ -429,7 +436,7 @@ struct KernelCore::Impl {      KThread* GetCurrentEmuThread() {          const auto thread_id = GetCurrentHostThreadID();          if (thread_id >= Core::Hardware::NUM_CPU_CORES) { -            return GetHostDummyThread(); +            return GetHostDummyThread(nullptr);          }          return current_thread; @@ -1120,8 +1127,12 @@ void KernelCore::RegisterCoreThread(std::size_t core_id) {      impl->RegisterCoreThread(core_id);  } -void KernelCore::RegisterHostThread() { -    impl->RegisterHostThread(); +void KernelCore::RegisterHostThread(KThread* existing_thread) { +    impl->RegisterHostThread(existing_thread); + +    if (existing_thread != nullptr) { +        ASSERT(GetCurrentEmuThread() == existing_thread); +    }  }  u32 KernelCore::GetCurrentHostThreadID() const { @@ -1196,16 +1207,28 @@ void KernelCore::Suspend(bool suspended) {      const bool should_suspend{exception_exited || suspended};      const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; -    for (auto* process : GetProcessList()) { -        process->SetActivity(activity); +    std::vector<KScopedAutoObject<KThread>> process_threads; +    { +        KScopedSchedulerLock sl{*this}; + +        if (auto* process = CurrentProcess(); process != nullptr) { +            process->SetActivity(activity); + +            if (!should_suspend) { +                // Runnable now; no need to wait. +                return; +            } -        if (should_suspend) { -            // Wait for execution to stop              for (auto* thread : process->GetThreadList()) { -                thread->WaitUntilSuspended(); +                process_threads.emplace_back(thread);              }          }      } + +    // Wait for execution to stop. +    for (auto& thread : process_threads) { +        thread->WaitUntilSuspended(); +    }  }  void KernelCore::ShutdownCores() { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 29617d736..2e22fe0f6 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -240,7 +240,7 @@ public:      void RegisterCoreThread(std::size_t core_id);      /// Register the current thread as a non CPU core thread. -    void RegisterHostThread(); +    void RegisterHostThread(KThread* existing_thread = nullptr);      /// Gets the virtual memory manager for the kernel.      KMemoryManager& MemoryManager(); diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index d4375962f..3044922ac 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -12,7 +12,7 @@ namespace Kernel {  PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_)      : core_index{core_index_}, system{system_}, scheduler{scheduler_} { -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)      // TODO(bunnei): Initialization relies on a core being available. We may later replace this with      // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.      auto& kernel = system.Kernel(); @@ -26,7 +26,7 @@ PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KSche  PhysicalCore::~PhysicalCore() = default;  void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) { -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)      auto& kernel = system.Kernel();      if (!is_64_bit) {          // We already initialized a 64-bit core, replace with a 32-bit one. diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp index c8fe42537..e6e41ac34 100644 --- a/src/core/hle/kernel/service_thread.cpp +++ b/src/core/hle/kernel/service_thread.cpp @@ -36,11 +36,11 @@ public:  private:      KernelCore& kernel; -    std::jthread m_thread; +    std::jthread m_host_thread;      std::mutex m_session_mutex;      std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions;      KEvent* m_wakeup_event; -    KProcess* m_process; +    KThread* m_thread;      std::atomic<bool> m_shutdown_requested;      const std::string m_service_name;  }; @@ -132,7 +132,7 @@ void ServiceThread::Impl::SessionClosed(KServerSession* server_session,  void ServiceThread::Impl::LoopProcess() {      Common::SetCurrentThreadName(m_service_name.c_str()); -    kernel.RegisterHostThread(); +    kernel.RegisterHostThread(m_thread);      while (!m_shutdown_requested.load()) {          WaitAndProcessImpl(); @@ -160,7 +160,7 @@ ServiceThread::Impl::~Impl() {      // Shut down the processing thread.      m_shutdown_requested.store(true);      m_wakeup_event->Signal(); -    m_thread.join(); +    m_host_thread.join();      // Lock mutex.      m_session_mutex.lock(); @@ -177,33 +177,22 @@ ServiceThread::Impl::~Impl() {      m_wakeup_event->GetReadableEvent().Close();      m_wakeup_event->Close(); -    // Close process. -    m_process->Close(); +    // Close thread. +    m_thread->Close();  }  ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name)      : kernel{kernel_}, m_service_name{service_name} { -    // Initialize process. -    m_process = KProcess::Create(kernel); -    KProcess::Initialize(m_process, kernel.System(), service_name, -                         KProcess::ProcessType::KernelInternal, kernel.GetSystemResourceLimit()); - -    // Reserve a new event from the process resource limit -    KScopedResourceReservation event_reservation(m_process, LimitableResource::Events); -    ASSERT(event_reservation.Succeeded()); -      // Initialize event.      m_wakeup_event = KEvent::Create(kernel); -    m_wakeup_event->Initialize(m_process); - -    // Commit the event reservation. -    event_reservation.Commit(); +    m_wakeup_event->Initialize(nullptr); -    // Register the event. -    KEvent::Register(kernel, m_wakeup_event); +    // Initialize thread. +    m_thread = KThread::Create(kernel); +    ASSERT(KThread::InitializeDummyThread(m_thread, nullptr).IsSuccess());      // Start thread. -    m_thread = std::jthread([this] { LoopProcess(); }); +    m_host_thread = std::jthread([this] { LoopProcess(); });  }  ServiceThread::ServiceThread(KernelCore& kernel, const std::string& name) diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index ecac97a52..e520cab47 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -267,7 +267,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien      // Reserve a new session from the process resource limit.      // FIXME: LimitableResource_SessionCountMax -    KScopedResourceReservation session_reservation(&process, LimitableResource::Sessions); +    KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax);      if (session_reservation.Succeeded()) {          session = T::Create(system.Kernel());      } else { @@ -298,7 +298,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien          // We successfully allocated a session, so add the object we allocated to the resource          // limit. -        // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::Sessions, 1); +        // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1);      }      // Check that we successfully created a session. @@ -656,27 +656,12 @@ static Result ArbitrateUnlock32(Core::System& system, u32 address) {      return ArbitrateUnlock(system, address);  } -enum class BreakType : u32 { -    Panic = 0, -    AssertionFailed = 1, -    PreNROLoad = 3, -    PostNROLoad = 4, -    PreNROUnload = 5, -    PostNROUnload = 6, -    CppException = 7, -}; - -struct BreakReason { -    union { -        u32 raw; -        BitField<0, 30, BreakType> break_type; -        BitField<31, 1, u32> signal_debugger; -    }; -}; -  /// Break program execution  static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { -    BreakReason break_reason{reason}; +    BreakReason break_reason = +        static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag)); +    bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0; +      bool has_dumped_buffer{};      std::vector<u8> debug_buffer; @@ -705,57 +690,56 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {          }          has_dumped_buffer = true;      }; -    switch (break_reason.break_type) { -    case BreakType::Panic: -        LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}", -                     info1, info2); +    switch (break_reason) { +    case BreakReason::Panic: +        LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, +                     info2);          handle_debug_buffer(info1, info2);          break; -    case BreakType::AssertionFailed: -        LOG_CRITICAL(Debug_Emulated, -                     "Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}", +    case BreakReason::Assert: +        LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}",                       info1, info2);          handle_debug_buffer(info1, info2);          break; -    case BreakType::PreNROLoad: -        LOG_WARNING( -            Debug_Emulated, -            "Signalling debugger, Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", -            info1, info2); +    case BreakReason::User: +        LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); +        handle_debug_buffer(info1, info2);          break; -    case BreakType::PostNROLoad: -        LOG_WARNING(Debug_Emulated, -                    "Signalling debugger, Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, -                    info2); +    case BreakReason::PreLoadDll: +        LOG_INFO(Debug_Emulated, +                 "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, +                 info2);          break; -    case BreakType::PreNROUnload: -        LOG_WARNING( -            Debug_Emulated, -            "Signalling debugger, Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", -            info1, info2); +    case BreakReason::PostLoadDll: +        LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, +                 info2);          break; -    case BreakType::PostNROUnload: -        LOG_WARNING(Debug_Emulated, -                    "Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1, -                    info2); +    case BreakReason::PreUnloadDll: +        LOG_INFO(Debug_Emulated, +                 "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, +                 info2);          break; -    case BreakType::CppException: +    case BreakReason::PostUnloadDll: +        LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", +                 info1, info2); +        break; +    case BreakReason::CppException:          LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered.");          break;      default:          LOG_WARNING(              Debug_Emulated, -            "Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}", -            static_cast<u32>(break_reason.break_type.Value()), info1, info2); +            "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", +            reason, info1, info2);          handle_debug_buffer(info1, info2);          break;      } -    system.GetReporter().SaveSvcBreakReport( -        static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger.As<bool>(), -        info1, info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); +    system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2, +                                            has_dumped_buffer ? std::make_optional(debug_buffer) +                                                              : std::nullopt); -    if (!break_reason.signal_debugger) { +    if (!notification_only) {          LOG_CRITICAL(              Debug_Emulated,              "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", @@ -1716,13 +1700,13 @@ static Result QueryProcessMemory(Core::System& system, VAddr memory_info_address      auto& memory{system.Memory()};      const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()}; -    memory.Write64(memory_info_address + 0x00, memory_info.addr); +    memory.Write64(memory_info_address + 0x00, memory_info.base_address);      memory.Write64(memory_info_address + 0x08, memory_info.size);      memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff); -    memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attr)); -    memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.perm)); -    memory.Write32(memory_info_address + 0x1c, memory_info.ipc_refcount); -    memory.Write32(memory_info_address + 0x20, memory_info.device_refcount); +    memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute)); +    memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission)); +    memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count); +    memory.Write32(memory_info_address + 0x20, memory_info.device_count);      memory.Write32(memory_info_address + 0x24, 0);      // Page info appears to be currently unused by the kernel and is always set to zero. @@ -1943,7 +1927,7 @@ static Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry      // Reserve a new thread from the process resource limit (waiting up to 100ms).      KScopedResourceReservation thread_reservation( -        kernel.CurrentProcess(), LimitableResource::Threads, 1, +        kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1,          system.CoreTiming().GetGlobalTimeNs().count() + 100000000);      if (!thread_reservation.Succeeded()) {          LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); @@ -2344,7 +2328,7 @@ static Result CreateTransferMemory(Core::System& system, Handle* out, VAddr addr      // Reserve a new transfer memory from the process resource limit.      KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), -                                                 LimitableResource::TransferMemory); +                                                 LimitableResource::TransferMemoryCountMax);      R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached);      // Create the transfer memory. @@ -2496,7 +2480,7 @@ static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_r      // Reserve a new event from the process resource limit      KScopedResourceReservation event_reservation(kernel.CurrentProcess(), -                                                 LimitableResource::Events); +                                                 LimitableResource::EventCountMax);      R_UNLESS(event_reservation.Succeeded(), ResultLimitReached);      // Create a new event. @@ -2539,11 +2523,6 @@ static Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out  static Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {      LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); -    // This function currently only allows retrieving a process' status. -    enum class InfoType { -        Status, -    }; -      const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();      KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle);      if (process.IsNull()) { @@ -2552,9 +2531,9 @@ static Result GetProcessInfo(Core::System& system, u64* out, Handle process_hand          return ResultInvalidHandle;      } -    const auto info_type = static_cast<InfoType>(type); -    if (info_type != InfoType::Status) { -        LOG_ERROR(Kernel_SVC, "Expected info_type to be Status but got {} instead", type); +    const auto info_type = static_cast<ProcessInfoType>(type); +    if (info_type != ProcessInfoType::ProcessState) { +        LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type);          return ResultInvalidEnumValue;      } @@ -2722,14 +2701,24 @@ static Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr ou      return ResultSuccess;  } -static Result FlushProcessDataCache32([[maybe_unused]] Core::System& system, -                                      [[maybe_unused]] Handle handle, [[maybe_unused]] u32 address, -                                      [[maybe_unused]] u32 size) { -    // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op, -    // as all emulation is done in the same cache level in host architecture, thus data cache -    // does not need flushing. -    LOG_DEBUG(Kernel_SVC, "called"); -    return ResultSuccess; +static Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, +                                      u64 size) { +    // Validate address/size. +    R_UNLESS(size > 0, ResultInvalidSize); +    R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); +    R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); + +    // Get the process from its handle. +    KScopedAutoObject process = +        system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); +    R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + +    // Verify the region is within range. +    auto& page_table = process->PageTable(); +    R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + +    // Perform the operation. +    R_RETURN(system.Memory().FlushDataCache(*process, address, size));  }  namespace { diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 9b0305552..33eebcef6 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -8,6 +8,8 @@  namespace Kernel::Svc { +using Handle = u32; +  enum class MemoryState : u32 {      Free = 0x00,      Io = 0x01, @@ -55,17 +57,6 @@ enum class MemoryPermission : u32 {  };  DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission); -struct MemoryInfo { -    u64 addr{}; -    u64 size{}; -    MemoryState state{}; -    MemoryAttribute attr{}; -    MemoryPermission perm{}; -    u32 ipc_refcount{}; -    u32 device_refcount{}; -    u32 padding{}; -}; -  enum class SignalType : u32 {      Signal = 0,      SignalAndIncrementIfEqual = 1, @@ -124,7 +115,57 @@ enum class ProcessExitReason : u32 {  constexpr inline size_t ThreadLocalRegionSize = 0x200; -// Debug types. +struct PageInfo { +    u32 flags; +}; + +// Info Types. +enum class InfoType : u32 { +    CoreMask = 0, +    PriorityMask = 1, +    AliasRegionAddress = 2, +    AliasRegionSize = 3, +    HeapRegionAddress = 4, +    HeapRegionSize = 5, +    TotalMemorySize = 6, +    UsedMemorySize = 7, +    DebuggerAttached = 8, +    ResourceLimit = 9, +    IdleTickCount = 10, +    RandomEntropy = 11, +    AslrRegionAddress = 12, +    AslrRegionSize = 13, +    StackRegionAddress = 14, +    StackRegionSize = 15, +    SystemResourceSizeTotal = 16, +    SystemResourceSizeUsed = 17, +    ProgramId = 18, +    InitialProcessIdRange = 19, +    UserExceptionContextAddress = 20, +    TotalNonSystemMemorySize = 21, +    UsedNonSystemMemorySize = 22, +    IsApplication = 23, +    FreeThreadCount = 24, +    ThreadTickCount = 25, +    IsSvcPermitted = 26, + +    MesosphereMeta = 65000, +    MesosphereCurrentProcess = 65001, +}; + +enum class BreakReason : u32 { +    Panic = 0, +    Assert = 1, +    User = 2, +    PreLoadDll = 3, +    PostLoadDll = 4, +    PreUnloadDll = 5, +    PostUnloadDll = 6, +    CppException = 7, + +    NotificationOnlyFlag = 0x80000000, +}; +  enum class DebugEvent : u32 {      CreateProcess = 0,      CreateThread = 1, @@ -133,6 +174,14 @@ enum class DebugEvent : u32 {      Exception = 4,  }; +enum class DebugThreadParam : u32 { +    Priority = 0, +    State = 1, +    IdealCore = 2, +    CurrentCore = 3, +    AffinityMask = 4, +}; +  enum class DebugException : u32 {      UndefinedInstruction = 0,      InstructionAbort = 1, @@ -146,4 +195,401 @@ enum class DebugException : u32 {      MemorySystemError = 9,  }; +enum class DebugEventFlag : u32 { +    Stopped = (1u << 0), +}; + +enum class BreakPointType : u32 { +    HardwareInstruction = 0, +    HardwareData = 1, +}; + +enum class HardwareBreakPointRegisterName : u32 { +    I0 = 0, +    I1 = 1, +    I2 = 2, +    I3 = 3, +    I4 = 4, +    I5 = 5, +    I6 = 6, +    I7 = 7, +    I8 = 8, +    I9 = 9, +    I10 = 10, +    I11 = 11, +    I12 = 12, +    I13 = 13, +    I14 = 14, +    I15 = 15, +    D0 = 16, +    D1 = 17, +    D2 = 18, +    D3 = 19, +    D4 = 20, +    D5 = 21, +    D6 = 22, +    D7 = 23, +    D8 = 24, +    D9 = 25, +    D10 = 26, +    D11 = 27, +    D12 = 28, +    D13 = 29, +    D14 = 30, +    D15 = 31, +}; + +namespace lp64 { +struct LastThreadContext { +    u64 fp; +    u64 sp; +    u64 lr; +    u64 pc; +}; + +struct PhysicalMemoryInfo { +    PAddr physical_address; +    u64 virtual_address; +    u64 size; +}; + +struct DebugInfoCreateProcess { +    u64 program_id; +    u64 process_id; +    std::array<char, 0xC> name; +    u32 flags; +    u64 user_exception_context_address; // 5.0.0+ +}; + +struct DebugInfoCreateThread { +    u64 thread_id; +    u64 tls_address; +    // Removed in 11.0.0 u64 entrypoint; +}; + +struct DebugInfoExitProcess { +    ProcessExitReason reason; +}; + +struct DebugInfoExitThread { +    ThreadExitReason reason; +}; + +struct DebugInfoUndefinedInstructionException { +    u32 insn; +}; + +struct DebugInfoDataAbortException { +    u64 address; +}; + +struct DebugInfoAlignmentFaultException { +    u64 address; +}; + +struct DebugInfoBreakPointException { +    BreakPointType type; +    u64 address; +}; + +struct DebugInfoUserBreakException { +    BreakReason break_reason; +    u64 address; +    u64 size; +}; + +struct DebugInfoDebuggerBreakException { +    std::array<u64, 4> active_thread_ids; +}; + +struct DebugInfoUndefinedSystemCallException { +    u32 id; +}; + +union DebugInfoSpecificException { +    DebugInfoUndefinedInstructionException undefined_instruction; +    DebugInfoDataAbortException data_abort; +    DebugInfoAlignmentFaultException alignment_fault; +    DebugInfoBreakPointException break_point; +    DebugInfoUserBreakException user_break; +    DebugInfoDebuggerBreakException debugger_break; +    DebugInfoUndefinedSystemCallException undefined_system_call; +    u64 raw; +}; + +struct DebugInfoException { +    DebugException type; +    u64 address; +    DebugInfoSpecificException specific; +}; + +union DebugInfo { +    DebugInfoCreateProcess create_process; +    DebugInfoCreateThread create_thread; +    DebugInfoExitProcess exit_process; +    DebugInfoExitThread exit_thread; +    DebugInfoException exception; +}; + +struct DebugEventInfo { +    DebugEvent type; +    u32 flags; +    u64 thread_id; +    DebugInfo info; +}; +static_assert(sizeof(DebugEventInfo) >= 0x40); + +struct SecureMonitorArguments { +    std::array<u64, 8> r; +}; +static_assert(sizeof(SecureMonitorArguments) == 0x40); +} // namespace lp64 + +namespace ilp32 { +struct LastThreadContext { +    u32 fp; +    u32 sp; +    u32 lr; +    u32 pc; +}; + +struct PhysicalMemoryInfo { +    PAddr physical_address; +    u32 virtual_address; +    u32 size; +}; + +struct DebugInfoCreateProcess { +    u64 program_id; +    u64 process_id; +    std::array<char, 0xC> name; +    u32 flags; +    u32 user_exception_context_address; // 5.0.0+ +}; + +struct DebugInfoCreateThread { +    u64 thread_id; +    u32 tls_address; +    // Removed in 11.0.0 u32 entrypoint; +}; + +struct DebugInfoExitProcess { +    ProcessExitReason reason; +}; + +struct DebugInfoExitThread { +    ThreadExitReason reason; +}; + +struct DebugInfoUndefinedInstructionException { +    u32 insn; +}; + +struct DebugInfoDataAbortException { +    u32 address; +}; + +struct DebugInfoAlignmentFaultException { +    u32 address; +}; + +struct DebugInfoBreakPointException { +    BreakPointType type; +    u32 address; +}; + +struct DebugInfoUserBreakException { +    BreakReason break_reason; +    u32 address; +    u32 size; +}; + +struct DebugInfoDebuggerBreakException { +    std::array<u64, 4> active_thread_ids; +}; + +struct DebugInfoUndefinedSystemCallException { +    u32 id; +}; + +union DebugInfoSpecificException { +    DebugInfoUndefinedInstructionException undefined_instruction; +    DebugInfoDataAbortException data_abort; +    DebugInfoAlignmentFaultException alignment_fault; +    DebugInfoBreakPointException break_point; +    DebugInfoUserBreakException user_break; +    DebugInfoDebuggerBreakException debugger_break; +    DebugInfoUndefinedSystemCallException undefined_system_call; +    u64 raw; +}; + +struct DebugInfoException { +    DebugException type; +    u32 address; +    DebugInfoSpecificException specific; +}; + +union DebugInfo { +    DebugInfoCreateProcess create_process; +    DebugInfoCreateThread create_thread; +    DebugInfoExitProcess exit_process; +    DebugInfoExitThread exit_thread; +    DebugInfoException exception; +}; + +struct DebugEventInfo { +    DebugEvent type; +    u32 flags; +    u64 thread_id; +    DebugInfo info; +}; + +struct SecureMonitorArguments { +    std::array<u32, 8> r; +}; +static_assert(sizeof(SecureMonitorArguments) == 0x20); +} // namespace ilp32 + +struct ThreadContext { +    std::array<u64, 29> r; +    u64 fp; +    u64 lr; +    u64 sp; +    u64 pc; +    u32 pstate; +    u32 padding; +    std::array<u128, 32> v; +    u32 fpcr; +    u32 fpsr; +    u64 tpidr; +}; +static_assert(sizeof(ThreadContext) == 0x320); + +struct MemoryInfo { +    u64 base_address; +    u64 size; +    MemoryState state; +    MemoryAttribute attribute; +    MemoryPermission permission; +    u32 ipc_count; +    u32 device_count; +    u32 padding; +}; + +enum class LimitableResource : u32 { +    PhysicalMemoryMax = 0, +    ThreadCountMax = 1, +    EventCountMax = 2, +    TransferMemoryCountMax = 3, +    SessionCountMax = 4, +    Count, +}; + +enum class IoPoolType : u32 { +    // Not supported. +    Count = 0, +}; + +enum class MemoryMapping : u32 { +    IoRegister = 0, +    Uncached = 1, +    Memory = 2, +}; + +enum class KernelDebugType : u32 { +    Thread = 0, +    ThreadCallStack = 1, +    KernelObject = 2, +    Handle_ = 3, +    Memory = 4, +    PageTable = 5, +    CpuUtilization = 6, +    Process = 7, +    SuspendProcess = 8, +    ResumeProcess = 9, +    Port = 10, +}; + +enum class KernelTraceState : u32 { +    Disabled = 0, +    Enabled = 1, +}; + +enum class CodeMemoryOperation : u32 { +    Map = 0, +    MapToOwner = 1, +    Unmap = 2, +    UnmapFromOwner = 3, +}; + +enum class InterruptType : u32 { +    Edge = 0, +    Level = 1, +}; + +enum class DeviceName { +    Afi = 0, +    Avpc = 1, +    Dc = 2, +    Dcb = 3, +    Hc = 4, +    Hda = 5, +    Isp2 = 6, +    MsencNvenc = 7, +    Nv = 8, +    Nv2 = 9, +    Ppcs = 10, +    Sata = 11, +    Vi = 12, +    Vic = 13, +    XusbHost = 14, +    XusbDev = 15, +    Tsec = 16, +    Ppcs1 = 17, +    Dc1 = 18, +    Sdmmc1a = 19, +    Sdmmc2a = 20, +    Sdmmc3a = 21, +    Sdmmc4a = 22, +    Isp2b = 23, +    Gpu = 24, +    Gpub = 25, +    Ppcs2 = 26, +    Nvdec = 27, +    Ape = 28, +    Se = 29, +    Nvjpg = 30, +    Hc1 = 31, +    Se1 = 32, +    Axiap = 33, +    Etr = 34, +    Tsecb = 35, +    Tsec1 = 36, +    Tsecb1 = 37, +    Nvdec1 = 38, +    Count, +}; + +enum class SystemInfoType : u32 { +    TotalPhysicalMemorySize = 0, +    UsedPhysicalMemorySize = 1, +    InitialProcessIdRange = 2, +}; + +enum class ProcessInfoType : u32 { +    ProcessState = 0, +}; + +struct CreateProcessParameter { +    std::array<char, 12> name; +    u32 version; +    u64 program_id; +    u64 code_address; +    s32 code_num_pages; +    u32 flags; +    Handle reslimit; +    s32 system_resource_num_pages; +}; +static_assert(sizeof(CreateProcessParameter) == 0x30); +  } // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 272c54cf7..3730937fe 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -722,4 +722,12 @@ void SvcWrap32(Core::System& system) {      FuncReturn(system, retval);  } +// Used by Invalidate/Store/FlushProcessDataCache32 +template <Result func(Core::System&, Handle, u64, u64)> +void SvcWrap32(Core::System& system) { +    const u64 address = (Param(system, 3) << 32) | Param(system, 2); +    const u64 size = (Param(system, 4) << 32) | Param(system, 1); +    FuncReturn32(system, func(system, Param32(system, 0), address, size).raw); +} +  } // namespace Kernel diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 56c990728..240f06689 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -28,30 +28,49 @@ enum class ErrorModule : u32 {      Loader = 9,      CMIF = 10,      HIPC = 11, +    TMA = 12, +    DMNT = 13, +    GDS = 14,      PM = 15,      NS = 16, +    BSDSockets = 17,      HTC = 18, +    TSC = 19,      NCMContent = 20,      SM = 21,      RO = 22, +    GC = 23,      SDMMC = 24,      OVLN = 25,      SPL = 26, +    Socket = 27, +    HTCLOW = 29, +    DDSF = 30, +    HTCFS = 31, +    Async = 32, +    Util = 33, +    TIPC = 35, +    ANIF = 37,      ETHC = 100,      I2C = 101,      GPIO = 102,      UART = 103, +    CPAD = 104,      Settings = 105, +    FTM = 106,      WLAN = 107,      XCD = 108, +    TMP451 = 109,      NIFM = 110,      Hwopus = 111, +    LSM6DS3 = 112,      Bluetooth = 113,      VI = 114,      NFP = 115,      Time = 116,      FGM = 117,      OE = 118, +    BH1730FVC = 119,      PCIe = 120,      Friends = 121,      BCAT = 122, @@ -65,7 +84,7 @@ enum class ErrorModule : u32 {      AHID = 130,      Qlaunch = 132,      PCV = 133, -    OMM = 134, +    USBPD = 134,      BPC = 135,      PSM = 136,      NIM = 137, @@ -75,18 +94,22 @@ enum class ErrorModule : u32 {      NSD = 141,      PCTL = 142,      BTM = 143, +    LA = 144,      ETicket = 145,      NGC = 146,      ERPT = 147,      APM = 148, +    CEC = 149,      Profiler = 150,      ErrorUpload = 151, +    LIDBE = 152,      Audio = 153,      NPNS = 154,      NPNSHTTPSTREAM = 155,      ARP = 157,      SWKBD = 158,      BOOT = 159, +    NetDiag = 160,      NFCMifare = 161,      UserlandAssert = 162,      Fatal = 163, @@ -94,17 +117,68 @@ enum class ErrorModule : u32 {      SPSM = 165,      BGTC = 167,      UserlandCrash = 168, +    SASBUS = 169, +    PI = 170, +    AudioCtrl = 172, +    LBL = 173, +    JIT = 175, +    HDCP = 176, +    OMM = 177, +    PDM = 178, +    OLSC = 179,      SREPO = 180,      Dauth = 181, +    STDFU = 182, +    DBG = 183, +    DHCPS = 186, +    SPI = 187, +    AVM = 188, +    PWM = 189, +    RTC = 191, +    Regulator = 192, +    LED = 193, +    SIO = 195, +    PCM = 196, +    CLKRST = 197, +    POWCTL = 198, +    AudioOld = 201,      HID = 202,      LDN = 203, +    CS = 204,      Irsensor = 205,      Capture = 206,      Manu = 208,      ATK = 209, +    WEB = 210, +    LCS = 211,      GRC = 212, +    Repair = 213, +    Album = 214, +    RID = 215,      Migration = 216,      MigrationLdcServ = 217, +    HIDBUS = 218, +    ENS = 219, +    WebSocket = 223, +    DCDMTP = 227, +    PGL = 228, +    Notification = 229, +    INS = 230, +    LP2P = 231, +    RCD = 232, +    LCM40607 = 233, +    PRC = 235, +    TMAHTC = 237, +    ECTX = 238, +    MNPP = 239, +    HSHL = 240, +    CAPMTP = 242, +    DP2HDMI = 244, +    Cradle = 245, +    SProfile = 246, +    NDRM = 250, +    TSPM = 499, +    DevMenu = 500,      GeneralWebApplet = 800,      WifiWebAuthApplet = 809,      WhitelistedApplet = 810, diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp new file mode 100644 index 000000000..d0969b0f1 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_cabinet.cpp @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/frontend/applets/cabinet.h" +#include "core/hid/hid_core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applet_cabinet.h" +#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/nfp/nfp_device.h" + +namespace Service::AM::Applets { + +Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_, +                 const Core::Frontend::CabinetApplet& frontend_) +    : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_}, service_context{ +                                                                               system_, +                                                                               "CabinetApplet"} { + +    availability_change_event = +        service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent"); +} + +Cabinet::~Cabinet() = default; + +void Cabinet::Initialize() { +    Applet::Initialize(); + +    LOG_INFO(Service_HID, "Initializing Cabinet Applet."); + +    LOG_DEBUG(Service_HID, +              "Initializing Applet with common_args: arg_version={}, lib_version={}, " +              "play_startup_sound={}, size={}, system_tick={}, theme_color={}", +              common_args.arguments_version, common_args.library_version, +              common_args.play_startup_sound, common_args.size, common_args.system_tick, +              common_args.theme_color); + +    const auto storage = broker.PopNormalDataToApplet(); +    ASSERT(storage != nullptr); + +    const auto applet_input_data = storage->GetData(); +    ASSERT(applet_input_data.size() >= sizeof(StartParamForAmiiboSettings)); + +    std::memcpy(&applet_input_common, applet_input_data.data(), +                sizeof(StartParamForAmiiboSettings)); +} + +bool Cabinet::TransactionComplete() const { +    return is_complete; +} + +Result Cabinet::GetStatus() const { +    return ResultSuccess; +} + +void Cabinet::ExecuteInteractive() { +    ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet."); +} + +void Cabinet::Execute() { +    if (is_complete) { +        return; +    } + +    const auto callback = [this](bool apply_changes, const std::string& amiibo_name) { +        DisplayCompleted(apply_changes, amiibo_name); +    }; + +    // TODO: listen on all controllers +    if (nfp_device == nullptr) { +        nfp_device = std::make_shared<Service::NFP::NfpDevice>( +            system.HIDCore().GetFirstNpadId(), system, service_context, availability_change_event); +        nfp_device->Initialize(); +        nfp_device->StartDetection(Service::NFP::TagProtocol::All); +    } + +    const Core::Frontend::CabinetParameters parameters{ +        .tag_info = applet_input_common.tag_info, +        .register_info = applet_input_common.register_info, +        .mode = applet_input_common.applet_mode, +    }; + +    switch (applet_input_common.applet_mode) { +    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: +    case Service::NFP::CabinetMode::StartGameDataEraser: +    case Service::NFP::CabinetMode::StartRestorer: +    case Service::NFP::CabinetMode::StartFormatter: +        frontend.ShowCabinetApplet(callback, parameters, nfp_device); +        break; +    default: +        UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode); +        DisplayCompleted(false, {}); +        break; +    } +} + +void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) { +    Service::Mii::MiiManager manager; +    ReturnValueForAmiiboSettings applet_output{}; + +    if (!apply_changes) { +        Cancel(); +    } + +    if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound && +        nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) { +        Cancel(); +    } + +    if (nfp_device->GetCurrentState() == Service::NFP::DeviceState::TagFound) { +        nfp_device->Mount(Service::NFP::MountTarget::All); +    } + +    switch (applet_input_common.applet_mode) { +    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: { +        Service::NFP::AmiiboName name{}; +        std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1)); +        nfp_device->SetNicknameAndOwner(name); +        break; +    } +    case Service::NFP::CabinetMode::StartGameDataEraser: +        nfp_device->DeleteApplicationArea(); +        break; +    case Service::NFP::CabinetMode::StartRestorer: +        nfp_device->RestoreAmiibo(); +        break; +    case Service::NFP::CabinetMode::StartFormatter: +        nfp_device->DeleteAllData(); +        break; +    default: +        UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode); +        break; +    } + +    applet_output.device_handle = applet_input_common.device_handle; +    applet_output.result = CabinetResult::Cancel; +    const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info); +    const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info); +    nfp_device->Finalize(); + +    if (reg_result.IsSuccess()) { +        applet_output.result |= CabinetResult::RegisterInfo; +    } + +    if (tag_result.IsSuccess()) { +        applet_output.result |= CabinetResult::TagInfo; +    } + +    std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings)); +    std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings)); + +    is_complete = true; + +    broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); +    broker.SignalStateChanged(); +} + +void Cabinet::Cancel() { +    ReturnValueForAmiiboSettings applet_output{}; +    applet_output.device_handle = applet_input_common.device_handle; +    applet_output.result = CabinetResult::Cancel; +    nfp_device->Finalize(); + +    std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings)); +    std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings)); + +    is_complete = true; + +    broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data))); +    broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h new file mode 100644 index 000000000..84197a807 --- /dev/null +++ b/src/core/hle/service/am/applets/applet_cabinet.h @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nfp/nfp_types.h" + +namespace Kernel { +class KEvent; +class KReadableEvent; +} // namespace Kernel + +namespace Core { +class System; +} // namespace Core + +namespace Service::NFP { +class NfpDevice; +} + +namespace Service::AM::Applets { + +enum class CabinetAppletVersion : u32 { +    Version1 = 0x1, +}; + +enum class CabinetResult : u8 { +    Cancel = 0, +    TagInfo = 1 << 1, +    RegisterInfo = 1 << 2, +    All = TagInfo | RegisterInfo, +}; +DECLARE_ENUM_FLAG_OPERATORS(CabinetResult) + +// This is nn::nfp::AmiiboSettingsStartParam +struct AmiiboSettingsStartParam { +    u64 device_handle; +    std::array<u8, 0x20> param_1; +    u8 param_2; +}; +static_assert(sizeof(AmiiboSettingsStartParam) == 0x30, +              "AmiiboSettingsStartParam is an invalid size"); + +#pragma pack(push, 1) +// This is nn::nfp::StartParamForAmiiboSettings +struct StartParamForAmiiboSettings { +    u8 param_1; +    Service::NFP::CabinetMode applet_mode; +    u8 flags; +    u8 amiibo_settings_1; +    u64 device_handle; +    Service::NFP::TagInfo tag_info; +    Service::NFP::RegisterInfo register_info; +    std::array<u8, 0x20> amiibo_settings_3; +    INSERT_PADDING_BYTES(0x24); +}; +static_assert(sizeof(StartParamForAmiiboSettings) == 0x1A8, +              "StartParamForAmiiboSettings is an invalid size"); + +// This is nn::nfp::ReturnValueForAmiiboSettings +struct ReturnValueForAmiiboSettings { +    CabinetResult result; +    INSERT_PADDING_BYTES(0x3); +    u64 device_handle; +    Service::NFP::TagInfo tag_info; +    Service::NFP::RegisterInfo register_info; +    INSERT_PADDING_BYTES(0x24); +}; +static_assert(sizeof(ReturnValueForAmiiboSettings) == 0x188, +              "ReturnValueForAmiiboSettings is an invalid size"); +#pragma pack(pop) + +class Cabinet final : public Applet { +public: +    explicit Cabinet(Core::System& system_, LibraryAppletMode applet_mode_, +                     const Core::Frontend::CabinetApplet& frontend_); +    ~Cabinet() override; + +    void Initialize() override; + +    bool TransactionComplete() const override; +    Result GetStatus() const override; +    void ExecuteInteractive() override; +    void Execute() override; +    void DisplayCompleted(bool apply_changes, std::string_view amiibo_name); +    void Cancel(); + +private: +    const Core::Frontend::CabinetApplet& frontend; +    Core::System& system; + +    bool is_complete{false}; +    std::shared_ptr<Service::NFP::NfpDevice> nfp_device; +    Kernel::KEvent* availability_change_event; +    KernelHelpers::ServiceContext service_context; +    StartParamForAmiiboSettings applet_input_common{}; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp index fcf34bf7e..bae0d99a6 100644 --- a/src/core/hle/service/am/applets/applet_error.cpp +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -144,6 +144,7 @@ void Error::Initialize() {          break;      default:          UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); +        break;      }  } 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 c34ef08b3..e50acdaf6 100644 --- a/src/core/hle/service/am/applets/applet_general_backend.cpp +++ b/src/core/hle/service/am/applets/applet_general_backend.cpp @@ -129,6 +129,7 @@ void Auth::Execute() {      }      default:          unimplemented_log(); +        break;      }  } @@ -192,6 +193,7 @@ void PhotoViewer::Execute() {          break;      default:          UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode); +        break;      }  } diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 7062df21c..10afbc2da 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -5,6 +5,7 @@  #include "common/assert.h"  #include "core/core.h" +#include "core/frontend/applets/cabinet.h"  #include "core/frontend/applets/controller.h"  #include "core/frontend/applets/error.h"  #include "core/frontend/applets/general_frontend.h" @@ -16,6 +17,7 @@  #include "core/hle/service/am/am.h"  #include "core/hle/service/am/applet_ae.h"  #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/am/applets/applet_cabinet.h"  #include "core/hle/service/am/applets/applet_controller.h"  #include "core/hle/service/am/applets/applet_error.h"  #include "core/hle/service/am/applets/applet_general_backend.h" @@ -171,13 +173,15 @@ void Applet::Initialize() {  AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, +AppletFrontendSet::AppletFrontendSet(CabinetApplet cabinet_applet, +                                     ControllerApplet controller_applet, ErrorApplet error_applet,                                       MiiEdit mii_edit_,                                       ParentalControlsApplet parental_controls_applet,                                       PhotoViewer photo_viewer_, ProfileSelect profile_select_,                                       SoftwareKeyboard software_keyboard_, WebBrowser web_browser_) -    : controller{std::move(controller_applet)}, error{std::move(error_applet)}, -      mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)}, +    : cabinet{std::move(cabinet_applet)}, controller{std::move(controller_applet)}, +      error{std::move(error_applet)}, mii_edit{std::move(mii_edit_)}, +      parental_controls{std::move(parental_controls_applet)},        photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},        software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {} @@ -196,6 +200,10 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {  }  void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { +    if (set.cabinet != nullptr) { +        frontend.cabinet = std::move(set.cabinet); +    } +      if (set.controller != nullptr) {          frontend.controller = std::move(set.controller);      } @@ -235,6 +243,10 @@ void AppletManager::SetDefaultAppletFrontendSet() {  }  void AppletManager::SetDefaultAppletsIfMissing() { +    if (frontend.cabinet == nullptr) { +        frontend.cabinet = std::make_unique<Core::Frontend::DefaultCabinetApplet>(); +    } +      if (frontend.controller == nullptr) {          frontend.controller =              std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore()); @@ -279,6 +291,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode      switch (id) {      case AppletId::Auth:          return std::make_shared<Auth>(system, mode, *frontend.parental_controls); +    case AppletId::Cabinet: +        return std::make_shared<Cabinet>(system, mode, *frontend.cabinet);      case AppletId::Controller:          return std::make_shared<Controller>(system, mode, *frontend.controller);      case AppletId::Error: diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index 12c6a5b1a..a22eb62a8 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -16,6 +16,7 @@ class System;  }  namespace Core::Frontend { +class CabinetApplet;  class ControllerApplet;  class ECommerceApplet;  class ErrorApplet; @@ -176,6 +177,7 @@ protected:  };  struct AppletFrontendSet { +    using CabinetApplet = std::unique_ptr<Core::Frontend::CabinetApplet>;      using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;      using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;      using MiiEdit = std::unique_ptr<Core::Frontend::MiiEditApplet>; @@ -186,10 +188,11 @@ struct AppletFrontendSet {      using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;      AppletFrontendSet(); -    AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, -                      MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet, -                      PhotoViewer photo_viewer_, ProfileSelect profile_select_, -                      SoftwareKeyboard software_keyboard_, WebBrowser web_browser_); +    AppletFrontendSet(CabinetApplet cabinet_applet, ControllerApplet controller_applet, +                      ErrorApplet error_applet, MiiEdit mii_edit_, +                      ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, +                      ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, +                      WebBrowser web_browser_);      ~AppletFrontendSet();      AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -198,6 +201,7 @@ struct AppletFrontendSet {      AppletFrontendSet(AppletFrontendSet&&) noexcept;      AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; +    CabinetApplet cabinet;      ControllerApplet controller;      ErrorApplet error;      MiiEdit mii_edit; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 79375bd2f..bf28440c6 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -36,8 +36,9 @@ namespace Service::HID {  // Updating period for each HID device.  // Period time is obtained by measuring the number of samples in a second on HW using a homebrew -// Correct pad_update_ns is 4ms this is overclocked to lower input lag -constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz) +// Correct npad_update_ns is 4ms this is overclocked to lower input lag +constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000};    // (1ms, 1000Hz) +constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)  constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)  constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000};         // (5ms, 200Hz) @@ -75,11 +76,19 @@ IAppletResource::IAppletResource(Core::System& system_,      GetController<Controller_Stubbed>(HidController::UniquePad).SetCommonHeaderOffset(0x5A00);      // Register update callbacks -    pad_update_event = Core::Timing::CreateEvent( +    npad_update_event = Core::Timing::CreateEvent(          "HID::UpdatePadCallback",          [this](std::uintptr_t user_data, s64 time,                 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {              const auto guard = LockService(); +            UpdateNpad(user_data, ns_late); +            return std::nullopt; +        }); +    default_update_event = Core::Timing::CreateEvent( +        "HID::UpdateDefaultCallback", +        [this](std::uintptr_t user_data, s64 time, +               std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { +            const auto guard = LockService();              UpdateControllers(user_data, ns_late);              return std::nullopt;          }); @@ -100,7 +109,9 @@ IAppletResource::IAppletResource(Core::System& system_,              return std::nullopt;          }); -    system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event); +    system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event); +    system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns, +                                             default_update_event);      system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,                                               mouse_keyboard_update_event);      system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns, @@ -118,7 +129,8 @@ void IAppletResource::DeactivateController(HidController controller) {  }  IAppletResource::~IAppletResource() { -    system.CoreTiming().UnscheduleEvent(pad_update_event, 0); +    system.CoreTiming().UnscheduleEvent(npad_update_event, 0); +    system.CoreTiming().UnscheduleEvent(default_update_event, 0);      system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);      system.CoreTiming().UnscheduleEvent(motion_update_event, 0);  } @@ -144,10 +156,20 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,          if (controller == controllers[static_cast<size_t>(HidController::Mouse)]) {              continue;          } +        // Npad has it's own update event +        if (controller == controllers[static_cast<size_t>(HidController::NPad)]) { +            continue; +        }          controller->OnUpdate(core_timing);      }  } +void IAppletResource::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { +    auto& core_timing = system.CoreTiming(); + +    controllers[static_cast<size_t>(HidController::NPad)]->OnUpdate(core_timing); +} +  void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,                                            std::chrono::nanoseconds ns_late) {      auto& core_timing = system.CoreTiming(); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 340d26fdc..b7c2a23ef 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -71,12 +71,14 @@ private:      void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);      void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); +    void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);      void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);      void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);      KernelHelpers::ServiceContext& service_context; -    std::shared_ptr<Core::Timing::EventType> pad_update_event; +    std::shared_ptr<Core::Timing::EventType> npad_update_event; +    std::shared_ptr<Core::Timing::EventType> default_update_event;      std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;      std::shared_ptr<Core::Timing::EventType> motion_update_event; diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp index af133af93..42991928e 100644 --- a/src/core/hle/service/kernel_helpers.cpp +++ b/src/core/hle/service/kernel_helpers.cpp @@ -31,7 +31,7 @@ ServiceContext::~ServiceContext() {  Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {      // Reserve a new event from the process resource limit      Kernel::KScopedResourceReservation event_reservation(process, -                                                         Kernel::LimitableResource::Events); +                                                         Kernel::LimitableResource::EventCountMax);      if (!event_reservation.Succeeded()) {          LOG_CRITICAL(Service, "Resource limit reached!");          return {}; diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index b19672560..e1bf90d7c 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -77,6 +77,9 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {          LoadAmiibo(nfc_status.data);          break;      case Common::Input::NfcState::AmiiboRemoved: +        if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { +            break; +        }          if (device_state != DeviceState::SearchingForTag) {              CloseAmiibo();          } @@ -97,6 +100,8 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) {          return false;      } +    // TODO: Filter by allowed_protocols here +      memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));      device_state = DeviceState::TagFound; @@ -143,7 +148,7 @@ void NfpDevice::Finalize() {      device_state = DeviceState::Unavailable;  } -Result NfpDevice::StartDetection(s32 protocol_) { +Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {      if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {          LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);          return WrongDeviceState; @@ -155,7 +160,7 @@ Result NfpDevice::StartDetection(s32 protocol_) {      }      device_state = DeviceState::SearchingForTag; -    protocol = protocol_; +    allowed_protocols = allowed_protocol;      return ResultSuccess;  } @@ -469,6 +474,32 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {      return ResultSuccess;  } +Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const { +    application_area_id = {}; + +    if (device_state != DeviceState::TagMounted) { +        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); +        if (device_state == DeviceState::TagRemoved) { +            return TagRemoved; +        } +        return WrongDeviceState; +    } + +    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { +        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); +        return WrongDeviceState; +    } + +    if (tag_data.settings.settings.appdata_initialized.Value() == 0) { +        LOG_WARNING(Service_NFP, "Application area is not initialized"); +        return ApplicationAreaIsNotInitialized; +    } + +    application_area_id = tag_data.application_area_id; + +    return ResultSuccess; +} +  Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {      if (device_state != DeviceState::TagMounted) {          LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h index 76d0e9ae4..3d1cb4609 100644 --- a/src/core/hle/service/nfp/nfp_device.h +++ b/src/core/hle/service/nfp/nfp_device.h @@ -4,6 +4,7 @@  #pragma once  #include <array> +#include <span>  #include <vector>  #include "common/common_funcs.h" @@ -37,7 +38,7 @@ public:      void Initialize();      void Finalize(); -    Result StartDetection(s32 protocol_); +    Result StartDetection(TagProtocol allowed_protocol);      Result StopDetection();      Result Mount(MountTarget mount_target);      Result Unmount(); @@ -53,6 +54,7 @@ public:      Result DeleteAllData();      Result OpenApplicationArea(u32 access_id); +    Result GetApplicationAreaId(u32& application_area_id) const;      Result GetApplicationArea(std::vector<u8>& data) const;      Result SetApplicationArea(std::span<const u8> data);      Result CreateApplicationArea(u32 access_id, std::span<const u8> data); @@ -88,7 +90,7 @@ private:      bool is_data_moddified{};      bool is_app_area_open{}; -    s32 protocol{}; +    TagProtocol allowed_protocols{};      s64 current_posix_time{};      MountTarget mount_target{MountTarget::None};      DeviceState device_state{DeviceState::Unavailable}; diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 63d5917cb..69858096a 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -88,11 +88,22 @@ enum class PackedTagType : u8 {      Type5, // ISO15693 RW/RO 540 bytes 106kbit/s  }; +// Verify this enum. It might be completely wrong default protocol is 0x48  enum class TagProtocol : u32 {      None, -    TypeA, // ISO14443A -    TypeB, // ISO14443B -    TypeF, // Sony Felica +    TypeA = 1U << 0, // ISO14443A +    TypeB = 1U << 1, // ISO14443B +    TypeF = 1U << 2, // Sony Felica +    Unknown1 = 1U << 3, +    Unknown2 = 1U << 5, +    All = 0xFFFFFFFFU, +}; + +enum class CabinetMode : u8 { +    StartNicknameAndOwnerSettings, +    StartGameDataEraser, +    StartRestorer, +    StartFormatter,  };  using UniqueSerialNumber = std::array<u8, 7>; diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index 33e2ef518..ac492cc27 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -130,7 +130,7 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {  void IUser::StartDetection(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      const auto device_handle{rp.Pop<u64>()}; -    const auto nfp_protocol{rp.Pop<s32>()}; +    const auto nfp_protocol{rp.PopEnum<TagProtocol>()};      LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);      if (state == State::NonInitialized) { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index ced57dfe6..b97813fbc 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -300,11 +300,10 @@ Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {          return error_notifier_event;      case 2:          return unknown_event; -    default: { +    default:          LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id); +        return nullptr;      } -    } -    return nullptr;  }  } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 45a759fa8..e123564c6 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -364,11 +364,10 @@ Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {          return sm_exception_breakpoint_pause_report_event;      case 3:          return error_notifier_event; -    default: { +    default:          LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id); +        return nullptr;      } -    } -    return nullptr;  }  } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.cpp b/src/core/hle/service/nvflinger/buffer_queue_core.cpp index ea4a14ea4..3d1338e66 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp @@ -23,15 +23,17 @@ void BufferQueueCore::NotifyShutdown() {  }  void BufferQueueCore::SignalDequeueCondition() { +    dequeue_possible.store(true);      dequeue_condition.notify_all();  } -bool BufferQueueCore::WaitForDequeueCondition() { +bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {      if (is_shutting_down) {          return false;      } -    dequeue_condition.wait(mutex); +    dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); }); +    dequeue_possible.store(false);      return true;  } diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.h b/src/core/hle/service/nvflinger/buffer_queue_core.h index ca6baefaf..85b3bc4c1 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_core.h +++ b/src/core/hle/service/nvflinger/buffer_queue_core.h @@ -38,7 +38,7 @@ public:  private:      void SignalDequeueCondition(); -    bool WaitForDequeueCondition(); +    bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);      s32 GetMinUndequeuedBufferCountLocked(bool async) const;      s32 GetMinMaxBufferCountLocked(bool async) const; @@ -60,7 +60,8 @@ private:      BufferQueueDefs::SlotsType slots{};      std::vector<BufferItem> queue;      s32 override_max_buffer_count{}; -    mutable std::condition_variable_any dequeue_condition; +    std::condition_variable dequeue_condition; +    std::atomic<bool> dequeue_possible{};      const bool use_async_buffer{}; // This is always disabled on HOS      bool dequeue_buffer_cannot_block{};      PixelFormat default_buffer_format{PixelFormat::Rgba8888}; diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp index 41ba44b21..e601b5da1 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp @@ -121,8 +121,8 @@ Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {      return Status::NoError;  } -Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, -                                                      Status* return_flags) const { +Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags, +                                                      std::unique_lock<std::mutex>& lk) const {      bool try_again = true;      while (try_again) { @@ -214,7 +214,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found,                  return Status::WouldBlock;              } -            if (!core->WaitForDequeueCondition()) { +            if (!core->WaitForDequeueCondition(lk)) {                  // We are no longer running                  return Status::NoError;              } @@ -237,7 +237,7 @@ Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool      Status return_flags = Status::NoError;      bool attached_by_consumer = false;      { -        std::scoped_lock lock{core->mutex}; +        std::unique_lock lock{core->mutex};          core->WaitWhileAllocatingLocked();          if (format == PixelFormat::NoFormat) { @@ -248,7 +248,7 @@ Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool          usage |= core->consumer_usage_bit;          s32 found{}; -        Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags); +        Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock);          if (status != Status::NoError) {              return status;          } @@ -400,13 +400,13 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,          return Status::BadValue;      } -    std::scoped_lock lock{core->mutex}; +    std::unique_lock lock{core->mutex};      core->WaitWhileAllocatingLocked();      Status return_flags = Status::NoError;      s32 found{}; -    const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags); +    const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock);      if (status != Status::NoError) {          return status;      } diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.h b/src/core/hle/service/nvflinger/buffer_queue_producer.h index 7526bf8ec..1d380480f 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h @@ -70,7 +70,8 @@ public:  private:      BufferQueueProducer(const BufferQueueProducer&) = delete; -    Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags) const; +    Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags, +                                     std::unique_lock<std::mutex>& lk) const;      Kernel::KEvent* buffer_wait_event{};      Service::KernelHelpers::ServiceContext& service_context; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 5ab41c0c4..0de67f1e1 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -228,6 +228,7 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,          }          UNIMPLEMENTED_MSG("command_type={}", ctx.GetCommandType()); +        break;      }      // If emulation was shutdown, we are closing service threads, do not write the response back to diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp index 69e0fe808..1cf9dd1c4 100644 --- a/src/core/hle/service/sm/sm_controller.cpp +++ b/src/core/hle/service/sm/sm_controller.cpp @@ -34,8 +34,8 @@ void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {      // once this is a proper process      // Reserve a new session from the process resource limit. -    Kernel::KScopedResourceReservation session_reservation(&process, -                                                           Kernel::LimitableResource::Sessions); +    Kernel::KScopedResourceReservation session_reservation( +        &process, Kernel::LimitableResource::SessionCountMax);      ASSERT(session_reservation.Succeeded());      // Create the session. diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index 2aa675df9..f9ada7c93 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -280,6 +280,7 @@ static constexpr int TransitionTime(int year, Rule rule, int offset) {      }      default:          ASSERT(false); +        break;      }      return value + rule.transition_time + offset;  } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 3ca80c8ff..3141122f1 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -6,6 +6,7 @@  #include "common/assert.h"  #include "common/atomic_ops.h" +#include "common/cache_management.h"  #include "common/common_types.h"  #include "common/logging/log.h"  #include "common/page_table.h" @@ -329,6 +330,55 @@ struct Memory::Impl {              });      } +    template <typename Callback> +    Result PerformCacheOperation(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size, +                                 Callback&& cb) { +        class InvalidMemoryException : public std::exception {}; + +        try { +            WalkBlock( +                process, dest_addr, size, +                [&](const std::size_t block_size, const VAddr current_vaddr) { +                    LOG_ERROR(HW_Memory, "Unmapped cache maintenance @ {:#018X}", current_vaddr); +                    throw InvalidMemoryException(); +                }, +                [&](const std::size_t block_size, u8* const host_ptr) { cb(block_size, host_ptr); }, +                [&](const VAddr current_vaddr, const std::size_t block_size, u8* const host_ptr) { +                    system.GPU().FlushRegion(current_vaddr, block_size); +                    cb(block_size, host_ptr); +                }, +                [](const std::size_t block_size) {}); +        } catch (InvalidMemoryException&) { +            return Kernel::ResultInvalidCurrentMemory; +        } + +        return ResultSuccess; +    } + +    Result InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) { +        auto perform = [&](const std::size_t block_size, u8* const host_ptr) { +            // Do nothing; this operation (dc ivac) cannot be supported +            // from EL0 +        }; +        return PerformCacheOperation(process, dest_addr, size, perform); +    } + +    Result StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) { +        auto perform = [&](const std::size_t block_size, u8* const host_ptr) { +            // dc cvac: Store to point of coherency +            Common::DataCacheLineCleanByVAToPoC(host_ptr, block_size); +        }; +        return PerformCacheOperation(process, dest_addr, size, perform); +    } + +    Result FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) { +        auto perform = [&](const std::size_t block_size, u8* const host_ptr) { +            // dc civac: Store to point of coherency, and invalidate from cache +            Common::DataCacheLineCleanAndInvalidateByVAToPoC(host_ptr, block_size); +        }; +        return PerformCacheOperation(process, dest_addr, size, perform); +    } +      void MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {          if (vaddr == 0) {              return; @@ -786,6 +836,21 @@ void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const s      impl->ZeroBlock(process, dest_addr, size);  } +Result Memory::InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr, +                                   const std::size_t size) { +    return impl->InvalidateDataCache(process, dest_addr, size); +} + +Result Memory::StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr, +                              const std::size_t size) { +    return impl->StoreDataCache(process, dest_addr, size); +} + +Result Memory::FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr, +                              const std::size_t size) { +    return impl->FlushDataCache(process, dest_addr, size); +} +  void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {      impl->RasterizerMarkRegionCached(vaddr, size, cached);  } diff --git a/src/core/memory.h b/src/core/memory.h index 81eac448b..31fe699d8 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -7,6 +7,7 @@  #include <memory>  #include <string>  #include "common/common_types.h" +#include "core/hle/result.h"  namespace Common {  struct PageTable; @@ -450,6 +451,39 @@ public:      void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);      /** +     * Invalidates a range of bytes within the current process' address space at the specified +     * virtual address. +     * +     * @param process   The process that will have data invalidated within its address space. +     * @param dest_addr The destination virtual address to invalidate the data from. +     * @param size      The size of the range to invalidate, in bytes. +     * +     */ +    Result InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); + +    /** +     * Stores a range of bytes within the current process' address space at the specified +     * virtual address. +     * +     * @param process   The process that will have data stored within its address space. +     * @param dest_addr The destination virtual address to store the data from. +     * @param size      The size of the range to store, in bytes. +     * +     */ +    Result StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); + +    /** +     * Flushes a range of bytes within the current process' address space at the specified +     * virtual address. +     * +     * @param process   The process that will have data flushed within its address space. +     * @param dest_addr The destination virtual address to flush the data from. +     * @param size      The size of the range to flush, in bytes. +     * +     */ +    Result FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); + +    /**       * Marks each page within the specified address range as cached or uncached.       *       * @param vaddr  The virtual address indicating the start of the address range. diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 0cd5129da..564a188e5 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp @@ -60,6 +60,8 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData(          return Common::Input::NfcState::WriteFailed;      } +    amiibo_data = data; +      return Common::Input::NfcState::Success;  } @@ -91,6 +93,15 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {      return Info::Success;  } +VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { +    if (state == State::AmiiboIsOpen) { +        SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data}); +        return Info::Success; +    } + +    return LoadAmiibo(file_path); +} +  VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {      state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo                                                              : State::Initialized; @@ -98,4 +109,8 @@ VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {      return Info::Success;  } +std::string VirtualAmiibo::GetLastFilePath() const { +    return file_path; +} +  } // namespace InputCommon diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 9eac07544..9baeb3997 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h @@ -47,8 +47,11 @@ public:      State GetCurrentState() const;      Info LoadAmiibo(const std::string& amiibo_file); +    Info ReloadAmiibo();      Info CloseAmiibo(); +    std::string GetLastFilePath() const; +  private:      static constexpr std::size_t amiibo_size = 0x21C;      static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8; diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp index da4a3dca5..003a38da5 100644 --- a/src/input_common/helpers/touch_from_buttons.cpp +++ b/src/input_common/helpers/touch_from_buttons.cpp @@ -10,8 +10,8 @@ namespace InputCommon {  class TouchFromButtonDevice final : public Common::Input::InputDevice {  public:      using Button = std::unique_ptr<Common::Input::InputDevice>; -    TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_) -        : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) { +    TouchFromButtonDevice(Button button_, float x_, float y_) +        : button(std::move(button_)), x(x_), y(y_) {          last_button_value = false;          button->SetCallback({              .on_change = @@ -34,7 +34,6 @@ public:              .pressed = button_status,              .x = {},              .y = {}, -            .id = touch_id,          };          status.x.properties = properties;          status.y.properties = properties; @@ -62,7 +61,6 @@ public:  private:      Button button;      bool last_button_value; -    const int touch_id;      const float x;      const float y;      const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false}; @@ -73,10 +71,9 @@ std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(      const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();      auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(          params.Get("button", null_engine)); -    const auto touch_id = params.Get("touch_id", 0);      const float x = params.Get("x", 0.0f) / 1280.0f;      const float y = params.Get("y", 0.0f) / 720.0f; -    return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y); +    return std::make_unique<TouchFromButtonDevice>(std::move(button), x, y);  }  } // namespace InputCommon diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index d4c264a8e..6cbcf5207 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -133,7 +133,7 @@ public:          return Common::Input::CameraError::NotSupported;      } -    // Request nfc data from a controller +    // Returns success if nfc is supported      virtual Common::Input::NfcState SupportsNfc(          [[maybe_unused]] const PadIdentifier& identifier) const {          return Common::Input::NfcState::NotSupported; diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 4ac182147..fb8be42e2 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -229,13 +229,12 @@ private:  class InputFromTouch final : public Common::Input::InputDevice {  public: -    explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_, -                            bool inverted_, int axis_x_, int axis_y_, -                            Common::Input::AnalogProperties properties_x_, +    explicit InputFromTouch(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, +                            int axis_x_, int axis_y_, Common::Input::AnalogProperties properties_x_,                              Common::Input::AnalogProperties properties_y_,                              InputEngine* input_engine_) -        : identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_), -          inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), +        : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), +          axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),            properties_y(properties_y_), input_engine(input_engine_) {          UpdateCallback engine_callback{[this]() { OnChange(); }};          const InputIdentifier button_input_identifier{ @@ -271,8 +270,7 @@ public:      }      Common::Input::TouchStatus GetStatus() const { -        Common::Input::TouchStatus status; -        status.id = touch_id; +        Common::Input::TouchStatus status{};          status.pressed = {              .value = input_engine->GetButton(identifier, button),              .inverted = inverted, @@ -307,7 +305,6 @@ public:  private:      const PadIdentifier identifier; -    const int touch_id;      const int button;      const bool toggle;      const bool inverted; @@ -919,7 +916,6 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(  std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(      const Common::ParamPackage& params) { -    const auto touch_id = params.Get("touch_id", 0);      const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);      const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);      const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); @@ -954,8 +950,8 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(      input_engine->PreSetAxis(identifier, axis_x);      input_engine->PreSetAxis(identifier, axis_y);      input_engine->PreSetButton(identifier, button); -    return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x, -                                            axis_y, properties_x, properties_y, input_engine.get()); +    return std::make_unique<InputFromTouch>(identifier, button, toggle, inverted, axis_x, axis_y, +                                            properties_x, properties_y, input_engine.get());  }  std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice( diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp index 3b0176bf6..0cb1e193e 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp @@ -320,6 +320,7 @@ void SetupOptions(const IR::Program& program, const Profile& profile,      }      if (stage == Stage::Fragment) {          header += "OPTION ARB_draw_buffers;"; +        header += "OPTION ARB_fragment_layer_viewport;";      }  } diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp index 0a7d42dda..f0bd84ab2 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp @@ -104,6 +104,9 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr, Scal      case IR::Attribute::PrimitiveId:          ctx.Add("MOV.F {}.x,primitive.id;", inst);          break; +    case IR::Attribute::Layer: +        ctx.Add("MOV.F {}.x,fragment.layer;", inst); +        break;      case IR::Attribute::PositionX:      case IR::Attribute::PositionY:      case IR::Attribute::PositionZ: @@ -379,6 +382,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {      ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst);  } +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { +    switch (ctx.stage) { +    case Stage::TessellationControl: +    case Stage::TessellationEval: +        ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst); +        break; +    default: +        LOG_WARNING(Shader, "(STUBBED) called"); +        ctx.Add("MOV.S {}.x,0x00ff0000;", inst); +    } +} +  void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {      ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst);  } diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h index d645fd532..eaaf9ba39 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h +++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h @@ -69,6 +69,7 @@ void EmitSetOFlag(EmitContext& ctx);  void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);  void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);  void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);  void EmitSampleId(EmitContext& ctx, IR::Inst& inst);  void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);  void EmitYDirection(EmitContext& ctx, IR::Inst& inst); diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp index 89603c1c4..333a91cc5 100644 --- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp +++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp @@ -95,6 +95,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile      if (info.uses_invocation_id) {          Add("ATTRIB primitive_invocation=primitive.invocation;");      } +    if (info.uses_invocation_info && +        (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) { +        Add("ATTRIB primitive_vertexcount = primitive.vertexcount;"); +    }      if (info.stores_tess_level_outer) {          Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};");      } diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp index d7c845469..39579cf5d 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp @@ -205,6 +205,9 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr,      case IR::Attribute::PrimitiveId:          ctx.AddF32("{}=itof(gl_PrimitiveID);", inst);          break; +    case IR::Attribute::Layer: +        ctx.AddF32("{}=itof(gl_Layer);", inst); +        break;      case IR::Attribute::PositionX:      case IR::Attribute::PositionY:      case IR::Attribute::PositionZ: @@ -399,6 +402,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {      ctx.AddU32("{}=uint(gl_InvocationID);", inst);  } +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { +    switch (ctx.stage) { +    case Stage::TessellationControl: +    case Stage::TessellationEval: +        ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst); +        break; +    default: +        LOG_WARNING(Shader, "(STUBBED) called"); +        ctx.AddU32("{}=uint(0x00ff0000);", inst); +    } +} +  void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {      ctx.AddU32("{}=uint(gl_SampleID);", inst);  } diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h index 96e683b5e..4151c89de 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h +++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h @@ -83,6 +83,7 @@ void EmitSetOFlag(EmitContext& ctx);  void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);  void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);  void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);  void EmitSampleId(EmitContext& ctx, IR::Inst& inst);  void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);  void EmitYDirection(EmitContext& ctx, IR::Inst& inst); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index a4751b42d..01f6ec9b5 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -315,6 +315,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {      switch (attr) {      case IR::Attribute::PrimitiveId:          return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.primitive_id)); +    case IR::Attribute::Layer: +        return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.layer));      case IR::Attribute::PositionX:      case IR::Attribute::PositionY:      case IR::Attribute::PositionZ: @@ -512,6 +514,18 @@ Id EmitInvocationId(EmitContext& ctx) {      return ctx.OpLoad(ctx.U32[1], ctx.invocation_id);  } +Id EmitInvocationInfo(EmitContext& ctx) { +    switch (ctx.stage) { +    case Stage::TessellationControl: +    case Stage::TessellationEval: +        return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.patch_vertices_in), +                                      ctx.Const(16u)); +    default: +        LOG_WARNING(Shader, "(STUBBED) called"); +        return ctx.Const(0x00ff0000u); +    } +} +  Id EmitSampleId(EmitContext& ctx) {      return ctx.OpLoad(ctx.U32[1], ctx.sample_id);  } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 7070c8fda..e31cdc5e8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -72,6 +72,7 @@ void EmitSetOFlag(EmitContext& ctx);  Id EmitWorkgroupId(EmitContext& ctx);  Id EmitLocalInvocationId(EmitContext& ctx);  Id EmitInvocationId(EmitContext& ctx); +Id EmitInvocationInfo(EmitContext& ctx);  Id EmitSampleId(EmitContext& ctx);  Id EmitIsHelperInvocation(EmitContext& ctx);  Id EmitYDirection(EmitContext& ctx); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index c26ad8f93..8e3e40cd5 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1325,6 +1325,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {      if (info.uses_invocation_id) {          invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId);      } +    if (info.uses_invocation_info && +        (stage == Shader::Stage::TessellationControl || stage == Shader::Stage::TessellationEval)) { +        patch_vertices_in = DefineInput(*this, U32[1], false, spv::BuiltIn::PatchVertices); +    }      if (info.uses_sample_id) {          sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId);      } @@ -1355,6 +1359,11 @@ void EmitContext::DefineInputs(const IR::Program& program) {      if (loads[IR::Attribute::PrimitiveId]) {          primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId);      } +    if (loads[IR::Attribute::Layer]) { +        AddCapability(spv::Capability::Geometry); +        layer = DefineInput(*this, U32[1], false, spv::BuiltIn::Layer); +        Decorate(layer, spv::Decoration::Flat); +    }      if (loads.AnyComponent(IR::Attribute::PositionX)) {          const bool is_fragment{stage != Stage::Fragment};          const spv::BuiltIn built_in{is_fragment ? spv::BuiltIn::Position : spv::BuiltIn::FragCoord}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index c86e50911..dde45b4bc 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -204,6 +204,7 @@ public:      Id workgroup_id{};      Id local_invocation_id{};      Id invocation_id{}; +    Id patch_vertices_in{};      Id sample_id{};      Id is_helper_invocation{};      Id subgroup_local_invocation_id{}; diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp index d4425f06d..0cdac0eff 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp +++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp @@ -362,6 +362,10 @@ U32 IREmitter::InvocationId() {      return Inst<U32>(Opcode::InvocationId);  } +U32 IREmitter::InvocationInfo() { +    return Inst<U32>(Opcode::InvocationInfo); +} +  U32 IREmitter::SampleId() {      return Inst<U32>(Opcode::SampleId);  } diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h index f163c18d9..2df992feb 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.h +++ b/src/shader_recompiler/frontend/ir/ir_emitter.h @@ -97,6 +97,7 @@ public:      [[nodiscard]] U32 LocalInvocationIdZ();      [[nodiscard]] U32 InvocationId(); +    [[nodiscard]] U32 InvocationInfo();      [[nodiscard]] U32 SampleId();      [[nodiscard]] U1 IsHelperInvocation();      [[nodiscard]] F32 YDirection(); diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc index 88aa077ee..1fe3749cc 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.inc +++ b/src/shader_recompiler/frontend/ir/opcodes.inc @@ -59,6 +59,7 @@ OPCODE(SetOFlag,                                            Void,           U1,  OPCODE(WorkgroupId,                                         U32x3,                                                                                          )  OPCODE(LocalInvocationId,                                   U32x3,                                                                                          )  OPCODE(InvocationId,                                        U32,                                                                                            ) +OPCODE(InvocationInfo,                                      U32,                                                                                            )  OPCODE(SampleId,                                            U32,                                                                                            )  OPCODE(IsHelperInvocation,                                  U1,                                                                                             )  OPCODE(YDirection,                                          F32,                                                                                            ) diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h index 1e37c8eb6..5077e56c2 100644 --- a/src/shader_recompiler/frontend/ir/patch.h +++ b/src/shader_recompiler/frontend/ir/patch.h @@ -14,8 +14,6 @@ enum class Patch : u64 {      TessellationLodBottom,      TessellationLodInteriorU,      TessellationLodInteriorV, -    ComponentPadding0, -    ComponentPadding1,      Component0,      Component1,      Component2, @@ -137,7 +135,7 @@ enum class Patch : u64 {      Component118,      Component119,  }; -static_assert(static_cast<u64>(Patch::Component119) == 127); +static_assert(static_cast<u64>(Patch::Component119) == 125);  [[nodiscard]] bool IsGeneric(Patch patch) noexcept; diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp index 52be12f9c..753c62098 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp @@ -117,8 +117,7 @@ enum class SpecialRegister : u64 {      case SpecialRegister::SR_THREAD_KILL:          return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))};      case SpecialRegister::SR_INVOCATION_INFO: -        LOG_WARNING(Shader, "(STUBBED) SR_INVOCATION_INFO"); -        return ir.Imm32(0x00ff'0000); +        return ir.InvocationInfo();      case SpecialRegister::SR_TID: {          const IR::Value tid{ir.LocalInvocationId()};          return ir.BitFieldInsert(ir.BitFieldInsert(IR::U32{ir.CompositeExtract(tid, 0)}, diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index b7162f719..376aae0ea 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -223,7 +223,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo      Optimization::PositionPass(env, program);      Optimization::GlobalMemoryToStorageBufferPass(program); -    Optimization::TexturePass(env, program); +    Optimization::TexturePass(env, program, host_info);      if (Settings::values.resolution_info.active) {          Optimization::RescalingPass(program); diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h index 881874310..cc1500690 100644 --- a/src/shader_recompiler/host_translate_info.h +++ b/src/shader_recompiler/host_translate_info.h @@ -13,6 +13,7 @@ struct HostTranslateInfo {      bool support_float16{};      ///< True when the device supports 16-bit floats      bool support_int64{};        ///< True when the device supports 64-bit integers      bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered +    bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers  };  } // namespace Shader diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 7cff8ecdc..5a4195217 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -468,6 +468,9 @@ void VisitUsages(Info& info, IR::Inst& inst) {      case IR::Opcode::InvocationId:          info.uses_invocation_id = true;          break; +    case IR::Opcode::InvocationInfo: +        info.uses_invocation_info = true; +        break;      case IR::Opcode::SampleId:          info.uses_sample_id = true;          break; diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h index 24f609d69..586a0668f 100644 --- a/src/shader_recompiler/ir_opt/passes.h +++ b/src/shader_recompiler/ir_opt/passes.h @@ -6,6 +6,10 @@  #include "shader_recompiler/environment.h"  #include "shader_recompiler/frontend/ir/program.h" +namespace Shader { +struct HostTranslateInfo; +} +  namespace Shader::Optimization {  void CollectShaderInfoPass(Environment& env, IR::Program& program); @@ -18,7 +22,7 @@ void LowerInt64ToInt32(IR::Program& program);  void RescalingPass(IR::Program& program);  void SsaRewritePass(IR::Program& program);  void PositionPass(Environment& env, IR::Program& program); -void TexturePass(Environment& env, IR::Program& program); +void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info);  void VerificationPass(const IR::Program& program);  // Dual Vertex diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 9eff84a3d..f5c86fcb1 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -7,11 +7,11 @@  #include <boost/container/small_vector.hpp> -#include "common/settings.h"  #include "shader_recompiler/environment.h"  #include "shader_recompiler/frontend/ir/basic_block.h"  #include "shader_recompiler/frontend/ir/breadth_first_search.h"  #include "shader_recompiler/frontend/ir/ir_emitter.h" +#include "shader_recompiler/host_translate_info.h"  #include "shader_recompiler/ir_opt/passes.h"  #include "shader_recompiler/shader_info.h" @@ -461,7 +461,7 @@ void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {                          ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));  } -void PathTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) { +void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) {      const auto it{IR::Block::InstructionList::s_iterator_to(inst)};      IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};      auto get_max_value = [pixel_format]() -> float { @@ -494,7 +494,7 @@ void PathTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_f  }  } // Anonymous namespace -void TexturePass(Environment& env, IR::Program& program) { +void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info) {      TextureInstVector to_replace;      for (IR::Block* const block : program.post_order_blocks) {          for (IR::Inst& inst : block->Instructions()) { @@ -639,11 +639,11 @@ void TexturePass(Environment& env, IR::Program& program) {              inst->SetArg(0, IR::Value{});          } -        if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && -            inst->GetOpcode() == IR::Opcode::ImageFetch && flags.type == TextureType::Buffer) { +        if (!host_info.support_snorm_render_buffer && inst->GetOpcode() == IR::Opcode::ImageFetch && +            flags.type == TextureType::Buffer) {              const auto pixel_format = ReadTexturePixelFormat(env, cbuf);              if (pixel_format != TexturePixelFormat::OTHER) { -                PathTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format); +                PatchTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format);              }          }      } diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index f31e1f821..ee6252bb5 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -127,6 +127,7 @@ struct Info {      bool uses_workgroup_id{};      bool uses_local_invocation_id{};      bool uses_invocation_id{}; +    bool uses_invocation_info{};      bool uses_sample_id{};      bool uses_is_helper_invocation{};      bool uses_subgroup_invocation_id{}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 106991969..d7f7d336c 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -73,8 +73,6 @@ add_library(video_core STATIC      macro/macro_hle.h      macro/macro_interpreter.cpp      macro/macro_interpreter.h -    macro/macro_jit_x64.cpp -    macro/macro_jit_x64.h      fence_manager.h      gpu.cpp      gpu.h @@ -245,7 +243,7 @@ add_library(video_core STATIC  create_target_directory_groups(video_core)  target_link_libraries(video_core PUBLIC common core) -target_link_libraries(video_core PUBLIC glad shader_recompiler xbyak) +target_link_libraries(video_core PUBLIC glad shader_recompiler)  if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32)      add_dependencies(video_core ffmpeg-build) @@ -282,8 +280,19 @@ else()          -Wno-sign-conversion      ) + +    # xbyak +    set_source_files_properties(macro/macro_jit_x64.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-shadow")  endif()  if (ARCHITECTURE_x86_64) +    target_sources(video_core PRIVATE +        macro/macro_jit_x64.cpp +        macro/macro_jit_x64.h +    ) +    target_link_libraries(video_core PUBLIC xbyak) +endif() + +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)      target_link_libraries(video_core PRIVATE dynarmic)  endif() diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 4a2f2c1fd..5bb1427c1 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -232,7 +232,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume          use_topology_override = true;          return;      case MAXWELL3D_REG_INDEX(clear_surface): -        return ProcessClearBuffers(); +        return ProcessClearBuffers(1);      case MAXWELL3D_REG_INDEX(report_semaphore.query):          return ProcessQueryGet();      case MAXWELL3D_REG_INDEX(render_enable.mode): @@ -249,6 +249,11 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume          return;      case MAXWELL3D_REG_INDEX(fragment_barrier):          return rasterizer->FragmentBarrier(); +    case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache): +        rasterizer->InvalidateGPUCache(); +        return rasterizer->WaitForIdle(); +    case MAXWELL3D_REG_INDEX(tiled_cache_barrier): +        return rasterizer->TiledCacheBarrier();      }  } @@ -591,8 +596,8 @@ u32 Maxwell3D::GetRegisterValue(u32 method) const {      return regs.reg_array[method];  } -void Maxwell3D::ProcessClearBuffers() { -    rasterizer->Clear(); +void Maxwell3D::ProcessClearBuffers(u32 layer_count) { +    rasterizer->Clear(layer_count);  }  void Maxwell3D::ProcessDraw(u32 instance_count) { diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index a948fcb14..c3099f9a6 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -707,7 +707,7 @@ public:                  case Size::Size_A2_B10_G10_R10:                      return "2_10_10_10";                  case Size::Size_B10_G11_R11: -                    return "10_11_12"; +                    return "10_11_11";                  default:                      ASSERT(false);                      return {}; @@ -2639,7 +2639,7 @@ public:                  L2CacheControl l2_cache_control;                                       ///< 0x0218                  InvalidateShaderCache invalidate_shader_cache;                         ///< 0x021C                  INSERT_PADDING_BYTES_NOINIT(0xA8); -                SyncInfo sync_info; ///< 0x02C8 +                SyncInfo sync_info;                                                    ///< 0x02C8                  INSERT_PADDING_BYTES_NOINIT(0x4);                  u32 prim_circular_buffer_throttle;                                     ///< 0x02D0                  u32 flush_invalidate_rop_mini_cache;                                   ///< 0x02D4 @@ -2731,7 +2731,11 @@ public:                  s32 stencil_back_ref;                                                  ///< 0x0F54                  u32 stencil_back_mask;                                                 ///< 0x0F58                  u32 stencil_back_func_mask;                                            ///< 0x0F5C -                INSERT_PADDING_BYTES_NOINIT(0x24); +                INSERT_PADDING_BYTES_NOINIT(0x14); +                u32 invalidate_texture_data_cache;                                     ///< 0x0F74 Assumed - Not in official docs. +                INSERT_PADDING_BYTES_NOINIT(0x4); +                u32 tiled_cache_barrier;                                               ///< 0x0F7C Assumed - Not in official docs. +                INSERT_PADDING_BYTES_NOINIT(0x4);                  VertexStreamSubstitute vertex_stream_substitute;                       ///< 0x0F84                  u32 line_mode_clip_generated_edge_do_not_draw;                         ///< 0x0F8C                  u32 color_mask_common;                                                 ///< 0x0F90 @@ -2791,7 +2795,8 @@ public:                  FillViaTriangleMode fill_via_triangle_mode;                            ///< 0x113C                  u32 blend_per_format_snorm8_unorm16_snorm16_enabled;                   ///< 0x1140                  u32 flush_pending_writes_sm_gloal_store;                               ///< 0x1144 -                INSERT_PADDING_BYTES_NOINIT(0x18); +                u32 conservative_raster_enable;                                        ///< 0x1148 Assumed - Not in official docs. +                INSERT_PADDING_BYTES_NOINIT(0x14);                  std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; ///< 0x1160                  std::array<MsaaSampleLocation, 4> multisample_sample_locations;        ///< 0x11E0                  u32 offset_render_target_index_by_viewport_index;                      ///< 0x11F0 @@ -2970,7 +2975,7 @@ public:                  CullFace gl_cull_face;                                                 ///< 0x1920                  Viewport::PixelCenter viewport_pixel_center;                           ///< 0x1924                  INSERT_PADDING_BYTES_NOINIT(0x4); -                u32 viewport_scale_offset_enbled;                                      ///< 0x192C +                u32 viewport_scale_offset_enabled;                                     ///< 0x192C                  INSERT_PADDING_BYTES_NOINIT(0xC);                  ViewportClipControl viewport_clip_control;                             ///< 0x193C                  UserClip::Op user_clip_op;                                             ///< 0x1940 @@ -3081,6 +3086,9 @@ public:      std::vector<u8> inline_index_draw_indexes; +    /// Handles a write to the CLEAR_BUFFERS register. +    void ProcessClearBuffers(u32 layer_count); +  private:      void InitializeRegisterDefaults(); @@ -3115,9 +3123,6 @@ private:      /// Handles firmware blob 4      void ProcessFirmwareCall4(); -    /// Handles a write to the CLEAR_BUFFERS register. -    void ProcessClearBuffers(); -      /// Handles a write to the QUERY_GET register.      void ProcessQueryGet(); @@ -3287,6 +3292,8 @@ ASSERT_REG_POSITION(const_color_rendering, 0x0F40);  ASSERT_REG_POSITION(stencil_back_ref, 0x0F54);  ASSERT_REG_POSITION(stencil_back_mask, 0x0F58);  ASSERT_REG_POSITION(stencil_back_func_mask, 0x0F5C); +ASSERT_REG_POSITION(invalidate_texture_data_cache, 0x0F74); +ASSERT_REG_POSITION(tiled_cache_barrier, 0x0F7C);  ASSERT_REG_POSITION(vertex_stream_substitute, 0x0F84);  ASSERT_REG_POSITION(line_mode_clip_generated_edge_do_not_draw, 0x0F8C);  ASSERT_REG_POSITION(color_mask_common, 0x0F90); @@ -3343,6 +3350,7 @@ ASSERT_REG_POSITION(post_ps_use_pre_ps_coverage, 0x1138);  ASSERT_REG_POSITION(fill_via_triangle_mode, 0x113C);  ASSERT_REG_POSITION(blend_per_format_snorm8_unorm16_snorm16_enabled, 0x1140);  ASSERT_REG_POSITION(flush_pending_writes_sm_gloal_store, 0x1144); +ASSERT_REG_POSITION(conservative_raster_enable, 0x1148);  ASSERT_REG_POSITION(vertex_attrib_format, 0x1160);  ASSERT_REG_POSITION(multisample_sample_locations, 0x11E0);  ASSERT_REG_POSITION(offset_render_target_index_by_viewport_index, 0x11F0); @@ -3482,7 +3490,7 @@ ASSERT_REG_POSITION(gl_cull_test_enabled, 0x1918);  ASSERT_REG_POSITION(gl_front_face, 0x191C);  ASSERT_REG_POSITION(gl_cull_face, 0x1920);  ASSERT_REG_POSITION(viewport_pixel_center, 0x1924); -ASSERT_REG_POSITION(viewport_scale_offset_enbled, 0x192C); +ASSERT_REG_POSITION(viewport_scale_offset_enabled, 0x192C);  ASSERT_REG_POSITION(viewport_clip_control, 0x193C);  ASSERT_REG_POSITION(user_clip_op, 0x1940);  ASSERT_REG_POSITION(render_enable_override, 0x1944); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 4eb7a100d..1bf6ca2dd 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -102,26 +102,29 @@ void MaxwellDMA::Launch() {              const bool is_src_pitch = IsPitchKind(static_cast<PTEKind>(src_kind));              const bool is_dst_pitch = IsPitchKind(static_cast<PTEKind>(dst_kind));              if (!is_src_pitch && is_dst_pitch) { -                std::vector<u8> tmp_buffer(regs.line_length_in); -                std::vector<u8> dst_buffer(regs.line_length_in); -                memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), -                                               regs.line_length_in); -                for (u32 offset = 0; offset < regs.line_length_in; ++offset) { -                    dst_buffer[offset] = -                        tmp_buffer[convert_linear_2_blocklinear_addr(regs.offset_in + offset) - -                                   regs.offset_in]; +                UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); +                UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); +                UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); +                std::vector<u8> tmp_buffer(16); +                for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { +                    memory_manager.ReadBlockUnsafe( +                        convert_linear_2_blocklinear_addr(regs.offset_in + offset), +                        tmp_buffer.data(), tmp_buffer.size()); +                    memory_manager.WriteBlock(regs.offset_out + offset, tmp_buffer.data(), +                                              tmp_buffer.size());                  } -                memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in);              } else if (is_src_pitch && !is_dst_pitch) { -                std::vector<u8> tmp_buffer(regs.line_length_in); -                std::vector<u8> dst_buffer(regs.line_length_in); -                memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), -                                               regs.line_length_in); -                for (u32 offset = 0; offset < regs.line_length_in; ++offset) { -                    dst_buffer[convert_linear_2_blocklinear_addr(regs.offset_out + offset) - -                               regs.offset_out] = tmp_buffer[offset]; +                UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); +                UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); +                UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); +                std::vector<u8> tmp_buffer(16); +                for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { +                    memory_manager.ReadBlockUnsafe(regs.offset_in + offset, tmp_buffer.data(), +                                                   tmp_buffer.size()); +                    memory_manager.WriteBlock( +                        convert_linear_2_blocklinear_addr(regs.offset_out + offset), +                        tmp_buffer.data(), tmp_buffer.size());                  } -                memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in);              } else {                  if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) {                      std::vector<u8> tmp_buffer(regs.line_length_in); @@ -311,6 +314,7 @@ void MaxwellDMA::ReleaseSemaphore() {      }      default:          ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value())); +        break;      }  } diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 3977bb0fb..4d2278811 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -50,6 +50,7 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {          break;      default:          UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); +        break;      }  } @@ -65,6 +66,7 @@ void Puller::ProcessFenceActionMethod() {          break;      default:          UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value()); +        break;      }  } @@ -228,6 +230,7 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {          break;      default:          UNIMPLEMENTED_MSG("Unimplemented engine"); +        break;      }  } @@ -254,6 +257,7 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s          break;      default:          UNIMPLEMENTED_MSG("Unimplemented engine"); +        break;      }  } diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp index f61d5998e..505d81c1e 100644 --- a/src/video_core/macro/macro.cpp +++ b/src/video_core/macro/macro.cpp @@ -16,7 +16,10 @@  #include "video_core/macro/macro.h"  #include "video_core/macro/macro_hle.h"  #include "video_core/macro/macro_interpreter.h" + +#ifdef ARCHITECTURE_x86_64  #include "video_core/macro/macro_jit_x64.h" +#endif  namespace Tegra { diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp index f896591bf..0f3262edb 100644 --- a/src/video_core/macro/macro_hle.cpp +++ b/src/video_core/macro/macro_hle.cpp @@ -126,11 +126,25 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&      }  } -constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{ +// Multi-layer Clear +void HLE_EAD26C3E2109B06B(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) { +    ASSERT(parameters.size() == 1); + +    const Engines::Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; +    const u32 rt_index = clear_params.RT; +    const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; +    ASSERT(clear_params.layer == 0); + +    maxwell3d.regs.clear_surface.raw = clear_params.raw; +    maxwell3d.ProcessClearBuffers(num_layers); +} + +constexpr std::array<std::pair<u64, HLEFunction>, 5> hle_funcs{{      {0x771BB18C62444DA0, &HLE_771BB18C62444DA0},      {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},      {0x0217920100488FF7, &HLE_0217920100488FF7},      {0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164}, +    {0xEAD26C3E2109B06B, &HLE_EAD26C3E2109B06B},  }};  class HLEMacroImpl final : public CachedMacro { diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp index c0d32c112..0d63495a9 100644 --- a/src/video_core/macro/macro_interpreter.cpp +++ b/src/video_core/macro/macro_interpreter.cpp @@ -201,6 +201,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {      }      default:          UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value()); +        break;      }      // An instruction with the Exit flag will not actually @@ -297,6 +298,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r          break;      default:          UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); +        break;      }  } diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index 25c1ce798..7347cbd88 100644 --- a/src/video_core/macro/macro_jit_x64.cpp +++ b/src/video_core/macro/macro_jit_x64.cpp @@ -652,6 +652,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3          break;      default:          UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation); +        break;      }  } diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 1cbfef090..cfd872a40 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -43,7 +43,7 @@ public:      virtual void Draw(bool is_indexed, u32 instance_count) = 0;      /// Clear the current framebuffer -    virtual void Clear() = 0; +    virtual void Clear(u32 layer_count) = 0;      /// Dispatches a compute shader invocation      virtual void DispatchCompute() = 0; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 0503196de..354c6e429 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -136,7 +136,7 @@ void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_load      shader_cache.LoadDiskResources(title_id, stop_loading, callback);  } -void RasterizerOpenGL::Clear() { +void RasterizerOpenGL::Clear(u32 layer_count) {      MICROPROFILE_SCOPE(OpenGL_Clears);      if (!maxwell3d->ShouldExecute()) {          return; @@ -616,7 +616,7 @@ void RasterizerOpenGL::SyncViewport() {              }              flags[Dirty::Viewport0 + index] = false; -            if (!regs.viewport_scale_offset_enbled) { +            if (!regs.viewport_scale_offset_enabled) {                  const auto x = static_cast<GLfloat>(regs.surface_clip.x);                  const auto y = static_cast<GLfloat>(regs.surface_clip.y);                  const auto width = static_cast<GLfloat>(regs.surface_clip.width); @@ -768,7 +768,7 @@ void RasterizerOpenGL::SyncStencilTestState() {      if (regs.stencil_two_side_enable) {          glStencilFuncSeparate(GL_BACK, MaxwellToGL::ComparisonOp(regs.stencil_back_op.func), -                              regs.stencil_back_ref, regs.stencil_back_mask); +                              regs.stencil_back_ref, regs.stencil_back_func_mask);          glStencilOpSeparate(GL_BACK, MaxwellToGL::StencilOp(regs.stencil_back_op.fail),                              MaxwellToGL::StencilOp(regs.stencil_back_op.zfail),                              MaxwellToGL::StencilOp(regs.stencil_back_op.zpass)); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index c93ba3b42..fc183c3ca 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -69,7 +69,7 @@ public:      ~RasterizerOpenGL() override;      void Draw(bool is_indexed, u32 instance_count) override; -    void Clear() override; +    void Clear(u32 layer_count) override;      void DispatchCompute() override;      void ResetCounter(VideoCore::QueryType type) override;      void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 977709518..3fe04a115 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -76,7 +76,8 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,          }          break;      case Shader::Stage::TessellationEval: -        info.tess_clockwise = key.tessellation_clockwise != 0; +        // Flip the face, as OpenGL's drawing is flipped. +        info.tess_clockwise = key.tessellation_clockwise == 0;          info.tess_primitive = [&key] {              switch (key.tessellation_primitive) {              case Maxwell::Tessellation::DomainType::Isolines: @@ -218,6 +219,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo            .support_float16 = false,            .support_int64 = device.HasShaderInt64(),            .needs_demote_reorder = device.IsAmd(), +          .support_snorm_render_buffer = false,        } {      if (use_asynchronous_shaders) {          workers = CreateWorkers(); diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp index a359f96f1..d53b422ca 100644 --- a/src/video_core/renderer_opengl/gl_state_tracker.cpp +++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp @@ -70,8 +70,8 @@ void SetupDirtyViewports(Tables& tables) {      FillBlock(tables[1], OFF(viewport_transform), NUM(viewport_transform), Viewports);      FillBlock(tables[1], OFF(viewports), NUM(viewports), Viewports); -    tables[0][OFF(viewport_scale_offset_enbled)] = ViewportTransform; -    tables[1][OFF(viewport_scale_offset_enbled)] = Viewports; +    tables[0][OFF(viewport_scale_offset_enabled)] = ViewportTransform; +    tables[1][OFF(viewport_scale_offset_enabled)] = Viewports;  }  void SetupDirtyScissors(Tables& tables) { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 99cd11d1e..9f7ce7414 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -891,6 +891,7 @@ void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t b          break;      default:          ASSERT(false); +        break;      }  } @@ -927,6 +928,7 @@ void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t b          break;      default:          ASSERT(false); +        break;      }      // Compressed formats don't have a pixel format or type      const bool is_compressed = gl_format == GL_NONE; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 8bd5eba7e..f29462f7c 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -340,6 +340,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,          texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;          // UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",          //                   static_cast<u32>(framebuffer.pixel_format)); +        break;      }      texture.resource.Release(); diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index f85ed8e5b..98cc26679 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -90,6 +90,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,      depth_format.Assign(static_cast<u32>(regs.zeta.format));      y_negate.Assign(regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft ? 1 : 0);      provoking_vertex_last.Assign(regs.provoking_vertex == Maxwell::ProvokingVertex::Last ? 1 : 0); +    conservative_raster_enable.Assign(regs.conservative_raster_enable != 0 ? 1 : 0);      smooth_lines.Assign(regs.line_anti_alias_enable != 0 ? 1 : 0);      for (size_t i = 0; i < regs.rt.size(); ++i) { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 43441209c..1afdef329 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -193,6 +193,7 @@ struct FixedPipelineState {          BitField<6, 5, u32> depth_format;          BitField<11, 1, u32> y_negate;          BitField<12, 1, u32> provoking_vertex_last; +        BitField<13, 1, u32> conservative_raster_enable;          BitField<14, 1, u32> smooth_lines;      };      std::array<u8, Maxwell::NumRenderTargets> color_formats; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index d8131232a..c2a95200b 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -172,6 +172,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {  }  void RendererVulkan::Report() const { +    using namespace Common::Literals;      const std::string vendor_name{device.GetVendorName()};      const std::string model_name{device.GetModelName()};      const std::string driver_version = GetDriverVersion(device); @@ -181,9 +182,12 @@ void RendererVulkan::Report() const {      const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions()); +    const auto available_vram = static_cast<f64>(device.GetDeviceLocalMemory()) / f64{1_GiB}; +      LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);      LOG_INFO(Render_Vulkan, "Device: {}", model_name);      LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version); +    LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);      static constexpr auto field = Common::Telemetry::FieldType::UserSystem;      telemetry_session.AddField(field, "GPU_Vendor", vendor_name); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 1aa116cea..ef75c126c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -680,6 +680,15 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {          .lineStippleFactor = 0,          .lineStipplePattern = 0,      }; +    VkPipelineRasterizationConservativeStateCreateInfoEXT conservative_raster{ +        .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT, +        .pNext = nullptr, +        .flags = 0, +        .conservativeRasterizationMode = key.state.conservative_raster_enable != 0 +                                             ? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT +                                             : VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT, +        .extraPrimitiveOverestimationSize = 0.0f, +    };      VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{          .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT,          .pNext = nullptr, @@ -690,6 +699,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {      if (IsLine(input_assembly_topology) && device.IsExtLineRasterizationSupported()) {          line_state.pNext = std::exchange(rasterization_ci.pNext, &line_state);      } +    if (device.IsExtConservativeRasterizationSupported()) { +        conservative_raster.pNext = std::exchange(rasterization_ci.pNext, &conservative_raster); +    }      if (device.IsExtProvokingVertexSupported()) {          provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex);      } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b42e5be1e..d4b0a542a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -166,6 +166,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program          }          break;      case Shader::Stage::TessellationEval: +        info.tess_clockwise = key.state.tessellation_clockwise != 0;          info.tess_primitive = [&key] {              const u32 raw{key.state.tessellation_primitive.Value()};              switch (static_cast<Maxwell::Tessellation::DomainType>(raw)) { @@ -325,6 +326,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device          .support_int64 = device.IsShaderInt64Supported(),          .needs_demote_reorder = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY_KHR ||                                  driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE_KHR, +        .support_snorm_render_buffer = true,      };  } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6f94dcbc7..12b13cc59 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -211,7 +211,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {      EndTransformFeedback();  } -void RasterizerVulkan::Clear() { +void RasterizerVulkan::Clear(u32 layer_count) {      MICROPROFILE_SCOPE(Vulkan_Clearing);      if (!maxwell3d->ShouldExecute()) { @@ -254,7 +254,7 @@ void RasterizerVulkan::Clear() {          .rect = regs.clear_control.use_scissor ? GetScissorState(regs, 0, up_scale, down_shift)                                                 : default_scissor,          .baseArrayLayer = regs.clear_surface.layer, -        .layerCount = 1, +        .layerCount = layer_count,      };      if (clear_rect.rect.extent.width == 0 || clear_rect.rect.extent.height == 0) {          return; @@ -681,7 +681,7 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg      if (!state_tracker.TouchViewports()) {          return;      } -    if (!regs.viewport_scale_offset_enbled) { +    if (!regs.viewport_scale_offset_enabled) {          const auto x = static_cast<float>(regs.surface_clip.x);          const auto y = static_cast<float>(regs.surface_clip.y);          const auto width = static_cast<float>(regs.surface_clip.width); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index fa2238c5f..ee483cfd9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -65,7 +65,7 @@ public:      ~RasterizerVulkan() override;      void Draw(bool is_indexed, u32 instance_count) override; -    void Clear() override; +    void Clear(u32 layer_count) override;      void DispatchCompute() override;      void ResetCounter(VideoCore::QueryType type) override;      void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 7934f2a51..4a7b633b7 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -221,6 +221,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s              [[fallthrough]];          default:              vk::Check(result); +            break;          }      });      chunk->MarkSubmit(); diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index b87c3be66..edb41b171 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -51,7 +51,7 @@ Flags MakeInvalidationFlags() {  void SetupDirtyViewports(Tables& tables) {      FillBlock(tables[0], OFF(viewport_transform), NUM(viewport_transform), Viewports);      FillBlock(tables[0], OFF(viewports), NUM(viewports), Viewports); -    tables[0][OFF(viewport_scale_offset_enbled)] = Viewports; +    tables[0][OFF(viewport_scale_offset_enabled)] = Viewports;      tables[1][OFF(window_origin)] = Viewports;  } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 853b80d8a..a65bbeb1c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -108,6 +108,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {              break;          default:              ASSERT_MSG(false, "Invalid surface type"); +            break;          }      }      if (info.storage) { diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 37bb76b72..f24f320b6 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -352,7 +352,7 @@ Shader::TexturePixelFormat GraphicsEnvironment::ReadTexturePixelFormat(u32 handl  u32 GraphicsEnvironment::ReadViewportTransformState() {      const auto& regs{maxwell3d->regs}; -    viewport_transform_state = regs.viewport_scale_offset_enbled; +    viewport_transform_state = regs.viewport_scale_offset_enabled;      return viewport_transform_state;  } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index fd1a4b987..59120cd09 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -170,6 +170,7 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe  #undef BPP_CASE      default:          ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); +        break;      }  } @@ -217,6 +218,7 @@ void SwizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_p  #undef BPP_CASE      default:          ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); +        break;      }  } @@ -240,6 +242,7 @@ void UnswizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes  #undef BPP_CASE      default:          ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel); +        break;      }  } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 239f12382..adad36221 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -18,6 +18,9 @@ add_executable(yuzu      about_dialog.cpp      about_dialog.h      aboutdialog.ui +    applets/qt_amiibo_settings.cpp +    applets/qt_amiibo_settings.h +    applets/qt_amiibo_settings.ui      applets/qt_controller.cpp      applets/qt_controller.h      applets/qt_controller.ui @@ -385,6 +388,6 @@ if (NOT APPLE)      target_compile_definitions(yuzu PRIVATE HAS_OPENGL)  endif() -if (ARCHITECTURE_x86_64) +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)      target_link_libraries(yuzu PRIVATE dynarmic)  endif() diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp new file mode 100644 index 000000000..efb7f6ecc --- /dev/null +++ b/src/yuzu/applets/qt_amiibo_settings.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <thread> +#include <fmt/format.h> +#include <nlohmann/json.hpp> + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/hle/service/nfp/nfp_device.h" +#include "core/hle/service/nfp/nfp_result.h" +#include "input_common/drivers/virtual_amiibo.h" +#include "input_common/main.h" +#include "ui_qt_amiibo_settings.h" +#include "web_service/web_backend.h" +#include "yuzu/applets/qt_amiibo_settings.h" +#include "yuzu/main.h" + +QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent, +                                               Core::Frontend::CabinetParameters parameters_, +                                               InputCommon::InputSubsystem* input_subsystem_, +                                               std::shared_ptr<Service::NFP::NfpDevice> nfp_device_) +    : QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()), +      input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)}, +      parameters(std::move(parameters_)) { +    ui->setupUi(this); + +    LoadInfo(); + +    resize(0, 0); +} + +QtAmiiboSettingsDialog::~QtAmiiboSettingsDialog() = default; + +int QtAmiiboSettingsDialog::exec() { +    if (!is_initalized) { +        return QDialog::Rejected; +    } +    return QDialog::exec(); +} + +std::string QtAmiiboSettingsDialog::GetName() const { +    return ui->amiiboCustomNameValue->text().toStdString(); +} + +void QtAmiiboSettingsDialog::LoadInfo() { +    if (input_subsystem->GetVirtualAmiibo()->ReloadAmiibo() != +        InputCommon::VirtualAmiibo::Info::Success) { +        return; +    } + +    if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound && +        nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) { +        return; +    } +    nfp_device->Mount(Service::NFP::MountTarget::All); + +    LoadAmiiboInfo(); +    LoadAmiiboData(); +    LoadAmiiboGameInfo(); + +    ui->amiiboDirectoryValue->setText( +        QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath())); + +    SetSettingsDescription(); +    is_initalized = true; +} + +void QtAmiiboSettingsDialog::LoadAmiiboInfo() { +    Service::NFP::ModelInfo model_info{}; +    const auto model_result = nfp_device->GetModelInfo(model_info); + +    if (model_result.IsFailure()) { +        ui->amiiboImageLabel->setVisible(false); +        ui->amiiboInfoGroup->setVisible(false); +        return; +    } + +    const auto amiibo_id = +        fmt::format("{:04x}{:02x}{:02x}{:04x}{:02x}02", Common::swap16(model_info.character_id), +                    model_info.character_variant, model_info.amiibo_type, model_info.model_number, +                    model_info.series); + +    LOG_DEBUG(Frontend, "Loading amiibo id {}", amiibo_id); +    // Note: This function is not being used until we host the images on our server +    // LoadAmiiboApiInfo(amiibo_id); +    ui->amiiboImageLabel->setVisible(false); +    ui->amiiboInfoGroup->setVisible(false); +} + +void QtAmiiboSettingsDialog::LoadAmiiboApiInfo(std::string_view amiibo_id) { +    // TODO: Host this data on our website +    WebService::Client client{"https://amiiboapi.com", {}, {}}; +    WebService::Client image_client{"https://raw.githubusercontent.com", {}, {}}; +    const auto url_path = fmt::format("/api/amiibo/?id={}", amiibo_id); + +    const auto amiibo_json = client.GetJson(url_path, true).returned_data; +    if (amiibo_json.empty()) { +        ui->amiiboImageLabel->setVisible(false); +        ui->amiiboInfoGroup->setVisible(false); +        return; +    } + +    std::string amiibo_series{}; +    std::string amiibo_name{}; +    std::string amiibo_image_url{}; +    std::string amiibo_type{}; + +    const auto parsed_amiibo_json_json = nlohmann::json::parse(amiibo_json).at("amiibo"); +    parsed_amiibo_json_json.at("amiiboSeries").get_to(amiibo_series); +    parsed_amiibo_json_json.at("name").get_to(amiibo_name); +    parsed_amiibo_json_json.at("image").get_to(amiibo_image_url); +    parsed_amiibo_json_json.at("type").get_to(amiibo_type); + +    ui->amiiboSeriesValue->setText(QString::fromStdString(amiibo_series)); +    ui->amiiboNameValue->setText(QString::fromStdString(amiibo_name)); +    ui->amiiboTypeValue->setText(QString::fromStdString(amiibo_type)); + +    if (amiibo_image_url.size() < 34) { +        ui->amiiboImageLabel->setVisible(false); +    } + +    const auto image_url_path = amiibo_image_url.substr(34, amiibo_image_url.size() - 34); +    const auto image_data = image_client.GetImage(image_url_path, true).returned_data; + +    if (image_data.empty()) { +        ui->amiiboImageLabel->setVisible(false); +    } + +    QPixmap pixmap; +    pixmap.loadFromData(reinterpret_cast<const u8*>(image_data.data()), +                        static_cast<uint>(image_data.size())); +    pixmap = pixmap.scaled(250, 350, Qt::AspectRatioMode::KeepAspectRatio, +                           Qt::TransformationMode::SmoothTransformation); +    ui->amiiboImageLabel->setPixmap(pixmap); +} + +void QtAmiiboSettingsDialog::LoadAmiiboData() { +    Service::NFP::RegisterInfo register_info{}; +    Service::NFP::CommonInfo common_info{}; +    const auto register_result = nfp_device->GetRegisterInfo(register_info); +    const auto common_result = nfp_device->GetCommonInfo(common_info); + +    if (register_result.IsFailure()) { +        ui->creationDateValue->setDisabled(true); +        ui->modificationDateValue->setDisabled(true); +        ui->amiiboCustomNameValue->setReadOnly(false); +        ui->amiiboOwnerValue->setReadOnly(false); +        return; +    } + +    if (parameters.mode == Service::NFP::CabinetMode::StartNicknameAndOwnerSettings) { +        ui->creationDateValue->setDisabled(true); +        ui->modificationDateValue->setDisabled(true); +    } + +    const auto amiibo_name = std::string(register_info.amiibo_name.data()); +    const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data()); +    const auto creation_date = +        QDate(register_info.creation_date.year, register_info.creation_date.month, +              register_info.creation_date.day); + +    ui->amiiboCustomNameValue->setText(QString::fromStdString(amiibo_name)); +    ui->amiiboOwnerValue->setText(QString::fromStdString(owner_name)); +    ui->amiiboCustomNameValue->setReadOnly(true); +    ui->amiiboOwnerValue->setReadOnly(true); +    ui->creationDateValue->setDate(creation_date); + +    if (common_result.IsFailure()) { +        ui->modificationDateValue->setDisabled(true); +        return; +    } + +    const auto modification_date = +        QDate(common_info.last_write_date.year, common_info.last_write_date.month, +              common_info.last_write_date.day); +    ui->modificationDateValue->setDate(modification_date); +} + +void QtAmiiboSettingsDialog::LoadAmiiboGameInfo() { +    u32 application_area_id{}; +    const auto application_result = nfp_device->GetApplicationAreaId(application_area_id); + +    if (application_result.IsFailure()) { +        ui->gameIdValue->setVisible(false); +        ui->gameIdLabel->setText(tr("No game data present")); +        return; +    } + +    SetGameDataName(application_area_id); +} + +void QtAmiiboSettingsDialog::SetGameDataName(u32 application_area_id) { +    static constexpr std::array<std::pair<u32, const char*>, 12> game_name_list = { +        // 3ds, wii u +        std::pair<u32, const char*>{0x10110E00, "Super Smash Bros (3DS/WiiU)"}, +        {0x00132600, "Mario & Luigi: Paper Jam"}, +        {0x0014F000, "Animal Crossing: Happy Home Designer"}, +        {0x00152600, "Chibi-Robo!: Zip Lash"}, +        {0x10161f00, "Mario Party 10"}, +        {0x1019C800, "The Legend of Zelda: Twilight Princess HD"}, +        // switch +        {0x10162B00, "Splatoon 2"}, +        {0x1016e100, "Shovel Knight: Treasure Trove"}, +        {0x1019C800, "The Legend of Zelda: Breath of the Wild"}, +        {0x34F80200, "Super Smash Bros. Ultimate"}, +        {0x38600500, "Splatoon 3"}, +        {0x3B440400, "The Legend of Zelda: Link's Awakening"}, +    }; + +    for (const auto& [game_id, game_name] : game_name_list) { +        if (application_area_id == game_id) { +            ui->gameIdValue->setText(QString::fromStdString(game_name)); +            return; +        } +    } + +    const auto application_area_string = fmt::format("{:016x}", application_area_id); +    ui->gameIdValue->setText(QString::fromStdString(application_area_string)); +} + +void QtAmiiboSettingsDialog::SetSettingsDescription() { +    switch (parameters.mode) { +    case Service::NFP::CabinetMode::StartFormatter: +        ui->cabinetActionDescriptionLabel->setText( +            tr("The following amiibo data will be formatted:")); +        break; +    case Service::NFP::CabinetMode::StartGameDataEraser: +        ui->cabinetActionDescriptionLabel->setText(tr("The following game data will removed:")); +        break; +    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: +        ui->cabinetActionDescriptionLabel->setText(tr("Set nickname and owner:")); +        break; +    case Service::NFP::CabinetMode::StartRestorer: +        ui->cabinetActionDescriptionLabel->setText(tr("Do you wish to restore this amiibo?")); +        break; +    } +} + +QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) { +    connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent, +            &GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection); +    connect(&parent, &GMainWindow::AmiiboSettingsFinished, this, +            &QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection); +} + +QtAmiiboSettings::~QtAmiiboSettings() = default; + +void QtAmiiboSettings::ShowCabinetApplet( +    const Core::Frontend::CabinetCallback& callback_, +    const Core::Frontend::CabinetParameters& parameters, +    std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const { +    callback = std::move(callback_); +    emit MainWindowShowAmiiboSettings(parameters, nfp_device); +} + +void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) { +    callback(is_success, name); +} diff --git a/src/yuzu/applets/qt_amiibo_settings.h b/src/yuzu/applets/qt_amiibo_settings.h new file mode 100644 index 000000000..930c96739 --- /dev/null +++ b/src/yuzu/applets/qt_amiibo_settings.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> +#include "core/frontend/applets/cabinet.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class QtAmiiboSettingsDialog; +} + +namespace Service::NFP { +class NfpDevice; +} // namespace Service::NFP + +class QtAmiiboSettingsDialog final : public QDialog { +    Q_OBJECT + +public: +    explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_, +                                    InputCommon::InputSubsystem* input_subsystem_, +                                    std::shared_ptr<Service::NFP::NfpDevice> nfp_device_); +    ~QtAmiiboSettingsDialog() override; + +    int exec() override; + +    std::string GetName() const; + +private: +    void LoadInfo(); +    void LoadAmiiboInfo(); +    void LoadAmiiboApiInfo(std::string_view amiibo_id); +    void LoadAmiiboData(); +    void LoadAmiiboGameInfo(); +    void SetGameDataName(u32 application_area_id); +    void SetSettingsDescription(); + +    std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui; + +    InputCommon::InputSubsystem* input_subsystem; +    std::shared_ptr<Service::NFP::NfpDevice> nfp_device; + +    // Parameters sent in from the backend HLE applet. +    Core::Frontend::CabinetParameters parameters; + +    // If false amiibo settings failed to load +    bool is_initalized{}; +}; + +class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApplet { +    Q_OBJECT + +public: +    explicit QtAmiiboSettings(GMainWindow& parent); +    ~QtAmiiboSettings() override; + +    void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_, +                           const Core::Frontend::CabinetParameters& parameters, +                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override; + +signals: +    void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters, +                                      std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const; + +private: +    void MainWindowFinished(bool is_success, const std::string& name); + +    mutable Core::Frontend::CabinetCallback callback; +}; diff --git a/src/yuzu/applets/qt_amiibo_settings.ui b/src/yuzu/applets/qt_amiibo_settings.ui new file mode 100644 index 000000000..f377a6e61 --- /dev/null +++ b/src/yuzu/applets/qt_amiibo_settings.ui @@ -0,0 +1,494 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtAmiiboSettingsDialog</class> + <widget class="QDialog" name="QtAmiiboSettingsDialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>839</width> +    <height>500</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Amiibo Settings</string> +  </property> +  <property name="styleSheet"> +   <string notr="true"/> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout" stretch="0"> +   <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="QWidget" name="mainControllerApplet" native="true"> +     <layout class="QVBoxLayout" name="verticalLayout_1" stretch="0,3,0"> +      <property name="spacing"> +       <number>0</number> +      </property> +      <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="QWidget" name="topControllerApplet" native="true"> +        <layout class="QHBoxLayout" name="horizontalLayout"> +         <property name="spacing"> +          <number>10</number> +         </property> +         <property name="leftMargin"> +          <number>20</number> +         </property> +         <property name="topMargin"> +          <number>15</number> +         </property> +         <property name="rightMargin"> +          <number>0</number> +         </property> +         <property name="bottomMargin"> +          <number>15</number> +         </property> +         <item> +          <widget class="QLabel" name="cabinetActionDescriptionLabel"> +           <property name="font"> +            <font> +             <pointsize>12</pointsize> +             <weight>75</weight> +             <bold>true</bold> +            </font> +           </property> +           <property name="text"> +            <string/> +           </property> +           <property name="alignment"> +            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> +           </property> +           <property name="wordWrap"> +            <bool>false</bool> +           </property> +          </widget> +         </item> +         <item> +          <spacer name="horizontalSpacer_2"> +           <property name="orientation"> +            <enum>Qt::Horizontal</enum> +           </property> +           <property name="sizeHint" stdset="0"> +            <size> +             <width>40</width> +             <height>20</height> +            </size> +           </property> +          </spacer> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QWidget" name="middleControllerApplet" native="true"> +        <layout class="QVBoxLayout" name="verticalLayout_3"> +         <property name="spacing"> +          <number>0</number> +         </property> +         <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> +          <layout class="QHBoxLayout" name="horizontalLayout_2"> +           <property name="spacing"> +            <number>20</number> +           </property> +           <property name="leftMargin"> +            <number>15</number> +           </property> +           <property name="rightMargin"> +            <number>15</number> +           </property> +           <item> +            <widget class="QLabel" name="amiiboImageLabel"> +             <property name="minimumSize"> +              <size> +               <width>250</width> +               <height>350</height> +              </size> +             </property> +             <property name="maximumSize"> +              <size> +               <width>236</width> +               <height>350</height> +              </size> +             </property> +             <property name="text"> +              <string/> +             </property> +             <property name="alignment"> +              <set>Qt::AlignCenter</set> +             </property> +            </widget> +           </item> +           <item> +            <layout class="QVBoxLayout" name="verticalLayout_4"> +             <property name="leftMargin"> +              <number>0</number> +             </property> +             <property name="topMargin"> +              <number>8</number> +             </property> +             <property name="bottomMargin"> +              <number>15</number> +             </property> +             <item> +              <widget class="QGroupBox" name="amiiboInfoGroup"> +               <property name="title"> +                <string>Amiibo Info</string> +               </property> +               <layout class="QVBoxLayout" name="verticalLayout_5"> +                <item> +                 <layout class="QGridLayout" name="gridLayout_1"> +                  <item row="0" column="0"> +                   <widget class="QLabel" name="amiiboSeriesLabel"> +                    <property name="text"> +                     <string>Series</string> +                    </property> +                   </widget> +                  </item> +                  <item row="0" column="1"> +                   <widget class="QLineEdit" name="amiiboSeriesValue"> +                    <property name="sizePolicy"> +                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                      <horstretch>0</horstretch> +                      <verstretch>0</verstretch> +                     </sizepolicy> +                    </property> +                    <property name="readOnly"> +                     <bool>true</bool> +                    </property> +                   </widget> +                  </item> +                  <item row="1" column="0"> +                   <widget class="QLabel" name="amiiboTypeLabel"> +                    <property name="text"> +                     <string>Type</string> +                    </property> +                   </widget> +                  </item> +                  <item row="1" column="1"> +                   <widget class="QLineEdit" name="amiiboTypeValue"> +                    <property name="sizePolicy"> +                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                      <horstretch>0</horstretch> +                      <verstretch>0</verstretch> +                     </sizepolicy> +                    </property> +                    <property name="readOnly"> +                     <bool>true</bool> +                    </property> +                   </widget> +                  </item> +                  <item row="2" column="0"> +                   <widget class="QLabel" name="amiiboNameLabel"> +                    <property name="text"> +                     <string>Name</string> +                    </property> +                   </widget> +                  </item> +                  <item row="2" column="1"> +                   <widget class="QLineEdit" name="amiiboNameValue"> +                    <property name="sizePolicy"> +                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                      <horstretch>0</horstretch> +                      <verstretch>0</verstretch> +                     </sizepolicy> +                    </property> +                    <property name="readOnly"> +                     <bool>true</bool> +                    </property> +                   </widget> +                  </item> +                 </layout> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QGroupBox" name="amiiboDataGroup"> +               <property name="title"> +                <string>Amiibo Data</string> +               </property> +               <layout class="QVBoxLayout" name="verticalLayout_6"> +                <item> +                 <layout class="QGridLayout" name="gridLayout_2"> +                  <item row="0" column="0"> +                   <widget class="QLabel" name="amiiboCustomNameLabel"> +                    <property name="text"> +                     <string>Custom Name</string> +                    </property> +                   </widget> +                  </item> +                  <item row="0" column="1"> +                   <widget class="QLineEdit" name="amiiboCustomNameValue"> +                    <property name="sizePolicy"> +                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                      <horstretch>0</horstretch> +                      <verstretch>0</verstretch> +                     </sizepolicy> +                    </property> +                    <property name="maxLength"> +                     <number>10</number> +                    </property> +                   </widget> +                  </item> +                  <item row="1" column="0"> +                   <widget class="QLabel" name="amiiboOwnerLabel"> +                    <property name="text"> +                     <string>Owner</string> +                    </property> +                   </widget> +                  </item> +                  <item row="1" column="1"> +                   <widget class="QLineEdit" name="amiiboOwnerValue"> +                    <property name="sizePolicy"> +                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                      <horstretch>0</horstretch> +                      <verstretch>0</verstretch> +                     </sizepolicy> +                    </property> +                    <property name="maxLength"> +                     <number>10</number> +                    </property> +                   </widget> +                  </item> +                  <item row="2" column="0"> +                   <widget class="QLabel" name="creationDateLabel"> +                    <property name="text"> +                     <string>Creation Date</string> +                    </property> +                   </widget> +                  </item> +                  <item row="2" column="1"> +                   <widget class="QDateTimeEdit" name="creationDateValue"> +                    <property name="readOnly"> +                     <bool>true</bool> +                    </property> +                    <property name="minimumDate"> +                     <date> +                      <year>1970</year> +                      <month>1</month> +                      <day>1</day> +                     </date> +                    </property> +                    <property name="displayFormat"> +                     <string>dd/MM/yyyy</string> +                    </property> +                   </widget> +                  </item> +                  <item row="3" column="0"> +                   <widget class="QLabel" name="modificationDateLabel"> +                    <property name="text"> +                     <string>Modification Date</string> +                    </property> +                   </widget> +                  </item> +                  <item row="3" column="1"> +                   <widget class="QDateTimeEdit" name="modificationDateValue"> +                    <property name="readOnly"> +                     <bool>true</bool> +                    </property> +                    <property name="minimumDate"> +                     <date> +                      <year>1970</year> +                      <month>1</month> +                      <day>1</day> +                     </date> +                    </property> +                    <property name="displayFormat"> +                     <string>dd/MM/yyyy </string> +                    </property> +                   </widget> +                  </item> +                 </layout> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QGroupBox" name="gameDataGroup"> +               <property name="minimumSize"> +                <size> +                 <width>500</width> +                 <height>0</height> +                </size> +               </property> +               <property name="title"> +                <string>Game Data</string> +               </property> +               <layout class="QGridLayout" name="gridLayout_3"> +                <item row="0" column="0"> +                 <widget class="QLabel" name="gameIdLabel"> +                  <property name="text"> +                   <string>Game Id</string> +                  </property> +                 </widget> +                </item> +                <item row="0" column="1"> +                 <widget class="QLineEdit" name="gameIdValue"> +                  <property name="sizePolicy"> +                   <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +                    <horstretch>0</horstretch> +                    <verstretch>0</verstretch> +                   </sizepolicy> +                  </property> +                  <property name="readOnly"> +                   <bool>true</bool> +                  </property> +                 </widget> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <widget class="QGroupBox" name="MountAmiiboGroup"> +               <property name="minimumSize"> +                <size> +                 <width>500</width> +                 <height>0</height> +                </size> +               </property> +               <property name="title"> +                <string>Mount Amiibo</string> +               </property> +               <layout class="QGridLayout" name="gridLayout_4"> +                <item row="0" column="3"> +                 <widget class="QToolButton" name="amiiboDirectoryButton"> +                  <property name="text"> +                   <string>...</string> +                  </property> +                 </widget> +                </item> +                <item row="0" column="1"> +                 <spacer name="horizontalSpacer"> +                  <property name="orientation"> +                   <enum>Qt::Horizontal</enum> +                  </property> +                  <property name="sizeType"> +                   <enum>QSizePolicy::Maximum</enum> +                  </property> +                  <property name="sizeHint" stdset="0"> +                   <size> +                    <width>60</width> +                    <height>20</height> +                   </size> +                  </property> +                 </spacer> +                </item> +                <item row="0" column="0"> +                 <widget class="QLabel" name="amiiboDirectoryLabel"> +                  <property name="text"> +                   <string>File Path</string> +                  </property> +                 </widget> +                </item> +                <item row="0" column="2"> +                 <widget class="QLineEdit" name="amiiboDirectoryValue"/> +                </item> +               </layout> +              </widget> +             </item> +             <item> +              <spacer name="verticalSpacer"> +               <property name="orientation"> +                <enum>Qt::Vertical</enum> +               </property> +               <property name="sizeHint" stdset="0"> +                <size> +                 <width>20</width> +                 <height>40</height> +                </size> +               </property> +              </spacer> +             </item> +            </layout> +           </item> +          </layout> +         </item> +        </layout> +       </widget> +      </item> +      <item> +       <widget class="QWidget" name="bottomControllerApplet" native="true"> +        <layout class="QHBoxLayout" name="horizontalLayout_6"> +         <property name="spacing"> +          <number>15</number> +         </property> +         <property name="leftMargin"> +          <number>15</number> +         </property> +         <property name="topMargin"> +          <number>8</number> +         </property> +         <property name="rightMargin"> +          <number>20</number> +         </property> +         <property name="bottomMargin"> +          <number>8</number> +         </property> +         <item alignment="Qt::AlignBottom"> +          <widget class="QDialogButtonBox" name="buttonBox"> +           <property name="enabled"> +            <bool>true</bool> +           </property> +           <property name="standardButtons"> +            <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +           </property> +          </widget> +         </item> +        </layout> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>QtAmiiboSettingsDialog</receiver> +   <slot>accept()</slot> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>QtAmiiboSettingsDialog</receiver> +   <slot>reject()</slot> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 6acfb7b06..d88efacd7 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -401,224 +401,127 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {  }  int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { -    switch (qt_key) { -    case Qt::Key_A: -        return Settings::NativeKeyboard::A; -    case Qt::Key_B: -        return Settings::NativeKeyboard::B; -    case Qt::Key_C: -        return Settings::NativeKeyboard::C; -    case Qt::Key_D: -        return Settings::NativeKeyboard::D; -    case Qt::Key_E: -        return Settings::NativeKeyboard::E; -    case Qt::Key_F: -        return Settings::NativeKeyboard::F; -    case Qt::Key_G: -        return Settings::NativeKeyboard::G; -    case Qt::Key_H: -        return Settings::NativeKeyboard::H; -    case Qt::Key_I: -        return Settings::NativeKeyboard::I; -    case Qt::Key_J: -        return Settings::NativeKeyboard::J; -    case Qt::Key_K: -        return Settings::NativeKeyboard::K; -    case Qt::Key_L: -        return Settings::NativeKeyboard::L; -    case Qt::Key_M: -        return Settings::NativeKeyboard::M; -    case Qt::Key_N: -        return Settings::NativeKeyboard::N; -    case Qt::Key_O: -        return Settings::NativeKeyboard::O; -    case Qt::Key_P: -        return Settings::NativeKeyboard::P; -    case Qt::Key_Q: -        return Settings::NativeKeyboard::Q; -    case Qt::Key_R: -        return Settings::NativeKeyboard::R; -    case Qt::Key_S: -        return Settings::NativeKeyboard::S; -    case Qt::Key_T: -        return Settings::NativeKeyboard::T; -    case Qt::Key_U: -        return Settings::NativeKeyboard::U; -    case Qt::Key_V: -        return Settings::NativeKeyboard::V; -    case Qt::Key_W: -        return Settings::NativeKeyboard::W; -    case Qt::Key_X: -        return Settings::NativeKeyboard::X; -    case Qt::Key_Y: -        return Settings::NativeKeyboard::Y; -    case Qt::Key_Z: -        return Settings::NativeKeyboard::Z; -    case Qt::Key_1: -        return Settings::NativeKeyboard::N1; -    case Qt::Key_2: -        return Settings::NativeKeyboard::N2; -    case Qt::Key_3: -        return Settings::NativeKeyboard::N3; -    case Qt::Key_4: -        return Settings::NativeKeyboard::N4; -    case Qt::Key_5: -        return Settings::NativeKeyboard::N5; -    case Qt::Key_6: -        return Settings::NativeKeyboard::N6; -    case Qt::Key_7: -        return Settings::NativeKeyboard::N7; -    case Qt::Key_8: -        return Settings::NativeKeyboard::N8; -    case Qt::Key_9: -        return Settings::NativeKeyboard::N9; -    case Qt::Key_0: -        return Settings::NativeKeyboard::N0; -    case Qt::Key_Return: -        return Settings::NativeKeyboard::Return; -    case Qt::Key_Escape: -        return Settings::NativeKeyboard::Escape; -    case Qt::Key_Backspace: -        return Settings::NativeKeyboard::Backspace; -    case Qt::Key_Tab: -        return Settings::NativeKeyboard::Tab; -    case Qt::Key_Space: -        return Settings::NativeKeyboard::Space; -    case Qt::Key_Minus: -        return Settings::NativeKeyboard::Minus; -    case Qt::Key_Plus: -    case Qt::Key_questiondown: -        return Settings::NativeKeyboard::Plus; -    case Qt::Key_BracketLeft: -    case Qt::Key_BraceLeft: -        return Settings::NativeKeyboard::OpenBracket; -    case Qt::Key_BracketRight: -    case Qt::Key_BraceRight: -        return Settings::NativeKeyboard::CloseBracket; -    case Qt::Key_Bar: -        return Settings::NativeKeyboard::Pipe; -    case Qt::Key_Dead_Tilde: -        return Settings::NativeKeyboard::Tilde; -    case Qt::Key_Ntilde: -    case Qt::Key_Semicolon: -        return Settings::NativeKeyboard::Semicolon; -    case Qt::Key_Apostrophe: -        return Settings::NativeKeyboard::Quote; -    case Qt::Key_Dead_Grave: -        return Settings::NativeKeyboard::Backquote; -    case Qt::Key_Comma: -        return Settings::NativeKeyboard::Comma; -    case Qt::Key_Period: -        return Settings::NativeKeyboard::Period; -    case Qt::Key_Slash: -        return Settings::NativeKeyboard::Slash; -    case Qt::Key_CapsLock: -        return Settings::NativeKeyboard::CapsLock; -    case Qt::Key_F1: -        return Settings::NativeKeyboard::F1; -    case Qt::Key_F2: -        return Settings::NativeKeyboard::F2; -    case Qt::Key_F3: -        return Settings::NativeKeyboard::F3; -    case Qt::Key_F4: -        return Settings::NativeKeyboard::F4; -    case Qt::Key_F5: -        return Settings::NativeKeyboard::F5; -    case Qt::Key_F6: -        return Settings::NativeKeyboard::F6; -    case Qt::Key_F7: -        return Settings::NativeKeyboard::F7; -    case Qt::Key_F8: -        return Settings::NativeKeyboard::F8; -    case Qt::Key_F9: -        return Settings::NativeKeyboard::F9; -    case Qt::Key_F10: -        return Settings::NativeKeyboard::F10; -    case Qt::Key_F11: -        return Settings::NativeKeyboard::F11; -    case Qt::Key_F12: -        return Settings::NativeKeyboard::F12; -    case Qt::Key_Print: -        return Settings::NativeKeyboard::PrintScreen; -    case Qt::Key_ScrollLock: -        return Settings::NativeKeyboard::ScrollLock; -    case Qt::Key_Pause: -        return Settings::NativeKeyboard::Pause; -    case Qt::Key_Insert: -        return Settings::NativeKeyboard::Insert; -    case Qt::Key_Home: -        return Settings::NativeKeyboard::Home; -    case Qt::Key_PageUp: -        return Settings::NativeKeyboard::PageUp; -    case Qt::Key_Delete: -        return Settings::NativeKeyboard::Delete; -    case Qt::Key_End: -        return Settings::NativeKeyboard::End; -    case Qt::Key_PageDown: -        return Settings::NativeKeyboard::PageDown; -    case Qt::Key_Right: -        return Settings::NativeKeyboard::Right; -    case Qt::Key_Left: -        return Settings::NativeKeyboard::Left; -    case Qt::Key_Down: -        return Settings::NativeKeyboard::Down; -    case Qt::Key_Up: -        return Settings::NativeKeyboard::Up; -    case Qt::Key_NumLock: -        return Settings::NativeKeyboard::NumLock; -    // Numpad keys are missing here -    case Qt::Key_F13: -        return Settings::NativeKeyboard::F13; -    case Qt::Key_F14: -        return Settings::NativeKeyboard::F14; -    case Qt::Key_F15: -        return Settings::NativeKeyboard::F15; -    case Qt::Key_F16: -        return Settings::NativeKeyboard::F16; -    case Qt::Key_F17: -        return Settings::NativeKeyboard::F17; -    case Qt::Key_F18: -        return Settings::NativeKeyboard::F18; -    case Qt::Key_F19: -        return Settings::NativeKeyboard::F19; -    case Qt::Key_F20: -        return Settings::NativeKeyboard::F20; -    case Qt::Key_F21: -        return Settings::NativeKeyboard::F21; -    case Qt::Key_F22: -        return Settings::NativeKeyboard::F22; -    case Qt::Key_F23: -        return Settings::NativeKeyboard::F23; -    case Qt::Key_F24: -        return Settings::NativeKeyboard::F24; -    // case Qt::: -    //    return Settings::NativeKeyboard::KPComma; -    // case Qt::: -    //    return Settings::NativeKeyboard::Ro; -    case Qt::Key_Hiragana_Katakana: -        return Settings::NativeKeyboard::KatakanaHiragana; -    case Qt::Key_yen: -        return Settings::NativeKeyboard::Yen; -    case Qt::Key_Henkan: -        return Settings::NativeKeyboard::Henkan; -    case Qt::Key_Muhenkan: -        return Settings::NativeKeyboard::Muhenkan; -    // case Qt::: -    //    return Settings::NativeKeyboard::NumPadCommaPc98; -    case Qt::Key_Hangul: -        return Settings::NativeKeyboard::HangulEnglish; -    case Qt::Key_Hangul_Hanja: -        return Settings::NativeKeyboard::Hanja; -    case Qt::Key_Katakana: -        return Settings::NativeKeyboard::KatakanaKey; -    case Qt::Key_Hiragana: -        return Settings::NativeKeyboard::HiraganaKey; -    case Qt::Key_Zenkaku_Hankaku: -        return Settings::NativeKeyboard::ZenkakuHankaku; -    // Modifier keys are handled by the modifier property -    default: -        return Settings::NativeKeyboard::None; +    static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = { +        std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A}, +        {Qt::Key_A, Settings::NativeKeyboard::A}, +        {Qt::Key_B, Settings::NativeKeyboard::B}, +        {Qt::Key_C, Settings::NativeKeyboard::C}, +        {Qt::Key_D, Settings::NativeKeyboard::D}, +        {Qt::Key_E, Settings::NativeKeyboard::E}, +        {Qt::Key_F, Settings::NativeKeyboard::F}, +        {Qt::Key_G, Settings::NativeKeyboard::G}, +        {Qt::Key_H, Settings::NativeKeyboard::H}, +        {Qt::Key_I, Settings::NativeKeyboard::I}, +        {Qt::Key_J, Settings::NativeKeyboard::J}, +        {Qt::Key_K, Settings::NativeKeyboard::K}, +        {Qt::Key_L, Settings::NativeKeyboard::L}, +        {Qt::Key_M, Settings::NativeKeyboard::M}, +        {Qt::Key_N, Settings::NativeKeyboard::N}, +        {Qt::Key_O, Settings::NativeKeyboard::O}, +        {Qt::Key_P, Settings::NativeKeyboard::P}, +        {Qt::Key_Q, Settings::NativeKeyboard::Q}, +        {Qt::Key_R, Settings::NativeKeyboard::R}, +        {Qt::Key_S, Settings::NativeKeyboard::S}, +        {Qt::Key_T, Settings::NativeKeyboard::T}, +        {Qt::Key_U, Settings::NativeKeyboard::U}, +        {Qt::Key_V, Settings::NativeKeyboard::V}, +        {Qt::Key_W, Settings::NativeKeyboard::W}, +        {Qt::Key_X, Settings::NativeKeyboard::X}, +        {Qt::Key_Y, Settings::NativeKeyboard::Y}, +        {Qt::Key_Z, Settings::NativeKeyboard::Z}, +        {Qt::Key_1, Settings::NativeKeyboard::N1}, +        {Qt::Key_2, Settings::NativeKeyboard::N2}, +        {Qt::Key_3, Settings::NativeKeyboard::N3}, +        {Qt::Key_4, Settings::NativeKeyboard::N4}, +        {Qt::Key_5, Settings::NativeKeyboard::N5}, +        {Qt::Key_6, Settings::NativeKeyboard::N6}, +        {Qt::Key_7, Settings::NativeKeyboard::N7}, +        {Qt::Key_8, Settings::NativeKeyboard::N8}, +        {Qt::Key_9, Settings::NativeKeyboard::N9}, +        {Qt::Key_0, Settings::NativeKeyboard::N0}, +        {Qt::Key_Return, Settings::NativeKeyboard::Return}, +        {Qt::Key_Escape, Settings::NativeKeyboard::Escape}, +        {Qt::Key_Backspace, Settings::NativeKeyboard::Backspace}, +        {Qt::Key_Tab, Settings::NativeKeyboard::Tab}, +        {Qt::Key_Space, Settings::NativeKeyboard::Space}, +        {Qt::Key_Minus, Settings::NativeKeyboard::Minus}, +        {Qt::Key_Plus, Settings::NativeKeyboard::Plus}, +        {Qt::Key_questiondown, Settings::NativeKeyboard::Plus}, +        {Qt::Key_BracketLeft, Settings::NativeKeyboard::OpenBracket}, +        {Qt::Key_BraceLeft, Settings::NativeKeyboard::OpenBracket}, +        {Qt::Key_BracketRight, Settings::NativeKeyboard::CloseBracket}, +        {Qt::Key_BraceRight, Settings::NativeKeyboard::CloseBracket}, +        {Qt::Key_Bar, Settings::NativeKeyboard::Pipe}, +        {Qt::Key_Dead_Tilde, Settings::NativeKeyboard::Tilde}, +        {Qt::Key_Ntilde, Settings::NativeKeyboard::Semicolon}, +        {Qt::Key_Semicolon, Settings::NativeKeyboard::Semicolon}, +        {Qt::Key_Apostrophe, Settings::NativeKeyboard::Quote}, +        {Qt::Key_Dead_Grave, Settings::NativeKeyboard::Backquote}, +        {Qt::Key_Comma, Settings::NativeKeyboard::Comma}, +        {Qt::Key_Period, Settings::NativeKeyboard::Period}, +        {Qt::Key_Slash, Settings::NativeKeyboard::Slash}, +        {Qt::Key_CapsLock, Settings::NativeKeyboard::CapsLockKey}, +        {Qt::Key_F1, Settings::NativeKeyboard::F1}, +        {Qt::Key_F2, Settings::NativeKeyboard::F2}, +        {Qt::Key_F3, Settings::NativeKeyboard::F3}, +        {Qt::Key_F4, Settings::NativeKeyboard::F4}, +        {Qt::Key_F5, Settings::NativeKeyboard::F5}, +        {Qt::Key_F6, Settings::NativeKeyboard::F6}, +        {Qt::Key_F7, Settings::NativeKeyboard::F7}, +        {Qt::Key_F8, Settings::NativeKeyboard::F8}, +        {Qt::Key_F9, Settings::NativeKeyboard::F9}, +        {Qt::Key_F10, Settings::NativeKeyboard::F10}, +        {Qt::Key_F11, Settings::NativeKeyboard::F11}, +        {Qt::Key_F12, Settings::NativeKeyboard::F12}, +        {Qt::Key_Print, Settings::NativeKeyboard::PrintScreen}, +        {Qt::Key_ScrollLock, Settings::NativeKeyboard::ScrollLockKey}, +        {Qt::Key_Pause, Settings::NativeKeyboard::Pause}, +        {Qt::Key_Insert, Settings::NativeKeyboard::Insert}, +        {Qt::Key_Home, Settings::NativeKeyboard::Home}, +        {Qt::Key_PageUp, Settings::NativeKeyboard::PageUp}, +        {Qt::Key_Delete, Settings::NativeKeyboard::Delete}, +        {Qt::Key_End, Settings::NativeKeyboard::End}, +        {Qt::Key_PageDown, Settings::NativeKeyboard::PageDown}, +        {Qt::Key_Right, Settings::NativeKeyboard::Right}, +        {Qt::Key_Left, Settings::NativeKeyboard::Left}, +        {Qt::Key_Down, Settings::NativeKeyboard::Down}, +        {Qt::Key_Up, Settings::NativeKeyboard::Up}, +        {Qt::Key_NumLock, Settings::NativeKeyboard::NumLockKey}, +        // Numpad keys are missing here +        {Qt::Key_F13, Settings::NativeKeyboard::F13}, +        {Qt::Key_F14, Settings::NativeKeyboard::F14}, +        {Qt::Key_F15, Settings::NativeKeyboard::F15}, +        {Qt::Key_F16, Settings::NativeKeyboard::F16}, +        {Qt::Key_F17, Settings::NativeKeyboard::F17}, +        {Qt::Key_F18, Settings::NativeKeyboard::F18}, +        {Qt::Key_F19, Settings::NativeKeyboard::F19}, +        {Qt::Key_F20, Settings::NativeKeyboard::F20}, +        {Qt::Key_F21, Settings::NativeKeyboard::F21}, +        {Qt::Key_F22, Settings::NativeKeyboard::F22}, +        {Qt::Key_F23, Settings::NativeKeyboard::F23}, +        {Qt::Key_F24, Settings::NativeKeyboard::F24}, +        // {Qt::..., Settings::NativeKeyboard::KPComma}, +        // {Qt::..., Settings::NativeKeyboard::Ro}, +        {Qt::Key_Hiragana_Katakana, Settings::NativeKeyboard::KatakanaHiragana}, +        {Qt::Key_yen, Settings::NativeKeyboard::Yen}, +        {Qt::Key_Henkan, Settings::NativeKeyboard::Henkan}, +        {Qt::Key_Muhenkan, Settings::NativeKeyboard::Muhenkan}, +        // {Qt::..., Settings::NativeKeyboard::NumPadCommaPc98}, +        {Qt::Key_Hangul, Settings::NativeKeyboard::HangulEnglish}, +        {Qt::Key_Hangul_Hanja, Settings::NativeKeyboard::Hanja}, +        {Qt::Key_Katakana, Settings::NativeKeyboard::KatakanaKey}, +        {Qt::Key_Hiragana, Settings::NativeKeyboard::HiraganaKey}, +        {Qt::Key_Zenkaku_Hankaku, Settings::NativeKeyboard::ZenkakuHankaku}, +        // Modifier keys are handled by the modifier property +    }; + +    for (const auto& [qkey, nkey] : key_map) { +        if (qt_key == qkey) { +            return nkey; +        }      } + +    return Settings::NativeKeyboard::None;  }  int GRenderWindow::QtModifierToSwitchModifier(Qt::KeyboardModifiers qt_modifiers) { diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index f46fff340..05f49c0d2 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -15,12 +15,22 @@ CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent)      : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),        ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} {      ui->setupUi(this); -    connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); -    connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); -    connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); -    connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); -    connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); -    connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); + +    connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_GameBoot_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Gameplay_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Gameplay_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_NoFreeze_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_NoFreeze_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Complete_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Complete_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Graphical_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Graphical_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Graphical_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Audio_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Audio_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Audio_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); +      connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);      connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this,              &CompatDB::OnTestcaseSubmitted); @@ -30,29 +40,82 @@ CompatDB::~CompatDB() = default;  enum class CompatDBPage {      Intro = 0, -    Selection = 1, -    Final = 2, +    GameBoot = 1, +    GamePlay = 2, +    Freeze = 3, +    Completion = 4, +    Graphical = 5, +    Audio = 6, +    Final = 7,  };  void CompatDB::Submit() { -    QButtonGroup* compatibility = new QButtonGroup(this); -    compatibility->addButton(ui->radioButton_Perfect, 0); -    compatibility->addButton(ui->radioButton_Great, 1); -    compatibility->addButton(ui->radioButton_Okay, 2); -    compatibility->addButton(ui->radioButton_Bad, 3); -    compatibility->addButton(ui->radioButton_IntroMenu, 4); -    compatibility->addButton(ui->radioButton_WontBoot, 5); +    QButtonGroup* compatibility_GameBoot = new QButtonGroup(this); +    compatibility_GameBoot->addButton(ui->radioButton_GameBoot_Yes, 0); +    compatibility_GameBoot->addButton(ui->radioButton_GameBoot_No, 1); + +    QButtonGroup* compatibility_Gameplay = new QButtonGroup(this); +    compatibility_Gameplay->addButton(ui->radioButton_Gameplay_Yes, 0); +    compatibility_Gameplay->addButton(ui->radioButton_Gameplay_No, 1); + +    QButtonGroup* compatibility_NoFreeze = new QButtonGroup(this); +    compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_Yes, 0); +    compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_No, 1); + +    QButtonGroup* compatibility_Complete = new QButtonGroup(this); +    compatibility_Complete->addButton(ui->radioButton_Complete_Yes, 0); +    compatibility_Complete->addButton(ui->radioButton_Complete_No, 1); + +    QButtonGroup* compatibility_Graphical = new QButtonGroup(this); +    compatibility_Graphical->addButton(ui->radioButton_Graphical_Major, 0); +    compatibility_Graphical->addButton(ui->radioButton_Graphical_Minor, 1); +    compatibility_Graphical->addButton(ui->radioButton_Graphical_No, 2); + +    QButtonGroup* compatibility_Audio = new QButtonGroup(this); +    compatibility_Audio->addButton(ui->radioButton_Audio_Major, 0); +    compatibility_Graphical->addButton(ui->radioButton_Audio_Minor, 1); +    compatibility_Audio->addButton(ui->radioButton_Audio_No, 2); + +    const int compatiblity = static_cast<int>(CalculateCompatibility()); +      switch ((static_cast<CompatDBPage>(currentId()))) { -    case CompatDBPage::Selection: -        if (compatibility->checkedId() == -1) { +    case CompatDBPage::Intro: +        break; +    case CompatDBPage::GameBoot: +        if (compatibility_GameBoot->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::GamePlay: +        if (compatibility_Gameplay->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::Freeze: +        if (compatibility_NoFreeze->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::Completion: +        if (compatibility_Complete->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::Graphical: +        if (compatibility_Graphical->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::Audio: +        if (compatibility_Audio->checkedId() == -1) {              button(NextButton)->setEnabled(false);          }          break;      case CompatDBPage::Final:          back(); -        LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); +        LOG_INFO(Frontend, "Compatibility Rating: {}", compatiblity);          telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", -                                   compatibility->checkedId()); +                                   compatiblity);          button(NextButton)->setEnabled(false);          button(NextButton)->setText(tr("Submitting")); @@ -63,9 +126,70 @@ void CompatDB::Submit() {          break;      default:          LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); +        break; +    } +} + +int CompatDB::nextId() const { +    switch ((static_cast<CompatDBPage>(currentId()))) { +    case CompatDBPage::Intro: +        return static_cast<int>(CompatDBPage::GameBoot); +    case CompatDBPage::GameBoot: +        if (ui->radioButton_GameBoot_No->isChecked()) { +            return static_cast<int>(CompatDBPage::Final); +        } +        return static_cast<int>(CompatDBPage::GamePlay); +    case CompatDBPage::GamePlay: +        if (ui->radioButton_Gameplay_No->isChecked()) { +            return static_cast<int>(CompatDBPage::Final); +        } +        return static_cast<int>(CompatDBPage::Freeze); +    case CompatDBPage::Freeze: +        if (ui->radioButton_NoFreeze_No->isChecked()) { +            return static_cast<int>(CompatDBPage::Final); +        } +        return static_cast<int>(CompatDBPage::Completion); +    case CompatDBPage::Completion: +        if (ui->radioButton_Complete_No->isChecked()) { +            return static_cast<int>(CompatDBPage::Final); +        } +        return static_cast<int>(CompatDBPage::Graphical); +    case CompatDBPage::Graphical: +        return static_cast<int>(CompatDBPage::Audio); +    case CompatDBPage::Audio: +        return static_cast<int>(CompatDBPage::Final); +    case CompatDBPage::Final: +        return -1; +    default: +        LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); +        return static_cast<int>(CompatDBPage::Intro);      }  } +CompatibilityStatus CompatDB::CalculateCompatibility() const { +    if (ui->radioButton_GameBoot_No->isChecked()) { +        return CompatibilityStatus::WontBoot; +    } + +    if (ui->radioButton_Gameplay_No->isChecked()) { +        return CompatibilityStatus::IntroMenu; +    } + +    if (ui->radioButton_NoFreeze_No->isChecked() || ui->radioButton_Complete_No->isChecked()) { +        return CompatibilityStatus::Ingame; +    } + +    if (ui->radioButton_Graphical_Major->isChecked() || ui->radioButton_Audio_Major->isChecked()) { +        return CompatibilityStatus::Ingame; +    } + +    if (ui->radioButton_Graphical_Minor->isChecked() || ui->radioButton_Audio_Minor->isChecked()) { +        return CompatibilityStatus::Playable; +    } + +    return CompatibilityStatus::Perfect; +} +  void CompatDB::OnTestcaseSubmitted() {      if (!testcase_watcher.result()) {          QMessageBox::critical(this, tr("Communication error"), diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h index 3252fc47a..37e11278b 100644 --- a/src/yuzu/compatdb.h +++ b/src/yuzu/compatdb.h @@ -12,12 +12,22 @@ namespace Ui {  class CompatDB;  } +enum class CompatibilityStatus { +    Perfect = 0, +    Playable = 1, +    // Unused: Okay = 2, +    Ingame = 3, +    IntroMenu = 4, +    WontBoot = 5, +}; +  class CompatDB : public QWizard {      Q_OBJECT  public:      explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr);      ~CompatDB(); +    int nextId() const override;  private:      QFutureWatcher<bool> testcase_watcher; @@ -25,6 +35,7 @@ private:      std::unique_ptr<Ui::CompatDB> ui;      void Submit(); +    CompatibilityStatus CalculateCompatibility() const;      void OnTestcaseSubmitted();      void EnableNext(); diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui index 3ca55eda6..d11669df2 100644 --- a/src/yuzu/compatdb.ui +++ b/src/yuzu/compatdb.ui @@ -58,128 +58,311 @@      </item>     </layout>    </widget> -  <widget class="QWizardPage" name="wizard_Report"> +  <widget class="QWizardPage" name="wizard_GameBoot">     <property name="title">      <string>Report Game Compatibility</string>     </property>     <attribute name="pageId">      <string notr="true">1</string>     </attribute> -   <layout class="QFormLayout" name="formLayout"> +   <layout class="QFormLayout" name="formLayout1"> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent1"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property> +      <property name="text"> +       <string><html><head/><body><p>Does the game boot?</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer1"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item>      <item row="2" column="0"> -     <widget class="QRadioButton" name="radioButton_Perfect"> +     <widget class="QRadioButton" name="radioButton_GameBoot_Yes">        <property name="text"> -       <string>Perfect</string> +       <string>Yes   The game starts to output video or audio</string>        </property>       </widget>      </item> -    <item row="2" column="1"> -     <widget class="QLabel" name="lbl_Perfect"> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_GameBoot_No">        <property name="text"> -       <string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> +       <string>No    The game doesn't get past the "Launching..." screen</string>        </property> -      <property name="wordWrap"> -       <bool>true</bool> +     </widget> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_GamePlay"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">2</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout2"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_Gameplay_Yes"> +      <property name="text"> +       <string>Yes   The game gets past the intro/menu and into gameplay</string>        </property>       </widget>      </item>      <item row="4" column="0"> -     <widget class="QRadioButton" name="radioButton_Great"> +     <widget class="QRadioButton" name="radioButton_Gameplay_No">        <property name="text"> -       <string>Great</string> +       <string>No    The game crashes or freezes while loading or using the menu</string>        </property>       </widget>      </item> -    <item row="4" column="1"> -     <widget class="QLabel" name="lbl_Great"> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent2"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property>        <property name="text"> -       <string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> +       <string><html><head/><body><p>Does the game reach gameplay?</p></body></html></string>        </property>        <property name="wordWrap">         <bool>true</bool>        </property>       </widget>      </item> -    <item row="5" column="0"> -     <widget class="QRadioButton" name="radioButton_Okay"> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer2"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_NoFreeze"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">3</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout3"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_NoFreeze_Yes">        <property name="text"> -       <string>Okay</string> +       <string>Yes   The game works without crashes</string>        </property>       </widget>      </item> -    <item row="5" column="1"> -     <widget class="QLabel" name="lbl_Okay"> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_NoFreeze_No"> +      <property name="text"> +       <string>No    The game crashes or freezes during gameplay</string> +      </property> +     </widget> +    </item> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent3"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property>        <property name="text"> -       <string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> +       <string><html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html></string>        </property>        <property name="wordWrap">         <bool>true</bool>        </property>       </widget>      </item> -    <item row="6" column="0"> -     <widget class="QRadioButton" name="radioButton_Bad"> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer3"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_Complete"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">4</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout4"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_Complete_Yes">        <property name="text"> -       <string>Bad</string> +       <string>Yes   The game can be finished without any workarounds</string>        </property>       </widget>      </item> -    <item row="6" column="1"> -     <widget class="QLabel" name="lbl_Bad"> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_Complete_No"> +      <property name="text"> +       <string>No    The game can't progress past a certain area</string> +      </property> +     </widget> +    </item> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent4"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property>        <property name="text"> -       <string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> +       <string><html><head/><body><p>Is the game completely playable from start to finish?</p></body></html></string>        </property>        <property name="wordWrap">         <bool>true</bool>        </property>       </widget>      </item> -    <item row="7" column="0"> -     <widget class="QRadioButton" name="radioButton_IntroMenu"> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer4"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_Graphical"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">5</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout5"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_Graphical_Major"> +      <property name="text"> +       <string>Major   The game has major graphical errors</string> +      </property> +     </widget> +    </item> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_Graphical_Minor">        <property name="text"> -       <string>Intro/Menu</string> +       <string>Minor   The game has minor graphical errors</string>        </property>       </widget>      </item> -    <item row="7" column="1"> -     <widget class="QLabel" name="lbl_IntroMenu"> +    <item row="6" column="0"> +     <widget class="QRadioButton" name="radioButton_Graphical_No"> +      <property name="text"> +       <string>None     Everything is rendered as it looks on the Nintendo Switch</string> +      </property> +     </widget> +    </item> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent5"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property>        <property name="text"> -       <string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> +       <string><html><head/><body><p>Does the game have any graphical glitches?</p></body></html></string>        </property>        <property name="wordWrap">         <bool>true</bool>        </property>       </widget>      </item> -    <item row="8" column="0"> -     <widget class="QRadioButton" name="radioButton_WontBoot"> -      <property name="text"> -       <string>Won't Boot</string> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer5"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum>        </property> -      <property name="checkable"> -       <bool>true</bool> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size>        </property> -      <property name="checked"> -       <bool>false</bool> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_Audio"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">6</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout6"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_Audio_Major"> +      <property name="text"> +       <string>Major   The game has major audio errors</string> +      </property> +     </widget> +    </item> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_Audio_Minor"> +      <property name="text"> +       <string>Minor   The game has minor audio errors</string>        </property>       </widget>      </item> -    <item row="8" column="1"> -     <widget class="QLabel" name="lbl_WontBoot"> +    <item row="6" column="0"> +     <widget class="QRadioButton" name="radioButton_Audio_No">        <property name="text"> -       <string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> +       <string>None     Audio is played perfectly</string>        </property>       </widget>      </item>      <item row="0" column="0" colspan="2"> -     <widget class="QLabel" name="lbl_Independent"> +     <widget class="QLabel" name="lbl_Independent6">        <property name="font">         <font>          <pointsize>10</pointsize>         </font>        </property>        <property name="text"> -       <string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> +       <string><html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html></string>        </property>        <property name="wordWrap">         <bool>true</bool> @@ -187,7 +370,7 @@       </widget>      </item>      <item row="1" column="0" colspan="2"> -     <spacer name="verticalSpacer"> +     <spacer name="verticalSpacer6">        <property name="orientation">         <enum>Qt::Vertical</enum>        </property> @@ -206,7 +389,7 @@      <string>Thank you for your submission!</string>     </property>     <attribute name="pageId"> -    <string notr="true">2</string> +    <string notr="true">7</string>     </attribute>    </widget>   </widget> diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 5c0217ba8..a47089988 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -2,6 +2,9 @@  // SPDX-License-Identifier: GPL-2.0-or-later  #include <algorithm> +#include <functional> +#include <QDialog> +#include <QDialogButtonBox>  #include <QFileDialog>  #include <QGraphicsItem>  #include <QHeaderView> @@ -108,9 +111,12 @@ ConfigureProfileManager::ConfigureProfileManager(const Core::System& system_, QW      connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser);      connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser); -    connect(ui->pm_remove, &QPushButton::clicked, this, &ConfigureProfileManager::DeleteUser); +    connect(ui->pm_remove, &QPushButton::clicked, this, +            &ConfigureProfileManager::ConfirmDeleteUser);      connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage); +    confirm_dialog = new ConfigureProfileManagerDeleteDialog(this); +      scene = new QGraphicsScene;      ui->current_user_icon->setScene(scene); @@ -230,26 +236,23 @@ void ConfigureProfileManager::RenameUser() {      UpdateCurrentUser();  } -void ConfigureProfileManager::DeleteUser() { +void ConfigureProfileManager::ConfirmDeleteUser() {      const auto index = tree_view->currentIndex().row();      const auto uuid = profile_manager->GetUser(index);      ASSERT(uuid);      const auto username = GetAccountUsername(*profile_manager, *uuid); -    const auto confirm = QMessageBox::question( -        this, tr("Confirm Delete"), -        tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); - -    if (confirm == QMessageBox::No) { -        return; -    } +    confirm_dialog->SetInfo(username, *uuid, [this, uuid]() { DeleteUser(*uuid); }); +    confirm_dialog->show(); +} +void ConfigureProfileManager::DeleteUser(const Common::UUID& uuid) {      if (Settings::values.current_user.GetValue() == tree_view->currentIndex().row()) {          Settings::values.current_user = 0;      }      UpdateCurrentUser(); -    if (!profile_manager->RemoveUser(*uuid)) { +    if (!profile_manager->RemoveUser(uuid)) {          return;      } @@ -319,3 +322,47 @@ void ConfigureProfileManager::SetUserImage() {                          new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});      UpdateCurrentUser();  } + +ConfigureProfileManagerDeleteDialog::ConfigureProfileManagerDeleteDialog(QWidget* parent) +    : QDialog{parent} { +    auto dialog_vbox_layout = new QVBoxLayout(this); +    dialog_button_box = +        new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, Qt::Horizontal, parent); +    auto label_message = +        new QLabel(tr("Delete this user? All of the user's save data will be deleted."), this); +    label_info = new QLabel(this); +    auto dialog_hbox_layout_widget = new QWidget(this); +    auto dialog_hbox_layout = new QHBoxLayout(dialog_hbox_layout_widget); +    icon_scene = new QGraphicsScene(0, 0, 64, 64, this); +    auto icon_view = new QGraphicsView(icon_scene, this); + +    dialog_hbox_layout_widget->setLayout(dialog_hbox_layout); +    icon_view->setMaximumSize(64, 64); +    icon_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +    icon_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +    this->setLayout(dialog_vbox_layout); +    this->setWindowTitle(tr("Confirm Delete")); +    this->setSizeGripEnabled(false); +    dialog_vbox_layout->addWidget(label_message); +    dialog_vbox_layout->addWidget(dialog_hbox_layout_widget); +    dialog_vbox_layout->addWidget(dialog_button_box); +    dialog_hbox_layout->addWidget(icon_view); +    dialog_hbox_layout->addWidget(label_info); + +    connect(dialog_button_box, &QDialogButtonBox::rejected, this, [this]() { close(); }); +} + +ConfigureProfileManagerDeleteDialog::~ConfigureProfileManagerDeleteDialog() = default; + +void ConfigureProfileManagerDeleteDialog::SetInfo(const QString& username, const Common::UUID& uuid, +                                                  std::function<void()> accept_callback) { +    label_info->setText( +        tr("Name: %1\nUUID: %2").arg(username, QString::fromStdString(uuid.FormattedString()))); +    icon_scene->clear(); +    icon_scene->addPixmap(GetIcon(uuid)); + +    connect(dialog_button_box, &QDialogButtonBox::accepted, this, [this, accept_callback]() { +        close(); +        accept_callback(); +    }); +} diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h index fe9033779..c4b1a334e 100644 --- a/src/yuzu/configuration/configure_profile_manager.h +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -3,16 +3,24 @@  #pragma once +#include <functional>  #include <memory> +#include <QDialog>  #include <QList>  #include <QWidget> +namespace Common { +struct UUID; +} +  namespace Core {  class System;  }  class QGraphicsScene; +class QDialogButtonBox; +class QLabel;  class QStandardItem;  class QStandardItemModel;  class QTreeView; @@ -26,6 +34,20 @@ namespace Ui {  class ConfigureProfileManager;  } +class ConfigureProfileManagerDeleteDialog : public QDialog { +public: +    explicit ConfigureProfileManagerDeleteDialog(QWidget* parent); +    ~ConfigureProfileManagerDeleteDialog(); + +    void SetInfo(const QString& username, const Common::UUID& uuid, +                 std::function<void()> accept_callback); + +private: +    QDialogButtonBox* dialog_button_box; +    QGraphicsScene* icon_scene; +    QLabel* label_info; +}; +  class ConfigureProfileManager : public QWidget {      Q_OBJECT @@ -47,7 +69,8 @@ private:      void SelectUser(const QModelIndex& index);      void AddUser();      void RenameUser(); -    void DeleteUser(); +    void ConfirmDeleteUser(); +    void DeleteUser(const Common::UUID& uuid);      void SetUserImage();      QVBoxLayout* layout; @@ -55,6 +78,8 @@ private:      QStandardItemModel* item_model;      QGraphicsScene* scene; +    ConfigureProfileManagerDeleteDialog* confirm_dialog; +      std::vector<QList<QStandardItem*>> list_items;      std::unique_ptr<Ui::ConfigureProfileManager> ui; diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui index cfe7478c8..bd6dea4f4 100644 --- a/src/yuzu/configuration/configure_profile_manager.ui +++ b/src/yuzu/configuration/configure_profile_manager.ui @@ -57,6 +57,12 @@                <height>48</height>               </size>              </property> +            <property name="frameShape"> +             <enum>QFrame::NoFrame</enum> +            </property> +            <property name="frameShadow"> +             <enum>QFrame::Plain</enum> +            </property>              <property name="verticalScrollBarPolicy">               <enum>Qt::ScrollBarAlwaysOff</enum>              </property> diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 6198d1e4e..1800f090f 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -145,12 +145,14 @@ public:              const char* tooltip;          };          // clang-format off +        const auto ingame_status = +                       CompatStatus{QStringLiteral("#f2d624"), QT_TR_NOOP("Ingame"),     QT_TR_NOOP("Game starts, but crashes or major glitches prevent it from being completed.")};          static const std::map<QString, CompatStatus> status_data = { -            {QStringLiteral("0"),  {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"),    QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, -            {QStringLiteral("1"),  {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"),      QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, -            {QStringLiteral("2"),  {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"),       QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, -            {QStringLiteral("3"),  {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"),        QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, -            {QStringLiteral("4"),  {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, +            {QStringLiteral("0"),  {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"),    QT_TR_NOOP("Game can be played without issues.")}}, +            {QStringLiteral("1"),  {QStringLiteral("#47d35c"), QT_TR_NOOP("Playable"),   QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish.")}}, +            {QStringLiteral("2"),  ingame_status}, +            {QStringLiteral("3"),  ingame_status}, // Fallback for the removed "Okay" category +            {QStringLiteral("4"),  {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game loads, but is unable to progress past the Start Screen.")}},              {QStringLiteral("5"),  {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},              {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}},          }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 59e56633a..4081af391 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -15,6 +15,7 @@  #endif  // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/qt_amiibo_settings.h"  #include "applets/qt_controller.h"  #include "applets/qt_error.h"  #include "applets/qt_profile_select.h" @@ -26,6 +27,7 @@  #include "configuration/configure_tas.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h" +#include "core/frontend/applets/cabinet.h"  #include "core/frontend/applets/controller.h"  #include "core/frontend/applets/general_frontend.h"  #include "core/frontend/applets/mii_edit.h" @@ -342,6 +344,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan      const auto override_build =          fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);      const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; +    const auto processor_count = std::thread::hardware_concurrency();      LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);      LogRuntimes(); @@ -360,7 +363,11 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan          }      }      LOG_INFO(Frontend, "Host CPU: {}", cpu_string); +    if (std::optional<int> processor_core = Common::GetProcessorCount()) { +        LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); +    }  #endif +    LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);      LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());      LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",               Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); @@ -543,6 +550,11 @@ void GMainWindow::RegisterMetaTypes() {      // Register applet types +    // Cabinet Applet +    qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters"); +    qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>( +        "std::shared_ptr<Service::NFP::NfpDevice>"); +      // Controller Applet      qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); @@ -564,6 +576,21 @@ void GMainWindow::RegisterMetaTypes() {      qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");  } +void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, +                                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) { +    QtAmiiboSettingsDialog dialog(this, parameters, input_subsystem.get(), nfp_device); + +    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | +                          Qt::WindowTitleHint | Qt::WindowSystemMenuHint); +    dialog.setWindowModality(Qt::WindowModal); +    if (dialog.exec() == QDialog::Rejected) { +        emit AmiiboSettingsFinished(false, {}); +        return; +    } + +    emit AmiiboSettingsFinished(true, dialog.GetName()); +} +  void GMainWindow::ControllerSelectorReconfigureControllers(      const Core::Frontend::ControllerParameters& parameters) {      QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system); @@ -1541,6 +1568,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p      system->SetFilesystem(vfs);      system->SetAppletFrontendSet({ +        std::make_unique<QtAmiiboSettings>(*this),     // Amiibo Settings          std::make_unique<QtControllerSelector>(*this), // Controller Selector          std::make_unique<QtErrorDisplay>(*this),       // Error Display          nullptr,                                       // Mii Editor @@ -1951,6 +1979,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target      }      default:          UNIMPLEMENTED(); +        break;      }      const QString qpath = QString::fromStdString(Common::FS::PathToUTF8String(path)); @@ -2018,38 +2047,50 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src      return true;  } +QString GMainWindow::GetGameListErrorRemoving(InstalledEntryType type) const { +    switch (type) { +    case InstalledEntryType::Game: +        return tr("Error Removing Contents"); +    case InstalledEntryType::Update: +        return tr("Error Removing Update"); +    case InstalledEntryType::AddOnContent: +        return tr("Error Removing DLC"); +    default: +        return QStringLiteral("Error Removing <Invalid Type>"); +    } +}  void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { -    const QString entry_type = [type] { +    const QString entry_question = [type] {          switch (type) {          case InstalledEntryType::Game: -            return tr("Contents"); +            return tr("Remove Installed Game Contents?");          case InstalledEntryType::Update: -            return tr("Update"); +            return tr("Remove Installed Game Update?");          case InstalledEntryType::AddOnContent: -            return tr("DLC"); +            return tr("Remove Installed Game DLC?");          default: -            return QString{}; +            return QStringLiteral("Remove Installed Game <Invalid Type>?");          }      }(); -    if (QMessageBox::question( -            this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type), -            QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { +    if (QMessageBox::question(this, tr("Remove Entry"), entry_question, +                              QMessageBox::Yes | QMessageBox::No, +                              QMessageBox::No) != QMessageBox::Yes) {          return;      }      switch (type) {      case InstalledEntryType::Game: -        RemoveBaseContent(program_id, entry_type); +        RemoveBaseContent(program_id, type);          [[fallthrough]];      case InstalledEntryType::Update: -        RemoveUpdateContent(program_id, entry_type); +        RemoveUpdateContent(program_id, type);          if (type != InstalledEntryType::Game) {              break;          }          [[fallthrough]];      case InstalledEntryType::AddOnContent: -        RemoveAddOnContent(program_id, entry_type); +        RemoveAddOnContent(program_id, type);          break;      }      Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / @@ -2057,7 +2098,7 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT      game_list->PopulateAsync(UISettings::values.game_dirs);  } -void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) { +void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {      const auto& fs_controller = system->GetFileSystemController();      const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||                       fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); @@ -2067,12 +2108,12 @@ void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) {                                   tr("Successfully removed the installed base game."));      } else {          QMessageBox::warning( -            this, tr("Error Removing %1").arg(entry_type), +            this, GetGameListErrorRemoving(type),              tr("The base game is not installed in the NAND and cannot be removed."));      }  } -void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) { +void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {      const auto update_id = program_id | 0x800;      const auto& fs_controller = system->GetFileSystemController();      const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || @@ -2082,12 +2123,12 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type)          QMessageBox::information(this, tr("Successfully Removed"),                                   tr("Successfully removed the installed update."));      } else { -        QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), +        QMessageBox::warning(this, GetGameListErrorRemoving(type),                               tr("There is no update installed for this title."));      }  } -void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) { +void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) {      u32 count{};      const auto& fs_controller = system->GetFileSystemController();      const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( @@ -2105,7 +2146,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)      }      if (count == 0) { -        QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type), +        QMessageBox::warning(this, GetGameListErrorRemoving(type),                               tr("There are no DLC installed for this title."));          return;      } @@ -2803,6 +2844,20 @@ void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_tex  }  void GMainWindow::OnMenuReportCompatibility() { +    const auto& caps = Common::GetCPUCaps(); +    const bool has_fma = caps.fma || caps.fma4; +    const auto processor_count = std::thread::hardware_concurrency(); +    const bool has_4threads = processor_count == 0 || processor_count >= 4; +    const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; +    const bool has_broken_vulkan = UISettings::values.has_broken_vulkan; + +    if (!has_fma || !has_4threads || !has_8gb_ram || has_broken_vulkan) { +        QMessageBox::critical(this, tr("Hardware requirements not met"), +                              tr("Your system does not meet the recommended hardware requirements. " +                                 "Compatibility reporting has been disabled.")); +        return; +    } +      if (!Settings::values.yuzu_token.GetValue().empty() &&          !Settings::values.yuzu_username.GetValue().empty()) {          CompatDB compatdb{system->TelemetrySession(), this}; @@ -3168,6 +3223,7 @@ void GMainWindow::OnToggleGpuAccuracy() {      case Settings::GPUAccuracy::Extreme:      default: {          Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::High); +        break;      }      } @@ -3500,6 +3556,7 @@ void GMainWindow::UpdateGPUAccuracyButton() {      default: {          gpu_accuracy_button->setText(tr("GPU ERROR"));          gpu_accuracy_button->setChecked(true); +        break;      }      }  } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 150ada84c..6a9992d05 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -55,6 +55,7 @@ class System;  } // namespace Core  namespace Core::Frontend { +struct CabinetParameters;  struct ControllerParameters;  struct InlineAppearParameters;  struct InlineTextParameters; @@ -82,6 +83,10 @@ enum class SwkbdReplyType : u32;  enum class WebExitReason : u32;  } // namespace Service::AM::Applets +namespace Service::NFP { +class NfpDevice; +} // namespace Service::NFP +  namespace Ui {  class MainWindow;  } @@ -149,6 +154,8 @@ signals:      void UpdateInstallProgress(); +    void AmiiboSettingsFinished(bool is_success, const std::string& name); +      void ControllerSelectorReconfigureFinished();      void ErrorDisplayFinished(); @@ -170,6 +177,8 @@ public slots:      void OnExecuteProgram(std::size_t program_index);      void OnExit();      void OnSaveConfig(); +    void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, +                                  std::shared_ptr<Service::NFP::NfpDevice> nfp_device);      void ControllerSelectorReconfigureControllers(          const Core::Frontend::ControllerParameters& parameters);      void SoftwareKeyboardInitialize( @@ -324,9 +333,10 @@ private slots:      void OnMouseActivity();  private: -    void RemoveBaseContent(u64 program_id, const QString& entry_type); -    void RemoveUpdateContent(u64 program_id, const QString& entry_type); -    void RemoveAddOnContent(u64 program_id, const QString& entry_type); +    QString GetGameListErrorRemoving(InstalledEntryType type) const; +    void RemoveBaseContent(u64 program_id, InstalledEntryType type); +    void RemoveUpdateContent(u64 program_id, InstalledEntryType type); +    void RemoveAddOnContent(u64 program_id, InstalledEntryType type);      void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target);      void RemoveAllTransferableShaderCaches(u64 program_id);      void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 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 65455c86e..25948328c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -84,6 +84,7 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste      default:          LOG_CRITICAL(Frontend, "Window manager subsystem not implemented");          std::exit(EXIT_FAILURE); +        break;      }      OnResize(); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index e16f79eb4..dfe5a30ea 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -351,6 +351,7 @@ int main(int argc, char** argv) {                           "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",                           loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));          } +        break;      }      system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL"); | 
