diff options
109 files changed, 2772 insertions, 511 deletions
| diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 3aea9b0f2..5005ba519 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -54,8 +54,9 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,              double l = 0.0;              double r = 0.0;              for (std::size_t j = 0; j < h.size(); j++) { -                l += Lanczos(taps, pos + j - taps + 1) * h[j][0]; -                r += Lanczos(taps, pos + j - taps + 1) * h[j][1]; +                const double lanczos_calc = Lanczos(taps, pos + j - taps + 1); +                l += lanczos_calc * h[j][0]; +                r += lanczos_calc * h[j][1];              }              output.emplace_back(static_cast<s16>(std::clamp(l, -32768.0, 32767.0)));              output.emplace_back(static_cast<s16>(std::clamp(r, -32768.0, 32767.0))); diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 0c8f5b18e..cbba17632 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {  StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,                                 Stream::ReleaseCallback&& release_callback) {      if (!sink) { -        const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id); -        sink = sink_details.factory(Settings::values.audio_device_id); +        sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);      }      return std::make_shared<Stream>( diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 2e59894ab..2683f3a5f 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -285,8 +285,11 @@ void AudioRenderer::VoiceState::RefreshBuffer() {          break;      } -    samples = -        Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE); +    // Only interpolate when necessary, expensive. +    if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { +        samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, +                              STREAM_SAMPLE_RATE); +    }      is_refresh_pending = false;  } diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index d31a1c844..097328901 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -107,7 +107,7 @@ private:      static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);  }; -CubebSink::CubebSink(std::string target_device_name) { +CubebSink::CubebSink(std::string_view target_device_name) {      if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {          LOG_CRITICAL(Audio_Sink, "cubeb_init failed");          return; diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 59cbf05e9..efb9d1634 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -15,7 +15,7 @@ namespace AudioCore {  class CubebSink final : public Sink {  public: -    explicit CubebSink(std::string device_id); +    explicit CubebSink(std::string_view device_id);      ~CubebSink() override;      SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index a78d78893..61a28d542 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -10,7 +10,7 @@ namespace AudioCore {  class NullSink final : public Sink {  public: -    explicit NullSink(std::string){}; +    explicit NullSink(std::string_view) {}      ~NullSink() override = default;      SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 67cf1f3b2..a848eb1c9 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -14,31 +14,68 @@  #include "common/logging/log.h"  namespace AudioCore { +namespace { +struct SinkDetails { +    using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); +    using ListDevicesFn = std::vector<std::string> (*)(); -// g_sink_details is ordered in terms of desirability, with the best choice at the top. -const std::vector<SinkDetails> g_sink_details = { +    /// Name for this sink. +    const char* id; +    /// A method to call to construct an instance of this type of sink. +    FactoryFn factory; +    /// A method to call to list available devices. +    ListDevicesFn list_devices; +}; + +// sink_details is ordered in terms of desirability, with the best choice at the top. +constexpr SinkDetails sink_details[] = {  #ifdef HAVE_CUBEB -    SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices}, +    SinkDetails{"cubeb", +                [](std::string_view device_id) -> std::unique_ptr<Sink> { +                    return std::make_unique<CubebSink>(device_id); +                }, +                &ListCubebSinkDevices},  #endif -    SinkDetails{"null", &std::make_unique<NullSink, std::string>, +    SinkDetails{"null", +                [](std::string_view device_id) -> std::unique_ptr<Sink> { +                    return std::make_unique<NullSink>(device_id); +                },                  [] { return std::vector<std::string>{"null"}; }},  };  const SinkDetails& GetSinkDetails(std::string_view sink_id) {      auto iter = -        std::find_if(g_sink_details.begin(), g_sink_details.end(), +        std::find_if(std::begin(sink_details), std::end(sink_details),                       [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); -    if (sink_id == "auto" || iter == g_sink_details.end()) { +    if (sink_id == "auto" || iter == std::end(sink_details)) {          if (sink_id != "auto") {              LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);          }          // Auto-select. -        // g_sink_details is ordered in terms of desirability, with the best choice at the front. -        iter = g_sink_details.begin(); +        // sink_details is ordered in terms of desirability, with the best choice at the front. +        iter = std::begin(sink_details);      }      return *iter;  } +} // Anonymous namespace + +std::vector<const char*> GetSinkIDs() { +    std::vector<const char*> sink_ids(std::size(sink_details)); + +    std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), +                   [](const auto& sink) { return sink.id; }); + +    return sink_ids; +} + +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { +    return GetSinkDetails(sink_id).list_devices(); +} + +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { +    return GetSinkDetails(sink_id).factory(device_id); +}  } // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 03534b187..bc8786270 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -4,34 +4,21 @@  #pragma once -#include <functional> -#include <memory>  #include <string>  #include <string_view> -#include <utility>  #include <vector>  namespace AudioCore {  class Sink; -struct SinkDetails { -    using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>; -    using ListDevicesFn = std::function<std::vector<std::string>()>; +/// Retrieves the IDs for all available audio sinks. +std::vector<const char*> GetSinkIDs(); -    SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_) -        : id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {} +/// Gets the list of devices for a particular sink identified by the given ID. +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); -    /// Name for this sink. -    const char* id; -    /// A method to call to construct an instance of this type of sink. -    FactoryFn factory; -    /// A method to call to list available devices. -    ListDevicesFn list_devices; -}; - -extern const std::vector<SinkDetails> g_sink_details; - -const SinkDetails& GetSinkDetails(std::string_view sink_id); +/// Creates an audio sink identified by the given device ID. +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);  } // namespace AudioCore diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 5753b871a..12f6d0114 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -13,7 +13,7 @@  #include <vector>  #ifdef _WIN32  #include <share.h>   // For _SH_DENYWR -#include <windows.h> // For OutputDebugStringA +#include <windows.h> // For OutputDebugStringW  #else  #define _SH_DENYWR 0  #endif @@ -148,7 +148,7 @@ void FileBackend::Write(const Entry& entry) {  void DebuggerBackend::Write(const Entry& entry) {  #ifdef _WIN32 -    ::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str()); +    ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());  #endif  } diff --git a/src/common/quaternion.h b/src/common/quaternion.h index ea39298c1..c528c0b68 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -12,7 +12,7 @@ template <typename T>  class Quaternion {  public:      Math::Vec3<T> xyz; -    T w; +    T w{};      Quaternion<decltype(-T{})> Inverse() const {          return {-xyz, w}; diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index 133122c5f..e7594db68 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -49,6 +49,22 @@ struct ThreadQueueList {          return T();      } +    template <typename UnaryPredicate> +    T get_first_filter(UnaryPredicate filter) const { +        const Queue* cur = first; +        while (cur != nullptr) { +            if (!cur->data.empty()) { +                for (const auto& item : cur->data) { +                    if (filter(item)) +                        return item; +                } +            } +            cur = cur->next_nonempty; +        } + +        return T(); +    } +      T pop_first() {          Queue* cur = first;          while (cur != nullptr) { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 882c9ab59..93f5ba3fe 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -83,6 +83,8 @@ add_library(core STATIC      file_sys/vfs_vector.h      file_sys/xts_archive.cpp      file_sys/xts_archive.h +    frontend/applets/profile_select.cpp +    frontend/applets/profile_select.h      frontend/applets/software_keyboard.cpp      frontend/applets/software_keyboard.h      frontend/emu_window.cpp @@ -162,6 +164,8 @@ add_library(core STATIC      hle/service/am/applet_oe.h      hle/service/am/applets/applets.cpp      hle/service/am/applets/applets.h +    hle/service/am/applets/profile_select.cpp +    hle/service/am/applets/profile_select.h      hle/service/am/applets/software_keyboard.cpp      hle/service/am/applets/software_keyboard.h      hle/service/am/applets/stub_applet.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 4d2491870..afbda8d8b 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -151,6 +151,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {      config.tpidr_el0 = &cb->tpidr_el0;      config.dczid_el0 = 4;      config.ctr_el0 = 0x8444c004; +    config.cntfrq_el0 = 19200000; // Value from fusee.      // Unpredictable instructions      config.define_unpredictable_behaviour = true; diff --git a/src/core/core.cpp b/src/core/core.cpp index 795fabc65..fd10199ec 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@  #include <thread>  #include <utility> +#include "common/file_util.h"  #include "common/logging/log.h"  #include "common/string_util.h"  #include "core/arm/exclusive_monitor.h" @@ -40,7 +41,6 @@ namespace Core {  /*static*/ System System::s_instance; -namespace {  FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,                                           const std::string& path) {      // To account for split 00+01+etc files. @@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,          return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());      } +    if (FileUtil::IsDirectory(path)) +        return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); +      return vfs->OpenFile(path, FileSys::Mode::Read);  } -} // Anonymous namespace -  struct System::Impl { +      Cpu& CurrentCpuCore() {          return cpu_core_manager.GetCurrentCore();      } @@ -97,6 +99,8 @@ struct System::Impl {              virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();          /// Create default implementations of applets if one is not provided. +        if (profile_selector == nullptr) +            profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();          if (software_keyboard == nullptr)              software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); @@ -227,6 +231,7 @@ struct System::Impl {      bool is_powered_on = false;      /// Frontend applets +    std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;      std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;      /// Service manager @@ -422,6 +427,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {      return impl->virtual_filesystem;  } +void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) { +    impl->profile_selector = std::move(applet); +} + +const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const { +    return *impl->profile_selector; +} +  void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {      impl->software_keyboard = std::move(applet);  } diff --git a/src/core/core.h b/src/core/core.h index be71bd437..869921493 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,7 +9,9 @@  #include <string>  #include "common/common_types.h" +#include "core/file_sys/vfs_types.h"  #include "core/hle/kernel/object.h" +#include "frontend/applets/profile_select.h"  namespace Core::Frontend {  class EmuWindow; @@ -55,6 +57,9 @@ class TelemetrySession;  struct PerfStatsResults; +FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, +                                         const std::string& path); +  class System {  public:      System(const System&) = delete; @@ -237,6 +242,10 @@ public:      std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; +    void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet); + +    const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const; +      void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);      const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 12bb90ec8..6690aa575 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -29,8 +29,8 @@ struct Entry {          filename[copy_size] = '\0';      } -    char filename[0x300]; -    INSERT_PADDING_BYTES(4); +    char filename[0x301]; +    INSERT_PADDING_BYTES(3);      EntryType type;      INSERT_PADDING_BYTES(3);      u64 file_size; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6b14e08be..61706966e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -56,6 +56,10 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}  PatchManager::~PatchManager() = default; +u64 PatchManager::GetTitleID() const { +    return title_id; +} +  VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {      LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); @@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {      const auto installed = Service::FileSystem::GetUnionContents(); +    const auto& disabled = Settings::values.disabled_addons[title_id]; +    const auto update_disabled = +        std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); +      // Game Updates      const auto update_tid = GetUpdateTitleID(title_id);      const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); -    if (update != nullptr && update->GetExeFS() != nullptr && +    if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&          update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {          LOG_INFO(Loader, "    ExeFS: Update ({}) applied successfully",                   FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); @@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {          std::vector<VirtualDir> layers;          layers.reserve(patch_dirs.size() + 1);          for (const auto& subdir : patch_dirs) { +            if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) +                continue; +              auto exefs_dir = subdir->GetSubdirectory("exefs");              if (exefs_dir != nullptr)                  layers.push_back(std::move(exefs_dir)); @@ -111,11 +122,16 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {      return exefs;  } -static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, -                                               const std::string& build_id) { +std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, +                                                      const std::string& build_id) const { +    const auto& disabled = Settings::values.disabled_addons[title_id]; +      std::vector<VirtualFile> out;      out.reserve(patch_dirs.size());      for (const auto& subdir : patch_dirs) { +        if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) +            continue; +          auto exefs_dir = subdir->GetSubdirectory("exefs");          if (exefs_dir != nullptr) {              for (const auto& file : exefs_dir->GetFiles()) { @@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t          return;      } +    const auto& disabled = Settings::values.disabled_addons[title_id];      auto patch_dirs = load_dir->GetSubdirectories();      std::sort(patch_dirs.begin(), patch_dirs.end(),                [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -237,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t      layers.reserve(patch_dirs.size() + 1);      layers_ext.reserve(patch_dirs.size() + 1);      for (const auto& subdir : patch_dirs) { +        if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) +            continue; +          auto romfs_dir = subdir->GetSubdirectory("romfs");          if (romfs_dir != nullptr)              layers.push_back(std::move(romfs_dir)); @@ -266,13 +286,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t  VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,                                       VirtualFile update_raw) const {      const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", -                                        title_id, static_cast<u8>(type)) -                                .c_str(); +                                        title_id, static_cast<u8>(type));      if (type == ContentRecordType::Program || type == ContentRecordType::Data) -        LOG_INFO(Loader, log_string); +        LOG_INFO(Loader, "{}", log_string);      else -        LOG_DEBUG(Loader, log_string); +        LOG_DEBUG(Loader, "{}", log_string);      if (romfs == nullptr)          return romfs; @@ -282,7 +301,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content      // Game Updates      const auto update_tid = GetUpdateTitleID(title_id);      const auto update = installed.GetEntryRaw(update_tid, type); -    if (update != nullptr) { + +    const auto& disabled = Settings::values.disabled_addons[title_id]; +    const auto update_disabled = +        std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + +    if (!update_disabled && update != nullptr) {          const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);          if (new_nca->GetStatus() == Loader::ResultStatus::Success &&              new_nca->GetRomFS() != nullptr) { @@ -290,7 +314,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content                       FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));              romfs = new_nca->GetRomFS();          } -    } else if (update_raw != nullptr) { +    } else if (!update_disabled && update_raw != nullptr) {          const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);          if (new_nca->GetStatus() == Loader::ResultStatus::Success &&              new_nca->GetRomFS() != nullptr) { @@ -320,25 +344,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam      VirtualFile update_raw) const {      std::map<std::string, std::string, std::less<>> out;      const auto installed = Service::FileSystem::GetUnionContents(); +    const auto& disabled = Settings::values.disabled_addons[title_id];      // Game Updates      const auto update_tid = GetUpdateTitleID(title_id);      PatchManager update{update_tid};      auto [nacp, discard_icon_file] = update.GetControlMetadata(); +    const auto update_disabled = +        std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); +    const auto update_label = update_disabled ? "[D] Update" : "Update"; +      if (nacp != nullptr) { -        out.insert_or_assign("Update", nacp->GetVersionString()); +        out.insert_or_assign(update_label, nacp->GetVersionString());      } else {          if (installed.HasEntry(update_tid, ContentRecordType::Program)) {              const auto meta_ver = installed.GetEntryVersion(update_tid);              if (meta_ver.value_or(0) == 0) { -                out.insert_or_assign("Update", ""); +                out.insert_or_assign(update_label, "");              } else {                  out.insert_or_assign( -                    "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); +                    update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));              }          } else if (update_raw != nullptr) { -            out.insert_or_assign("Update", "PACKED"); +            out.insert_or_assign(update_label, "PACKED");          }      } @@ -378,7 +407,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam              if (types.empty())                  continue; -            out.insert_or_assign(mod->GetName(), types); +            const auto mod_disabled = +                std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); +            out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);          }      } @@ -401,7 +432,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam          list += fmt::format("{}", dlc_match.back().title_id & 0x7FF); -        out.insert_or_assign("DLC", std::move(list)); +        const auto dlc_disabled = +            std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); +        out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));      }      return out; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 7d168837f..b8a1652fd 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -30,6 +30,8 @@ public:      explicit PatchManager(u64 title_id);      ~PatchManager(); +    u64 GetTitleID() const; +      // Currently tracked ExeFS patches:      // - Game Updates      VirtualDir PatchExeFS(VirtualDir exefs) const; @@ -63,6 +65,9 @@ public:      std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;  private: +    std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, +                                            const std::string& build_id) const; +      u64 title_id;  }; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 5434f2149..d63b7f19b 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -18,7 +18,11 @@ std::string SaveDataDescriptor::DebugInfo() const {                         static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);  } -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} +SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { +    // Delete all temporary storages +    // On hardware, it is expected that temporary storage be empty at first use. +    dir->DeleteSubdirectoryRecursive("temp"); +}  SaveDataFactory::~SaveDataFactory() = default; @@ -120,8 +124,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ      case SaveDataType::TemporaryStorage:          return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],                             title_id); +    case SaveDataType::CacheStorage: +        return fmt::format("{}save/cache/{:016X}", out, title_id);      default:          ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); +        return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id);      }  } diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 2a0088040..bd4919610 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -17,8 +17,10 @@ namespace FileSys {  enum class SaveDataSpaceId : u8 {      NandSystem = 0,      NandUser = 1, -    SdCard = 2, +    SdCardSystem = 2,      TemporaryStorage = 3, +    SdCardUser = 4, +    ProperSystem = 100,  };  enum class SaveDataType : u8 { diff --git a/src/core/frontend/applets/profile_select.cpp b/src/core/frontend/applets/profile_select.cpp new file mode 100644 index 000000000..fbf5f2a9e --- /dev/null +++ b/src/core/frontend/applets/profile_select.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/applets/profile_select.h" +#include "core/settings.h" + +namespace Core::Frontend { + +ProfileSelectApplet::~ProfileSelectApplet() = default; + +void DefaultProfileSelectApplet::SelectProfile( +    std::function<void(std::optional<Service::Account::UUID>)> callback) const { +    Service::Account::ProfileManager manager; +    callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{})); +    LOG_INFO(Service_ACC, "called, selecting current user instead of prompting..."); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/profile_select.h b/src/core/frontend/applets/profile_select.h new file mode 100644 index 000000000..fc8f7ae94 --- /dev/null +++ b/src/core/frontend/applets/profile_select.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include "core/hle/service/acc/profile_manager.h" + +namespace Core::Frontend { + +class ProfileSelectApplet { +public: +    virtual ~ProfileSelectApplet(); + +    virtual void SelectProfile( +        std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0; +}; + +class DefaultProfileSelectApplet final : public ProfileSelectApplet { +public: +    void SelectProfile( +        std::function<void(std::optional<Service::Account::UUID>)> callback) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index e6b5171ee..a1cad4fcb 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -201,11 +201,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {      modules.push_back(std::move(module));  } -static Kernel::Thread* FindThreadById(int id) { +static Kernel::Thread* FindThreadById(s64 id) {      for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {          const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();          for (auto& thread : threads) { -            if (thread->GetThreadID() == static_cast<u32>(id)) { +            if (thread->GetThreadID() == static_cast<u64>(id)) {                  current_core = core;                  return thread.get();              } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index e441c5bc6..1c2290651 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -112,7 +112,7 @@ struct KernelCore::Impl {      void Shutdown() {          next_object_id = 0; -        next_process_id = 10; +        next_process_id = Process::ProcessIDMin;          next_thread_id = 1;          process_list.clear(); @@ -153,10 +153,8 @@ struct KernelCore::Impl {      }      std::atomic<u32> next_object_id{0}; -    // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are -    // reserved for low-level services -    std::atomic<u32> next_process_id{10}; -    std::atomic<u32> next_thread_id{1}; +    std::atomic<u64> next_process_id{Process::ProcessIDMin}; +    std::atomic<u64> next_thread_id{1};      // Lists all processes that exist in the current session.      std::vector<SharedPtr<Process>> process_list; @@ -242,11 +240,11 @@ u32 KernelCore::CreateNewObjectID() {      return impl->next_object_id++;  } -u32 KernelCore::CreateNewThreadID() { +u64 KernelCore::CreateNewThreadID() {      return impl->next_thread_id++;  } -u32 KernelCore::CreateNewProcessID() { +u64 KernelCore::CreateNewProcessID() {      return impl->next_process_id++;  } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index ea00c89f5..58c9d108b 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -88,10 +88,10 @@ private:      u32 CreateNewObjectID();      /// Creates a new process ID, incrementing the internal process ID counter; -    u32 CreateNewProcessID(); +    u64 CreateNewProcessID();      /// Creates a new thread ID, incrementing the internal thread ID counter. -    u32 CreateNewThreadID(); +    u64 CreateNewThreadID();      /// Creates a timer callback handle for the given timer.      ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer); diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index 0ea851a74..806078638 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -32,6 +32,7 @@ bool Object::IsWaitable() const {      }      UNREACHABLE(); +    return false;  }  } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 211bf6686..5356a4a3f 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -66,6 +66,7 @@ ResultCode Process::ClearSignalState() {  void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {      program_id = metadata.GetTitleID(); +    ideal_processor = metadata.GetMainThreadCore();      is_64bit_process = metadata.Is64BitProgram();      vm_manager.Reset(metadata.GetAddressSpaceType());  } @@ -149,7 +150,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {      vm_manager          .MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,                          std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, -                        MemoryState::Mapped) +                        MemoryState::Stack)          .Unwrap();      vm_manager.LogLayout(); diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index bcb9ac4b8..7da367251 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -120,6 +120,18 @@ struct CodeSet final {  class Process final : public WaitObject {  public: +    enum : u64 { +        /// Lowest allowed process ID for a kernel initial process. +        InitialKIPIDMin = 1, +        /// Highest allowed process ID for a kernel initial process. +        InitialKIPIDMax = 80, + +        /// Lowest allowed process ID for a userland process. +        ProcessIDMin = 81, +        /// Highest allowed process ID for a userland process. +        ProcessIDMax = 0xFFFFFFFFFFFFFFFF, +    }; +      static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;      static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name); @@ -162,7 +174,7 @@ public:      }      /// Gets the unique ID that identifies this particular process. -    u32 GetProcessID() const { +    u64 GetProcessID() const {          return process_id;      } @@ -262,8 +274,7 @@ public:      ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);      ResultCode HeapFree(VAddr target, u32 size); -    ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, -                            MemoryState state = MemoryState::Mapped); +    ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);      ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); @@ -289,10 +300,10 @@ private:      ProcessStatus status;      /// The ID of this process -    u32 process_id = 0; +    u64 process_id = 0;      /// Title ID corresponding to the process -    u64 program_id; +    u64 program_id = 0;      /// Resource limit descriptor for this process      SharedPtr<ResourceLimit> resource_limit; diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 5a5f4cef1..df4d6cf0a 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -9,6 +9,7 @@  #include "common/logging/log.h"  #include "core/arm/arm_interface.h"  #include "core/core.h" +#include "core/core_cpu.h"  #include "core/core_timing.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/process.h" @@ -179,4 +180,69 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {          ready_queue.prepare(priority);  } +Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const { +    std::lock_guard<std::mutex> lock(scheduler_mutex); + +    const u32 mask = 1U << core; +    return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) { +        return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority; +    }); +} + +void Scheduler::YieldWithoutLoadBalancing(Thread* thread) { +    ASSERT(thread != nullptr); +    // Avoid yielding if the thread isn't even running. +    ASSERT(thread->GetStatus() == ThreadStatus::Running); + +    // Sanity check that the priority is valid +    ASSERT(thread->GetPriority() < THREADPRIO_COUNT); + +    // Yield this thread -- sleep for zero time and force reschedule to different thread +    WaitCurrentThread_Sleep(); +    GetCurrentThread()->WakeAfterDelay(0); +} + +void Scheduler::YieldWithLoadBalancing(Thread* thread) { +    ASSERT(thread != nullptr); +    const auto priority = thread->GetPriority(); +    const auto core = static_cast<u32>(thread->GetProcessorID()); + +    // Avoid yielding if the thread isn't even running. +    ASSERT(thread->GetStatus() == ThreadStatus::Running); + +    // Sanity check that the priority is valid +    ASSERT(priority < THREADPRIO_COUNT); + +    // Sleep for zero time to be able to force reschedule to different thread +    WaitCurrentThread_Sleep(); +    GetCurrentThread()->WakeAfterDelay(0); + +    Thread* suggested_thread = nullptr; + +    // Search through all of the cpu cores (except this one) for a suggested thread. +    // Take the first non-nullptr one +    for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) { +        const auto res = +            Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread( +                core, priority); + +        // If scheduler provides a suggested thread +        if (res != nullptr) { +            // And its better than the current suggested thread (or is the first valid one) +            if (suggested_thread == nullptr || +                suggested_thread->GetPriority() > res->GetPriority()) { +                suggested_thread = res; +            } +        } +    } + +    // If a suggested thread was found, queue that for this core +    if (suggested_thread != nullptr) +        suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask()); +} + +void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) { +    UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!"); +} +  } // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index c63032b7d..97ced4dfc 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -51,6 +51,75 @@ public:      /// Sets the priority of a thread in the scheduler      void SetThreadPriority(Thread* thread, u32 priority); +    /// Gets the next suggested thread for load balancing +    Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const; + +    /** +     * YieldWithoutLoadBalancing -- analogous to normal yield on a system +     * Moves the thread to the end of the ready queue for its priority, and then reschedules the +     * system to the new head of the queue. +     * +     * Example (Single Core -- but can be extrapolated to multi): +     * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->) +     * Currently Running: ThreadR +     * +     * ThreadR calls YieldWithoutLoadBalancing +     * +     * ThreadR is moved to the end of ready_queue[prio=0]: +     * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->) +     * Currently Running: Nothing +     * +     * System is rescheduled (ThreadA is popped off of queue): +     * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->) +     * Currently Running: ThreadA +     * +     * If the queue is empty at time of call, no yielding occurs. This does not cross between cores +     * or priorities at all. +     */ +    void YieldWithoutLoadBalancing(Thread* thread); + +    /** +     * YieldWithLoadBalancing -- yield but with better selection of the new running thread +     * Moves the current thread to the end of the ready queue for its priority, then selects a +     * 'suggested thread' (a thread on a different core that could run on this core) from the +     * scheduler, changes its core, and reschedules the current core to that thread. +     * +     * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were +     * single core): +     * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant +     * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] +     * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 +     * +     * ThreadQ calls YieldWithLoadBalancing +     * +     * ThreadQ is moved to the end of ready_queue[core=0][prio=0]: +     * ready_queue[core=0][prio=0]: ThreadA, ThreadB +     * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] +     * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 +     * +     * A list of suggested threads for each core is compiled +     * Suggested Threads: {ThreadC on Core 1} +     * If this were quad core (as the switch is), there could be between 0 and 3 threads in this +     * list. If there are more than one, the thread is selected by highest prio. +     * +     * ThreadC is core changed to Core 0: +     * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ +     * ready_queue[core=1][prio=0]: ThreadD +     * Currently Running: None on Core 0 || ThreadP on Core 1 +     * +     * System is rescheduled (ThreadC is popped off of queue): +     * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ +     * ready_queue[core=1][prio=0]: ThreadD +     * Currently Running: ThreadC on Core 0 || ThreadP on Core 1 +     * +     * If no suggested threads can be found this will behave just as normal yield. If there are +     * multiple candidates for the suggested thread on a core, the highest prio is taken. +     */ +    void YieldWithLoadBalancing(Thread* thread); + +    /// Currently unknown -- asserts as unimplemented on call +    void YieldAndWaitForLoadBalancing(Thread* thread); +      /// Returns a list of all threads managed by the scheduler      const std::vector<SharedPtr<Thread>>& GetThreadList() const {          return thread_list; diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 0494581f5..22d0c1dd5 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -17,13 +17,13 @@ namespace Kernel {  SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {}  SharedMemory::~SharedMemory() = default; -SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Process> owner_process, -                                             u64 size, MemoryPermission permissions, +SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_process, u64 size, +                                             MemoryPermission permissions,                                               MemoryPermission other_permissions, VAddr address,                                               MemoryRegion region, std::string name) {      SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); -    shared_memory->owner_process = std::move(owner_process); +    shared_memory->owner_process = owner_process;      shared_memory->name = std::move(name);      shared_memory->size = size;      shared_memory->permissions = permissions; @@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce                  shared_memory->backing_block.get());          }      } else { -        auto& vm_manager = shared_memory->owner_process->VMManager(); +        const auto& vm_manager = shared_memory->owner_process->VMManager();          // The memory is already available and mapped in the owner process. -        auto vma = vm_manager.FindVMA(address); -        ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); +        const auto vma = vm_manager.FindVMA(address); +        ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address");          ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");          // The returned VMA might be a bigger one encompassing the desired address. -        auto vma_offset = address - vma->first; +        const auto vma_offset = address - vma->first;          ASSERT_MSG(vma_offset + size <= vma->second.size,                     "Shared memory exceeds bounds of mapped block"); diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 0b48db699..dab2a6bea 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -45,8 +45,8 @@ public:       * linear heap.       * @param name Optional object name, used for debugging purposes.       */ -    static SharedPtr<SharedMemory> Create(KernelCore& kernel, SharedPtr<Process> owner_process, -                                          u64 size, MemoryPermission permissions, +    static SharedPtr<SharedMemory> Create(KernelCore& kernel, Process* owner_process, u64 size, +                                          MemoryPermission permissions,                                            MemoryPermission other_permissions, VAddr address = 0,                                            MemoryRegion region = MemoryRegion::BASE,                                            std::string name = "Unknown"); @@ -139,7 +139,7 @@ private:      /// Permission restrictions applied to other processes mapping the block.      MemoryPermission other_permissions{};      /// Process that created this shared memory block. -    SharedPtr<Process> owner_process; +    Process* owner_process;      /// Address of shared memory block in the owner process if specified.      VAddr base_address = 0;      /// Name of shared memory object. diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 84df2040e..28268e112 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -35,6 +35,7 @@  #include "core/hle/lock.h"  #include "core/hle/result.h"  #include "core/hle/service/service.h" +#include "core/memory.h"  namespace Kernel {  namespace { @@ -239,7 +240,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {      }      const VMManager::VMAHandle iter = vm_manager.FindVMA(addr); -    if (iter == vm_manager.vma_map.end()) { +    if (!vm_manager.IsValidHandle(iter)) {          LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr);          return ERR_INVALID_ADDRESS_STATE;      } @@ -253,11 +254,52 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {      return vm_manager.ReprotectRange(addr, size, converted_permissions);  } -static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) { -    LOG_WARNING(Kernel_SVC, -                "(STUBBED) called, addr=0x{:X}, size=0x{:X}, state0=0x{:X}, state1=0x{:X}", addr, -                size, state0, state1); -    return RESULT_SUCCESS; +static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attribute) { +    LOG_DEBUG(Kernel_SVC, +              "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, +              size, mask, attribute); + +    if (!Common::Is4KBAligned(address)) { +        LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address); +        return ERR_INVALID_ADDRESS; +    } + +    if (size == 0 || !Common::Is4KBAligned(size)) { +        LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.", +                  size); +        return ERR_INVALID_ADDRESS; +    } + +    if (!IsValidAddressRange(address, size)) { +        LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})", +                  address, size); +        return ERR_INVALID_ADDRESS_STATE; +    } + +    const auto mem_attribute = static_cast<MemoryAttribute>(attribute); +    const auto mem_mask = static_cast<MemoryAttribute>(mask); +    const auto attribute_with_mask = mem_attribute | mem_mask; + +    if (attribute_with_mask != mem_mask) { +        LOG_ERROR(Kernel_SVC, +                  "Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}", +                  attribute, mask); +        return ERR_INVALID_COMBINATION; +    } + +    if ((attribute_with_mask | MemoryAttribute::Uncached) != MemoryAttribute::Uncached) { +        LOG_ERROR(Kernel_SVC, "Specified attribute isn't equal to MemoryAttributeUncached (8)."); +        return ERR_INVALID_COMBINATION; +    } + +    auto& vm_manager = Core::CurrentProcess()->VMManager(); +    if (!IsInsideAddressSpace(vm_manager, address, size)) { +        LOG_ERROR(Kernel_SVC, +                  "Given address (0x{:016X}) is outside the bounds of the address space.", address); +        return ERR_INVALID_ADDRESS_STATE; +    } + +    return vm_manager.SetMemoryAttribute(address, size, mem_mask, mem_attribute);  }  /// Maps a memory range into a different range. @@ -273,7 +315,7 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {          return result;      } -    return current_process->MirrorMemory(dst_addr, src_addr, size); +    return current_process->MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack);  }  /// Unmaps a region that was previously mapped with svcMapMemory @@ -349,7 +391,7 @@ static ResultCode SendSyncRequest(Handle handle) {  }  /// Get the ID for the specified thread. -static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { +static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) {      LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);      const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); @@ -363,20 +405,33 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {      return RESULT_SUCCESS;  } -/// Get the ID of the specified process -static ResultCode GetProcessId(u32* process_id, Handle process_handle) { -    LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); +/// Gets the ID of the specified process or a specified thread's owning process. +static ResultCode GetProcessId(u64* process_id, Handle handle) { +    LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);      const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); -    const SharedPtr<Process> process = handle_table.Get<Process>(process_handle); -    if (!process) { -        LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", -                  process_handle); -        return ERR_INVALID_HANDLE; +    const SharedPtr<Process> process = handle_table.Get<Process>(handle); +    if (process) { +        *process_id = process->GetProcessID(); +        return RESULT_SUCCESS;      } -    *process_id = process->GetProcessID(); -    return RESULT_SUCCESS; +    const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle); +    if (thread) { +        const Process* const owner_process = thread->GetOwnerProcess(); +        if (!owner_process) { +            LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered."); +            return ERR_INVALID_HANDLE; +        } + +        *process_id = owner_process->GetProcessID(); +        return RESULT_SUCCESS; +    } + +    // NOTE: This should also handle debug objects before returning. + +    LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle); +    return ERR_INVALID_HANDLE;  }  /// Default thread wakeup callback for WaitSynchronization @@ -1066,10 +1121,9 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64      return shared_memory->Unmap(*current_process, addr);  } -/// Query process memory -static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/, -                                     Handle process_handle, u64 addr) { -    LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr); +static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_address, +                                     Handle process_handle, VAddr address) { +    LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);      const auto& handle_table = Core::CurrentProcess()->GetHandleTable();      SharedPtr<Process> process = handle_table.Get<Process>(process_handle);      if (!process) { @@ -1077,26 +1131,34 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i                    process_handle);          return ERR_INVALID_HANDLE;      } -    auto vma = process->VMManager().FindVMA(addr); -    memory_info->attributes = 0; -    if (vma == process->VMManager().vma_map.end()) { -        memory_info->base_address = 0; -        memory_info->permission = static_cast<u32>(VMAPermission::None); -        memory_info->size = 0; -        memory_info->type = static_cast<u32>(MemoryState::Unmapped); -    } else { -        memory_info->base_address = vma->second.base; -        memory_info->permission = static_cast<u32>(vma->second.permissions); -        memory_info->size = vma->second.size; -        memory_info->type = static_cast<u32>(vma->second.meminfo_state); -    } + +    const auto& vm_manager = process->VMManager(); +    const MemoryInfo memory_info = vm_manager.QueryMemory(address); + +    Memory::Write64(memory_info_address, memory_info.base_address); +    Memory::Write64(memory_info_address + 8, memory_info.size); +    Memory::Write32(memory_info_address + 16, memory_info.state); +    Memory::Write32(memory_info_address + 20, memory_info.attributes); +    Memory::Write32(memory_info_address + 24, memory_info.permission); +    Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count); +    Memory::Write32(memory_info_address + 28, memory_info.device_ref_count); +    Memory::Write32(memory_info_address + 36, 0); + +    // Page info appears to be currently unused by the kernel and is always set to zero. +    Memory::Write32(page_info_address, 0); +      return RESULT_SUCCESS;  } -/// Query memory -static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) { -    LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr); -    return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr); +static ResultCode QueryMemory(VAddr memory_info_address, VAddr page_info_address, +                              VAddr query_address) { +    LOG_TRACE(Kernel_SVC, +              "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, " +              "query_address=0x{:016X}", +              memory_info_address, page_info_address, query_address); + +    return QueryProcessMemory(memory_info_address, page_info_address, CurrentProcess, +                              query_address);  }  /// Exits the current process @@ -1200,18 +1262,38 @@ static void ExitThread() {  static void SleepThread(s64 nanoseconds) {      LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); -    // Don't attempt to yield execution if there are no available threads to run, -    // this way we avoid a useless reschedule to the idle thread. -    if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) -        return; +    enum class SleepType : s64 { +        YieldWithoutLoadBalancing = 0, +        YieldWithLoadBalancing = -1, +        YieldAndWaitForLoadBalancing = -2, +    }; -    // Sleep current thread and check for next thread to schedule -    WaitCurrentThread_Sleep(); +    if (nanoseconds <= 0) { +        auto& scheduler{Core::System::GetInstance().CurrentScheduler()}; +        switch (static_cast<SleepType>(nanoseconds)) { +        case SleepType::YieldWithoutLoadBalancing: +            scheduler.YieldWithoutLoadBalancing(GetCurrentThread()); +            break; +        case SleepType::YieldWithLoadBalancing: +            scheduler.YieldWithLoadBalancing(GetCurrentThread()); +            break; +        case SleepType::YieldAndWaitForLoadBalancing: +            scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread()); +            break; +        default: +            UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); +        } +    } else { +        // Sleep current thread and check for next thread to schedule +        WaitCurrentThread_Sleep(); -    // Create an event to wake the thread up after the specified nanosecond delay has passed -    GetCurrentThread()->WakeAfterDelay(nanoseconds); +        // Create an event to wake the thread up after the specified nanosecond delay has passed +        GetCurrentThread()->WakeAfterDelay(nanoseconds); +    } -    Core::System::GetInstance().PrepareReschedule(); +    // Reschedule all CPU cores +    for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) +        Core::System::GetInstance().CpuCore(i).PrepareReschedule();  }  /// Wait process wide key atomic @@ -1483,9 +1565,9 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32      }      auto& kernel = Core::System::GetInstance().Kernel(); -    auto& handle_table = Core::CurrentProcess()->GetHandleTable(); -    const auto shared_mem_handle = SharedMemory::Create( -        kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr); +    auto process = kernel.CurrentProcess(); +    auto& handle_table = process->GetHandleTable(); +    const auto shared_mem_handle = SharedMemory::Create(kernel, process, size, perms, perms, addr);      CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));      return RESULT_SUCCESS; @@ -1595,10 +1677,9 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss      }      auto& kernel = Core::System::GetInstance().Kernel(); -    auto& handle_table = Core::CurrentProcess()->GetHandleTable(); -    auto shared_mem_handle = -        SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size, -                             local_perms, remote_perms); +    auto process = kernel.CurrentProcess(); +    auto& handle_table = process->GetHandleTable(); +    auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms);      CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));      return RESULT_SUCCESS; @@ -1904,7 +1985,7 @@ static const FunctionDef SVC_Table[] = {      {0x73, nullptr, "SetProcessMemoryPermission"},      {0x74, nullptr, "MapProcessMemory"},      {0x75, nullptr, "UnmapProcessMemory"}, -    {0x76, nullptr, "QueryProcessMemory"}, +    {0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},      {0x77, nullptr, "MapProcessCodeMemory"},      {0x78, nullptr, "UnmapProcessCodeMemory"},      {0x79, nullptr, "CreateProcess"}, diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index b06aac4ec..c37ae0f98 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -8,22 +8,6 @@  namespace Kernel { -struct MemoryInfo { -    u64 base_address; -    u64 size; -    u32 type; -    u32 attributes; -    u32 permission; -    u32 device_refcount; -    u32 ipc_refcount; -    INSERT_PADDING_WORDS(1); -}; -static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size."); - -struct PageInfo { -    u64 flags; -}; -  void CallSVC(u32 immediate);  } // namespace Kernel diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 24aef46c9..2a2c2c5ea 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -7,9 +7,7 @@  #include "common/common_types.h"  #include "core/arm/arm_interface.h"  #include "core/core.h" -#include "core/hle/kernel/svc.h"  #include "core/hle/result.h" -#include "core/memory.h"  namespace Kernel { @@ -75,7 +73,15 @@ void SvcWrap() {  template <ResultCode func(u32*, u64)>  void SvcWrap() {      u32 param_1 = 0; -    u32 retval = func(¶m_1, Param(1)).raw; +    const u32 retval = func(¶m_1, Param(1)).raw; +    Core::CurrentArmInterface().SetReg(1, param_1); +    FuncReturn(retval); +} + +template <ResultCode func(u64*, u32)> +void SvcWrap() { +    u64 param_1 = 0; +    const u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw;      Core::CurrentArmInterface().SetReg(1, param_1);      FuncReturn(retval);  } @@ -129,7 +135,12 @@ void SvcWrap() {  template <ResultCode func(u64, u64, u32, u32)>  void SvcWrap() {      FuncReturn( -        func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw); +        func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw); +} + +template <ResultCode func(u64, u64, u32, u64)> +void SvcWrap() { +    FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);  }  template <ResultCode func(u32, u64, u32)> @@ -191,21 +202,6 @@ void SvcWrap() {      FuncReturn(retval);  } -template <ResultCode func(MemoryInfo*, PageInfo*, u64)> -void SvcWrap() { -    MemoryInfo memory_info = {}; -    PageInfo page_info = {}; -    u32 retval = func(&memory_info, &page_info, Param(2)).raw; - -    Memory::Write64(Param(0), memory_info.base_address); -    Memory::Write64(Param(0) + 8, memory_info.size); -    Memory::Write32(Param(0) + 16, memory_info.type); -    Memory::Write32(Param(0) + 20, memory_info.attributes); -    Memory::Write32(Param(0) + 24, memory_info.permission); - -    FuncReturn(retval); -} -  template <ResultCode func(u32*, u64, u64, u32)>  void SvcWrap() {      u32 param_1 = 0; diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 4ffb76818..63f8923fd 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -158,6 +158,9 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd      context.cpu_registers[0] = arg;      context.pc = entry_point;      context.sp = stack_top; +    // TODO(merry): Perform a hardware test to determine the below value. +    // AHP = 0, DN = 1, FTZ = 1, RMode = Round towards zero +    context.fpcr = 0x03C00000;  }  ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point, diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index d384d50db..d6e7981d3 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -26,6 +26,7 @@ enum ThreadPriority : u32 {      THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps      THREADPRIO_DEFAULT = 44,      ///< Default thread priority for userland apps      THREADPRIO_LOWEST = 63,       ///< Lowest thread priority +    THREADPRIO_COUNT = 64,        ///< Total number of possible thread priorities.  };  enum ThreadProcessorId : s32 { @@ -150,7 +151,7 @@ public:       * Gets the thread's thread ID       * @return The thread's ID       */ -    u32 GetThreadID() const { +    u64 GetThreadID() const {          return thread_id;      } @@ -378,7 +379,7 @@ private:      Core::ARM_Interface::ThreadContext context{}; -    u32 thread_id = 0; +    u64 thread_id = 0;      ThreadStatus status = ThreadStatus::Dormant; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 100f8f6bf..f39e096ca 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -25,19 +25,19 @@ static const char* GetMemoryStateName(MemoryState state) {          "CodeMutable",      "Heap",          "Shared",           "Unknown1",          "ModuleCodeStatic", "ModuleCodeMutable", -        "IpcBuffer0",       "Mapped", +        "IpcBuffer0",       "Stack",          "ThreadLocal",      "TransferMemoryIsolated",          "TransferMemory",   "ProcessMemory", -        "Unknown2",         "IpcBuffer1", +        "Inaccessible",     "IpcBuffer1",          "IpcBuffer3",       "KernelStack",      }; -    return names[static_cast<int>(state)]; +    return names[ToSvcMemoryState(state)];  }  bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {      ASSERT(base + size == next.base); -    if (permissions != next.permissions || meminfo_state != next.meminfo_state || +    if (permissions != next.permissions || state != next.state || attribute != next.attribute ||          type != next.type) {          return false;      } @@ -87,6 +87,10 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {      }  } +bool VMManager::IsValidHandle(VMAHandle handle) const { +    return handle != vma_map.cend(); +} +  ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,                                                            std::shared_ptr<std::vector<u8>> block,                                                            std::size_t offset, u64 size, @@ -111,7 +115,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,      final_vma.type = VMAType::AllocatedMemoryBlock;      final_vma.permissions = VMAPermission::ReadWrite; -    final_vma.meminfo_state = state; +    final_vma.state = state;      final_vma.backing_block = std::move(block);      final_vma.offset = offset;      UpdatePageTableForVMA(final_vma); @@ -136,7 +140,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me      final_vma.type = VMAType::BackingMemory;      final_vma.permissions = VMAPermission::ReadWrite; -    final_vma.meminfo_state = state; +    final_vma.state = state;      final_vma.backing_memory = memory;      UpdatePageTableForVMA(final_vma); @@ -173,7 +177,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u6      final_vma.type = VMAType::MMIO;      final_vma.permissions = VMAPermission::ReadWrite; -    final_vma.meminfo_state = state; +    final_vma.state = state;      final_vma.paddr = paddr;      final_vma.mmio_handler = std::move(mmio_handler);      UpdatePageTableForVMA(final_vma); @@ -185,7 +189,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {      VirtualMemoryArea& vma = vma_handle->second;      vma.type = VMAType::Free;      vma.permissions = VMAPermission::None; -    vma.meminfo_state = MemoryState::Unmapped; +    vma.state = MemoryState::Unmapped;      vma.backing_block = nullptr;      vma.offset = 0; @@ -298,6 +302,54 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) {      return RESULT_SUCCESS;  } +MemoryInfo VMManager::QueryMemory(VAddr address) const { +    const auto vma = FindVMA(address); +    MemoryInfo memory_info{}; + +    if (IsValidHandle(vma)) { +        memory_info.base_address = vma->second.base; +        memory_info.attributes = ToSvcMemoryAttribute(vma->second.attribute); +        memory_info.permission = static_cast<u32>(vma->second.permissions); +        memory_info.size = vma->second.size; +        memory_info.state = ToSvcMemoryState(vma->second.state); +    } else { +        memory_info.base_address = address_space_end; +        memory_info.permission = static_cast<u32>(VMAPermission::None); +        memory_info.size = 0 - address_space_end; +        memory_info.state = static_cast<u32>(MemoryState::Inaccessible); +    } + +    return memory_info; +} + +ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask, +                                         MemoryAttribute attribute) { +    constexpr auto ignore_mask = MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped; +    constexpr auto attribute_mask = ~ignore_mask; + +    const auto result = CheckRangeState( +        address, size, MemoryState::FlagUncached, MemoryState::FlagUncached, VMAPermission::None, +        VMAPermission::None, attribute_mask, MemoryAttribute::None, ignore_mask); + +    if (result.Failed()) { +        return result.Code(); +    } + +    const auto [prev_state, prev_permissions, prev_attributes] = *result; +    const auto new_attribute = (prev_attributes & ~mask) | (mask & attribute); + +    const auto carve_result = CarveVMARange(address, size); +    if (carve_result.Failed()) { +        return carve_result.Code(); +    } + +    auto vma_iter = *carve_result; +    vma_iter->second.attribute = new_attribute; + +    MergeAdjacent(vma_iter); +    return RESULT_SUCCESS; +} +  ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {      const auto vma = FindVMA(src_addr); @@ -341,7 +393,7 @@ void VMManager::LogLayout() const {                    (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',                    (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',                    (u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-', -                  GetMemoryStateName(vma.meminfo_state)); +                  GetMemoryStateName(vma.state));      }  } @@ -568,6 +620,66 @@ void VMManager::ClearPageTable() {                Memory::PageType::Unmapped);  } +VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask, +                                                   MemoryState state, VMAPermission permission_mask, +                                                   VMAPermission permissions, +                                                   MemoryAttribute attribute_mask, +                                                   MemoryAttribute attribute, +                                                   MemoryAttribute ignore_mask) const { +    auto iter = FindVMA(address); + +    // If we don't have a valid VMA handle at this point, then it means this is +    // being called with an address outside of the address space, which is definitely +    // indicative of a bug, as this function only operates on mapped memory regions. +    DEBUG_ASSERT(IsValidHandle(iter)); + +    const VAddr end_address = address + size - 1; +    const MemoryAttribute initial_attributes = iter->second.attribute; +    const VMAPermission initial_permissions = iter->second.permissions; +    const MemoryState initial_state = iter->second.state; + +    while (true) { +        // The iterator should be valid throughout the traversal. Hitting the end of +        // the mapped VMA regions is unquestionably indicative of a bug. +        DEBUG_ASSERT(IsValidHandle(iter)); + +        const auto& vma = iter->second; + +        if (vma.state != initial_state) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if ((vma.state & state_mask) != state) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if (vma.permissions != initial_permissions) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if ((vma.permissions & permission_mask) != permissions) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if ((vma.attribute | ignore_mask) != (initial_attributes | ignore_mask)) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if ((vma.attribute & attribute_mask) != attribute) { +            return ERR_INVALID_ADDRESS_STATE; +        } + +        if (end_address <= vma.EndAddress()) { +            break; +        } + +        ++iter; +    } + +    return MakeResult( +        std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask)); +} +  u64 VMManager::GetTotalMemoryUsage() const {      LOG_WARNING(Kernel, "(STUBBED) called");      return 0xF8000000; diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index d522404fe..6091533bc 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -6,6 +6,7 @@  #include <map>  #include <memory> +#include <tuple>  #include <vector>  #include "common/common_types.h"  #include "core/hle/result.h" @@ -43,26 +44,211 @@ enum class VMAPermission : u8 {      ReadWriteExecute = Read | Write | Execute,  }; -/// Set of values returned in MemoryInfo.state by svcQueryMemory. +constexpr VMAPermission operator|(VMAPermission lhs, VMAPermission rhs) { +    return static_cast<VMAPermission>(u32(lhs) | u32(rhs)); +} + +constexpr VMAPermission operator&(VMAPermission lhs, VMAPermission rhs) { +    return static_cast<VMAPermission>(u32(lhs) & u32(rhs)); +} + +constexpr VMAPermission operator^(VMAPermission lhs, VMAPermission rhs) { +    return static_cast<VMAPermission>(u32(lhs) ^ u32(rhs)); +} + +constexpr VMAPermission operator~(VMAPermission permission) { +    return static_cast<VMAPermission>(~u32(permission)); +} + +constexpr VMAPermission& operator|=(VMAPermission& lhs, VMAPermission rhs) { +    lhs = lhs | rhs; +    return lhs; +} + +constexpr VMAPermission& operator&=(VMAPermission& lhs, VMAPermission rhs) { +    lhs = lhs & rhs; +    return lhs; +} + +constexpr VMAPermission& operator^=(VMAPermission& lhs, VMAPermission rhs) { +    lhs = lhs ^ rhs; +    return lhs; +} + +/// Attribute flags that can be applied to a VMA +enum class MemoryAttribute : u32 { +    Mask = 0xFF, + +    /// No particular qualities +    None = 0, +    /// Memory locked/borrowed for use. e.g. This would be used by transfer memory. +    Locked = 1, +    /// Memory locked for use by IPC-related internals. +    LockedForIPC = 2, +    /// Mapped as part of the device address space. +    DeviceMapped = 4, +    /// Uncached memory +    Uncached = 8, +}; + +constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) { +    return static_cast<MemoryAttribute>(u32(lhs) | u32(rhs)); +} + +constexpr MemoryAttribute operator&(MemoryAttribute lhs, MemoryAttribute rhs) { +    return static_cast<MemoryAttribute>(u32(lhs) & u32(rhs)); +} + +constexpr MemoryAttribute operator^(MemoryAttribute lhs, MemoryAttribute rhs) { +    return static_cast<MemoryAttribute>(u32(lhs) ^ u32(rhs)); +} + +constexpr MemoryAttribute operator~(MemoryAttribute attribute) { +    return static_cast<MemoryAttribute>(~u32(attribute)); +} + +constexpr MemoryAttribute& operator|=(MemoryAttribute& lhs, MemoryAttribute rhs) { +    lhs = lhs | rhs; +    return lhs; +} + +constexpr MemoryAttribute& operator&=(MemoryAttribute& lhs, MemoryAttribute rhs) { +    lhs = lhs & rhs; +    return lhs; +} + +constexpr MemoryAttribute& operator^=(MemoryAttribute& lhs, MemoryAttribute rhs) { +    lhs = lhs ^ rhs; +    return lhs; +} + +constexpr u32 ToSvcMemoryAttribute(MemoryAttribute attribute) { +    return static_cast<u32>(attribute & MemoryAttribute::Mask); +} + +// clang-format off +/// Represents memory states and any relevant flags, as used by the kernel. +/// svcQueryMemory interprets these by masking away all but the first eight +/// bits when storing memory state into a MemoryInfo instance.  enum class MemoryState : u32 { -    Unmapped = 0x0, -    Io = 0x1, -    Normal = 0x2, -    CodeStatic = 0x3, -    CodeMutable = 0x4, -    Heap = 0x5, -    Shared = 0x6, -    ModuleCodeStatic = 0x8, -    ModuleCodeMutable = 0x9, -    IpcBuffer0 = 0xA, -    Mapped = 0xB, -    ThreadLocal = 0xC, -    TransferMemoryIsolated = 0xD, -    TransferMemory = 0xE, -    ProcessMemory = 0xF, -    IpcBuffer1 = 0x11, -    IpcBuffer3 = 0x12, -    KernelStack = 0x13, +    Mask                            = 0xFF, +    FlagProtect                     = 1U << 8, +    FlagDebug                       = 1U << 9, +    FlagIPC0                        = 1U << 10, +    FlagIPC3                        = 1U << 11, +    FlagIPC1                        = 1U << 12, +    FlagMapped                      = 1U << 13, +    FlagCode                        = 1U << 14, +    FlagAlias                       = 1U << 15, +    FlagModule                      = 1U << 16, +    FlagTransfer                    = 1U << 17, +    FlagQueryPhysicalAddressAllowed = 1U << 18, +    FlagSharedDevice                = 1U << 19, +    FlagSharedDeviceAligned         = 1U << 20, +    FlagIPCBuffer                   = 1U << 21, +    FlagMemoryPoolAllocated         = 1U << 22, +    FlagMapProcess                  = 1U << 23, +    FlagUncached                    = 1U << 24, +    FlagCodeMemory                  = 1U << 25, + +    // Convenience flag sets to reduce repetition +    IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1, + +    CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed | +                FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + +    DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer | +                FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned | +                FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached, + +    Unmapped               = 0x00, +    Io                     = 0x01 | FlagMapped, +    Normal                 = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed, +    CodeStatic             = 0x03 | CodeFlags  | FlagMapProcess, +    CodeMutable            = 0x04 | CodeFlags  | FlagMapProcess | FlagCodeMemory, +    Heap                   = 0x05 | DataFlags  | FlagCodeMemory, +    Shared                 = 0x06 | FlagMapped | FlagMemoryPoolAllocated, +    ModuleCodeStatic       = 0x08 | CodeFlags  | FlagModule | FlagMapProcess, +    ModuleCodeMutable      = 0x09 | DataFlags  | FlagModule | FlagMapProcess | FlagCodeMemory, + +    IpcBuffer0             = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated | +                                    IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned, + +    Stack                  = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed | +                                    FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + +    ThreadLocal            = 0x0C | FlagMapped | FlagMemoryPoolAllocated, + +    TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed | +                                    FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated | +                                    FlagUncached, + +    TransferMemory         = 0x0E | FlagIPC3   | FlagIPC1   | FlagMapped | FlagQueryPhysicalAddressAllowed | +                                    FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + +    ProcessMemory          = 0x0F | FlagIPC3   | FlagIPC1   | FlagMapped | FlagMemoryPoolAllocated, + +    // Used to signify an inaccessible or invalid memory region with memory queries +    Inaccessible           = 0x10, + +    IpcBuffer1             = 0x11 | FlagIPC3   | FlagIPC1   | FlagMapped | FlagQueryPhysicalAddressAllowed | +                                    FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + +    IpcBuffer3             = 0x12 | FlagIPC3   | FlagMapped | FlagQueryPhysicalAddressAllowed | +                                    FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + +    KernelStack            = 0x13 | FlagMapped, +}; +// clang-format on + +constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) { +    return static_cast<MemoryState>(u32(lhs) | u32(rhs)); +} + +constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) { +    return static_cast<MemoryState>(u32(lhs) & u32(rhs)); +} + +constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) { +    return static_cast<MemoryState>(u32(lhs) ^ u32(rhs)); +} + +constexpr MemoryState operator~(MemoryState lhs) { +    return static_cast<MemoryState>(~u32(lhs)); +} + +constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) { +    lhs = lhs | rhs; +    return lhs; +} + +constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) { +    lhs = lhs & rhs; +    return lhs; +} + +constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) { +    lhs = lhs ^ rhs; +    return lhs; +} + +constexpr u32 ToSvcMemoryState(MemoryState state) { +    return static_cast<u32>(state & MemoryState::Mask); +} + +struct MemoryInfo { +    u64 base_address; +    u64 size; +    u32 state; +    u32 attributes; +    u32 permission; +    u32 ipc_ref_count; +    u32 device_ref_count; +}; +static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size."); + +struct PageInfo { +    u32 flags;  };  /** @@ -71,6 +257,16 @@ enum class MemoryState : u32 {   * also backed by a single host memory allocation.   */  struct VirtualMemoryArea { +    /// Gets the starting (base) address of this VMA. +    VAddr StartAddress() const { +        return base; +    } + +    /// Gets the ending address of this VMA. +    VAddr EndAddress() const { +        return base + size - 1; +    } +      /// Virtual base address of the region.      VAddr base = 0;      /// Size of the region. @@ -78,8 +274,8 @@ struct VirtualMemoryArea {      VMAType type = VMAType::Free;      VMAPermission permissions = VMAPermission::None; -    /// Tag returned by svcQueryMemory. Not otherwise used. -    MemoryState meminfo_state = MemoryState::Unmapped; +    MemoryState state = MemoryState::Unmapped; +    MemoryAttribute attribute = MemoryAttribute::None;      // Settings for type = AllocatedMemoryBlock      /// Memory block backing this VMA. @@ -113,16 +309,10 @@ struct VirtualMemoryArea {   *  - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/   */  class VMManager final { +    using VMAMap = std::map<VAddr, VirtualMemoryArea>; +  public: -    /** -     * A map covering the entirety of the managed address space, keyed by the `base` field of each -     * VMA. It must always be modified by splitting or merging VMAs, so that the invariant -     * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be -     * merged when possible so that no two similar and adjacent regions exist that have not been -     * merged. -     */ -    std::map<VAddr, VirtualMemoryArea> vma_map; -    using VMAHandle = decltype(vma_map)::const_iterator; +    using VMAHandle = VMAMap::const_iterator;      VMManager();      ~VMManager(); @@ -133,6 +323,9 @@ public:      /// Finds the VMA in which the given address is included in, or `vma_map.end()`.      VMAHandle FindVMA(VAddr target) const; +    /// Indicates whether or not the given handle is within the VMA map. +    bool IsValidHandle(VMAHandle handle) const; +      // TODO(yuriks): Should these functions actually return the handle?      /** @@ -189,8 +382,28 @@ public:      ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);      ResultCode HeapFree(VAddr target, u64 size); -    ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, -                            MemoryState state = MemoryState::Mapped); +    ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state); + +    /// Queries the memory manager for information about the given address. +    /// +    /// @param address The address to query the memory manager about for information. +    /// +    /// @return A MemoryInfo instance containing information about the given address. +    /// +    MemoryInfo QueryMemory(VAddr address) const; + +    /// Sets an attribute across the given address range. +    /// +    /// @param address   The starting address +    /// @param size      The size of the range to set the attribute on. +    /// @param mask      The attribute mask +    /// @param attribute The attribute to set across the given address range +    /// +    /// @returns RESULT_SUCCESS if successful +    /// @returns ERR_INVALID_ADDRESS_STATE if the attribute could not be set. +    /// +    ResultCode SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask, +                                  MemoryAttribute attribute);      /**       * Scans all VMAs and updates the page table range of any that use the given vector as backing @@ -281,7 +494,7 @@ public:      Memory::PageTable page_table;  private: -    using VMAIter = decltype(vma_map)::iterator; +    using VMAIter = VMAMap::iterator;      /// Converts a VMAHandle to a mutable VMAIter.      VMAIter StripIterConstness(const VMAHandle& iter); @@ -328,6 +541,44 @@ private:      /// Clears out the page table      void ClearPageTable(); +    using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>; + +    /// Checks if an address range adheres to the specified states provided. +    /// +    /// @param address         The starting address of the address range. +    /// @param size            The size of the address range. +    /// @param state_mask      The memory state mask. +    /// @param state           The state to compare the individual VMA states against, +    ///                        which is done in the form of: (vma.state & state_mask) != state. +    /// @param permission_mask The memory permissions mask. +    /// @param permissions     The permission to compare the individual VMA permissions against, +    ///                        which is done in the form of: +    ///                        (vma.permission & permission_mask) != permission. +    /// @param attribute_mask  The memory attribute mask. +    /// @param attribute       The memory attributes to compare the individual VMA attributes +    ///                        against, which is done in the form of: +    ///                        (vma.attributes & attribute_mask) != attribute. +    /// @param ignore_mask     The memory attributes to ignore during the check. +    /// +    /// @returns If successful, returns a tuple containing the memory attributes +    ///          (with ignored bits specified by ignore_mask unset), memory permissions, and +    ///          memory state across the memory range. +    /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE. +    /// +    CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state, +                                 VMAPermission permission_mask, VMAPermission permissions, +                                 MemoryAttribute attribute_mask, MemoryAttribute attribute, +                                 MemoryAttribute ignore_mask) const; + +    /** +     * A map covering the entirety of the managed address space, keyed by the `base` field of each +     * VMA. It must always be modified by splitting or merging VMAs, so that the invariant +     * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be +     * merged when possible so that no two similar and adjacent regions exist that have not been +     * merged. +     */ +    VMAMap vma_map; +      u32 address_space_width = 0;      VAddr address_space_base = 0;      VAddr address_space_end = 0; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3a7b6da84..5fc02a521 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -19,6 +19,7 @@  #include "core/hle/service/am/applet_ae.h"  #include "core/hle/service/am/applet_oe.h"  #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/profile_select.h"  #include "core/hle/service/am/applets/software_keyboard.h"  #include "core/hle/service/am/applets/stub_applet.h"  #include "core/hle/service/am/idle.h" @@ -39,6 +40,7 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};  constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};  enum class AppletId : u32 { +    ProfileSelect = 0x10,      SoftwareKeyboard = 0x11,  }; @@ -71,10 +73,13 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {  IWindowController::~IWindowController() = default;  void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { -    LOG_WARNING(Service_AM, "(STUBBED) called"); +    const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); + +    LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); +      IPC::ResponseBuilder rb{ctx, 4};      rb.Push(RESULT_SUCCESS); -    rb.Push<u64>(0); +    rb.Push<u64>(process_id);  }  void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) { @@ -565,7 +570,6 @@ private:      void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {          LOG_DEBUG(Service_AM, "called"); -        applet->GetBroker().SignalStateChanged();          const auto event = applet->GetBroker().GetStateChangedEvent();          IPC::ResponseBuilder rb{ctx, 2, 1}; @@ -773,6 +777,8 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default;  static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {      switch (id) { +    case AppletId::ProfileSelect: +        return std::make_shared<Applets::ProfileSelect>();      case AppletId::SoftwareKeyboard:          return std::make_shared<Applets::SoftwareKeyboard>();      default: diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 47da35537..7698ca819 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -16,11 +16,11 @@ namespace Service::AM::Applets {  AppletDataBroker::AppletDataBroker() {      auto& kernel = Core::System::GetInstance().Kernel();      state_changed_event = Kernel::WritableEvent::CreateEventPair( -        kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:StateChangedEvent"); +        kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:StateChangedEvent");      pop_out_data_event = Kernel::WritableEvent::CreateEventPair( -        kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopDataOutEvent"); +        kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopDataOutEvent");      pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair( -        kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); +        kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");  }  AppletDataBroker::~AppletDataBroker() = default; diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp new file mode 100644 index 000000000..4c7b45454 --- /dev/null +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -0,0 +1,77 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/profile_select.h" + +namespace Service::AM::Applets { + +constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; + +ProfileSelect::ProfileSelect() = default; +ProfileSelect::~ProfileSelect() = default; + +void ProfileSelect::Initialize() { +    complete = false; +    status = RESULT_SUCCESS; +    final_data.clear(); + +    Applet::Initialize(); + +    const auto user_config_storage = broker.PopNormalDataToApplet(); +    ASSERT(user_config_storage != nullptr); +    const auto& user_config = user_config_storage->GetData(); + +    ASSERT(user_config.size() >= sizeof(UserSelectionConfig)); +    std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig)); +} + +bool ProfileSelect::TransactionComplete() const { +    return complete; +} + +ResultCode ProfileSelect::GetStatus() const { +    return status; +} + +void ProfileSelect::ExecuteInteractive() { +    UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void ProfileSelect::Execute() { +    if (complete) { +        broker.PushNormalDataFromApplet(IStorage{final_data}); +        return; +    } + +    const auto& frontend{Core::System::GetInstance().GetProfileSelector()}; + +    frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); }); +} + +void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) { +    UserSelectionOutput output{}; + +    if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) { +        output.result = 0; +        output.uuid_selected = uuid->uuid; +    } else { +        status = ERR_USER_CANCELLED_SELECTION; +        output.result = ERR_USER_CANCELLED_SELECTION.raw; +        output.uuid_selected = Account::INVALID_UUID; +    } + +    final_data = std::vector<u8>(sizeof(UserSelectionOutput)); +    std::memcpy(final_data.data(), &output, final_data.size()); +    broker.PushNormalDataFromApplet(IStorage{final_data}); +    broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h new file mode 100644 index 000000000..787485f22 --- /dev/null +++ b/src/core/hle/service/am/applets/profile_select.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "common/common_funcs.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +struct UserSelectionConfig { +    // TODO(DarkLordZach): RE this structure +    // It seems to be flags and the like that determine the UI of the applet on the switch... from +    // my research this is safe to ignore for now. +    INSERT_PADDING_BYTES(0xA0); +}; +static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size."); + +struct UserSelectionOutput { +    u64 result; +    u128 uuid_selected; +}; +static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size."); + +class ProfileSelect final : public Applet { +public: +    ProfileSelect(); +    ~ProfileSelect() override; + +    void Initialize() override; + +    bool TransactionComplete() const override; +    ResultCode GetStatus() const override; +    void ExecuteInteractive() override; +    void Execute() override; + +    void SelectionComplete(std::optional<Account::UUID> uuid); + +private: +    UserSelectionConfig config; +    bool complete = false; +    ResultCode status = RESULT_SUCCESS; +    std::vector<u8> final_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index 981bdec51..f255f74b5 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -146,11 +146,10 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {          if (complete) {              broker.PushNormalDataFromApplet(IStorage{output_main}); +            broker.SignalStateChanged();          } else {              broker.PushInteractiveDataFromApplet(IStorage{output_sub});          } - -        broker.SignalStateChanged();      } else {          output_main[0] = 1;          complete = true; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 0417fdb92..b506bc3dd 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -20,6 +20,7 @@  #include "core/hle/service/aoc/aoc_u.h"  #include "core/hle/service/filesystem/filesystem.h"  #include "core/loader/loader.h" +#include "core/settings.h"  namespace Service::AOC { @@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {      rb.Push(RESULT_SUCCESS);      const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + +    const auto& disabled = Settings::values.disabled_addons[current]; +    if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { +        rb.Push<u32>(0); +        return; +    } +      rb.Push<u32>(static_cast<u32>(          std::count_if(add_on_content.begin(), add_on_content.end(),                        [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }))); @@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {              out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));      } +    const auto& disabled = Settings::values.disabled_addons[current]; +    if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) +        out = {}; +      if (out.size() < offset) {          IPC::ResponseBuilder rb{ctx, 2};          // TODO(DarkLordZach): Find the correct error code. diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index d2ffd5776..74c4e583b 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -45,8 +45,12 @@ public:      explicit IStorage(FileSys::VirtualFile backend_)          : ServiceFramework("IStorage"), backend(std::move(backend_)) {          static const FunctionInfo functions[] = { -            {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"},   {2, nullptr, "Flush"}, -            {3, nullptr, "SetSize"},      {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, +            {0, &IStorage::Read, "Read"}, +            {1, nullptr, "Write"}, +            {2, nullptr, "Flush"}, +            {3, nullptr, "SetSize"}, +            {4, &IStorage::GetSize, "GetSize"}, +            {5, nullptr, "OperateRange"},          };          RegisterHandlers(functions);      } @@ -83,6 +87,15 @@ private:          IPC::ResponseBuilder rb{ctx, 2};          rb.Push(RESULT_SUCCESS);      } + +    void GetSize(Kernel::HLERequestContext& ctx) { +        const u64 size = backend->GetSize(); +        LOG_DEBUG(Service_FS, "called, size={}", size); + +        IPC::ResponseBuilder rb{ctx, 4}; +        rb.Push(RESULT_SUCCESS); +        rb.Push<u64>(size); +    }  };  class IFile final : public ServiceFramework<IFile> { @@ -796,9 +809,18 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&  void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {      LOG_WARNING(Service_FS, "(STUBBED) called"); +    enum class LogMode : u32 { +        Off, +        Log, +        RedirectToSdCard, +        LogToSdCard = Log | RedirectToSdCard, +    }; + +    // Given we always want to receive logging information, +    // we always specify logging as enabled.      IPC::ResponseBuilder rb{ctx, 3};      rb.Push(RESULT_SUCCESS); -    rb.Push<u32>(5); +    rb.PushEnum(LogMode::Log);  }  void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 453d90a22..13bcefe07 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -408,13 +408,13 @@ private:      using SHA256Hash = std::array<u8, 0x20>;      struct NROHeader { -        u32_le entrypoint_insn; +        INSERT_PADDING_WORDS(1);          u32_le mod_offset;          INSERT_PADDING_WORDS(2);          u32_le magic; -        INSERT_PADDING_WORDS(1); +        u32_le version;          u32_le nro_size; -        INSERT_PADDING_WORDS(1); +        u32_le flags;          u32_le text_offset;          u32_le text_size;          u32_le ro_offset; @@ -430,9 +430,10 @@ private:      struct NRRHeader {          u32_le magic; -        INSERT_PADDING_BYTES(0x1C); +        INSERT_PADDING_BYTES(12);          u64_le title_id_mask;          u64_le title_id_pattern; +        INSERT_PADDING_BYTES(16);          std::array<u8, 0x100> modulus;          std::array<u8, 0x100> signature_1;          std::array<u8, 0x100> signature_2; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index d5df112a0..a7bed0040 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -317,8 +317,8 @@ private:      }      bool has_attached_handle{}; -    const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')}; -    const u32 npad_id{0}; // Player 1 controller +    const u64 device_handle{0}; // Npad device 1 +    const u32 npad_id{0};       // Player 1 controller      State state{State::NonInitialized};      DeviceState device_state{DeviceState::Initialized};      Kernel::EventPair deactivate_event; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 3bfce0110..0a650f36c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -137,6 +137,10 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<  }  static void PushGPUEntries(Tegra::CommandList&& entries) { +    if (entries.empty()) { +        return; +    } +      auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()};      dma_pusher.Push(std::move(entries));      dma_pusher.DispatchCalls(); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 1ec340466..d25b80ab0 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -70,10 +70,6 @@  #include "core/hle/service/vi/vi.h"  #include "core/hle/service/wlan/wlan.h" -using Kernel::ClientPort; -using Kernel::ServerPort; -using Kernel::SharedPtr; -  namespace Service {  /** @@ -101,33 +97,33 @@ ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_ses  ServiceFrameworkBase::~ServiceFrameworkBase() = default;  void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { -    ASSERT(port == nullptr); -    port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); +    ASSERT(!port_installed); + +    auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap();      port->SetHleHandler(shared_from_this()); +    port_installed = true;  }  void ServiceFrameworkBase::InstallAsNamedPort() { -    ASSERT(port == nullptr); +    ASSERT(!port_installed);      auto& kernel = Core::System::GetInstance().Kernel(); -    SharedPtr<ServerPort> server_port; -    SharedPtr<ClientPort> client_port; -    std::tie(server_port, client_port) = -        ServerPort::CreatePortPair(kernel, max_sessions, service_name); +    auto [server_port, client_port] = +        Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);      server_port->SetHleHandler(shared_from_this());      kernel.AddNamedPort(service_name, std::move(client_port)); +    port_installed = true;  }  Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { -    ASSERT(port == nullptr); +    ASSERT(!port_installed);      auto& kernel = Core::System::GetInstance().Kernel(); -    Kernel::SharedPtr<Kernel::ServerPort> server_port; -    Kernel::SharedPtr<Kernel::ClientPort> client_port; -    std::tie(server_port, client_port) = +    auto [server_port, client_port] =          Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); -    port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap(); +    auto port = MakeResult(std::move(server_port)).Unwrap();      port->SetHleHandler(shared_from_this()); +    port_installed = true;      return client_port;  } @@ -152,8 +148,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext      }      buf.push_back('}'); -    LOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf)); -    UNIMPLEMENTED(); +    UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));  }  void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 98483ecf1..029533628 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -96,11 +96,9 @@ private:      /// Maximum number of concurrent sessions that this service can handle.      u32 max_sessions; -    /** -     * Port where incoming connections will be received. Only created when InstallAsService() or -     * InstallAsNamedPort() are called. -     */ -    Kernel::SharedPtr<Kernel::ServerPort> port; +    /// Flag to store if a port was already create/installed to detect multiple install attempts, +    /// which is not supported. +    bool port_installed = false;      /// Function used to safely up-cast pointers to the derived class before invoking a handler.      InvokerFn* handler_invoker; diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 0d0f63a78..142929124 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -54,13 +54,11 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService          return ERR_ALREADY_REGISTERED;      auto& kernel = Core::System::GetInstance().Kernel(); -    Kernel::SharedPtr<Kernel::ServerPort> server_port; -    Kernel::SharedPtr<Kernel::ClientPort> client_port; -    std::tie(server_port, client_port) = +    auto [server_port, client_port] =          Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);      registered_services.emplace(std::move(name), std::move(client_port)); -    return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)); +    return MakeResult(std::move(server_port));  }  ResultCode ServiceManager::UnregisterService(const std::string& name) { @@ -83,7 +81,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort(          return ERR_SERVICE_NOT_REGISTERED;      } -    return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second); +    return MakeResult(it->second);  }  ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService( @@ -147,12 +145,13 @@ void SM::RegisterService(Kernel::HLERequestContext& ctx) {      const std::string name(name_buf.begin(), end); -    const auto unk_bool = static_cast<bool>(rp.PopRaw<u32>()); -    const auto session_count = rp.PopRaw<u32>(); +    const auto is_light = static_cast<bool>(rp.PopRaw<u32>()); +    const auto max_session_count = rp.PopRaw<u32>(); -    LOG_DEBUG(Service_SM, "called with unk_bool={}", unk_bool); +    LOG_DEBUG(Service_SM, "called with name={}, max_session_count={}, is_light={}", name, +              max_session_count, is_light); -    auto handle = service_manager->RegisterService(name, session_count); +    auto handle = service_manager->RegisterService(name, max_session_count);      if (handle.Failed()) {          LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}",                    handle.Code().raw); diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index d109ed2b5..1615cb5a8 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -33,7 +33,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 6af76441c..a2d33021c 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -22,7 +22,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7686634bf..0838e303b 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -12,6 +12,7 @@  #include <vector>  #include "common/common_types.h" +#include "core/file_sys/control_metadata.h"  #include "core/file_sys/vfs.h"  namespace Kernel { @@ -131,7 +132,7 @@ public:       * Returns the type of this file       * @return FileType corresponding to the loaded file       */ -    virtual FileType GetFileType() = 0; +    virtual FileType GetFileType() const = 0;      /**       * Load the application and return the created Process instance @@ -243,6 +244,15 @@ public:          return ResultStatus::ErrorNotImplemented;      } +    /** +     * Get the developer of the application +     * @param developer Reference to store the application developer into +     * @return ResultStatus result of function +     */ +    virtual ResultStatus ReadDeveloper(std::string& developer) { +        return ResultStatus::ErrorNotImplemented; +    } +  protected:      FileSys::VirtualFile file;      bool is_loaded = false; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 42f4a777b..a093e3d36 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -37,7 +37,7 @@ FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {      return IdentifyTypeImpl(nax);  } -FileType AppLoader_NAX::GetFileType() { +FileType AppLoader_NAX::GetFileType() const {      return IdentifyTypeImpl(*nax);  } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index b4d93bd01..0a97511b8 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -31,7 +31,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override; +    FileType GetFileType() const override;      ResultStatus Load(Kernel::Process& process) override; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 95d9b73a1..cbbe701d2 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -29,7 +29,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 6deff3a51..013d629c0 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -33,7 +33,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 0c1defbb6..135b6ea5a 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -37,7 +37,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 080d89904..b4ab88ae8 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {      title = nacp_file->GetApplicationName();      return ResultStatus::Success;  } + +ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) { +    if (nacp_file == nullptr) +        return ResultStatus::ErrorNoControl; +    developer = nacp_file->GetDeveloperName(); +    return ResultStatus::Success; +}  } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index db91cd01e..2b1e0719b 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -31,7 +31,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } @@ -43,6 +43,7 @@ public:      ResultStatus ReadProgramId(u64& out_program_id) override;      ResultStatus ReadIcon(std::vector<u8>& buffer) override;      ResultStatus ReadTitle(std::string& title) override; +    ResultStatus ReadDeveloper(std::string& developer) override;  private:      std::unique_ptr<FileSys::NSP> nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 461607c95..bd5a83b49 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {      title = nacp_file->GetApplicationName();      return ResultStatus::Success;  } + +ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) { +    if (nacp_file == nullptr) +        return ResultStatus::ErrorNoControl; +    developer = nacp_file->GetDeveloperName(); +    return ResultStatus::Success; +}  } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 46f8dfc9e..15d1b1a23 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -31,7 +31,7 @@ public:       */      static FileType IdentifyType(const FileSys::VirtualFile& file); -    FileType GetFileType() override { +    FileType GetFileType() const override {          return IdentifyType(file);      } @@ -43,6 +43,7 @@ public:      ResultStatus ReadProgramId(u64& out_program_id) override;      ResultStatus ReadIcon(std::vector<u8>& buffer) override;      ResultStatus ReadTitle(std::string& title) override; +    ResultStatus ReadDeveloper(std::string& developer) override;  private:      std::unique_ptr<FileSys::XCI> xci; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 41fd2a6a0..e9166dbd9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -125,14 +125,13 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin   * using a VMA from the current process   */  static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { -    u8* direct_pointer = nullptr; - -    auto& vm_manager = process.VMManager(); +    const auto& vm_manager = process.VMManager(); -    auto it = vm_manager.FindVMA(vaddr); -    ASSERT(it != vm_manager.vma_map.end()); +    const auto it = vm_manager.FindVMA(vaddr); +    DEBUG_ASSERT(vm_manager.IsValidHandle(it)); -    auto& vma = it->second; +    u8* direct_pointer = nullptr; +    const auto& vma = it->second;      switch (vma.type) {      case Kernel::VMAType::AllocatedMemoryBlock:          direct_pointer = vma.backing_block->data() + vma.offset; @@ -188,6 +187,7 @@ T Read(const VAddr vaddr) {      default:          UNREACHABLE();      } +    return {};  }  template <typename T> diff --git a/src/core/settings.h b/src/core/settings.h index a0c5fd447..de01b05c0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,8 +6,10 @@  #include <array>  #include <atomic> +#include <map>  #include <optional>  #include <string> +#include <vector>  #include "common/common_types.h"  namespace Settings { @@ -411,6 +413,9 @@ struct Values {      std::string web_api_url;      std::string yuzu_username;      std::string yuzu_token; + +    // Add-Ons +    std::map<u64, std::vector<std::string>> disabled_addons;  } extern values;  void Apply(); diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index a3b08c740..09ed74d78 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -103,13 +103,8 @@ bool VerifyLogin(const std::string& username, const std::string& token) {  TelemetrySession::TelemetrySession() {  #ifdef ENABLE_WEB_SERVICE -    if (Settings::values.enable_telemetry) { -        backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url, -                                                              Settings::values.yuzu_username, -                                                              Settings::values.yuzu_token); -    } else { -        backend = std::make_unique<Telemetry::NullVisitor>(); -    } +    backend = std::make_unique<WebService::TelemetryJson>( +        Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);  #else      backend = std::make_unique<Telemetry::NullVisitor>();  #endif @@ -180,7 +175,8 @@ TelemetrySession::~TelemetrySession() {      // This is just a placeholder to wrap up the session once the core completes and this is      // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.      field_collection.Accept(*backend); -    backend->Complete(); +    if (Settings::values.enable_telemetry) +        backend->Complete();      backend = nullptr;  } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 25bb7604a..0faff6fdf 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -164,6 +164,7 @@ public:                      return 3;                  default:                      UNREACHABLE(); +                    return 1;                  }              } @@ -871,6 +872,7 @@ public:                              return 4;                          }                          UNREACHABLE(); +                        return 1;                      }                      GPUVAddr StartAddress() const { diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 5ea094e64..eb703bb5a 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -575,7 +575,7 @@ union Instruction {      union {          BitField<39, 2, u64> tab5cb8_2; -        BitField<41, 3, u64> tab5c68_1; +        BitField<41, 3, u64> postfactor;          BitField<44, 2, u64> tab5c68_0;          BitField<48, 1, u64> negate_b;      } fmul; @@ -609,7 +609,7 @@ union Instruction {          BitField<31, 1, u64> negate_b;          BitField<30, 1, u64> abs_b; -        BitField<47, 2, HalfType> type_b; +        BitField<28, 2, HalfType> type_b;          BitField<35, 2, HalfType> type_c;      } alu_half; @@ -1065,6 +1065,7 @@ union Instruction {              LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",                           static_cast<u32>(texture_info.Value()));              UNREACHABLE(); +            return TextureType::Texture1D;          }          TextureProcessMode GetTextureProcessMode() const { @@ -1145,6 +1146,7 @@ union Instruction {              LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",                           static_cast<u32>(texture_info.Value()));              UNREACHABLE(); +            return TextureType::Texture1D;          }          TextureProcessMode GetTextureProcessMode() const { diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 88c45a423..08cf6268f 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -102,6 +102,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {          return 1;      default:          UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format)); +        return 1;      }  } @@ -119,6 +120,7 @@ u32 DepthFormatBytesPerPixel(DepthFormat format) {          return 2;      default:          UNIMPLEMENTED_MSG("Unimplemented Depth format {}", static_cast<u32>(format)); +        return 1;      }  } diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index 9c55e9f1e..64f75db43 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp @@ -171,6 +171,7 @@ u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b)      default:          UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation)); +        return 0;      }  } @@ -268,6 +269,7 @@ bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value)          return value != 0;      }      UNREACHABLE(); +    return true;  }  } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index a310491a8..47e76d8fe 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -192,6 +192,7 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor          return linear_to_morton_fns[static_cast<std::size_t>(format)];      }      UNREACHABLE(); +    return morton_to_linear_fns[static_cast<std::size_t>(format)];  }  /// 8x8 Z-Order coordinate from 2D coordinates diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 5f4cdd119..7ea07631a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -101,8 +101,18 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,      params.srgb_conversion = config.tic.IsSrgbConversionEnabled();      params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),                                                         params.srgb_conversion); + +    if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) { +        // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled, +        // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also +        // causes GetFormatType to properly return 'Depth' below). +        params.pixel_format = PixelFormat::Z16; +    } +      params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());      params.type = GetFormatType(params.pixel_format); +    UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled); +      params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));      params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));      params.unaligned_height = config.tic.Height(); @@ -257,7 +267,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex      {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false},           // R8UI      {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false},                 // RGBA16F      {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false},              // RGBA16U -    {GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UInt, false},             // RGBA16UI +    {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false},     // RGBA16UI      {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,       false},                                                                     // R11FG11FB10F      {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 038b25c75..aea6bf1af 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -2,7 +2,9 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <boost/functional/hash.hpp>  #include "common/assert.h" +#include "common/hash.h"  #include "core/core.h"  #include "core/memory.h"  #include "video_core/engines/maxwell_3d.h" @@ -66,14 +68,17 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)          // stage here.          setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));      case Maxwell::ShaderProgram::VertexB: +        CalculateProperties();          program_result = GLShader::GenerateVertexShader(setup);          gl_type = GL_VERTEX_SHADER;          break;      case Maxwell::ShaderProgram::Geometry: +        CalculateProperties();          program_result = GLShader::GenerateGeometryShader(setup);          gl_type = GL_GEOMETRY_SHADER;          break;      case Maxwell::ShaderProgram::Fragment: +        CalculateProperties();          program_result = GLShader::GenerateFragmentShader(setup);          gl_type = GL_FRAGMENT_SHADER;          break; @@ -140,6 +145,46 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,      return target_program.handle;  }; +static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { +    // sched instructions appear once every 4 instructions. +    static constexpr std::size_t SchedPeriod = 4; +    const std::size_t absolute_offset = offset - main_offset; +    return (absolute_offset % SchedPeriod) == 0; +} + +static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { +    constexpr std::size_t start_offset = 10; +    std::size_t offset = start_offset; +    std::size_t size = start_offset * sizeof(u64); +    while (offset < program.size()) { +        const u64 inst = program[offset]; +        if (!IsSchedInstruction(offset, start_offset)) { +            if (inst == 0 || (inst >> 52) == 0x50b) { +                break; +            } +        } +        size += sizeof(inst); +        offset++; +    } +    return size; +} + +void CachedShader::CalculateProperties() { +    setup.program.real_size = CalculateProgramSize(setup.program.code); +    setup.program.real_size_b = 0; +    setup.program.unique_identifier = Common::CityHash64( +        reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size); +    if (program_type == Maxwell::ShaderProgram::VertexA) { +        std::size_t seed = 0; +        boost::hash_combine(seed, setup.program.unique_identifier); +        setup.program.real_size_b = CalculateProgramSize(setup.program.code_b); +        const u64 identifier_b = Common::CityHash64( +            reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b); +        boost::hash_combine(seed, identifier_b); +        setup.program.unique_identifier = static_cast<u64>(seed); +    } +} +  ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}  Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 08f470de3..de3671acf 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -67,6 +67,7 @@ public:                                         6, "ShaderTrianglesAdjacency");          default:              UNREACHABLE_MSG("Unknown primitive mode."); +            return LazyGeometryProgram(geometry_programs.points, "points", 1, "ShaderPoints");          }      } @@ -81,6 +82,8 @@ private:      GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,                                 u32 max_vertices, const std::string& debug_name); +    void CalculateProperties(); +      VAddr addr;      std::size_t shader_length;      Maxwell::ShaderProgram program_type; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 4fc09cac6..4e685fa2c 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -364,6 +364,7 @@ public:              return value;          default:              UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size)); +            return value;          }      } @@ -626,6 +627,7 @@ public:              return "floatBitsToInt(" + value + ')';          } else {              UNREACHABLE(); +            return value;          }      } @@ -928,7 +930,7 @@ private:          case Attribute::Index::FrontFacing:              // TODO(Subv): Find out what the values are for the other elements.              ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); -            return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))"; +            return "vec4(0, 0, 0, intBitsToFloat(gl_FrontFacing ? -1 : 0))";          default:              const u32 index{static_cast<u32>(attribute) -                              static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -1591,23 +1593,21 @@ private:                                  process_mode == Tegra::Shader::TextureProcessMode::LL ||                                  process_mode == Tegra::Shader::TextureProcessMode::LLA; +        // LOD selection (either via bias or explicit textureLod) not supported in GL for +        // sampler2DArrayShadow and samplerCubeArrayShadow.          const bool gl_lod_supported = !(              (texture_type == Tegra::Shader::TextureType::Texture2D && is_array && depth_compare) || -            (texture_type == Tegra::Shader::TextureType::TextureCube && !is_array && -             depth_compare)); +            (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && depth_compare));          const std::string read_method = lod_needed && gl_lod_supported ? "textureLod(" : "texture(";          std::string texture = read_method + sampler + ", coord"; -        if (process_mode != Tegra::Shader::TextureProcessMode::None) { +        UNIMPLEMENTED_IF(process_mode != Tegra::Shader::TextureProcessMode::None && +                         !gl_lod_supported); + +        if (process_mode != Tegra::Shader::TextureProcessMode::None && gl_lod_supported) {              if (process_mode == Tegra::Shader::TextureProcessMode::LZ) { -                if (gl_lod_supported) { -                    texture += ", 0"; -                } else { -                    // Lod 0 is emulated by a big negative bias -                    // in scenarios that are not supported by glsl -                    texture += ", -1000"; -                } +                texture += ", 0.0";              } else {                  // If present, lod or bias are always stored in the register indexed by the                  // gpr20 @@ -1645,15 +1645,15 @@ private:          if (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) {              coord += ",0.0";          } +        if (is_array) { +            coord += ',' + regs.GetRegisterAsInteger(array_register); +        }          if (depth_compare) {              // Depth is always stored in the register signaled by gpr20              // or in the next register if lod or bias are used              const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);              coord += ',' + regs.GetRegisterAsFloat(depth_register);          } -        if (is_array) { -            coord += ',' + regs.GetRegisterAsInteger(array_register); -        }          coord += ");";          return std::make_pair(              coord, GetTextureCode(instr, texture_type, process_mode, depth_compare, is_array, 0)); @@ -1681,20 +1681,20 @@ private:          for (size_t i = 0; i < coord_count; ++i) {              const bool last = (i == (coord_count - 1)) && (coord_count > 1);              coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i); -            if (!last) { +            if (i < coord_count - 1) {                  coord += ',';              }          } +        if (is_array) { +            coord += ',' + regs.GetRegisterAsInteger(array_register); +        }          if (depth_compare) {              // Depth is always stored in the register signaled by gpr20              // or in the next register if lod or bias are used              const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);              coord += ',' + regs.GetRegisterAsFloat(depth_register);          } -        if (is_array) { -            coord += ',' + regs.GetRegisterAsInteger(array_register); -        }          coord += ");";          return std::make_pair(coord, @@ -1702,6 +1702,99 @@ private:                                               is_array, (coord_count > 2 ? 1 : 0)));      } +    std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr, +                                                    const Tegra::Shader::TextureType texture_type, +                                                    const bool depth_compare, const bool is_array) { + +        const size_t coord_count = TextureCoordinates(texture_type); +        const size_t total_coord_count = coord_count + (is_array ? 1 : 0); +        const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); + +        constexpr std::array<const char*, 5> coord_container{ +            {"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}}; + +        // If enabled arrays index is always stored in the gpr8 field +        const u64 array_register = instr.gpr8.Value(); +        // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used +        const u64 coord_register = array_register + (is_array ? 1 : 0); + +        std::string coord = coord_container[total_coord_count]; +        for (size_t i = 0; i < coord_count;) { +            coord += regs.GetRegisterAsFloat(coord_register + i); +            ++i; +            if (i != coord_count) { +                coord += ','; +            } +        } + +        if (is_array) { +            coord += ',' + regs.GetRegisterAsInteger(array_register); +        } +        coord += ");"; + +        const std::string sampler = +            GetSampler(instr.sampler, texture_type, is_array, depth_compare); + +        std::string texture = "textureGather(" + sampler + ", coord, "; +        if (depth_compare) { +            // Depth is always stored in the register signaled by gpr20 +            texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')'; +        } else { +            texture += std::to_string(instr.tld4.component) + ')'; +        } +        return std::make_pair(coord, texture); +    } + +    std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr, +                                                    const Tegra::Shader::TextureType texture_type, +                                                    const bool is_array) { + +        const size_t coord_count = TextureCoordinates(texture_type); +        const size_t total_coord_count = coord_count + (is_array ? 1 : 0); +        const bool lod_enabled = +            instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL; + +        constexpr std::array<const char*, 4> coord_container{ +            {"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}}; + +        std::string coord = coord_container[total_coord_count]; + +        // If enabled arrays index is always stored in the gpr8 field +        const u64 array_register = instr.gpr8.Value(); + +        // if is array gpr20 is used +        const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value(); + +        const u64 last_coord_register = +            ((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array +                ? static_cast<u64>(instr.gpr20.Value()) +                : coord_register + 1; + +        for (size_t i = 0; i < coord_count; ++i) { +            const bool last = (i == (coord_count - 1)) && (coord_count > 1); +            coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i); +            if (i < coord_count - 1) { +                coord += ','; +            } +        } +        if (is_array) { +            coord += ',' + regs.GetRegisterAsInteger(array_register); +        } +        coord += ");"; + +        const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); + +        std::string texture = "texelFetch(" + sampler + ", coords"; + +        if (lod_enabled) { +            // When lod is used always is in grp20 +            texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')'; +        } else { +            texture += ", 0)"; +        } +        return std::make_pair(coord, texture); +    } +      /**       * Compiles a single instruction from Tegra to GLSL.       * @param offset the offset of the Tegra shader instruction. @@ -1774,9 +1867,6 @@ private:                  UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0,                                       "FMUL tab5cb8_2({}) is not implemented",                                       instr.fmul.tab5cb8_2.Value()); -                UNIMPLEMENTED_IF_MSG(instr.fmul.tab5c68_1 != 0, -                                     "FMUL tab5cb8_1({}) is not implemented", -                                     instr.fmul.tab5c68_1.Value());                  UNIMPLEMENTED_IF_MSG(                      instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented",                      instr.fmul.tab5c68_0 @@ -1786,7 +1876,26 @@ private:                  op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); -                regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, +                std::string postfactor_op; +                if (instr.fmul.postfactor != 0) { +                    s8 postfactor = static_cast<s8>(instr.fmul.postfactor); + +                    // postfactor encoded as 3-bit 1's complement in instruction, +                    // interpreted with below logic. +                    if (postfactor >= 4) { +                        postfactor = 7 - postfactor; +                    } else { +                        postfactor = 0 - postfactor; +                    } + +                    if (postfactor > 0) { +                        postfactor_op = " * " + std::to_string(1 << postfactor); +                    } else { +                        postfactor_op = " / " + std::to_string(1 << -postfactor); +                    } +                } + +                regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + postfactor_op, 1, 1,                                          instr.alu.saturate_d, 0, true);                  break;              } @@ -1955,6 +2064,8 @@ private:                              std::to_string(instr.alu.GetSignedImm20_20())};                  default:                      UNREACHABLE(); +                    return {regs.GetRegisterAsInteger(instr.gpr39, 0, false), +                            std::to_string(instr.alu.GetSignedImm20_20())};                  }              }();              const std::string offset = '(' + packed_shift + " & 0xff)"; @@ -2825,9 +2936,6 @@ private:                  const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};                  const bool is_array{instr.tlds.IsArrayTexture()}; -                ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D); -                ASSERT(is_array == false); -                  UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),                                       "NODEP is not implemented");                  UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), @@ -2835,54 +2943,16 @@ private:                  UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),                                       "MZ is not implemented"); -                u32 extra_op_offset = 0; - -                ShaderScopedScope scope = shader.Scope(); - -                switch (texture_type) { -                case Tegra::Shader::TextureType::Texture1D: { -                    const std::string x = regs.GetRegisterAsInteger(instr.gpr8); -                    shader.AddLine("float coords = " + x + ';'); -                    break; -                } -                case Tegra::Shader::TextureType::Texture2D: { -                    UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture"); - -                    const std::string x = regs.GetRegisterAsInteger(instr.gpr8); -                    const std::string y = regs.GetRegisterAsInteger(instr.gpr20); -                    // shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");"); -                    shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");"); -                    extra_op_offset = 1; -                    break; -                } -                default: -                    UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); -                } -                const std::string sampler = -                    GetSampler(instr.sampler, texture_type, is_array, false); +                const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array); -                const std::string texture = [&]() { -                    switch (instr.tlds.GetTextureProcessMode()) { -                    case Tegra::Shader::TextureProcessMode::LZ: -                        return "texelFetch(" + sampler + ", coords, 0)"; -                    case Tegra::Shader::TextureProcessMode::LL: -                        shader.AddLine( -                            "float lod = " + -                            regs.GetRegisterAsInteger(instr.gpr20.Value() + extra_op_offset) + ';'); -                        return "texelFetch(" + sampler + ", coords, lod)"; -                    default: -                        UNIMPLEMENTED_MSG("Unhandled texture process mode {}", -                                          static_cast<u32>(instr.tlds.GetTextureProcessMode())); -                        return "texelFetch(" + sampler + ", coords, 0)"; -                    } -                }(); +                const auto scope = shader.Scope(); -                WriteTexsInstructionFloat(instr, texture); +                shader.AddLine(coord); +                shader.AddLine("vec4 texture_tmp = " + texture + ';'); +                WriteTexsInstructionFloat(instr, "texture_tmp");                  break;              }              case OpCode::Id::TLD4: { -                ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); -                ASSERT(instr.tld4.array == 0);                  UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),                                       "NODEP is not implemented"); @@ -2892,56 +2962,29 @@ private:                                       "NDV is not implemented");                  UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),                                       "PTP is not implemented"); + +                auto texture_type = instr.tld4.texture_type.Value();                  const bool depth_compare =                      instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); -                auto texture_type = instr.tld4.texture_type.Value(); -                u32 num_coordinates = TextureCoordinates(texture_type); -                if (depth_compare) -                    num_coordinates += 1; - -                const auto scope = shader.Scope(); +                const bool is_array = instr.tld4.array != 0; -                switch (num_coordinates) { -                case 2: { -                    const std::string x = regs.GetRegisterAsFloat(instr.gpr8); -                    const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); -                    shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); -                    break; -                } -                case 3: { -                    const std::string x = regs.GetRegisterAsFloat(instr.gpr8); -                    const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); -                    const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); -                    shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"); -                    break; -                } -                default: -                    UNIMPLEMENTED_MSG("Unhandled coordinates number {}", -                                      static_cast<u32>(num_coordinates)); -                    const std::string x = regs.GetRegisterAsFloat(instr.gpr8); -                    const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); -                    shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); -                    texture_type = Tegra::Shader::TextureType::Texture2D; -                } +                const auto [coord, texture] = +                    GetTLD4Code(instr, texture_type, depth_compare, is_array); -                const std::string sampler = -                    GetSampler(instr.sampler, texture_type, false, depth_compare); +                const auto scope = shader.Scope(); -                const std::string texture = "textureGather(" + sampler + ", coords, " + -                                            std::to_string(instr.tld4.component) + ')'; +                shader.AddLine(coord); +                std::size_t dest_elem{}; -                if (depth_compare) { -                    regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); -                } else { -                    std::size_t dest_elem{}; -                    for (std::size_t elem = 0; elem < 4; ++elem) { -                        if (!instr.tex.IsComponentEnabled(elem)) { -                            // Skip disabled components -                            continue; -                        } -                        regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); -                        ++dest_elem; +                shader.AddLine("vec4 texture_tmp = " + texture + ';'); +                for (std::size_t elem = 0; elem < 4; ++elem) { +                    if (!instr.tex.IsComponentEnabled(elem)) { +                        // Skip disabled components +                        continue;                      } +                    regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false, +                                            dest_elem); +                    ++dest_elem;                  }                  break;              } @@ -2955,28 +2998,31 @@ private:                  const auto scope = shader.Scope(); +                std::string coords; +                  const bool depth_compare =                      instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); -                const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); -                const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); -                // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. +                  const std::string sampler = GetSampler(                      instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare); -                if (depth_compare) { -                    // Note: TLD4S coordinate encoding works just like TEXS's -                    const std::string op_y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); -                    shader.AddLine("vec3 coords = vec3(" + op_a + ", " + op_y + ", " + op_b + ");"); -                } else { -                    shader.AddLine("vec2 coords = vec2(" + op_a + ", " + op_b + ");"); -                } -                std::string texture = "textureGather(" + sampler + ", coords, " + -                                      std::to_string(instr.tld4s.component) + ')'; -                if (depth_compare) { -                    texture = "vec4(" + texture + ')'; -                } +                const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); +                coords = "vec2 coords = vec2(" + op_a + ", "; +                std::string texture = "textureGather(" + sampler + ", coords, "; -                WriteTexsInstructionFloat(instr, texture); +                if (!depth_compare) { +                    const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); +                    coords += op_b + ");"; +                    texture += std::to_string(instr.tld4s.component) + ')'; +                } else { +                    const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); +                    const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); +                    coords += op_b + ");"; +                    texture += op_c + ')'; +                } +                shader.AddLine(coords); +                shader.AddLine("vec4 texture_tmp = " + texture + ';'); +                WriteTexsInstructionFloat(instr, "texture_tmp");                  break;              }              case OpCode::Id::TXQ: { @@ -3270,6 +3316,7 @@ private:                      return std::to_string(instr.r2p.immediate_mask);                  default:                      UNREACHABLE(); +                    return std::to_string(instr.r2p.immediate_mask);                  }              }();              const std::string mask = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + @@ -3733,7 +3780,10 @@ private:                  }                  break;              } -            default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); } +            default: { +                UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); +                break; +            }              }              break; @@ -3888,4 +3938,4 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u      return {};  } -} // namespace OpenGL::GLShader::Decompiler
\ No newline at end of file +} // namespace OpenGL::GLShader::Decompiler diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 23ed91e27..5d0819dc5 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -2,6 +2,7 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <fmt/format.h>  #include "common/assert.h"  #include "video_core/engines/maxwell_3d.h"  #include "video_core/renderer_opengl/gl_shader_decompiler.h" @@ -16,6 +17,8 @@ static constexpr u32 PROGRAM_OFFSET{10};  ProgramResult GenerateVertexShader(const ShaderSetup& setup) {      std::string out = "#version 430 core\n";      out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; +    const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); +    out += "// Shader Unique Id: VS" + id + "\n\n";      out += Decompiler::GetCommonDeclarations();      out += R"( @@ -84,6 +87,8 @@ void main() {  ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {      // Version is intentionally skipped in shader generation, it's added by the lazy compilation.      std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; +    const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); +    out += "// Shader Unique Id: GS" + id + "\n\n";      out += Decompiler::GetCommonDeclarations();      out += "bool exec_geometry();\n"; @@ -117,6 +122,8 @@ void main() {  ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {      std::string out = "#version 430 core\n";      out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; +    const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); +    out += "// Shader Unique Id: FS" + id + "\n\n";      out += Decompiler::GetCommonDeclarations();      out += "bool exec_fragment();\n"; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 4fa6d7612..fcc20d3b4 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -177,6 +177,9 @@ struct ShaderSetup {      struct {          ProgramCode code;          ProgramCode code_b; // Used for dual vertex shaders +        u64 unique_identifier; +        std::size_t real_size; +        std::size_t real_size_b;      } program;      /// Used in scenarios where we have a dual vertex shaders diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 49a1989e4..235732d86 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -462,6 +462,7 @@ static const char* GetSource(GLenum source) {          RET(OTHER);      default:          UNREACHABLE(); +        return "Unknown source";      }  #undef RET  } @@ -480,6 +481,7 @@ static const char* GetType(GLenum type) {          RET(MARKER);      default:          UNREACHABLE(); +        return "Unknown type";      }  #undef RET  } diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 9582dd2ca..a97b1562b 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -65,6 +65,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {      default:          LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));          UNREACHABLE(); +        return PixelFormat::S8Z24;      }  } @@ -141,6 +142,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)      default:          LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));          UNREACHABLE(); +        return PixelFormat::RGBA8_SRGB;      }  } @@ -327,6 +329,7 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,          LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),                       static_cast<u32>(component_type));          UNREACHABLE(); +        return PixelFormat::ABGR8U;      }  } @@ -346,6 +349,7 @@ ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {      default:          LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));          UNREACHABLE(); +        return ComponentType::UNorm;      }  } @@ -393,6 +397,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {      default:          LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));          UNREACHABLE(); +        return ComponentType::UNorm;      }  } @@ -403,6 +408,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat      default:          LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));          UNREACHABLE(); +        return PixelFormat::ABGR8U;      }  } @@ -418,6 +424,7 @@ ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {      default:          LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));          UNREACHABLE(); +        return ComponentType::UNorm;      }  } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index bbae9285f..5db75de22 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -226,7 +226,7 @@ u32 BytesPerPixel(TextureFormat format) {          return 8;      default:          UNIMPLEMENTED_MSG("Format not implemented"); -        break; +        return 1;      }  } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index cfca8f4a8..17ecaafde 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu      Info.plist      about_dialog.cpp      about_dialog.h +    applets/profile_select.cpp +    applets/profile_select.h      applets/software_keyboard.cpp      applets/software_keyboard.h      bootmanager.cpp @@ -31,10 +33,14 @@ add_executable(yuzu      configuration/configure_input.h      configuration/configure_input_player.cpp      configuration/configure_input_player.h +    configuration/configure_input_simple.cpp +    configuration/configure_input_simple.h      configuration/configure_mouse_advanced.cpp      configuration/configure_mouse_advanced.h      configuration/configure_system.cpp      configuration/configure_system.h +    configuration/configure_per_general.cpp +    configuration/configure_per_general.h      configuration/configure_touchscreen_advanced.cpp      configuration/configure_touchscreen_advanced.h      configuration/configure_web.cpp @@ -85,7 +91,9 @@ set(UIS      configuration/configure_graphics.ui      configuration/configure_input.ui      configuration/configure_input_player.ui +    configuration/configure_input_simple.ui      configuration/configure_mouse_advanced.ui +    configuration/configure_per_general.ui      configuration/configure_system.ui      configuration/configure_touchscreen_advanced.ui      configuration/configure_web.ui diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp new file mode 100644 index 000000000..5c1b65a2c --- /dev/null +++ b/src/yuzu/applets/profile_select.cpp @@ -0,0 +1,168 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <QDialogButtonBox> +#include <QLabel> +#include <QLineEdit> +#include <QScrollArea> +#include <QStandardItemModel> +#include <QVBoxLayout> +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/hle/lock.h" +#include "yuzu/applets/profile_select.h" +#include "yuzu/main.h" + +// Same backup JPEG used by acc IProfile::GetImage if no jpeg found +constexpr std::array<u8, 107> backup_jpeg{ +    0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, +    0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, +    0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, +    0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, +    0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, +    0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, +    0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, +}; + +QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { +    return QtProfileSelectionDialog::tr( +               "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " +                         "00112233-4455-6677-8899-AABBCCDDEEFF))") +        .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QString GetImagePath(Service::Account::UUID uuid) { +    const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + +                      "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; +    return QString::fromStdString(path); +} + +QPixmap GetIcon(Service::Account::UUID uuid) { +    QPixmap icon{GetImagePath(uuid)}; + +    if (!icon) { +        icon.fill(Qt::black); +        icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); +    } + +    return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) +    : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) { +    outer_layout = new QVBoxLayout; + +    instruction_label = new QLabel(tr("Select a user:")); + +    scroll_area = new QScrollArea; + +    buttons = new QDialogButtonBox; +    buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); +    buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole); + +    connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); +    connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); + +    outer_layout->addWidget(instruction_label); +    outer_layout->addWidget(scroll_area); +    outer_layout->addWidget(buttons); + +    layout = new QVBoxLayout; +    tree_view = new QTreeView; +    item_model = new QStandardItemModel(tree_view); +    tree_view->setModel(item_model); + +    tree_view->setAlternatingRowColors(true); +    tree_view->setSelectionMode(QHeaderView::SingleSelection); +    tree_view->setSelectionBehavior(QHeaderView::SelectRows); +    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setSortingEnabled(true); +    tree_view->setEditTriggers(QHeaderView::NoEditTriggers); +    tree_view->setUniformRowHeights(true); +    tree_view->setIconSize({64, 64}); +    tree_view->setContextMenuPolicy(Qt::NoContextMenu); + +    item_model->insertColumns(0, 1); +    item_model->setHeaderData(0, Qt::Horizontal, "Users"); + +    // We must register all custom types with the Qt Automoc system so that we are able to use it +    // with signals/slots. In this case, QList falls under the umbrells of custom types. +    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + +    layout->setContentsMargins(0, 0, 0, 0); +    layout->setSpacing(0); +    layout->addWidget(tree_view); + +    scroll_area->setLayout(layout); + +    connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + +    const auto& profiles = profile_manager->GetAllUsers(); +    for (const auto& user : profiles) { +        Service::Account::ProfileBase profile; +        if (!profile_manager->GetProfileBase(user, profile)) +            continue; + +        const auto username = Common::StringFromFixedZeroTerminatedBuffer( +            reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + +        list_items.push_back(QList<QStandardItem*>{new QStandardItem{ +            GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); +    } + +    for (const auto& item : list_items) +        item_model->appendRow(item); + +    setLayout(outer_layout); +    setWindowTitle(tr("Profile Selector")); +    resize(550, 400); +} + +QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; + +void QtProfileSelectionDialog::accept() { +    ok = true; +    QDialog::accept(); +} + +void QtProfileSelectionDialog::reject() { +    ok = false; +    user_index = 0; +    QDialog::reject(); +} + +bool QtProfileSelectionDialog::GetStatus() const { +    return ok; +} + +u32 QtProfileSelectionDialog::GetIndex() const { +    return user_index; +} + +void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { +    user_index = index.row(); +} + +QtProfileSelector::QtProfileSelector(GMainWindow& parent) { +    connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, +            &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); +    connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, +            &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); +} + +QtProfileSelector::~QtProfileSelector() = default; + +void QtProfileSelector::SelectProfile( +    std::function<void(std::optional<Service::Account::UUID>)> callback) const { +    this->callback = std::move(callback); +    emit MainWindowSelectProfile(); +} + +void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    callback(uuid); +} diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h new file mode 100644 index 000000000..868573324 --- /dev/null +++ b/src/yuzu/applets/profile_select.h @@ -0,0 +1,73 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <QDialog> +#include <QList> +#include "core/frontend/applets/profile_select.h" + +class GMainWindow; +class QDialogButtonBox; +class QGraphicsScene; +class QLabel; +class QScrollArea; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +class QtProfileSelectionDialog final : public QDialog { +    Q_OBJECT + +public: +    explicit QtProfileSelectionDialog(QWidget* parent); +    ~QtProfileSelectionDialog() override; + +    void accept() override; +    void reject() override; + +    bool GetStatus() const; +    u32 GetIndex() const; + +private: +    bool ok = false; +    u32 user_index = 0; + +    void SelectUser(const QModelIndex& index); + +    QVBoxLayout* layout; +    QTreeView* tree_view; +    QStandardItemModel* item_model; +    QGraphicsScene* scene; + +    std::vector<QList<QStandardItem*>> list_items; + +    QVBoxLayout* outer_layout; +    QLabel* instruction_label; +    QScrollArea* scroll_area; +    QDialogButtonBox* buttons; + +    std::unique_ptr<Service::Account::ProfileManager> profile_manager; +}; + +class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { +    Q_OBJECT + +public: +    explicit QtProfileSelector(GMainWindow& parent); +    ~QtProfileSelector() override; + +    void SelectProfile( +        std::function<void(std::optional<Service::Account::UUID>)> callback) const override; + +signals: +    void MainWindowSelectProfile() const; + +private: +    void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid); + +    mutable std::function<void(std::optional<Service::Account::UUID>)> callback; +}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 02e09fa18..c4349ccc8 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,6 +4,7 @@  #include <QSettings>  #include "common/file_util.h" +#include "configure_input_simple.h"  #include "core/hle/service/acc/profile_manager.h"  #include "core/hle/service/hid/controllers/npad.h"  #include "input_common/main.h" @@ -339,6 +340,13 @@ void Config::ReadTouchscreenValues() {      qt_config->endGroup();  } +void Config::ApplyDefaultProfileIfInputInvalid() { +    if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), +                     [](const Settings::PlayerInput& in) { return in.connected; })) { +        ApplyInputProfileConfiguration(UISettings::values.profile_index); +    } +} +  void Config::ReadValues() {      qt_config->beginGroup("Controls"); @@ -441,6 +449,21 @@ void Config::ReadValues() {      Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();      qt_config->endGroup(); +    const auto size = qt_config->beginReadArray("DisabledAddOns"); +    for (int i = 0; i < size; ++i) { +        qt_config->setArrayIndex(i); +        const auto title_id = qt_config->value("title_id", 0).toULongLong(); +        std::vector<std::string> out; +        const auto d_size = qt_config->beginReadArray("disabled"); +        for (int j = 0; j < d_size; ++j) { +            qt_config->setArrayIndex(j); +            out.push_back(qt_config->value("d", "").toString().toStdString()); +        } +        qt_config->endArray(); +        Settings::values.disabled_addons.insert_or_assign(title_id, out); +    } +    qt_config->endArray(); +      qt_config->beginGroup("UI");      UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();      UISettings::values.enable_discord_presence = @@ -505,6 +528,9 @@ void Config::ReadValues() {      UISettings::values.first_start = qt_config->value("firstStart", true).toBool();      UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();      UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); +    UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt(); + +    ApplyDefaultProfileIfInputInvalid();      qt_config->endGroup();  } @@ -647,6 +673,21 @@ void Config::SaveValues() {      qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));      qt_config->endGroup(); +    qt_config->beginWriteArray("DisabledAddOns"); +    int i = 0; +    for (const auto& elem : Settings::values.disabled_addons) { +        qt_config->setArrayIndex(i); +        qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); +        qt_config->beginWriteArray("disabled"); +        for (std::size_t j = 0; j < elem.second.size(); ++j) { +            qt_config->setArrayIndex(j); +            qt_config->setValue("d", QString::fromStdString(elem.second[j])); +        } +        qt_config->endArray(); +        ++i; +    } +    qt_config->endArray(); +      qt_config->beginGroup("UI");      qt_config->setValue("theme", UISettings::values.theme);      qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); @@ -695,6 +736,7 @@ void Config::SaveValues() {      qt_config->setValue("firstStart", UISettings::values.first_start);      qt_config->setValue("calloutFlags", UISettings::values.callout_flags);      qt_config->setValue("showConsole", UISettings::values.show_console); +    qt_config->setValue("profileIndex", UISettings::values.profile_index);      qt_config->endGroup();  } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index a1c27bbf9..e73ad19bb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -34,6 +34,7 @@ private:      void ReadKeyboardValues();      void ReadMouseValues();      void ReadTouchscreenValues(); +    void ApplyDefaultProfileIfInputInvalid();      void SaveValues();      void SavePlayerValues(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9b297df28..8706b80d2 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -7,7 +7,7 @@      <x>0</x>      <y>0</y>      <width>461</width> -    <height>500</height> +    <height>659</height>     </rect>    </property>    <property name="windowTitle"> @@ -24,17 +24,17 @@         <string>General</string>        </attribute>       </widget> -      <widget class="ConfigureGameList" name="gameListTab"> -        <attribute name="title"> -          <string>Game List</string> -        </attribute> -      </widget> +     <widget class="ConfigureGameList" name="gameListTab"> +      <attribute name="title"> +       <string>Game List</string> +      </attribute> +     </widget>       <widget class="ConfigureSystem" name="systemTab">        <attribute name="title">         <string>System</string>        </attribute>       </widget> -     <widget class="ConfigureInput" name="inputTab"> +     <widget class="ConfigureInputSimple" name="inputTab">        <attribute name="title">         <string>Input</string>        </attribute> @@ -54,11 +54,11 @@         <string>Debug</string>        </attribute>       </widget> -      <widget class="ConfigureWeb" name="webTab"> -        <attribute name="title"> -          <string>Web</string> -        </attribute> -      </widget> +     <widget class="ConfigureWeb" name="webTab"> +      <attribute name="title"> +       <string>Web</string> +      </attribute> +     </widget>      </widget>     </item>     <item> @@ -77,12 +77,12 @@     <header>configuration/configure_general.h</header>     <container>1</container>    </customwidget> -   <customwidget> -     <class>ConfigureGameList</class> -     <extends>QWidget</extends> -     <header>configuration/configure_gamelist.h</header> -     <container>1</container> -   </customwidget> +  <customwidget> +   <class>ConfigureGameList</class> +   <extends>QWidget</extends> +   <header>configuration/configure_gamelist.h</header> +   <container>1</container> +  </customwidget>    <customwidget>     <class>ConfigureSystem</class>     <extends>QWidget</extends> @@ -102,9 +102,9 @@     <container>1</container>    </customwidget>    <customwidget> -   <class>ConfigureInput</class> +   <class>ConfigureInputSimple</class>     <extends>QWidget</extends> -   <header>configuration/configure_input.h</header> +   <header>configuration/configure_input_simple.h</header>     <container>1</container>    </customwidget>    <customwidget> @@ -113,12 +113,12 @@     <header>configuration/configure_graphics.h</header>     <container>1</container>    </customwidget> -   <customwidget> -     <class>ConfigureWeb</class> -     <extends>QWidget</extends> -     <header>configuration/configure_web.h</header> -     <container>1</container> -   </customwidget> +  <customwidget> +   <class>ConfigureWeb</class> +   <extends>QWidget</extends> +   <header>configuration/configure_web.h</header> +   <container>1</container> +  </customwidget>   </customwidgets>   <resources/>   <connections> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index eb1da0f9e..5d9ccc6e8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)      ui->output_sink_combo_box->clear();      ui->output_sink_combo_box->addItem("auto"); -    for (const auto& sink_detail : AudioCore::g_sink_details) { -        ui->output_sink_combo_box->addItem(sink_detail.id); +    for (const char* id : AudioCore::GetSinkIDs()) { +        ui->output_sink_combo_box->addItem(id);      }      connect(ui->volume_slider, &QSlider::valueChanged, this, @@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {      ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);      const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); -    const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); -    for (const auto& device : device_list) { +    for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {          ui->audio_device_combo_box->addItem(QString::fromStdString(device));      }  } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 830d26115..f39d57998 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,33 @@  #include "yuzu/configuration/configure_input_player.h"  #include "yuzu/configuration/configure_mouse_advanced.h" +void OnDockedModeChanged(bool last_state, bool new_state) { +    if (last_state == new_state) { +        return; +    } + +    Core::System& system{Core::System::GetInstance()}; +    if (!system.IsPoweredOn()) { +        return; +    } +    Service::SM::ServiceManager& sm = system.ServiceManager(); + +    // Message queue is shared between these services, we just need to signal an operation +    // change to one and it will handle both automatically +    auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); +    auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); +    bool has_signalled = false; + +    if (applet_oe != nullptr) { +        applet_oe->GetMessageQueue()->OperationModeChanged(); +        has_signalled = true; +    } + +    if (applet_ae != nullptr && !has_signalled) { +        applet_ae->GetMessageQueue()->OperationModeChanged(); +    } +} +  namespace {  template <typename Dialog, typename... Args>  void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { @@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {  } // Anonymous namespace  ConfigureInput::ConfigureInput(QWidget* parent) -    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {      ui->setupUi(this);      players_controller = { @@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent)  ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { -    if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) { -        ui->handheld_connected->setChecked(false); -    } - -    if (last_state == new_state) { -        return; -    } - -    Core::System& system{Core::System::GetInstance()}; -    if (!system.IsPoweredOn()) { -        return; -    } -    Service::SM::ServiceManager& sm = system.ServiceManager(); - -    // Message queue is shared between these services, we just need to signal an operation -    // change to one and it will handle both automatically -    auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); -    auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); -    bool has_signalled = false; - -    if (applet_oe != nullptr) { -        applet_oe->GetMessageQueue()->OperationModeChanged(); -        has_signalled = true; -    } - -    if (applet_ae != nullptr && !has_signalled) { -        applet_ae->GetMessageQueue()->OperationModeChanged(); -    } -} -  void ConfigureInput::applyConfiguration() {      for (std::size_t i = 0; i < players_controller.size(); ++i) {          const auto controller_type_index = players_controller[i]->currentIndex(); diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 1649e4c0b..b8e62cc2b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,8 +7,8 @@  #include <array>  #include <memory> +#include <QDialog>  #include <QKeyEvent> -#include <QWidget>  #include "ui_configure_input.h" @@ -20,7 +20,9 @@ namespace Ui {  class ConfigureInput;  } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog {      Q_OBJECT  public: @@ -33,8 +35,6 @@ public:  private:      void updateUIEnabled(); -    void OnDockedModeChanged(bool last_state, bool new_state); -      /// Load configuration settings.      void loadConfiguration();      /// Restore all buttons to their default values. diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index dae8277bc..0a2d9f024 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,13 +1,13 @@  <?xml version="1.0" encoding="UTF-8"?>  <ui version="4.0">   <class>ConfigureInput</class> - <widget class="QWidget" name="ConfigureInput"> + <widget class="QDialog" name="ConfigureInput">    <property name="geometry">     <rect>      <x>0</x>      <y>0</y> -    <width>473</width> -    <height>685</height> +    <width>384</width> +    <height>576</height>     </rect>    </property>    <property name="windowTitle"> @@ -478,6 +478,13 @@           </property>          </spacer>         </item> +       <item> +        <widget class="QDialogButtonBox" name="buttonBox"> +         <property name="standardButtons"> +          <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +         </property> +        </widget> +       </item>        </layout>       </item>      </layout> @@ -485,5 +492,38 @@    </layout>   </widget>   <resources/> - <connections/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureInput</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>294</x> +     <y>553</y> +    </hint> +    <hint type="destinationlabel"> +     <x>191</x> +     <y>287</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureInput</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>294</x> +     <y>553</y> +    </hint> +    <hint type="destinationlabel"> +     <x>191</x> +     <y>287</y> +    </hint> +   </hints> +  </connection> + </connections>  </ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7dadd83c1..ba2b32c4f 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -6,6 +6,7 @@  #include <memory>  #include <utility>  #include <QColorDialog> +#include <QGridLayout>  #include <QMenu>  #include <QMessageBox>  #include <QTimer> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp new file mode 100644 index 000000000..b4f3724bd --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -0,0 +1,142 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstring> +#include <functional> +#include <tuple> + +#include <QDialog> + +#include "ui_configure_input_simple.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_simple.h" +#include "yuzu/ui_settings.h" + +namespace { + +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { +    caller->applyConfiguration(); +    Dialog dialog(caller, std::forward<Args>(args)...); + +    const auto res = dialog.exec(); +    if (res == QDialog::Accepted) { +        dialog.applyConfiguration(); +    } +} + +// OnProfileSelect functions should (when applicable): +// - Set controller types +// - Set controller enabled +// - Set docked mode +// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) +// +// OnProfileSelect function should NOT however: +// - Reset any button mappings +// - Open any dialogs +// - Block in any way + +constexpr std::size_t HANDHELD_INDEX = 8; + +void HandheldOnProfileSelect() { +    Settings::values.players[HANDHELD_INDEX].connected = true; +    Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; + +    for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { +        Settings::values.players[player].connected = false; +    } + +    Settings::values.use_docked_mode = false; +    Settings::values.keyboard_enabled = false; +    Settings::values.mouse_enabled = false; +    Settings::values.debug_pad_enabled = false; +    Settings::values.touchscreen.enabled = true; +} + +void DualJoyconsDockedOnProfileSelect() { +    Settings::values.players[0].connected = true; +    Settings::values.players[0].type = Settings::ControllerType::DualJoycon; + +    for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { +        Settings::values.players[player].connected = false; +    } + +    Settings::values.use_docked_mode = true; +    Settings::values.keyboard_enabled = false; +    Settings::values.mouse_enabled = false; +    Settings::values.debug_pad_enabled = false; +    Settings::values.touchscreen.enabled = false; +} + +// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure +// is clicked) +using InputProfile = +    std::tuple<QString, std::function<void()>, std::function<void(ConfigureInputSimple*)>>; + +const std::array<InputProfile, 3> INPUT_PROFILES{{ +    {ConfigureInputSimple::tr("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); +     }}, +    {ConfigureInputSimple::tr("Single Player - Dual Joycons - Docked"), +     DualJoyconsDockedOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false); +     }}, +    {ConfigureInputSimple::tr("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, +}}; + +} // namespace + +void ApplyInputProfileConfiguration(int profile_index) { +    std::get<1>( +        INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); +} + +ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { +    ui->setupUi(this); + +    for (const auto& profile : INPUT_PROFILES) { +        ui->profile_combobox->addItem(std::get<0>(profile), std::get<0>(profile)); +    } + +    connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureInputSimple::OnSelectProfile); +    connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); + +    this->loadConfiguration(); +} + +ConfigureInputSimple::~ConfigureInputSimple() = default; + +void ConfigureInputSimple::applyConfiguration() { +    auto index = ui->profile_combobox->currentIndex(); +    // Make the stored index for "Custom" very large so that if new profiles are added it +    // doesn't change. +    if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) +        index = std::numeric_limits<int>::max(); + +    UISettings::values.profile_index = index; +} + +void ConfigureInputSimple::loadConfiguration() { +    const auto index = UISettings::values.profile_index; +    if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) +        ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); +    else +        ui->profile_combobox->setCurrentIndex(index); +} + +void ConfigureInputSimple::OnSelectProfile(int index) { +    const auto old_docked = Settings::values.use_docked_mode; +    ApplyInputProfileConfiguration(index); +    OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); +} + +void ConfigureInputSimple::OnConfigure() { +    std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); +} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h new file mode 100644 index 000000000..5b6b69994 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.h @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QWidget> + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputSimple; +} + +// Used by configuration loader to apply a profile if the input is invalid. +void ApplyInputProfileConfiguration(int profile_index); + +class ConfigureInputSimple : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureInputSimple(QWidget* parent = nullptr); +    ~ConfigureInputSimple() override; + +    /// Save all button configurations to settings file +    void applyConfiguration(); + +private: +    /// Load configuration settings. +    void loadConfiguration(); + +    void OnSelectProfile(int index); +    void OnConfigure(); + +    std::unique_ptr<Ui::ConfigureInputSimple> ui; +}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui new file mode 100644 index 000000000..c4889caa9 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputSimple</class> + <widget class="QWidget" name="ConfigureInputSimple"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>473</width> +    <height>685</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>ConfigureInputSimple</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout_5"> +   <item> +    <layout class="QVBoxLayout" name="verticalLayout"> +     <item> +      <widget class="QGroupBox" name="gridGroupBox"> +       <property name="title"> +        <string>Profile</string> +       </property> +       <layout class="QGridLayout" name="gridLayout"> +        <item row="1" column="2"> +         <widget class="QPushButton" name="profile_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="1" column="0"> +         <spacer name="horizontalSpacer"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>40</width> +            <height>20</height> +           </size> +          </property> +         </spacer> +        </item> +        <item row="1" column="3"> +         <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> +        <item row="1" column="1"> +         <widget class="QComboBox" name="profile_combobox"> +          <property name="minimumSize"> +           <size> +            <width>250</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="0" column="1" colspan="2"> +         <widget class="QLabel" name="label"> +          <property name="text"> +           <string>Choose a controller configuration:</string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </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> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..80109b434 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,170 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QHeaderView> +#include <QMenu> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" +#include "yuzu/util/util.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { + +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); +    setWindowTitle(tr("Properties")); + +    layout = new QVBoxLayout; +    tree_view = new QTreeView; +    item_model = new QStandardItemModel(tree_view); +    tree_view->setModel(item_model); +    tree_view->setAlternatingRowColors(true); +    tree_view->setSelectionMode(QHeaderView::SingleSelection); +    tree_view->setSelectionBehavior(QHeaderView::SelectRows); +    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setSortingEnabled(true); +    tree_view->setEditTriggers(QHeaderView::NoEditTriggers); +    tree_view->setUniformRowHeights(true); +    tree_view->setContextMenuPolicy(Qt::NoContextMenu); + +    item_model->insertColumns(0, 2); +    item_model->setHeaderData(0, Qt::Horizontal, "Patch Name"); +    item_model->setHeaderData(1, Qt::Horizontal, "Version"); + +    // We must register all custom types with the Qt Automoc system so that we are able to use it +    // with signals/slots. In this case, QList falls under the umbrells of custom types. +    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + +    layout->setContentsMargins(0, 0, 0, 0); +    layout->setSpacing(0); +    layout->addWidget(tree_view); + +    ui->scrollArea->setLayout(layout); + +    scene = new QGraphicsScene; +    ui->icon_view->setScene(scene); + +    connect(item_model, &QStandardItemModel::itemChanged, +            [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + +    this->loadConfiguration(); +} + +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + +void ConfigurePerGameGeneral::applyConfiguration() { +    std::vector<std::string> disabled_addons; + +    for (const auto& item : list_items) { +        const auto disabled = item.front()->checkState() == Qt::Unchecked; +        if (disabled) +            disabled_addons.push_back(item.front()->text().toStdString()); +    } + +    Settings::values.disabled_addons[title_id] = disabled_addons; +} + +void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { +    this->file = std::move(file); +    this->loadConfiguration(); +} + +void ConfigurePerGameGeneral::loadConfiguration() { +    if (file == nullptr) +        return; + +    const auto loader = Loader::GetLoader(file); + +    ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str()); + +    FileSys::PatchManager pm{title_id}; +    const auto control = pm.GetControlMetadata(); + +    if (control.first != nullptr) { +        ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); +        ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); +        ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); +    } else { +        std::string title; +        if (loader->ReadTitle(title) == Loader::ResultStatus::Success) +            ui->display_name->setText(QString::fromStdString(title)); + +        std::string developer; +        if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) +            ui->display_developer->setText(QString::fromStdString(developer)); + +        ui->display_version->setText(QStringLiteral("1.0.0")); +    } + +    if (control.second != nullptr) { +        scene->clear(); + +        QPixmap map; +        const auto bytes = control.second->ReadAllBytes(); +        map.loadFromData(bytes.data(), bytes.size()); + +        scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +    } else { +        std::vector<u8> bytes; +        if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { +            scene->clear(); + +            QPixmap map; +            map.loadFromData(bytes.data(), bytes.size()); + +            scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                        Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +        } +    } + +    FileSys::VirtualFile update_raw; +    loader->ReadUpdateRaw(update_raw); + +    const auto& disabled = Settings::values.disabled_addons[title_id]; + +    for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { +        QStandardItem* first_item = new QStandardItem; +        const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); +        first_item->setText(name); +        first_item->setCheckable(true); + +        const auto patch_disabled = +            std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + +        first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); + +        list_items.push_back(QList<QStandardItem*>{ +            first_item, new QStandardItem{QString::fromStdString(patch.second)}}); +        item_model->appendRow(list_items.back()); +    } + +    tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + +    ui->display_filename->setText(QString::fromStdString(file->GetName())); + +    ui->display_format->setText( +        QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + +    const auto valueText = ReadableByteSize(file->GetSize()); +    ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..a4494446c --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,50 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QKeyEvent> +#include <QList> +#include <QWidget> + +#include "core/file_sys/vfs_types.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); +    ~ConfigurePerGameGeneral() override; + +    /// Save all button configurations to settings file +    void applyConfiguration(); + +    void loadFromFile(FileSys::VirtualFile file); + +private: +    std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; +    FileSys::VirtualFile file; +    u64 title_id; + +    QVBoxLayout* layout; +    QTreeView* tree_view; +    QStandardItemModel* item_model; +    QGraphicsScene* scene; + +    std::vector<QList<QStandardItem*>> list_items; + +    void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameGeneral</class> + <widget class="QDialog" name="ConfigurePerGameGeneral"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>520</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>ConfigurePerGameGeneral</string> +  </property> +  <layout class="QHBoxLayout" name="HorizontalLayout"> +   <item> +    <layout class="QVBoxLayout" name="VerticalLayout"> +     <item> +      <widget class="QGroupBox" name="GeneralGroupBox"> +       <property name="title"> +        <string>Info</string> +       </property> +       <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> +        <item> +         <layout class="QGridLayout" name="gridLayout_2"> +          <item row="6" column="1" colspan="2"> +           <widget class="QLineEdit" name="display_filename"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="1"> +           <widget class="QLineEdit" name="display_name"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="1" column="0"> +           <widget class="QLabel" name="label_2"> +            <property name="text"> +             <string>Developer</string> +            </property> +           </widget> +          </item> +          <item row="5" column="1" colspan="2"> +           <widget class="QLineEdit" name="display_size"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="0"> +           <widget class="QLabel" name="label"> +            <property name="text"> +             <string>Name</string> +            </property> +           </widget> +          </item> +          <item row="6" column="0"> +           <widget class="QLabel" name="label_7"> +            <property name="text"> +             <string>Filename</string> +            </property> +           </widget> +          </item> +          <item row="2" column="0"> +           <widget class="QLabel" name="label_3"> +            <property name="text"> +             <string>Version</string> +            </property> +           </widget> +          </item> +          <item row="4" column="0"> +           <widget class="QLabel" name="label_5"> +            <property name="text"> +             <string>Format</string> +            </property> +           </widget> +          </item> +          <item row="2" column="1"> +           <widget class="QLineEdit" name="display_version"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="4" column="1"> +           <widget class="QLineEdit" name="display_format"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="5" column="0"> +           <widget class="QLabel" name="label_6"> +            <property name="text"> +             <string>Size</string> +            </property> +           </widget> +          </item> +          <item row="1" column="1"> +           <widget class="QLineEdit" name="display_developer"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="3" column="0"> +           <widget class="QLabel" name="label_4"> +            <property name="text"> +             <string>Title ID</string> +            </property> +           </widget> +          </item> +          <item row="3" column="1"> +           <widget class="QLineEdit" name="display_title_id"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="2" rowspan="5"> +           <widget class="QGraphicsView" name="icon_view"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="minimumSize"> +             <size> +              <width>128</width> +              <height>128</height> +             </size> +            </property> +            <property name="maximumSize"> +             <size> +              <width>128</width> +              <height>128</height> +             </size> +            </property> +            <property name="verticalScrollBarPolicy"> +             <enum>Qt::ScrollBarAlwaysOff</enum> +            </property> +            <property name="horizontalScrollBarPolicy"> +             <enum>Qt::ScrollBarAlwaysOff</enum> +            </property> +            <property name="sizeAdjustPolicy"> +             <enum>QAbstractScrollArea::AdjustToContents</enum> +            </property> +            <property name="interactive"> +             <bool>false</bool> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QGroupBox" name="PerformanceGroupBox"> +       <property name="title"> +        <string>Add-Ons</string> +       </property> +       <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> +        <item> +         <widget class="QScrollArea" name="scrollArea"> +          <property name="widgetResizable"> +           <bool>true</bool> +          </property> +          <widget class="QWidget" name="scrollAreaWidgetContents"> +           <property name="geometry"> +            <rect> +             <x>0</x> +             <y>0</y> +             <width>350</width> +             <height>169</height> +            </rect> +           </property> +          </widget> +         </widget> +        </item> +        <item> +         <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <spacer name="verticalSpacer"> +       <property name="orientation"> +        <enum>Qt::Vertical</enum> +       </property> +       <property name="sizeType"> +        <enum>QSizePolicy::Fixed</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>20</width> +         <height>40</height> +        </size> +       </property> +      </spacer> +     </item> +     <item> +      <widget class="QDialogButtonBox" name="buttonBox"> +       <property name="standardButtons"> +        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +       </property> +      </widget> +     </item> +    </layout> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigurePerGameGeneral</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>269</x> +     <y>567</y> +    </hint> +    <hint type="destinationlabel"> +     <x>269</x> +     <y>294</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigurePerGameGeneral</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>269</x> +     <y>567</y> +    </hint> +    <hint type="destinationlabel"> +     <x>269</x> +     <y>294</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 707747422..209798521 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -30,6 +30,7 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat(          return Tegra::Texture::TextureFormat::A2B10G10R10;      default:          UNIMPLEMENTED_MSG("Unimplemented RT format"); +        return Tegra::Texture::TextureFormat::A8R8G8B8;      }  } diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index f9c18ede4..6b3a757e0 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()      return item_list;  } -WaitTreeText::WaitTreeText(const QString& t) : text(t) {} +WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}  WaitTreeText::~WaitTreeText() = default;  QString WaitTreeText::GetText() const { diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 492fb6ac9..e639ef412 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -52,7 +52,7 @@ private:  class WaitTreeText : public WaitTreeItem {      Q_OBJECT  public: -    explicit WaitTreeText(const QString& text); +    explicit WaitTreeText(QString text);      ~WaitTreeText() override;      QString GetText() const override; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b52a50915..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));      QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));      QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); +    context_menu.addSeparator(); +    QAction* properties = context_menu.addAction(tr("Properties"));      open_save_location->setEnabled(program_id != 0);      auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });      connect(navigate_to_gamedb_entry, &QAction::triggered,              [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); +    connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });      context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));  } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 05e115e19..b317eb2fc 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -70,6 +70,7 @@ signals:      void CopyTIDRequested(u64 program_id);      void NavigateToGamedbEntryRequested(u64 program_id,                                          const CompatibilityList& compatibility_list); +    void OpenPerGameGeneralRequested(const std::string& file);  private slots:      void onTextChanged(const QString& newText); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 9fd074223..b37710f59 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,      FileSys::VirtualFile update_raw;      loader.ReadUpdateRaw(update_raw);      for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { -        const bool is_update = kv.first == "Update"; +        const bool is_update = kv.first == "Update" || kv.first == "[D] Update";          if (!updatable && is_update) {              continue;          } @@ -99,12 +99,14 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri          compatibility = it->second.first;      } +    const auto file_type = loader.GetFileType(); +    const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); +      QList<QStandardItem*> list{ -        new GameListItemPath( -            FormatGameName(path), icon, QString::fromStdString(name), -            QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType())), program_id), +        new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), +                             file_type_string, program_id),          new GameListItemCompat(compatibility), -        new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType()))), +        new GameListItem(file_type_string),          new GameListItemSize(FileUtil::GetSize(path)),      }; @@ -196,12 +198,16 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign          const bool is_dir = FileUtil::IsDirectory(physical_name);          if (!is_dir &&              (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { -            std::unique_ptr<Loader::AppLoader> loader = -                Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); -            if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || -                             loader->GetFileType() == Loader::FileType::Error) && -                            !UISettings::values.show_unknown)) +            auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); +            if (!loader) {                  return true; +            } + +            const auto file_type = loader->GetFileType(); +            if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) && +                !UISettings::values.show_unknown) { +                return true; +            }              std::vector<u8> icon;              const auto res1 = loader->ReadIcon(icon); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 808f14fb3..01a0f94ab 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,7 +8,9 @@  #include <thread>  // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/profile_select.h"  #include "applets/software_keyboard.h" +#include "configuration/configure_per_general.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h"  #include "core/hle/service/acc/profile_manager.h" @@ -207,6 +209,28 @@ GMainWindow::~GMainWindow() {          delete render_window;  } +void GMainWindow::ProfileSelectorSelectProfile() { +    QtProfileSelectionDialog dialog(this); +    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | +                          Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); +    dialog.setWindowModality(Qt::WindowModal); +    dialog.exec(); + +    if (!dialog.GetStatus()) { +        emit ProfileSelectorFinishedSelection(std::nullopt); +        return; +    } + +    Service::Account::ProfileManager manager; +    const auto uuid = manager.GetUser(dialog.GetIndex()); +    if (!uuid.has_value()) { +        emit ProfileSelectorFinishedSelection(std::nullopt); +        return; +    } + +    emit ProfileSelectorFinishedSelection(uuid); +} +  void GMainWindow::SoftwareKeyboardGetText(      const Core::Frontend::SoftwareKeyboardParameters& parameters) {      QtSoftwareKeyboardDialog dialog(this, parameters); @@ -450,6 +474,8 @@ void GMainWindow::ConnectWidgetEvents() {      connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);      connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,              &GMainWindow::OnGameListNavigateToGamedbEntry); +    connect(game_list, &GameList::OpenPerGameGeneralRequested, this, +            &GMainWindow::OnGameListOpenPerGameProperties);      connect(this, &GMainWindow::EmulationStarting, render_window,              &GRenderWindow::OnEmulationStarting); @@ -584,6 +610,7 @@ bool GMainWindow::LoadROM(const QString& filename) {      system.SetGPUDebugContext(debug_context); +    system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));      system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));      const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -1002,6 +1029,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,      QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));  } +void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { +    u64 title_id{}; +    const auto v_file = Core::GetGameFileFromPath(vfs, file); +    const auto loader = Loader::GetLoader(v_file); +    if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { +        QMessageBox::information(this, tr("Properties"), +                                 tr("The game properties could not be loaded.")); +        return; +    } + +    ConfigurePerGameGeneral dialog(this, title_id); +    dialog.loadFromFile(v_file); +    auto result = dialog.exec(); +    if (result == QDialog::Accepted) { +        dialog.applyConfiguration(); + +        const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); +        if (reload) { +            game_list->PopulateAsync(UISettings::values.gamedir, +                                     UISettings::values.gamedir_deepscan); +        } + +        config->Save(); +    } +} +  void GMainWindow::OnMenuLoadFile() {      const QString extensions =          QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 9a1df5168..4e37f6a2d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -99,10 +99,12 @@ signals:      // Signal that tells widgets to update icons to use the current theme      void UpdateThemedIcons(); +    void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);      void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);      void SoftwareKeyboardFinishedCheckDialog();  public slots: +    void ProfileSelectorSelectProfile();      void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);      void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); @@ -168,6 +170,7 @@ private slots:      void OnGameListCopyTID(u64 program_id);      void OnGameListNavigateToGamedbEntry(u64 program_id,                                           const CompatibilityList& compatibility_list); +    void OnGameListOpenPerGameProperties(const std::string& file);      void OnMenuLoadFile();      void OnMenuLoadFolder();      void OnMenuInstallToNAND(); diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index b03bc2de6..58ba240fd 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -62,6 +62,9 @@ struct Values {      // logging      bool show_console; +    // Controllers +    int profile_index; +      // Game List      bool show_unknown;      bool show_add_ons; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 097c1fbe3..fe0d1eebf 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <memory> +#include <sstream>  #include <SDL.h>  #include <inih/cpp/INIReader.h>  #include "common/file_util.h" @@ -369,6 +370,23 @@ void Config::ReadValues() {      Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);      Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); +    const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); +    std::stringstream ss(title_list); +    std::string line; +    while (std::getline(ss, line, '|')) { +        const auto title_id = std::stoul(line, nullptr, 16); +        const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + +        std::stringstream inner_ss(disabled_list); +        std::string inner_line; +        std::vector<std::string> out; +        while (std::getline(inner_ss, inner_line, '|')) { +            out.push_back(inner_line); +        } + +        Settings::values.disabled_addons.insert_or_assign(title_id, out); +    } +      // Web Service      Settings::values.enable_telemetry =          sdl2_config->GetBoolean("WebService", "enable_telemetry", true); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index d73669f36..0f3f8da50 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -221,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org  # See https://profile.yuzu-emu.org/ for more info  yuzu_username =  yuzu_token = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey  )";  } | 
