diff options
Diffstat (limited to 'src')
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 )"; } |