diff options
91 files changed, 2263 insertions, 239 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/externals/dynarmic b/externals/dynarmic -Subproject 424fdb5c5026ec5bdd7553271190397f63fb503 +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/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/core/CMakeLists.txt b/src/core/CMakeLists.txt index 705667080..5629980d9 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 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/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/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index bda098511..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. diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 5387bf5fe..612fc76fa 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -2301,6 +2301,7 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size,              break;          default:              ASSERT(false); +            break;          }      } @@ -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 55a9c5fae..d1dc62401 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -395,6 +395,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:      default:          ASSERT(false); +        break;      }      // Create TLS region diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 9962ad171..e520cab47 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2701,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_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/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index 296603764..c860fd1a1 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -75,6 +75,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();          } @@ -95,6 +98,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; @@ -141,7 +146,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; @@ -153,7 +158,7 @@ Result NfpDevice::StartDetection(s32 protocol_) {      }      device_state = DeviceState::SearchingForTag; -    protocol = protocol_; +    allowed_protocols = allowed_protocol;      return ResultSuccess;  } @@ -467,6 +472,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 6d8fc1099..b6a46f2ac 100644 --- a/src/core/hle/service/nfp/nfp_device.h +++ b/src/core/hle/service/nfp/nfp_device.h @@ -3,6 +3,7 @@  #pragma once +#include <span>  #include <vector>  #include "common/common_types.h" @@ -36,7 +37,7 @@ public:      void Initialize();      void Finalize(); -    Result StartDetection(s32 protocol_); +    Result StartDetection(TagProtocol allowed_protocol);      Result StopDetection();      Result Mount(MountTarget mount_target);      Result Unmount(); @@ -52,6 +53,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); @@ -87,7 +89,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 3f7f17598..2fe3c0ea0 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -131,7 +131,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/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 d6562c842..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: 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 c1671c37b..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: 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 5b3b5d1f3..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: diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 0bfc2dd89..8e3e40cd5 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1359,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/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index d502d181c..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): @@ -596,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 34b085388..c3099f9a6 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -3086,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(); @@ -3120,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(); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 54523a4b2..1bf6ca2dd 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -314,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_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 d05a5f60b..115a5e010 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; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 793e0d608..449a14f12 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_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/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_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index f69c0c50f..67b88621a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -213,7 +213,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()) { @@ -256,7 +256,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; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b0bc306f5..70f36d58a 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_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/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 5cc1fbf32..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 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/compatdb.cpp b/src/yuzu/compatdb.cpp index b03e71248..05f49c0d2 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -126,6 +126,7 @@ void CompatDB::Submit() {          break;      default:          LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); +        break;      }  } 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/main.cpp b/src/yuzu/main.cpp index 032ff1cbc..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" @@ -361,11 +363,10 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan          }      }      LOG_INFO(Frontend, "Host CPU: {}", cpu_string); -#endif -      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", @@ -549,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"); @@ -570,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); @@ -1547,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 @@ -1957,6 +1979,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target      }      default:          UNIMPLEMENTED(); +        break;      }      const QString qpath = QString::fromStdString(Common::FS::PathToUTF8String(path)); @@ -3200,6 +3223,7 @@ void GMainWindow::OnToggleGpuAccuracy() {      case Settings::GPUAccuracy::Extreme:      default: {          Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::High); +        break;      }      } @@ -3532,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 b73f550dd..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( 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"); | 
