diff --git a/.ci/license-header.sh b/.ci/license-header.sh index 70a842e01d..6b19f91185 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -115,7 +115,7 @@ for file in $FILES; do *.cmake|*.sh|*CMakeLists.txt) begin="#" ;; - *.kt*|*.cpp|*.h) + *.kt*|*.cpp|*.h|*.qml) begin="//" ;; *) @@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then begin="#" shell=true ;; - *.kt*|*.cpp|*.h) + *) begin="//" shell="false" ;; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfb311b327..a9fa5314b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -242,7 +242,6 @@ if (YUZU_CMD) endif() if (ENABLE_QT) - add_definitions(-DYUZU_QT_WIDGETS) add_subdirectory(qt_common) add_subdirectory(yuzu) endif() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt index cbca66e13a..0bfdc674e7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt @@ -29,6 +29,8 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : } binding.title.text = model.name binding.version.text = model.version + + binding.addonSwitch.setOnCheckedChangeListener(null) binding.addonSwitch.isChecked = model.enabled binding.addonSwitch.setOnCheckedChangeListener { _, checked -> @@ -40,7 +42,8 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : } } - val canDelete = model.isRemovable + val canDelete = model.isRemovable && !model.isCheat() + binding.deleteCard.isEnabled = canDelete binding.buttonDelete.isEnabled = canDelete binding.deleteCard.alpha = if (canDelete) 1f else 0.38f diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 96b7a8cce2..8f3f16f6ee 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -72,7 +72,7 @@ class AddonsFragment : Fragment() { } addonViewModel.addonList.collect(viewLifecycleOwner) { - (binding.listAddons.adapter as AddonAdapter).submitList(it) + (binding.listAddons.adapter as AddonAdapter).submitList(it.toList()) } addonViewModel.showModInstallPicker.collect( viewLifecycleOwner, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 2331630c4e..6808a81f23 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -31,12 +31,17 @@ class AddonViewModel : ViewModel() { val addonToDelete = _addonToDelete.asStateFlow() var game: Game? = null + private set + private var loadedGameKey: String? = null private val isRefreshing = AtomicBoolean(false) private val pendingRefresh = AtomicBoolean(false) fun onAddonsViewCreated(game: Game) { + if (this.game?.programId == game.programId && _patchList.value.isNotEmpty()) { + return + } this.game = game refreshAddons(commitEmpty = false) } @@ -66,8 +71,7 @@ class AddonViewModel : ViewModel() { NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId) } ?: return@launch - val patchList = patches.toMutableList() - patchList.sortBy { it.name } + val patchList = sortPatchesWithCheatsGrouped(patches.toMutableList()) // Ensure only one update is enabled ensureSingleUpdateEnabled(patchList) @@ -146,6 +150,7 @@ class AddonViewModel : ViewModel() { PatchType.Update -> NativeLibrary.removeUpdate(patch.programId) PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) + PatchType.Cheat -> {} } refreshAddons(force = true) } @@ -179,7 +184,7 @@ class AddonViewModel : ViewModel() { it.name } } else { - it.name + it.getStorageKey() } } }.toTypedArray() @@ -201,4 +206,28 @@ class AddonViewModel : ViewModel() { private fun gameKey(game: Game): String { return "${game.programId}|${game.path}" } + + private fun sortPatchesWithCheatsGrouped(patches: MutableList): MutableList { + val individualCheats = patches.filter { it.isCheat() } + val nonCheats = patches.filter { !it.isCheat() }.sortedBy { it.name } + + val cheatsByParent = individualCheats.groupBy { it.parentName } + + val result = mutableListOf() + for (patch in nonCheats) { + result.add(patch) + cheatsByParent[patch.name]?.sortedBy { it.name }?.let { childCheats -> + result.addAll(childCheats) + } + } + + val knownParents = nonCheats.map { it.name }.toSet() + for ((parentName, orphanCheats) in cheatsByParent) { + if (parentName !in knownParents) { + result.addAll(orphanCheats.sortedBy { it.name }) + } + } + + return result + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt index a3785dd3ac..91a191b8c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt @@ -17,7 +17,8 @@ data class Patch( val programId: String, val titleId: String, val numericVersion: Long = 0, - val source: Int = 0 + val source: Int = 0, + val parentName: String = "" // For cheats: name of the mod folder containing them ) { companion object { const val SOURCE_UNKNOWN = 0 @@ -29,4 +30,22 @@ data class Patch( val isRemovable: Boolean get() = source != SOURCE_EXTERNAL && source != SOURCE_PACKED + + /** + * Returns the storage key used for saving enabled/disabled state. + * For cheats with a parent, returns "ParentName::CheatName". + */ + fun getStorageKey(): String { + return if (parentName.isNotEmpty()) { + "$parentName::$name" + } else { + name + } + } + + /** + * Returns true if this patch is an individual cheat entry (not a cheat mod). + * Individual cheats have type=Cheat and a parent mod name. + */ + fun isCheat(): Boolean = type == PatchType.Cheat.int && parentName.isNotEmpty() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt index e9a54162b0..24317d350b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,8 @@ package org.yuzu.yuzu_emu.model enum class PatchType(val int: Int) { Update(0), DLC(1), - Mod(2); + Mod(2), + Cheat(3); companion object { fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 2108e05911..d77ccdbf9a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1396,7 +1396,10 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); - auto patches = pm.GetPatches(update_raw); + // Get build ID for individual cheat enumeration + const auto build_id = pm.GetBuildID(update_raw); + + auto patches = pm.GetPatches(update_raw, build_id); jobjectArray jpatchArray = env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr); int i = 0; @@ -1407,7 +1410,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env Common::Android::ToJString(env, patch.version), static_cast(patch.type), Common::Android::ToJString(env, std::to_string(patch.program_id)), Common::Android::ToJString(env, std::to_string(patch.title_id)), - static_cast(patch.numeric_version), static_cast(patch.source)); + static_cast(patch.numeric_version), static_cast(patch.source), + Common::Android::ToJString(env, patch.parent_name)); env->SetObjectArrayElement(jpatchArray, i, jpatch); ++i; } diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 76af1e0fb2..1cc45323c8 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -516,7 +516,7 @@ namespace Common::Android { s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); s_patch_constructor = env->GetMethodID( patch_class, "", - "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V"); + "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V"); s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); diff --git a/src/common/settings.cpp b/src/common/settings.cpp index c952567e63..4197c99663 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -27,6 +27,10 @@ #include "common/settings.h" #include "common/time_zone.h" +#if defined(__linux__ ) && defined(ARCHITECTURE_arm64) +#include +#endif + namespace Settings { // Clang 14 and earlier have errors when explicitly instantiating these classes @@ -178,7 +182,11 @@ bool IsFastmemEnabled() { return bool(values.cpuopt_fastmem); else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe) return bool(values.cpuopt_unsafe_host_mmu); -#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32) +#if defined(__linux__) && defined(ARCHITECTURE_arm64) + // Only 4kb systems support host MMU right now + // TODO: Support this + return getpagesize() == 4096; +#elif !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32) && !defined(__linux__) return false; #else return true; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7566372b51..0b89551a5f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -616,6 +616,18 @@ add_library(core STATIC hle/service/caps/caps_u.h hle/service/cmif_serialization.h hle/service/cmif_types.h + hle/service/dmnt/cheat_interface.cpp + hle/service/dmnt/cheat_interface.h + hle/service/dmnt/cheat_parser.cpp + hle/service/dmnt/cheat_parser.h + hle/service/dmnt/cheat_process_manager.cpp + hle/service/dmnt/cheat_process_manager.h + hle/service/dmnt/cheat_virtual_machine.cpp + hle/service/dmnt/cheat_virtual_machine.h + hle/service/dmnt/dmnt.cpp + hle/service/dmnt/dmnt.h + hle/service/dmnt/dmnt_results.h + hle/service/dmnt/dmnt_types.h hle/service/erpt/erpt.cpp hle/service/erpt/erpt.h hle/service/es/es.cpp @@ -1153,11 +1165,6 @@ add_library(core STATIC loader/xci.h memory.cpp memory.h - memory/cheat_engine.cpp - memory/cheat_engine.h - memory/dmnt_cheat_types.h - memory/dmnt_cheat_vm.cpp - memory/dmnt_cheat_vm.h perf_stats.cpp perf_stats.h reporter.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 9db4589ceb..c1219c87f3 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -40,6 +40,7 @@ #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/process_creation.h" #include "core/hle/service/apm/apm_controller.h" +#include "core/hle/service/dmnt/cheat_process_manager.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/glue/time/static.h" @@ -54,7 +55,6 @@ #include "core/internal_network/network.h" #include "core/loader/loader.h" #include "core/memory.h" -#include "core/memory/cheat_engine.h" #include "core/perf_stats.h" #include "core/reporter.h" #include "core/tools/freezer.h" @@ -278,8 +278,17 @@ struct System::Impl { audio_core.emplace(system); service_manager = std::make_shared(kernel); + + // Create cheat_manager BEFORE services, as DMNT::LoopProcess needs it + cheat_manager = std::make_unique(system); + services.emplace(service_manager, system, stop_event.get_token()); + // Apply any pending cheats that were registered before cheat_manager was initialized + if (pending_cheats.has_pending) { + ApplyPendingCheats(system); + } + is_powered_on = true; exit_locked = false; exit_requested = false; @@ -340,11 +349,6 @@ struct System::Impl { return init_result; } - // Initialize cheat engine - if (cheat_engine) { - cheat_engine->Initialize(); - } - // Register with applet manager // All threads are started, begin main process execution, now that we're in the clear applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params); @@ -405,7 +409,6 @@ struct System::Impl { services.reset(); service_manager.reset(); fs_controller.Reset(); - cheat_engine.reset(); core_timing.ClearPendingEvents(); app_loader.reset(); audio_core.reset(); @@ -467,7 +470,6 @@ struct System::Impl { Core::SpeedLimiter speed_limiter; ExecuteProgramCallback execute_program_callback; ExitCallback exit_callback; - std::optional services; std::optional debugger; std::optional general_channel_context; @@ -476,7 +478,6 @@ struct System::Impl { std::optional host1x_core; std::optional device_memory; std::optional audio_core; - std::optional cheat_engine; std::optional memory_freezer; std::optional renderdoc_api; @@ -496,6 +497,17 @@ struct System::Impl { std::unique_ptr gpu_core; std::stop_source stop_event; + /// Cheat Manager (DMNT) + std::unique_ptr cheat_manager; + /// Pending cheats to register after cheat_manager is initialized + struct PendingCheats { + std::vector list; + std::array build_id{}; + u64 main_region_begin{}; + u64 main_region_size{}; + bool has_pending{false}; + } pending_cheats; + mutable std::mutex suspend_guard; std::mutex general_channel_mutex; std::atomic_bool is_paused{}; @@ -513,6 +525,61 @@ struct System::Impl { general_channel_event.emplace(*general_channel_context); } } + + void ApplyPendingCheats(System& system) { + if (!pending_cheats.has_pending || !cheat_manager) { + return; + } + + LOG_DEBUG(Core, "Applying {} pending cheats", pending_cheats.list.size()); + + const auto result = cheat_manager->AttachToApplicationProcess( + pending_cheats.build_id, pending_cheats.main_region_begin, + pending_cheats.main_region_size); + + if (result.IsError()) { + LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw); + pending_cheats = {}; + return; + } + + LOG_DEBUG(Core, "Cheat process attached successfully"); + + for (const auto& entry : pending_cheats.list) { + if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) { + LOG_DEBUG(Core, "Setting master cheat '{}' with {} opcodes", + entry.definition.readable_name.data(), entry.definition.num_opcodes); + const auto set_result = cheat_manager->SetMasterCheat(entry.definition); + if (set_result.IsError()) { + LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw); + } + break; + } + } + + // Add normal cheats (cheat_id != 0) + for (const auto& entry : pending_cheats.list) { + if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) { + continue; + } + + u32 assigned_id = 0; + LOG_DEBUG(Core, "Adding cheat '{}' (enabled={}, {} opcodes)", + entry.definition.readable_name.data(), entry.enabled, + entry.definition.num_opcodes); + const auto add_result = cheat_manager->AddCheat(assigned_id, entry.enabled, + entry.definition); + if (add_result.IsError()) { + LOG_WARNING(Core, + "Failed to add cheat (original_id={} enabled={} name='{}'): result={}", + entry.cheat_id, entry.enabled, + entry.definition.readable_name.data(), add_result.raw); + } + } + + // Clear pending cheats + pending_cheats = {}; + } }; System::System() : impl{std::make_unique(*this)} {} @@ -750,11 +817,61 @@ FileSys::VirtualFilesystem System::GetFilesystem() const { return impl->virtual_filesystem; } -void System::RegisterCheatList(const std::vector& list, +void System::RegisterCheatList(const std::vector& list, const std::array& build_id, u64 main_region_begin, u64 main_region_size) { - impl->cheat_engine.emplace(*this, list, build_id); - impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size); + // If cheat_manager is not yet initialized, cache the cheats for later + if (!impl->cheat_manager) { + impl->pending_cheats.list = list; + impl->pending_cheats.build_id = build_id; + impl->pending_cheats.main_region_begin = main_region_begin; + impl->pending_cheats.main_region_size = main_region_size; + impl->pending_cheats.has_pending = true; + LOG_INFO(Core, "Cached {} cheats for later registration", list.size()); + return; + } + + // Attach cheat process to the current application process + const auto result = impl->cheat_manager->AttachToApplicationProcess(build_id, main_region_begin, + main_region_size); + if (result.IsError()) { + LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw); + return; + } + + // Empty list: nothing more to do + if (list.empty()) { + return; + } + + // Set master cheat if present (cheat_id == 0) + for (const auto& entry : list) { + if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) { + const auto set_result = impl->cheat_manager->SetMasterCheat(entry.definition); + if (set_result.IsError()) { + LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw); + } + // Only one master cheat allowed + break; + } + } + + // Add normal cheats (cheat_id != 0) + for (const auto& entry : list) { + if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) { + continue; + } + + u32 assigned_id = 0; + const auto add_result = impl->cheat_manager->AddCheat(assigned_id, entry.enabled, + entry.definition); + if (add_result.IsError()) { + LOG_WARNING(Core, + "Failed to add cheat (original_id={} enabled={} name='{}'): result={}", + entry.cheat_id, entry.enabled, + entry.definition.readable_name.data(), add_result.raw); + } + } } void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) { @@ -894,6 +1011,14 @@ Tools::RenderdocAPI& System::GetRenderdocAPI() { return *impl->renderdoc_api; } +Service::DMNT::CheatProcessManager& System::GetCheatManager() { + return *impl->cheat_manager; +} + +const Service::DMNT::CheatProcessManager& System::GetCheatManager() const { + return *impl->cheat_manager; +} + void System::RunServer(std::unique_ptr&& server_manager) { return impl->kernel.RunServer(std::move(server_manager)); } diff --git a/src/core/core.h b/src/core/core.h index 012533c1fa..9b016dbccb 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -17,6 +17,7 @@ #include "common/common_types.h" #include "core/file_sys/vfs/vfs_types.h" +#include "core/hle/service/dmnt/dmnt_types.h" #include "core/hle/service/os/event.h" #include "core/hle/service/kernel_helpers.h" @@ -45,10 +46,14 @@ enum class ResultStatus : u16; } // namespace Loader namespace Core::Memory { -struct CheatEntry; class Memory; } // namespace Core::Memory +namespace Service::DMNT { + class CheatProcessManager; + struct CheatEntry; +} + namespace Service { namespace Account { @@ -335,7 +340,7 @@ public: [[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const; - void RegisterCheatList(const std::vector& list, + void RegisterCheatList(const std::vector& list, const std::array& build_id, u64 main_region_begin, u64 main_region_size); @@ -376,6 +381,9 @@ public: [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); + [[nodiscard]] Service::DMNT::CheatProcessManager& GetCheatManager(); + [[nodiscard]] const Service::DMNT::CheatProcessManager& GetCheatManager() const; + void SetExitLocked(bool locked); bool GetExitLocked() const; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 627646ee84..eadd3e3433 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -27,12 +27,13 @@ #include "core/file_sys/vfs/vfs_cached.h" #include "core/file_sys/vfs/vfs_layered.h" #include "core/file_sys/vfs/vfs_vector.h" +#include "core/hle/service/dmnt/cheat_parser.h" +#include "core/hle/service/dmnt/dmnt_types.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/set/settings_server.h" #include "core/loader/loader.h" #include "core/loader/nso.h" -#include "core/memory/cheat_engine.h" namespace FileSys { namespace { @@ -64,16 +65,15 @@ std::string FormatTitleVersion(u32 version, return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); } -// Returns a directory with name matching name case-insensitive. Returns nullptr if directory -// doesn't have a directory with name. -VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) { +// Returns a directory with name matching case-insensitively. +// Returns nullptr if directory doesn't contain a subdirectory with the given name. +VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name) { #ifdef _WIN32 return dir->GetSubdirectory(name); #else - const auto subdirs = dir->GetSubdirectories(); - for (const auto& subdir : subdirs) { - std::string dir_name = Common::ToLower(subdir->GetName()); - if (dir_name == name) { + const auto target = Common::ToLower(std::string(name)); + for (const auto& subdir : dir->GetSubdirectories()) { + if (Common::ToLower(subdir->GetName()) == target) { return subdir; } } @@ -82,36 +82,35 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) #endif } -std::optional> ReadCheatFileFromFolder( +std::optional> ReadCheatFileFromFolder( u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexToString(build_id_, upper); - const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); + const auto build_id = build_id_raw.substr(0, std::min(build_id_raw.size(), sizeof(u64) * 2)); const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); if (file == nullptr) { - LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", + LOG_DEBUG(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", title_id, build_id); return std::nullopt; } std::vector data(file->GetSize()); if (file->Read(data.data(), data.size()) != data.size()) { - LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", - title_id, build_id); + LOG_WARNING(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", + title_id, build_id); return std::nullopt; } - const Core::Memory::TextCheatParser parser; + const Service::DMNT::CheatParser parser; return parser.Parse(std::string_view(reinterpret_cast(data.data()), data.size())); } void AppendCommaIfNotEmpty(std::string& to, std::string_view with) { - if (to.empty()) { - to += with; - } else { + if (!to.empty()) { to += ", "; - to += with; } + to += with; } bool IsDirValidAndNonEmpty(const VirtualDir& dir) { @@ -468,7 +467,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) return !CollectPatches(patch_dirs, build_id).empty(); } -std::vector PatchManager::CreateCheatList(const BuildID& build_id_) const { +std::vector PatchManager::CreateCheatList(const BuildID& build_id_) const { const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); @@ -477,35 +476,69 @@ std::vector PatchManager::CreateCheatList(const BuildI const auto& disabled = Settings::values.disabled_addons[title_id]; auto patch_dirs = load_dir->GetSubdirectories(); - std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); + std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - // / / cheats / .txt - std::vector out; + // Load cheats from: //cheats/.txt + std::vector out; for (const auto& subdir : patch_dirs) { - if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) { - if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) { - if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) - std::copy(res->begin(), res->end(), std::back_inserter(out)); - if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) - std::copy(res->begin(), res->end(), std::back_inserter(out)); + const auto mod_name = subdir->GetName(); + + // Skip entirely disabled mods + if (std::find(disabled.cbegin(), disabled.cend(), mod_name) != disabled.cend()) { + continue; + } + + auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); + if (cheats_dir == nullptr) { + continue; + } + + // Try uppercase build_id first, then lowercase + std::optional> cheat_entries; + if (auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) { + cheat_entries = std::move(res); + } else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) { + cheat_entries = std::move(res_lower); + } + + if (cheat_entries) { + for (auto& entry : *cheat_entries) { + // Check if this individual cheat is disabled + const std::string cheat_name = entry.definition.readable_name.data(); + const std::string cheat_key = mod_name + "::" + cheat_name; + + if (std::find(disabled.cbegin(), disabled.cend(), cheat_key) != disabled.cend()) { + // Individual cheat is disabled - mark it as disabled but still include it + entry.enabled = false; + } + + out.push_back(entry); } } } - // Uncareless user-friendly loading of patches (must start with 'cheat_') - // / .txt - for (auto const& f : load_dir->GetFiles()) { - auto const name = f->GetName(); - if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) { - std::vector data(f->GetSize()); - if (f->Read(data.data(), data.size()) == data.size()) { - const Core::Memory::TextCheatParser parser; - auto const res = parser.Parse(std::string_view(reinterpret_cast(data.data()), data.size())); - std::copy(res.begin(), res.end(), std::back_inserter(out)); - } else { - LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id); - } + + // User-friendly cheat loading from: /cheat_*.txt + for (const auto& file : load_dir->GetFiles()) { + const auto& name = file->GetName(); + if (!name.starts_with("cheat_")) { + continue; } + if (std::find(disabled.cbegin(), disabled.cend(), name) != disabled.cend()) { + continue; + } + + std::vector data(file->GetSize()); + if (file->Read(data.data(), data.size()) != static_cast(data.size())) { + LOG_WARNING(Common_Filesystem, "Failed to read cheat file '{}' for title_id={:016X}", + name, title_id); + continue; + } + + const Service::DMNT::CheatParser parser; + auto entries = parser.Parse(std::string_view(reinterpret_cast(data.data()), data.size())); + out.insert(out.end(), entries.begin(), entries.end()); } + return out; } @@ -712,7 +745,53 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs return romfs; } -std::vector PatchManager::GetPatches(VirtualFile update_raw) const { +PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const { + BuildID build_id{}; + + // Get the base NCA + const auto base_nca = content_provider.GetEntry(title_id, ContentRecordType::Program); + if (base_nca == nullptr) { + return build_id; + } + + // Try to get ExeFS from update first, then base + VirtualDir exefs; + const auto update_tid = GetUpdateTitleID(title_id); + const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); + + if (update != nullptr && update->GetExeFS() != nullptr) { + exefs = update->GetExeFS(); + } else if (update_raw != nullptr) { + const auto new_nca = std::make_shared(update_raw, base_nca.get()); + if (new_nca->GetStatus() == Loader::ResultStatus::Success && + new_nca->GetExeFS() != nullptr) { + exefs = new_nca->GetExeFS(); + } + } + + if (exefs == nullptr) { + exefs = base_nca->GetExeFS(); + } + + if (exefs == nullptr) { + return build_id; + } + + // Try to read the main NSO header + const auto main_file = exefs->GetFile("main"); + if (main_file == nullptr || main_file->GetSize() < sizeof(Loader::NSOHeader)) { + return build_id; + } + + Loader::NSOHeader header{}; + if (main_file->Read(reinterpret_cast(&header), sizeof(header)) == sizeof(header)) { + build_id = header.build_id; + } + + return build_id; +} + +std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const { if (title_id == 0) { return {}; } @@ -749,7 +828,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { .program_id = title_id, .title_id = update_tid, .source = PatchSource::External, - .numeric_version = update_entry.version}; + .numeric_version = update_entry.version, + .parent_name = ""}; external_update_patches.push_back(update_patch); } @@ -895,26 +975,15 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } } + // Check if we have a valid build_id for cheat enumeration + const bool has_build_id = std::any_of(build_id.begin(), build_id.end(), [](u8 b) { return b != 0; }); + // General Mods (LayeredFS and IPS) const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); if (mod_dir != nullptr) { - for (auto const& f : mod_dir->GetFiles()) - if (auto const name = f->GetName(); name.starts_with("cheat_")) { - auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end(); - out.push_back({ - .enabled = !mod_disabled, - .name = name, - .version = "Cheats", - .type = PatchType::Mod, - .program_id = title_id, - .title_id = title_id, - .source = PatchSource::Unknown, - .location = f->GetFullPath(), - }); - } - for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; + bool has_cheats = false; const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs"); if (IsDirValidAndNonEmpty(exefs_dir)) { @@ -943,8 +1012,12 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) || IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); - if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats"))) + + const auto cheats_dir = FindSubdirectoryCaseless(mod, "cheats"); + if (IsDirValidAndNonEmpty(cheats_dir)) { + has_cheats = true; AppendCommaIfNotEmpty(types, "Cheats"); + } if (types.empty()) continue; @@ -957,7 +1030,47 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { .program_id = title_id, .title_id = title_id, .source = PatchSource::Unknown, - .location = mod->GetFullPath()}); + .location = mod->GetFullPath(), + .parent_name = ""}); + + // Add individual cheats as sub-entries + if (has_cheats) { + // Try to read cheat file with build_id first, fallback to all files + std::vector cheat_entries; + + if (has_build_id) { + if (auto res = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, true)) { + cheat_entries = std::move(*res); + } else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, false)) { + cheat_entries = std::move(*res_lower); + } + } + + for (const auto& cheat : cheat_entries) { + // Skip master cheat (id 0) with no readable name + if (cheat.cheat_id == 0 && cheat.definition.readable_name[0] == '\0') { + continue; + } + + const std::string cheat_name = cheat.definition.readable_name.data(); + if (cheat_name.empty()) { + continue; + } + + // Create unique key for this cheat: "ModName::CheatName" + const std::string cheat_key = mod->GetName() + "::" + cheat_name; + const auto cheat_disabled = + std::find(disabled.begin(), disabled.end(), cheat_key) != disabled.end(); + + out.push_back({.enabled = !cheat_disabled, + .name = cheat_name, + .version = types, + .type = PatchType::Cheat, + .program_id = title_id, + .title_id = title_id, + .parent_name = mod->GetName()}); + } + } } } @@ -982,7 +1095,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { .type = PatchType::Mod, .program_id = title_id, .title_id = title_id, - .source = PatchSource::Unknown}); + .source = PatchSource::Unknown, + .parent_name = ""}); } } @@ -1069,7 +1183,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { .type = PatchType::DLC, .program_id = title_id, .title_id = dlc_match.back().title_id, - .source = dlc_source}); + .source = dlc_source, + .parent_name = ""}); } return out; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 755d924fd4..5cb06b9052 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -13,7 +13,6 @@ #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs/vfs_types.h" -#include "core/memory/dmnt_cheat_types.h" namespace Core { class System; @@ -23,13 +22,17 @@ namespace Service::FileSystem { class FileSystemController; } +namespace Service::DMNT { +struct CheatEntry; +} + namespace FileSys { class ContentProvider; class NCA; class NACP; -enum class PatchType { Update, DLC, Mod }; +enum class PatchType { Update, DLC, Mod, Cheat }; enum class PatchSource { Unknown, @@ -49,6 +52,7 @@ struct Patch { PatchSource source; std::string location; u32 numeric_version{0}; + std::string parent_name; }; // A centralized class to manage patches to games. @@ -79,7 +83,7 @@ public: [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const; // Creates a CheatList object with all - [[nodiscard]] std::vector CreateCheatList( + [[nodiscard]] std::vector CreateCheatList( const BuildID& build_id) const; // Currently tracked RomFS patches: @@ -90,8 +94,11 @@ public: VirtualFile packed_update_raw = nullptr, bool apply_layeredfs = true) const; - // Returns a vector of patches - [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr) const; + // Returns a vector of patches including individual cheats + [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr, + const BuildID& build_id = {}) const; + + [[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const; // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be diff --git a/src/core/hle/service/dmnt/cheat_interface.cpp b/src/core/hle/service/dmnt/cheat_interface.cpp new file mode 100644 index 0000000000..4a938328b6 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_interface.cpp @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/dmnt/cheat_interface.h" +#include "core/hle/service/dmnt/cheat_process_manager.h" +#include "core/hle/service/dmnt/dmnt_results.h" +#include "core/hle/service/dmnt/dmnt_types.h" + +namespace Service::DMNT { + ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager) + : ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} { + // clang-format off + static const FunctionInfo functions[] = { + {65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"}, + {65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"}, + {65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"}, + {65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"}, + {65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"}, + {65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"}, + {65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"}, + {65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"}, + {65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"}, + {65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"}, + {65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"}, + {65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"}, + {65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"}, + {65201, C<&ICheatInterface::GetCheats>, "GetCheats"}, + {65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"}, + {65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"}, + {65204, C<&ICheatInterface::AddCheat>, "AddCheat"}, + {65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"}, + {65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"}, + {65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"}, + {65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"}, + {65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"}, + {65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"}, + {65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"}, + {65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"}, + {65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"}, + {65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + + ICheatInterface::~ICheatInterface() = default; + + Result ICheatInterface::HasCheatProcess(Out out_has_cheat) { + LOG_INFO(CheatEngine, "called"); + *out_has_cheat = cheat_process_manager.HasCheatProcess(); + R_SUCCEED(); + } + + Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle out_event) { + LOG_INFO(CheatEngine, "called"); + *out_event = &cheat_process_manager.GetCheatProcessEvent(); + R_SUCCEED(); + } + + Result ICheatInterface::GetCheatProcessMetadata(Out out_metadata) { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata)); + } + + Result ICheatInterface::ForceOpenCheatProcess() { + LOG_INFO(CheatEngine, "called"); + R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached); + R_SUCCEED(); + } + + Result ICheatInterface::PauseCheatProcess() { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.PauseCheatProcess()); + } + + Result ICheatInterface::ResumeCheatProcess() { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.ResumeCheatProcess()); + } + + Result ICheatInterface::ForceCloseCheatProcess() { + LOG_WARNING(CheatEngine, "(STUBBED) called"); + R_RETURN(cheat_process_manager.ForceCloseCheatProcess()); + } + + Result ICheatInterface::GetCheatProcessMappingCount(Out out_count) { + LOG_WARNING(CheatEngine, "(STUBBED) called"); + R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count)); + } + + Result ICheatInterface::GetCheatProcessMappings( + Out out_count, u64 offset, + OutArray out_mappings) { + LOG_INFO(CheatEngine, "called, offset={}", offset); + R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer); + R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings)); + } + + Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size, + OutBuffer out_buffer) { + LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size); + R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer); + R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer)); + } + + Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size, + InBuffer buffer) { + LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size); + R_UNLESS(!buffer.empty(), ResultCheatNullBuffer); + R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer)); + } + + Result ICheatInterface::QueryCheatProcessMemory(Out out_mapping, + u64 address) { + LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address); + R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address)); + } + + Result ICheatInterface::GetCheatCount(Out out_count) { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.GetCheatCount(*out_count)); + } + + Result ICheatInterface::GetCheats(Out out_count, u64 offset, + OutArray out_cheats) { + LOG_INFO(CheatEngine, "called, offset={}", offset); + R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer); + R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats)); + } + + Result ICheatInterface::GetCheatById(OutLargeData out_cheat, + u32 cheat_id) { + LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); + R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id)); + } + + Result ICheatInterface::ToggleCheat(u32 cheat_id) { + LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); + R_RETURN(cheat_process_manager.ToggleCheat(cheat_id)); + } + + Result ICheatInterface::AddCheat( + Out out_cheat_id, bool is_enabled, + InLargeData cheat_definition) { + LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled); + R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition)); + } + + Result ICheatInterface::RemoveCheat(u32 cheat_id) { + LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); + R_RETURN(cheat_process_manager.RemoveCheat(cheat_id)); + } + + Result ICheatInterface::ReadStaticRegister(Out out_value, u8 register_index) { + LOG_DEBUG(CheatEngine, "called, register_index={}", register_index); + R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index)); + } + + Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) { + LOG_DEBUG(CheatEngine, "called, register_index={}, value={}", register_index, value); + R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value)); + } + + Result ICheatInterface::ResetStaticRegisters() { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.ResetStaticRegisters()); + } + + Result ICheatInterface::SetMasterCheat( + InLargeData cheat_definition) { + LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(), + cheat_definition->num_opcodes); + R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition)); + } + + Result ICheatInterface::GetFrozenAddressCount(Out out_count) { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count)); + } + + Result ICheatInterface::GetFrozenAddresses( + Out out_count, u64 offset, + OutArray out_frozen_address) { + LOG_INFO(CheatEngine, "called, offset={}", offset); + R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer); + R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address)); + } + + Result ICheatInterface::GetFrozenAddress(Out out_frozen_address_entry, + u64 address) { + LOG_INFO(CheatEngine, "called, address={}", address); + R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address)); + } + + Result ICheatInterface::EnableFrozenAddress(Out out_value, u64 address, u64 width) { + LOG_INFO(CheatEngine, "called, address={}, width={}", address, width); + R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth); + R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth); + R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth); + R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width)); + } + + Result ICheatInterface::DisableFrozenAddress(u64 address) { + LOG_INFO(CheatEngine, "called, address={}", address); + R_RETURN(cheat_process_manager.DisableFrozenAddress(address)); + } + + void ICheatInterface::InitializeCheatManager() { + LOG_INFO(CheatEngine, "called"); + } + + Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span out_data, + size_t size) { + LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size); + R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size)); + } + + Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span data, + size_t size) { + LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size); + R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size)); + } + + Result ICheatInterface::PauseCheatProcessUnsafe() { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe()); + } + + Result ICheatInterface::ResumeCheatProcessUnsafe() { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe()); + } +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_interface.h b/src/core/hle/service/dmnt/cheat_interface.h new file mode 100644 index 0000000000..7d77e6fed5 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_interface.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Core { + class System; +} + +namespace Kernel { + class KEvent; + class KReadableEvent; +} // namespace Kernel + +namespace Kernel::Svc { + struct MemoryInfo; +} + +namespace Service::DMNT { + struct CheatDefinition; + struct CheatEntry; + struct CheatProcessMetadata; + struct FrozenAddressEntry; + class CheatProcessManager; + + class ICheatInterface final : public ServiceFramework { + public: + explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager); + ~ICheatInterface() override; + + private: + Result HasCheatProcess(Out out_has_cheat); + Result GetCheatProcessEvent(OutCopyHandle out_event); + Result GetCheatProcessMetadata(Out out_metadata); + Result ForceOpenCheatProcess(); + Result PauseCheatProcess(); + Result ResumeCheatProcess(); + Result ForceCloseCheatProcess(); + + Result GetCheatProcessMappingCount(Out out_count); + Result GetCheatProcessMappings( + Out out_count, u64 offset, + OutArray out_mappings); + Result ReadCheatProcessMemory(u64 address, u64 size, + OutBuffer out_buffer); + Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer buffer); + + Result QueryCheatProcessMemory(Out out_mapping, u64 address); + Result GetCheatCount(Out out_count); + Result GetCheats(Out out_count, u64 offset, + OutArray out_cheats); + Result GetCheatById(OutLargeData out_cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + + Result AddCheat(Out out_cheat_id, bool enabled, + InLargeData cheat_definition); + Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(Out out_value, u8 register_index); + Result WriteStaticRegister(u8 register_index, u64 value); + Result ResetStaticRegisters(); + Result SetMasterCheat(InLargeData cheat_definition); + Result GetFrozenAddressCount(Out out_count); + Result GetFrozenAddresses( + Out out_count, u64 offset, + OutArray out_frozen_address); + Result GetFrozenAddress(Out out_frozen_address_entry, u64 address); + Result EnableFrozenAddress(Out out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + + private: + void InitializeCheatManager(); + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span out_data, size_t size); + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span data, size_t size); + + Result PauseCheatProcessUnsafe(); + Result ResumeCheatProcessUnsafe(); + + CheatProcessManager& cheat_process_manager; + }; +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_parser.cpp b/src/core/hle/service/dmnt/cheat_parser.cpp new file mode 100644 index 0000000000..b0ebbfb83e --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_parser.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "core/hle/service/dmnt/cheat_parser.h" + +#include "core/hle/service/dmnt/dmnt_types.h" + +namespace Service::DMNT { + CheatParser::CheatParser() {} + + CheatParser::~CheatParser() = default; + + std::vector CheatParser::Parse(std::string_view data) const { + std::vector out(1); + std::optional current_entry; + + for (std::size_t i = 0; i < data.size(); ++i) { + if (std::isspace(data[i])) { + continue; + } + + if (data[i] == '{') { + current_entry = 0; + + if (out[*current_entry].definition.num_opcodes > 0) { + return {}; + } + + std::size_t name_size{}; + const auto name = ExtractName(name_size, data, i + 1, '}'); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name_size + 1; + } else if (data[i] == '[') { + current_entry = out.size(); + out.emplace_back(); + + std::size_t name_size{}; + const auto name = ExtractName(name_size, data, i + 1, ']'); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name_size + 1; + } else if (std::isxdigit(data[i])) { + if (!current_entry || out[*current_entry].definition.num_opcodes >= + out[*current_entry].definition.opcodes.size()) { + return {}; + } + + const auto hex = std::string(data.substr(i, 8)); + if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { + return {}; + } + + const auto value = static_cast(std::strtoul(hex.c_str(), nullptr, 0x10)); + out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = + value; + + i += 7; // 7 because the for loop will increment by 1 more + } else { + return {}; + } + } + + out[0].enabled = out[0].definition.num_opcodes > 0; + out[0].cheat_id = 0; + + for (u32 i = 1; i < out.size(); ++i) { + out[i].enabled = out[i].definition.num_opcodes > 0; + out[i].cheat_id = i; + } + + return out; + } + + std::string_view CheatParser::ExtractName(std::size_t& out_name_size, std::string_view data, + std::size_t start_index, char match) const { + auto end_index = start_index; + while (data[end_index] != match) { + ++end_index; + if (end_index > data.size()) { + return {}; + } + } + + out_name_size = end_index - start_index; + + // Clamp name if it's too big + if (out_name_size > sizeof(CheatDefinition::readable_name)) { + end_index = start_index + sizeof(CheatDefinition::readable_name); + } + + return data.substr(start_index, end_index - start_index); + } +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_parser.h b/src/core/hle/service/dmnt/cheat_parser.h new file mode 100644 index 0000000000..2c57c91749 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_parser.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace Service::DMNT { + struct CheatEntry; + + class CheatParser final { + public: + CheatParser(); + ~CheatParser(); + + std::vector Parse(std::string_view data) const; + + private: + std::string_view ExtractName(std::size_t& out_name_size, std::string_view data, + std::size_t start_index, char match) const; + }; +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_process_manager.cpp b/src/core/hle/service/dmnt/cheat_process_manager.cpp new file mode 100644 index 0000000000..7ffc95d28c --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_process_manager.cpp @@ -0,0 +1,599 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/arm/debug.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/dmnt/cheat_process_manager.h" +#include "core/hle/service/dmnt/cheat_virtual_machine.h" +#include "core/hle/service/dmnt/dmnt_results.h" +#include "core/hle/service/hid/hid_server.h" +#include "core/hle/service/sm/sm.h" +#include "hid_core/resource_manager.h" +#include "hid_core/resources/npad/npad.h" + +namespace Service::DMNT { + constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; + + CheatProcessManager::CheatProcessManager(Core::System& system_) + : system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} { + update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback", + [this](s64 time, std::chrono::nanoseconds ns_late) + -> std::optional { + FrameCallback(ns_late); + return std::nullopt; + }); + + for (size_t i = 0; i < MaxCheatCount; i++) { + ResetCheatEntry(i); + } + + cheat_vm = std::make_unique(*this); + + cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent"); + unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent"); + } + + CheatProcessManager::~CheatProcessManager() { + service_context.CloseEvent(cheat_process_event); + service_context.CloseEvent(unsafe_break_event); + core_timing.UnscheduleEvent(update_event); + } + + void CheatProcessManager::SetVirtualMachine(std::unique_ptr vm) { + if (vm) { + cheat_vm = std::move(vm); + SetNeedsReloadVm(true); + } + } + + bool CheatProcessManager::HasActiveCheatProcess() { + // Note: This function *MUST* be called only with the cheat lock held. + bool has_cheat_process = + cheat_process_debug_handle != InvalidHandle && + system.ApplicationProcess()->GetProcessId() == cheat_process_metadata.process_id; + + if (!has_cheat_process) { + CloseActiveCheatProcess(); + } + + return has_cheat_process; + } + + void CheatProcessManager::CloseActiveCheatProcess() { + if (cheat_process_debug_handle != InvalidHandle) { + broken_unsafe = false; + unsafe_break_event->Signal(); + core_timing.UnscheduleEvent(update_event); + + // Close resources. + cheat_process_debug_handle = InvalidHandle; + + // Save cheat toggles. + if (always_save_cheat_toggles || should_save_cheat_toggles) { + // TODO: save cheat toggles + should_save_cheat_toggles = false; + } + + cheat_process_metadata = {}; + + ResetAllCheatEntries(); + + { + auto it = frozen_addresses_map.begin(); + while (it != frozen_addresses_map.end()) { + it = frozen_addresses_map.erase(it); + } + } + + cheat_process_event->Signal(); + } + } + + Result CheatProcessManager::EnsureCheatProcess() { + R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached); + R_SUCCEED(); + } + + void CheatProcessManager::SetNeedsReloadVm(bool reload) { + needs_reload_vm = reload; + } + + void CheatProcessManager::ResetCheatEntry(size_t i) { + if (i < MaxCheatCount) { + cheat_entries[i] = {}; + cheat_entries[i].cheat_id = static_cast(i); + + SetNeedsReloadVm(true); + } + } + + void CheatProcessManager::ResetAllCheatEntries() { + for (size_t i = 0; i < MaxCheatCount; i++) { + ResetCheatEntry(i); + } + + cheat_vm->ResetStaticRegisters(); + } + + CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) { + if (i < MaxCheatCount) { + return cheat_entries.data() + i; + } + + return nullptr; + } + + CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) { + for (size_t i = 1; i < MaxCheatCount; i++) { + if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name, + sizeof(cheat_entries[i].definition.readable_name)) == 0) { + return cheat_entries.data() + i; + } + } + return nullptr; + } + + CheatEntry* CheatProcessManager::GetFreeCheatEntry() { + // Check all non-master cheats for availability. + for (size_t i = 1; i < MaxCheatCount; i++) { + if (cheat_entries[i].definition.num_opcodes == 0) { + return cheat_entries.data() + i; + } + } + + return nullptr; + } + + bool CheatProcessManager::HasCheatProcess() { + std::scoped_lock lk(cheat_lock); + return HasActiveCheatProcess(); + } + + Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const { + return cheat_process_event->GetReadableEvent(); + } + + Result CheatProcessManager::AttachToApplicationProcess(const std::array& build_id, + VAddr main_region_begin, + u64 main_region_size) { + std::scoped_lock lk(cheat_lock); + + { + if (this->HasActiveCheatProcess()) { + this->CloseActiveCheatProcess(); + } + } + + cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId(); + + { + const auto& page_table = system.ApplicationProcess()->GetPageTable(); + cheat_process_metadata.program_id = system.GetApplicationProcessProgramID(); + cheat_process_metadata.heap_extents = { + .base = GetInteger(page_table.GetHeapRegionStart()), + .size = page_table.GetHeapRegionSize(), + }; + cheat_process_metadata.aslr_extents = { + .base = GetInteger(page_table.GetAliasCodeRegionStart()), + .size = page_table.GetAliasCodeRegionSize(), + }; + cheat_process_metadata.alias_extents = { + .base = GetInteger(page_table.GetAliasRegionStart()), + .size = page_table.GetAliasRegionSize(), + }; + } + + { + cheat_process_metadata.main_nso_extents = { + .base = main_region_begin, + .size = main_region_size, + }; + cheat_process_metadata.main_nso_build_id = build_id; + } + + cheat_process_debug_handle = cheat_process_metadata.process_id; + + broken_unsafe = false; + unsafe_break_event->Signal(); + + core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event); + LOG_INFO(CheatEngine, "Cheat engine started"); + + // Signal to our fans. + cheat_process_event->Signal(); + + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + out_metadata = cheat_process_metadata; + R_SUCCEED(); + } + + Result CheatProcessManager::ForceOpenCheatProcess() { + // R_RETURN(AttachToApplicationProcess(false)); + R_SUCCEED(); + } + + Result CheatProcessManager::PauseCheatProcess() { + std::scoped_lock lk(cheat_lock); + + R_TRY(EnsureCheatProcess()); + R_RETURN(PauseCheatProcessUnsafe()); + } + + Result CheatProcessManager::PauseCheatProcessUnsafe() { + broken_unsafe = true; + unsafe_break_event->Clear(); + if (system.ApplicationProcess()->IsSuspended()) { + R_SUCCEED(); + } + R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused)); + } + + Result CheatProcessManager::ResumeCheatProcess() { + std::scoped_lock lk(cheat_lock); + + R_TRY(EnsureCheatProcess()); + R_RETURN(ResumeCheatProcessUnsafe()); + } + + Result CheatProcessManager::ResumeCheatProcessUnsafe() { + broken_unsafe = true; + unsafe_break_event->Clear(); + if (!system.ApplicationProcess()->IsSuspended()) { + R_SUCCEED(); + } + system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable); + R_SUCCEED(); + } + + Result CheatProcessManager::ForceCloseCheatProcess() { + CloseActiveCheatProcess(); + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) { + std::scoped_lock lk(cheat_lock); + R_TRY(this->EnsureCheatProcess()); + + // TODO: Call svc::QueryDebugProcessMemory + + out_count = 0; + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheatProcessMappings( + u64& out_count, u64 offset, std::span out_mappings) { + std::scoped_lock lk(cheat_lock); + R_TRY(this->EnsureCheatProcess()); + + // TODO: Call svc::QueryDebugProcessMemory + + out_count = 0; + R_SUCCEED(); + } + + Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size, + std::span out_data) { + std::scoped_lock lk(cheat_lock); + + R_TRY(EnsureCheatProcess()); + R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size)); + } + + Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, + size_t size) { + if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) { + std::memset(out_data, 0, size); + R_SUCCEED(); + } + + system.ApplicationMemory().ReadBlock(process_address, out_data, size); + R_SUCCEED(); + } + + Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size, + std::span data) { + std::scoped_lock lk(cheat_lock); + + R_TRY(EnsureCheatProcess()); + R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size)); + } + + Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, + size_t size) { + if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) { + R_SUCCEED(); + } + + if (system.ApplicationMemory().WriteBlock(process_address, data, size)) { + Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size); + } + + R_SUCCEED(); + } + + Result CheatProcessManager::QueryCheatProcessMemory(Out mapping, + u64 address) { + std::scoped_lock lk(cheat_lock); + R_TRY(this->EnsureCheatProcess()); + + // TODO: Call svc::QueryDebugProcessMemory + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheatCount(u64& out_count) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(), + [](const auto& entry) { return entry.definition.num_opcodes != 0; }); + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheats(u64& out_count, u64 offset, + std::span out_cheats) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + size_t count = 0, total_count = 0; + for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) { + if (cheat_entries[i].definition.num_opcodes) { + total_count++; + if (total_count > offset) { + out_cheats[count++] = cheat_entries[i]; + } + } + } + + out_count = count; + R_SUCCEED(); + } + + Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + const CheatEntry* entry = GetCheatEntryById(cheat_id); + R_UNLESS(entry != nullptr, ResultCheatUnknownId); + R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId); + + *out_cheat = *entry; + R_SUCCEED(); + } + + Result CheatProcessManager::ToggleCheat(u32 cheat_id) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + CheatEntry* entry = GetCheatEntryById(cheat_id); + R_UNLESS(entry != nullptr, ResultCheatUnknownId); + R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId); + + R_UNLESS(cheat_id != 0, ResultCheatCannotDisable); + + entry->enabled = !entry->enabled; + + SetNeedsReloadVm(true); + + R_SUCCEED(); + } + + Result CheatProcessManager::AddCheat(u32& out_cheat_id, bool enabled, + const CheatDefinition& cheat_definition) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid); + R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid); + + CheatEntry* new_entry = GetFreeCheatEntry(); + R_UNLESS(new_entry != nullptr, ResultCheatOutOfResource); + + new_entry->enabled = enabled; + new_entry->definition = cheat_definition; + + SetNeedsReloadVm(true); + + out_cheat_id = new_entry->cheat_id; + + R_SUCCEED(); + } + + Result CheatProcessManager::RemoveCheat(u32 cheat_id) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + R_UNLESS(cheat_id < MaxCheatCount, ResultCheatUnknownId); + + ResetCheatEntry(cheat_id); + SetNeedsReloadVm(true); + R_SUCCEED(); + } + + Result CheatProcessManager::ReadStaticRegister(u64& out_value, u64 register_index) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid); + + out_value = cheat_vm->GetStaticRegister(register_index); + R_SUCCEED(); + } + + Result CheatProcessManager::WriteStaticRegister(u64 register_index, u64 value) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid); + + cheat_vm->SetStaticRegister(register_index, value); + R_SUCCEED(); + } + + Result CheatProcessManager::ResetStaticRegisters() { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + cheat_vm->ResetStaticRegisters(); + R_SUCCEED(); + } + + Result CheatProcessManager::SetMasterCheat(const CheatDefinition& cheat_definition) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid); + R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid); + + cheat_entries[0] = { + .enabled = true, + .definition = cheat_definition, + }; + + SetNeedsReloadVm(true); + + R_SUCCEED(); + } + + Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end()); + R_SUCCEED(); + } + + Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset, + std::span out_frozen_address) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + u64 total_count = 0, written_count = 0; + for (const auto& [address, value] : frozen_addresses_map) { + if (written_count >= out_frozen_address.size()) { + break; + } + + if (offset <= total_count) { + out_frozen_address[written_count].address = address; + out_frozen_address[written_count].value = value; + written_count++; + } + total_count++; + } + + out_count = written_count; + R_SUCCEED(); + } + + Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, + u64 address) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + const auto it = frozen_addresses_map.find(address); + R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound); + + out_frozen_address_entry = { + .address = it->first, + .value = it->second, + }; + R_SUCCEED(); + } + + Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + const auto it = frozen_addresses_map.find(address); + R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists); + + FrozenAddressValue value{}; + value.width = static_cast(width); + R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width)); + + frozen_addresses_map.insert({address, value}); + out_value = value.value; + R_SUCCEED(); + } + + Result CheatProcessManager::DisableFrozenAddress(u64 address) { + std::scoped_lock lk(cheat_lock); + R_TRY(EnsureCheatProcess()); + + const auto it = frozen_addresses_map.find(address); + R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound); + + frozen_addresses_map.erase(it); + + R_SUCCEED(); + } + + u64 CheatProcessManager::HidKeysDown() const { + const auto hid = system.ServiceManager().GetService("hid"); + if (hid == nullptr) { + LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!"); + return 0; + } + + const auto applet_resource = hid->GetResourceManager(); + if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) { + LOG_WARNING(CheatEngine, + "Attempted to read input state, but applet resource is not initialized!"); + return 0; + } + + const auto press_state = applet_resource->GetNpad()->GetAndResetPressState(); + return static_cast(press_state & Core::HID::NpadButton::All); + } + + void CheatProcessManager::DebugLog(u8 id, u64 value) const { + LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); + } + + void CheatProcessManager::CommandLog(std::string_view data) const { + LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", + data.back() == '\n' ? data.substr(0, data.size() - 1) : data); + } + + void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) { + std::scoped_lock lk(cheat_lock); + + if (cheat_vm == nullptr) { + LOG_DEBUG(CheatEngine, "FrameCallback: VM is null"); + return; + } + + if (needs_reload_vm) { + LOG_INFO(CheatEngine, "Reloading cheat VM with {} entries", cheat_entries.size()); + + size_t enabled_count = 0; + for (const auto& entry : cheat_entries) { + if (entry.enabled && entry.definition.num_opcodes > 0) { + enabled_count++; + LOG_INFO(CheatEngine, " Cheat '{}': {} opcodes, enabled={}", + entry.definition.readable_name.data(), + entry.definition.num_opcodes, entry.enabled); + } + } + LOG_INFO(CheatEngine, "Total enabled cheats: {}", enabled_count); + + cheat_vm->LoadProgram(cheat_entries); + LOG_INFO(CheatEngine, "Cheat VM loaded, program size: {}", cheat_vm->GetProgramSize()); + needs_reload_vm = false; + } + + if (cheat_vm->GetProgramSize() == 0) { + return; + } + + cheat_vm->Execute(cheat_process_metadata); + } +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_process_manager.h b/src/core/hle/service/dmnt/cheat_process_manager.h new file mode 100644 index 0000000000..f4ecfdd9d3 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_process_manager.h @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/dmnt/dmnt_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +#include "common/intrusive_red_black_tree.h" + +namespace Core { + class System; +} + +namespace Kernel { + class KEvent; + class KReadableEvent; +} // namespace Kernel + +namespace Kernel::Svc { + struct MemoryInfo; +} + +namespace Service::DMNT { + class CheatVirtualMachine; + + class CheatProcessManager final { + public: + static constexpr size_t MaxCheatCount = 0x80; + static constexpr size_t MaxFrozenAddressCount = 0x80; + + CheatProcessManager(Core::System& system_); + ~CheatProcessManager(); + + void SetVirtualMachine(std::unique_ptr vm); + + bool HasCheatProcess(); + Kernel::KReadableEvent& GetCheatProcessEvent() const; + Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata); + Result AttachToApplicationProcess(const std::array& build_id, VAddr main_region_begin, + u64 main_region_size); + Result ForceOpenCheatProcess(); + Result PauseCheatProcess(); + Result PauseCheatProcessUnsafe(); + Result ResumeCheatProcess(); + Result ResumeCheatProcessUnsafe(); + Result ForceCloseCheatProcess(); + + Result GetCheatProcessMappingCount(u64& out_count); + Result GetCheatProcessMappings(u64& out_count, u64 offset, + std::span out_mappings); + Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span out_data); + Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size); + Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span data); + Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size); + + Result QueryCheatProcessMemory(Out mapping, u64 address); + Result GetCheatCount(u64& out_count); + Result GetCheats(u64& out_count, u64 offset, std::span out_cheats); + Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + + Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition); + Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(u64& out_value, u64 register_index); + Result WriteStaticRegister(u64 register_index, u64 value); + Result ResetStaticRegisters(); + Result SetMasterCheat(const CheatDefinition& cheat_definition); + Result GetFrozenAddressCount(u64& out_count); + Result GetFrozenAddresses(u64& out_count, u64 offset, + std::span out_frozen_address); + Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address); + Result EnableFrozenAddress(u64& out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + + u64 HidKeysDown() const; + void DebugLog(u8 id, u64 value) const; + void CommandLog(std::string_view data) const; + + private: + bool HasActiveCheatProcess(); + void CloseActiveCheatProcess(); + Result EnsureCheatProcess(); + void SetNeedsReloadVm(bool reload); + void ResetCheatEntry(size_t i); + void ResetAllCheatEntries(); + CheatEntry* GetCheatEntryById(size_t i); + CheatEntry* GetCheatEntryByReadableName(const char* readable_name); + CheatEntry* GetFreeCheatEntry(); + + void FrameCallback(std::chrono::nanoseconds ns_late); + + static constexpr u64 InvalidHandle = 0; + + mutable std::mutex cheat_lock; + Kernel::KEvent* unsafe_break_event; + + Kernel::KEvent* cheat_process_event; + u64 cheat_process_debug_handle = InvalidHandle; + CheatProcessMetadata cheat_process_metadata = {}; + + bool broken_unsafe = false; + bool needs_reload_vm = false; + std::unique_ptr cheat_vm; + + bool enable_cheats_by_default = true; + bool always_save_cheat_toggles = false; + bool should_save_cheat_toggles = false; + std::array cheat_entries = {}; + // TODO: Replace with IntrusiveRedBlackTree + std::map frozen_addresses_map = {}; + + Core::System& system; + KernelHelpers::ServiceContext service_context; + std::shared_ptr update_event; + Core::Timing::CoreTiming& core_timing; + }; +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_virtual_machine.cpp b/src/core/hle/service/dmnt/cheat_virtual_machine.cpp new file mode 100644 index 0000000000..9363b4b930 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_virtual_machine.cpp @@ -0,0 +1,1269 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "core/hle/service/dmnt/cheat_process_manager.h" +#include "core/hle/service/dmnt/cheat_virtual_machine.h" + +namespace Service::DMNT { + CheatVirtualMachine::CheatVirtualMachine(CheatProcessManager& cheat_manager) + : manager(cheat_manager) {} + + CheatVirtualMachine::~CheatVirtualMachine() = default; + + void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) const { + manager.DebugLog(static_cast(log_id), value); + } + + void CheatVirtualMachine::LogOpcode(const CheatVmOpcode& opcode) const { + if (auto store_static = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Store Static"); + manager.CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(store_static->mem_type))); + manager.CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); + manager.CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); + manager.CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); + } else if (auto begin_cond = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Begin Conditional"); + manager.CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); + manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast(begin_cond->mem_type))); + manager.CommandLog(fmt::format("Cond Type: {:X}", static_cast(begin_cond->cond_type))); + manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); + manager.CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); + } else if (std::holds_alternative(opcode.opcode)) { + manager.CommandLog("Opcode: End Conditional"); + } else if (auto ctrl_loop = std::get_if(&opcode.opcode)) { + if (ctrl_loop->start_loop) { + manager.CommandLog("Opcode: Start Loop"); + manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + manager.CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); + } else { + manager.CommandLog("Opcode: End Loop"); + manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + } + } else if (auto ldr_static = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Load Register Static"); + manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); + manager.CommandLog(fmt::format("Value: {:X}", ldr_static->value)); + } else if (auto ldr_memory = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Load Register Memory"); + manager.CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); + manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); + manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast(ldr_memory->mem_type))); + manager.CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); + manager.CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); + } else if (auto str_static = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Store Static to Address"); + manager.CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); + manager.CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); + if (str_static->add_offset_reg) { + manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index)); + } + manager.CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); + manager.CommandLog(fmt::format("Value: {:X}", str_static->value)); + } else if (auto perform_math_static = + std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Perform Static Arithmetic"); + manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); + manager.CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); + manager.CommandLog( + fmt::format("Math Type: {:X}", static_cast(perform_math_static->math_type))); + manager.CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); + } else if (auto begin_keypress_cond = + std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Begin Keypress Conditional"); + manager.CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); + } else if (auto perform_math_reg = + std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Perform Register Arithmetic"); + manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); + manager.CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); + manager.CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); + if (perform_math_reg->has_immediate) { + manager.CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); + } else { + manager.CommandLog(fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index)); + } + } else if (auto str_register = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Store Register to Address"); + manager.CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); + manager.CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); + manager.CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); + manager.CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + break; + case StoreRegisterOffsetType::Reg: + manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); + break; + case StoreRegisterOffsetType::Imm: + manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + case StoreRegisterOffsetType::MemReg: + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); + manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + } + } else if (auto begin_reg_cond = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Begin Register Conditional"); + manager.CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); + manager.CommandLog( + fmt::format("Cond Type: {:X}", static_cast(begin_reg_cond->cond_type))); + manager.CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::StaticValue: + manager.CommandLog("Comp Type: Static Value"); + manager.CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64)); + break; + case CompareRegisterValueType::OtherRegister: + manager.CommandLog("Comp Type: Other Register"); + manager.CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index)); + break; + case CompareRegisterValueType::MemoryRelAddr: + manager.CommandLog("Comp Type: Memory Relative Address"); + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::MemoryOfsReg: + manager.CommandLog("Comp Type: Memory Offset Register"); + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + case CompareRegisterValueType::RegisterRelAddr: + manager.CommandLog("Comp Type: Register Relative Address"); + manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::RegisterOfsReg: + manager.CommandLog("Comp Type: Register Offset Register"); + manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + } + } else if (auto save_restore_reg = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Save or Restore Register"); + manager.CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); + manager.CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); + manager.CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_reg->op_type))); + } else if (auto save_restore_regmask = + std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Save or Restore Register Mask"); + manager.CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_regmask->op_type))); + for (std::size_t i = 0; i < NumRegisters; i++) { + manager.CommandLog( + fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i])); + } + } else if (auto rw_static_reg = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Read/Write Static Register"); + if (rw_static_reg->static_idx < NumReadableStaticRegisters) { + manager.CommandLog("Op Type: ReadStaticRegister"); + } else { + manager.CommandLog("Op Type: WriteStaticRegister"); + } + manager.CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx)); + manager.CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx)); + } else if (auto debug_log = std::get_if(&opcode.opcode)) { + manager.CommandLog("Opcode: Debug Log"); + manager.CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); + manager.CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); + manager.CommandLog(fmt::format("Val Type: {:X}", static_cast(debug_log->val_type))); + switch (debug_log->val_type) { + case DebugLogValueType::RegisterValue: + manager.CommandLog("Val Type: Register Value"); + manager.CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index)); + break; + case DebugLogValueType::MemoryRelAddr: + manager.CommandLog("Val Type: Memory Relative Address"); + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::MemoryOfsReg: + manager.CommandLog("Val Type: Memory Offset Register"); + manager.CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + case DebugLogValueType::RegisterRelAddr: + manager.CommandLog("Val Type: Register Relative Address"); + manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::RegisterOfsReg: + manager.CommandLog("Val Type: Register Offset Register"); + manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + } + } else if (auto instr = std::get_if(&opcode.opcode)) { + manager.CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(instr->opcode))); + } + } + + bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode& out) { + // If we've ever seen a decode failure, return false. + bool valid = decode_success; + CheatVmOpcode opcode = {}; + SCOPE_EXIT { + decode_success &= valid; + if (valid) { + out = opcode; + } + }; + + // Helper function for getting instruction dwords. + const auto GetNextDword = [&] { + if (instruction_ptr >= num_opcodes) { + valid = false; + return static_cast(0); + } + return program[instruction_ptr++]; + }; + + // Helper function for parsing a VmInt. + const auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val{}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = static_cast(first_dword); + break; + case 2: + val.bit16 = static_cast(first_dword); + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (static_cast(first_dword) << 32ul) | static_cast(GetNextDword()); + break; + } + + return val; + }; + + // Read opcode. + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + auto opcode_type = static_cast(((first_dword >> 28) & 0xF)); + if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 24) & 0xF)); + } + if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 20) & 0xF)); + } + + // detect condition start. + switch (opcode_type) { + case CheatVmOpcodeType::BeginConditionalBlock: + case CheatVmOpcodeType::BeginKeypressConditionalBlock: + case CheatVmOpcodeType::BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode_type) { + case CheatVmOpcodeType::StoreStatic: { + // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + const u32 bit_width = (first_dword >> 24) & 0xF; + + opcode.opcode = StoreStaticOpcode{ + .bit_width = bit_width, + .mem_type = static_cast((first_dword >> 20) & 0xF), + .offset_register = (first_dword >> 16) & 0xF, + .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, + .value = GetNextVmInt(bit_width), + }; + } break; + case CheatVmOpcodeType::BeginConditionalBlock: { + // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + const u32 bit_width = (first_dword >> 24) & 0xF; + + opcode.opcode = BeginConditionalOpcode{ + .bit_width = bit_width, + .mem_type = static_cast((first_dword >> 20) & 0xF), + .cond_type = static_cast((first_dword >> 16) & 0xF), + .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, + .value = GetNextVmInt(bit_width), + }; + } break; + case CheatVmOpcodeType::EndConditionalBlock: { + // 20000000 + opcode.opcode = EndConditionalOpcode{ + .is_else = ((first_dword >> 24) & 0xf) == 1, + }; + } break; + case CheatVmOpcodeType::ControlLoop: { + // 300R0000 VVVVVVVV + // 310R0000 + // Parse register, whether loop start or loop end. + ControlLoopOpcode ctrl_loop{ + .start_loop = ((first_dword >> 24) & 0xF) == 0, + .reg_index = (first_dword >> 20) & 0xF, + .num_iters = 0, + }; + + // Read number of iters if loop start. + if (ctrl_loop.start_loop) { + ctrl_loop.num_iters = GetNextDword(); + } + opcode.opcode = ctrl_loop; + } break; + case CheatVmOpcodeType::LoadRegisterStatic: { + // 400R0000 VVVVVVVV VVVVVVVV + // Read additional words. + opcode.opcode = LoadRegisterStaticOpcode{ + .reg_index = (first_dword >> 16) & 0xF, + .value = (static_cast(GetNextDword()) << 32) | GetNextDword(), + }; + } break; + case CheatVmOpcodeType::LoadRegisterMemory: { + // 5TMRI0AA AAAAAAAA + // Read additional words. + const u32 second_dword = GetNextDword(); + opcode.opcode = LoadRegisterMemoryOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .mem_type = static_cast((first_dword >> 20) & 0xF), + .reg_index = ((first_dword >> 16) & 0xF), + .load_from_reg = ((first_dword >> 12) & 0xF) != 0, + .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, + }; + } break; + case CheatVmOpcodeType::StoreStaticToAddress: { + // 6T0RIor0 VVVVVVVV VVVVVVVV + // Read additional words. + opcode.opcode = StoreStaticToAddressOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .reg_index = (first_dword >> 16) & 0xF, + .increment_reg = ((first_dword >> 12) & 0xF) != 0, + .add_offset_reg = ((first_dword >> 8) & 0xF) != 0, + .offset_reg_index = (first_dword >> 4) & 0xF, + .value = (static_cast(GetNextDword()) << 32) | GetNextDword(), + }; + } break; + case CheatVmOpcodeType::PerformArithmeticStatic: { + // 7T0RC000 VVVVVVVV + // Read additional words. + opcode.opcode = PerformArithmeticStaticOpcode{ + .bit_width = (first_dword >> 24) & 0xF, + .reg_index = ((first_dword >> 16) & 0xF), + .math_type = static_cast((first_dword >> 12) & 0xF), + .value = GetNextDword(), + }; + } break; + case CheatVmOpcodeType::BeginKeypressConditionalBlock: { + // 8kkkkkkk + // Just parse the mask. + opcode.opcode = BeginKeypressConditionalOpcode{ + .key_mask = first_dword & 0x0FFFFFFF, + }; + } break; + case CheatVmOpcodeType::PerformArithmeticRegister: { + // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) + PerformArithmeticRegisterOpcode perform_math_reg{ + .bit_width = (first_dword >> 24) & 0xF, + .math_type = static_cast((first_dword >> 20) & 0xF), + .dst_reg_index = (first_dword >> 16) & 0xF, + .src_reg_1_index = (first_dword >> 12) & 0xF, + .src_reg_2_index = 0, + .has_immediate = ((first_dword >> 8) & 0xF) != 0, + .value = {}, + }; + if (perform_math_reg.has_immediate) { + perform_math_reg.src_reg_2_index = 0; + perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); + } else { + perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + opcode.opcode = perform_math_reg; + } break; + case CheatVmOpcodeType::StoreRegisterToAddress: { + // ATSRIOxa (aaaaaaaa) + // A = opcode 10 + // T = bit width + // S = src register index + // R = address register index + // I = 1 if increment address register, 0 if not increment address register + // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + + // Relative Address + // x = offset register (for offset type 1), memory type (for offset type 3) + // a = relative address (for offset type 2+3) + StoreRegisterToAddressOpcode str_register{ + .bit_width = (first_dword >> 24) & 0xF, + .str_reg_index = (first_dword >> 20) & 0xF, + .addr_reg_index = (first_dword >> 16) & 0xF, + .increment_reg = ((first_dword >> 12) & 0xF) != 0, + .ofs_type = static_cast(((first_dword >> 8) & 0xF)), + .mem_type = MemoryAccessType::MainNso, + .ofs_reg_index = (first_dword >> 4) & 0xF, + .rel_address = 0, + }; + switch (str_register.ofs_type) { + case StoreRegisterOffsetType::None: + case StoreRegisterOffsetType::Reg: + // Nothing more to do + break; + case StoreRegisterOffsetType::Imm: + str_register.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + case StoreRegisterOffsetType::MemReg: + str_register.mem_type = static_cast((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + str_register.mem_type = static_cast((first_dword >> 4) & 0xF); + str_register.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + default: + str_register.ofs_type = StoreRegisterOffsetType::None; + break; + } + opcode.opcode = str_register; + } break; + case CheatVmOpcodeType::BeginRegisterConditionalBlock: { + // C0TcSX## + // C0TcS0Ma aaaaaaaa + // C0TcS1Mr + // C0TcS2Ra aaaaaaaa + // C0TcS3Rr + // C0TcS400 VVVVVVVV (VVVVVVVV) + // C0TcS5X0 + // C0 = opcode 0xC0 + // T = bit width + // c = condition type. + // S = source register. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = static + // value, 5 = other register. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = other register. + // V = value. + + BeginRegisterConditionalOpcode begin_reg_cond{ + .bit_width = (first_dword >> 20) & 0xF, + .cond_type = static_cast((first_dword >> 16) & 0xF), + .val_reg_index = (first_dword >> 12) & 0xF, + .comp_type = static_cast((first_dword >> 8) & 0xF), + .mem_type = MemoryAccessType::MainNso, + .addr_reg_index = 0, + .other_reg_index = 0, + .ofs_reg_index = 0, + .rel_address = 0, + .value = {}, + }; + + switch (begin_reg_cond.comp_type) { + case CompareRegisterValueType::StaticValue: + begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); + break; + case CompareRegisterValueType::OtherRegister: + begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType::MemoryRelAddr: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + case CompareRegisterValueType::MemoryOfsReg: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType::RegisterRelAddr: + begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; + begin_reg_cond.rel_address = + (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + case CompareRegisterValueType::RegisterOfsReg: + begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; + begin_reg_cond.ofs_reg_index = first_dword & 0xF; + break; + } + opcode.opcode = begin_reg_cond; + } break; + case CheatVmOpcodeType::SaveRestoreRegister: { + // C10D0Sx0 + // C1 = opcode 0xC1 + // D = destination index. + // S = source index. + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring + // a register. + // NOTE: If we add more save slots later, current encoding is backwards compatible. + opcode.opcode = SaveRestoreRegisterOpcode{ + .dst_index = (first_dword >> 16) & 0xF, + .src_index = (first_dword >> 8) & 0xF, + .op_type = static_cast((first_dword >> 4) & 0xF), + }; + } break; + case CheatVmOpcodeType::SaveRestoreRegisterMask: { + // C2x0XXXX + // C2 = opcode 0xC2 + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. + // X = 16-bit bitmask, bit i --> save or restore register i. + SaveRestoreRegisterMaskOpcode save_restore_regmask{ + .op_type = static_cast((first_dword >> 20) & 0xF), + .should_operate = {}, + }; + for (std::size_t i = 0; i < NumRegisters; i++) { + save_restore_regmask.should_operate[i] = (first_dword & (1U << i)) != 0; + } + opcode.opcode = save_restore_regmask; + } break; + case CheatVmOpcodeType::ReadWriteStaticRegister: { + // C3000XXx + // C3 = opcode 0xC3. + // XX = static register index. + // x = register index. + opcode.opcode = ReadWriteStaticRegisterOpcode{ + .static_idx = (first_dword >> 4) & 0xFF, + .idx = first_dword & 0xF, + }; + } break; + case CheatVmOpcodeType::PauseProcess: { + /* FF0????? */ + /* FF0 = opcode 0xFF0 */ + /* Pauses the current process. */ + opcode.opcode = PauseProcessOpcode{}; + } break; + case CheatVmOpcodeType::ResumeProcess: { + /* FF0????? */ + /* FF0 = opcode 0xFF0 */ + /* Pauses the current process. */ + opcode.opcode = ResumeProcessOpcode{}; + } break; + case CheatVmOpcodeType::DebugLog: { + // FFFTIX## + // FFFTI0Ma aaaaaaaa + // FFFTI1Mr + // FFFTI2Ra aaaaaaaa + // FFFTI3Rr + // FFFTI4X0 + // FFF = opcode 0xFFF + // T = bit width. + // I = log id. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = register + // value. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = value register. + DebugLogOpcode debug_log{ + .bit_width = (first_dword >> 16) & 0xF, + .log_id = (first_dword >> 12) & 0xF, + .val_type = static_cast((first_dword >> 8) & 0xF), + .mem_type = MemoryAccessType::MainNso, + .addr_reg_index = 0, + .val_reg_index = 0, + .ofs_reg_index = 0, + .rel_address = 0, + }; + + switch (debug_log.val_type) { + case DebugLogValueType::RegisterValue: + debug_log.val_reg_index = (first_dword >> 4) & 0xF; + break; + case DebugLogValueType::MemoryRelAddr: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + debug_log.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + case DebugLogValueType::MemoryOfsReg: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = first_dword & 0xF; + break; + case DebugLogValueType::RegisterRelAddr: + debug_log.addr_reg_index = (first_dword >> 4) & 0xF; + debug_log.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); + break; + case DebugLogValueType::RegisterOfsReg: + debug_log.addr_reg_index = (first_dword >> 4) & 0xF; + debug_log.ofs_reg_index = first_dword & 0xF; + break; + } + opcode.opcode = debug_log; + } break; + case CheatVmOpcodeType::ExtendedWidth: + case CheatVmOpcodeType::DoubleExtendedWidth: + default: + // Unrecognized instruction cannot be decoded. + valid = false; + opcode.opcode = UnrecognizedInstruction{opcode_type}; + break; + } + + // End decoding. + return valid; + } + + void CheatVirtualMachine::SkipConditionalBlock(bool is_if) { + if (condition_depth > 0) { + // We want to continue until we're out of the current block. + const std::size_t desired_depth = condition_depth - 1; + + CheatVmOpcode skip_opcode{}; + while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) { + // Decode instructions until we see end of the current conditional block. + // NOTE: This is broken in gateway's implementation. + // Gateway currently checks for "0x2" instead of "0x20000000" + // In addition, they do a linear scan instead of correctly decoding opcodes. + // This causes issues if "0x2" appears as an immediate in the conditional block... + + // We also support nesting of conditional blocks, and Gateway does not. + if (skip_opcode.begin_conditional_block) { + condition_depth++; + } else if (auto end_cond = std::get_if(&skip_opcode.opcode)) { + if (!end_cond->is_else) { + condition_depth--; + } else if (is_if && condition_depth - 1 == desired_depth) { + break; + } + } + } + } else { + // Skipping, but condition_depth = 0. + // This is an error condition. + // However, I don't actually believe it is possible for this to happen. + // I guess we'll throw a fatal error here, so as to encourage me to fix the VM + // in the event that someone triggers it? I don't know how you'd do that. + UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); + } + } + + u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + // Invalid bit width -> return 0. + return 0; + } + } + + u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType::MainNso: + default: + return metadata.main_nso_extents.base + rel_address; + case MemoryAccessType::Heap: + return metadata.heap_extents.base + rel_address; + case MemoryAccessType::Alias: + return metadata.alias_extents.base + rel_address; + case MemoryAccessType::Aslr: + return metadata.aslr_extents.base + rel_address; + } + } + + void CheatVirtualMachine::ResetState() { + registers.fill(0); + saved_values.fill(0); + loop_tops.fill(0); + instruction_ptr = 0; + condition_depth = 0; + decode_success = true; + } + + bool CheatVirtualMachine::LoadProgram(std::span entries) { + // Reset opcode count. + num_opcodes = 0; + + for (std::size_t i = 0; i < entries.size(); i++) { + if (entries[i].enabled) { + // Bounds check. + if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { + num_opcodes = 0; + return false; + } + + for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { + program[num_opcodes++] = entries[i].definition.opcodes[n]; + } + } + } + + return true; + } + + void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) { + CheatVmOpcode cur_opcode{}; + + // Get Keys down. + u64 kDown = manager.HidKeysDown(); + + manager.CommandLog("Started VM execution."); + manager.CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); + manager.CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); + manager.CommandLog(fmt::format("Keys Down: {:08X}", static_cast(kDown & 0x0FFFFFFF))); + + // Clear VM state. + ResetState(); + + // Loop until program finishes. + while (DecodeNextOpcode(cur_opcode)) { + manager.CommandLog( + fmt::format("Instruction Ptr: {:04X}", static_cast(instruction_ptr))); + + for (std::size_t i = 0; i < NumRegisters; i++) { + manager.CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); + } + + for (std::size_t i = 0; i < NumRegisters; i++) { + manager.CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); + } + LogOpcode(cur_opcode); + + // Increment conditional depth, if relevant. + if (cur_opcode.begin_conditional_block) { + condition_depth++; + } + + if (auto store_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address, write value to memory. + u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, + store_static->rel_address + + registers[store_static->offset_register]); + u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); + switch (store_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, + store_static->bit_width); + break; + } + } else if (auto begin_cond = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. + u64 src_address = + GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); + u64 src_value = 0; + switch (begin_cond->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.ReadCheatProcessMemoryUnsafe(src_address, &src_value, + begin_cond->bit_width); + break; + } + // Check against condition. + u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); + bool cond_met = false; + switch (begin_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(true); + } + } else if (auto end_cond = std::get_if(&cur_opcode.opcode)) { + if (end_cond->is_else) { + /* Skip to the end of the conditional block. */ + this->SkipConditionalBlock(false); + } else { + /* Decrement the condition depth. */ + /* We will assume, graciously, that mismatched conditional block ends are a nop. */ + if (condition_depth > 0) { + condition_depth--; + } + } + } else if (auto ctrl_loop = std::get_if(&cur_opcode.opcode)) { + if (ctrl_loop->start_loop) { + // Start a loop. + registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; + loop_tops[ctrl_loop->reg_index] = instruction_ptr; + } else { + // End a loop. + registers[ctrl_loop->reg_index]--; + if (registers[ctrl_loop->reg_index] != 0) { + instruction_ptr = loop_tops[ctrl_loop->reg_index]; + } + } + } else if (auto ldr_static = std::get_if(&cur_opcode.opcode)) { + // Set a register to a static value. + registers[ldr_static->reg_index] = ldr_static->value; + } else if (auto ldr_memory = std::get_if(&cur_opcode.opcode)) { + // Choose source address. + u64 src_address; + if (ldr_memory->load_from_reg) { + src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; + } else { + src_address = + GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); + } + // Read into register. Gateway only reads on valid bitwidth. + switch (ldr_memory->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.ReadCheatProcessMemoryUnsafe(src_address, ®isters[ldr_memory->reg_index], + ldr_memory->bit_width); + break; + } + } else if (auto str_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_address = registers[str_static->reg_index]; + u64 dst_value = str_static->value; + if (str_static->add_offset_reg) { + dst_address += registers[str_static->offset_reg_index]; + } + // Write value to memory. Gateway only writes on valid bitwidth. + switch (str_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, + str_static->bit_width); + break; + } + // Increment register if relevant. + if (str_static->increment_reg) { + registers[str_static->reg_index] += str_static->bit_width; + } + } else if (auto perform_math_static = + std::get_if(&cur_opcode.opcode)) { + // Do requested math. + switch (perform_math_static->math_type) { + case RegisterArithmeticType::Addition: + registers[perform_math_static->reg_index] += + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::Subtraction: + registers[perform_math_static->reg_index] -= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::Multiplication: + registers[perform_math_static->reg_index] *= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::LeftShift: + registers[perform_math_static->reg_index] <<= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::RightShift: + registers[perform_math_static->reg_index] >>= + static_cast(perform_math_static->value); + break; + default: + // Do not handle extensions here. + break; + } + // Apply bit width. + switch (perform_math_static->bit_width) { + case 1: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 2: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 4: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 8: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + } + } else if (auto begin_keypress_cond = + std::get_if(&cur_opcode.opcode)) { + // Check for keypress. + if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { + // Keys not pressed. Skip conditional block. + SkipConditionalBlock(true); + } + } else if (auto perform_math_reg = + std::get_if(&cur_opcode.opcode)) { + const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index]; + const u64 operand_2_value = + perform_math_reg->has_immediate + ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) + : registers[perform_math_reg->src_reg_2_index]; + + u64 res_val = 0; + // Do requested math. + switch (perform_math_reg->math_type) { + case RegisterArithmeticType::Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType::Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType::Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType::LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType::RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType::LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType::LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType::LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType::LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType::None: + res_val = operand_1_value; + break; + } + + // Apply bit width. + switch (perform_math_reg->bit_width) { + case 1: + res_val = static_cast(res_val); + break; + case 2: + res_val = static_cast(res_val); + break; + case 4: + res_val = static_cast(res_val); + break; + case 8: + res_val = static_cast(res_val); + break; + } + + // Save to register. + registers[perform_math_reg->dst_reg_index] = res_val; + } else if (auto str_register = + std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_value = registers[str_register->str_reg_index]; + u64 dst_address = registers[str_register->addr_reg_index]; + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + // Nothing more to do + break; + case StoreRegisterOffsetType::Reg: + dst_address += registers[str_register->ofs_reg_index]; + break; + case StoreRegisterOffsetType::Imm: + dst_address += str_register->rel_address; + break; + case StoreRegisterOffsetType::MemReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index]); + break; + case StoreRegisterOffsetType::MemImm: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + str_register->rel_address); + break; + case StoreRegisterOffsetType::MemImmReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index] + + str_register->rel_address); + break; + } + + // Write value to memory. Write only on valid bitwidth. + switch (str_register->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, + str_register->bit_width); + break; + } + + // Increment register if relevant. + if (str_register->increment_reg) { + registers[str_register->addr_reg_index] += str_register->bit_width; + } + } else if (auto begin_reg_cond = + std::get_if(&cur_opcode.opcode)) { + // Get value from register. + u64 src_value = 0; + switch (begin_reg_cond->bit_width) { + case 1: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = + static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + + // Read value from memory. + u64 cond_value = 0; + if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { + cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); + } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { + switch (begin_reg_cond->bit_width) { + case 1: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFul); + break; + case 2: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast(registers[begin_reg_cond->other_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + begin_reg_cond->rel_address); + break; + case CompareRegisterValueType::MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + registers[begin_reg_cond->ofs_reg_index]); + break; + case CompareRegisterValueType::RegisterRelAddr: + cond_address = + registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address; + break; + case CompareRegisterValueType::RegisterOfsReg: + cond_address = registers[begin_reg_cond->addr_reg_index] + + registers[begin_reg_cond->ofs_reg_index]; + break; + default: + break; + } + switch (begin_reg_cond->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.ReadCheatProcessMemoryUnsafe(cond_address, &cond_value, + begin_reg_cond->bit_width); + break; + } + } + + // Check against condition. + bool cond_met = false; + switch (begin_reg_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(true); + } + } else if (auto save_restore_reg = + std::get_if(&cur_opcode.opcode)) { + // Save or restore a register. + switch (save_restore_reg->op_type) { + case SaveRestoreRegisterOpType::ClearRegs: + registers[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::ClearSaved: + saved_values[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; + break; + case SaveRestoreRegisterOpType::Restore: + default: + registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; + break; + } + } else if (auto save_restore_regmask = + std::get_if(&cur_opcode.opcode)) { + // Save or restore register mask. + u64* src; + u64* dst; + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::Save: + src = registers.data(); + dst = saved_values.data(); + break; + case SaveRestoreRegisterOpType::ClearRegs: + case SaveRestoreRegisterOpType::Restore: + default: + src = saved_values.data(); + dst = registers.data(); + break; + } + for (std::size_t i = 0; i < NumRegisters; i++) { + if (save_restore_regmask->should_operate[i]) { + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + case SaveRestoreRegisterOpType::Restore: + default: + dst[i] = src[i]; + break; + } + } + } + } else if (auto rw_static_reg = + std::get_if(&cur_opcode.opcode)) { + if (rw_static_reg->static_idx < NumReadableStaticRegisters) { + // Load a register with a static register. + registers[rw_static_reg->idx] = static_registers[rw_static_reg->static_idx]; + } else { + // Store a register to a static register. + static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx]; + } + } else if (std::holds_alternative(cur_opcode.opcode)) { + manager.PauseCheatProcessUnsafe(); + } else if (std::holds_alternative(cur_opcode.opcode)) { + manager.ResumeCheatProcessUnsafe(); + } else if (auto debug_log = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. + u64 log_value = 0; + if (debug_log->val_type == DebugLogValueType::RegisterValue) { + switch (debug_log->bit_width) { + case 1: + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = + static_cast(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast(registers[debug_log->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (debug_log->val_type) { + case DebugLogValueType::MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + debug_log->rel_address); + break; + case DebugLogValueType::MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + registers[debug_log->ofs_reg_index]); + break; + case DebugLogValueType::RegisterRelAddr: + val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; + break; + case DebugLogValueType::RegisterOfsReg: + val_address = + registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; + break; + default: + break; + } + switch (debug_log->bit_width) { + case 1: + case 2: + case 4: + case 8: + manager.ReadCheatProcessMemoryUnsafe(val_address, &log_value, + debug_log->bit_width); + break; + } + } + + // Log value. + DebugLog(debug_log->log_id, log_value); + } + } + } +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_virtual_machine.h b/src/core/hle/service/dmnt/cheat_virtual_machine.h new file mode 100644 index 0000000000..29f743807f --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_virtual_machine.h @@ -0,0 +1,323 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "core/hle/service/dmnt/dmnt_types.h" + +namespace Service::DMNT { + class CheatProcessManager; + + enum class CheatVmOpcodeType : u32 { + StoreStatic = 0, + BeginConditionalBlock = 1, + EndConditionalBlock = 2, + ControlLoop = 3, + LoadRegisterStatic = 4, + LoadRegisterMemory = 5, + StoreStaticToAddress = 6, + PerformArithmeticStatic = 7, + BeginKeypressConditionalBlock = 8, + + // These are not implemented by Gateway's VM. + PerformArithmeticRegister = 9, + StoreRegisterToAddress = 10, + Reserved11 = 11, + + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + ExtendedWidth = 12, + + // Extended width opcodes. + BeginRegisterConditionalBlock = 0xC0, + SaveRestoreRegister = 0xC1, + SaveRestoreRegisterMask = 0xC2, + ReadWriteStaticRegister = 0xC3, + + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + DoubleExtendedWidth = 0xF0, + + // Double-extended width opcodes. + PauseProcess = 0xFF0, + ResumeProcess = 0xFF1, + DebugLog = 0xFFF, + }; + + enum class MemoryAccessType : u32 { + MainNso = 0, + Heap = 1, + Alias = 2, + Aslr = 3, + }; + + enum class ConditionalComparisonType : u32 { + GT = 1, + GE = 2, + LT = 3, + LE = 4, + EQ = 5, + NE = 6, + }; + + enum class RegisterArithmeticType : u32 { + Addition = 0, + Subtraction = 1, + Multiplication = 2, + LeftShift = 3, + RightShift = 4, + + // These are not supported by Gateway's VM. + LogicalAnd = 5, + LogicalOr = 6, + LogicalNot = 7, + LogicalXor = 8, + + None = 9, + }; + + enum class StoreRegisterOffsetType : u32 { + None = 0, + Reg = 1, + Imm = 2, + MemReg = 3, + MemImm = 4, + MemImmReg = 5, + }; + + enum class CompareRegisterValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + StaticValue = 4, + OtherRegister = 5, + }; + + enum class SaveRestoreRegisterOpType : u32 { + Restore = 0, + Save = 1, + ClearSaved = 2, + ClearRegs = 3, + }; + + enum class DebugLogValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + RegisterValue = 4, + }; + + union VmInt { + u8 bit8; + u16 bit16; + u32 bit32; + u64 bit64; + }; + + struct StoreStaticOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + u32 offset_register{}; + u64 rel_address{}; + VmInt value{}; + }; + + struct BeginConditionalOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + ConditionalComparisonType cond_type{}; + u64 rel_address{}; + VmInt value{}; + }; + + struct EndConditionalOpcode { + bool is_else; + }; + + struct ControlLoopOpcode { + bool start_loop{}; + u32 reg_index{}; + u32 num_iters{}; + }; + + struct LoadRegisterStaticOpcode { + u32 reg_index{}; + u64 value{}; + }; + + struct LoadRegisterMemoryOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + u32 reg_index{}; + bool load_from_reg{}; + u64 rel_address{}; + }; + + struct StoreStaticToAddressOpcode { + u32 bit_width{}; + u32 reg_index{}; + bool increment_reg{}; + bool add_offset_reg{}; + u32 offset_reg_index{}; + u64 value{}; + }; + + struct PerformArithmeticStaticOpcode { + u32 bit_width{}; + u32 reg_index{}; + RegisterArithmeticType math_type{}; + u32 value{}; + }; + + struct BeginKeypressConditionalOpcode { + u32 key_mask{}; + }; + + struct PerformArithmeticRegisterOpcode { + u32 bit_width{}; + RegisterArithmeticType math_type{}; + u32 dst_reg_index{}; + u32 src_reg_1_index{}; + u32 src_reg_2_index{}; + bool has_immediate{}; + VmInt value{}; + }; + + struct StoreRegisterToAddressOpcode { + u32 bit_width{}; + u32 str_reg_index{}; + u32 addr_reg_index{}; + bool increment_reg{}; + StoreRegisterOffsetType ofs_type{}; + MemoryAccessType mem_type{}; + u32 ofs_reg_index{}; + u64 rel_address{}; + }; + + struct BeginRegisterConditionalOpcode { + u32 bit_width{}; + ConditionalComparisonType cond_type{}; + u32 val_reg_index{}; + CompareRegisterValueType comp_type{}; + MemoryAccessType mem_type{}; + u32 addr_reg_index{}; + u32 other_reg_index{}; + u32 ofs_reg_index{}; + u64 rel_address{}; + VmInt value{}; + }; + + struct SaveRestoreRegisterOpcode { + u32 dst_index{}; + u32 src_index{}; + SaveRestoreRegisterOpType op_type{}; + }; + + struct SaveRestoreRegisterMaskOpcode { + SaveRestoreRegisterOpType op_type{}; + std::array should_operate{}; + }; + + struct ReadWriteStaticRegisterOpcode { + u32 static_idx{}; + u32 idx{}; + }; + + struct PauseProcessOpcode {}; + + struct ResumeProcessOpcode {}; + + struct DebugLogOpcode { + u32 bit_width{}; + u32 log_id{}; + DebugLogValueType val_type{}; + MemoryAccessType mem_type{}; + u32 addr_reg_index{}; + u32 val_reg_index{}; + u32 ofs_reg_index{}; + u64 rel_address{}; + }; + + struct UnrecognizedInstruction { + CheatVmOpcodeType opcode{}; + }; + + struct CheatVmOpcode { + bool begin_conditional_block{}; + std::variant + opcode{}; + }; + + class CheatVirtualMachine { + public: + static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; + static constexpr std::size_t NumRegisters = 0x10; + static constexpr std::size_t NumReadableStaticRegisters = 0x80; + static constexpr std::size_t NumWritableStaticRegisters = 0x80; + static constexpr std::size_t NumStaticRegisters = + NumReadableStaticRegisters + NumWritableStaticRegisters; + + explicit CheatVirtualMachine(CheatProcessManager& cheat_manager); + ~CheatVirtualMachine(); + + std::size_t GetProgramSize() const { + return this->num_opcodes; + } + + bool LoadProgram(std::span cheats); + void Execute(const CheatProcessMetadata& metadata); + + u64 GetStaticRegister(std::size_t register_index) const { + return static_registers[register_index]; + } + + void SetStaticRegister(std::size_t register_index, u64 value) { + static_registers[register_index] = value; + } + + void ResetStaticRegisters() { + static_registers = {}; + } + + private: + bool DecodeNextOpcode(CheatVmOpcode& out); + void SkipConditionalBlock(bool is_if); + void ResetState(); + + // For implementing the DebugLog opcode. + void DebugLog(u32 log_id, u64 value) const; + + void LogOpcode(const CheatVmOpcode& opcode) const; + + static u64 GetVmInt(VmInt value, u32 bit_width); + static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address); + + CheatProcessManager& manager; + + std::size_t num_opcodes = 0; + std::size_t instruction_ptr = 0; + std::size_t condition_depth = 0; + bool decode_success = false; + std::array program{}; + std::array registers{}; + std::array saved_values{}; + std::array static_registers{}; + std::array loop_tops{}; + }; +}// namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/dmnt.cpp b/src/core/hle/service/dmnt/dmnt.cpp new file mode 100644 index 0000000000..18eb039d3b --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/core.h" +#include "core/hle/service/dmnt/cheat_interface.h" +#include "core/hle/service/dmnt/cheat_process_manager.h" +#include "core/hle/service/dmnt/cheat_virtual_machine.h" +#include "core/hle/service/dmnt/dmnt.h" +#include "core/hle/service/server_manager.h" + +namespace Service::DMNT { + void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + + auto& cheat_manager = system.GetCheatManager(); + auto cheat_vm = std::make_unique(cheat_manager); + cheat_manager.SetVirtualMachine(std::move(cheat_vm)); + + server_manager->RegisterNamedService("dmnt:cht", + std::make_shared(system, cheat_manager)); + ServerManager::RunServer(std::move(server_manager)); + } +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/dmnt.h b/src/core/hle/service/dmnt/dmnt.h new file mode 100644 index 0000000000..af715d9ce4 --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +namespace Core { + class System; +}; + +namespace Service::DMNT { + void LoopProcess(Core::System& system); +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/dmnt_results.h b/src/core/hle/service/dmnt/dmnt_results.h new file mode 100644 index 0000000000..3b220cf575 --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt_results.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Service::DMNT { + constexpr Result ResultDebuggingDisabled(ErrorModule::DMNT, 2); + + constexpr Result ResultCheatNotAttached(ErrorModule::DMNT, 6500); + constexpr Result ResultCheatNullBuffer(ErrorModule::DMNT, 6501); + constexpr Result ResultCheatInvalidBuffer(ErrorModule::DMNT, 6502); + constexpr Result ResultCheatUnknownId(ErrorModule::DMNT, 6503); + constexpr Result ResultCheatOutOfResource(ErrorModule::DMNT, 6504); + constexpr Result ResultCheatInvalid(ErrorModule::DMNT, 6505); + constexpr Result ResultCheatCannotDisable(ErrorModule::DMNT, 6506); + constexpr Result ResultFrozenAddressInvalidWidth(ErrorModule::DMNT, 6600); + constexpr Result ResultFrozenAddressAlreadyExists(ErrorModule::DMNT, 6601); + constexpr Result ResultFrozenAddressNotFound(ErrorModule::DMNT, 6602); + constexpr Result ResultFrozenAddressOutOfResource(ErrorModule::DMNT, 6603); + constexpr Result ResultVirtualMachineInvalidConditionDepth(ErrorModule::DMNT, 6700); +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/dmnt_types.h b/src/core/hle/service/dmnt/dmnt_types.h new file mode 100644 index 0000000000..7078d5422f --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt_types.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Service::DMNT { + struct MemoryRegionExtents { + u64 base{}; + u64 size{}; + }; + static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size"); + + struct CheatProcessMetadata { + u64 process_id{}; + u64 program_id{}; + MemoryRegionExtents main_nso_extents{}; + MemoryRegionExtents heap_extents{}; + MemoryRegionExtents alias_extents{}; + MemoryRegionExtents aslr_extents{}; + std::array main_nso_build_id{}; + }; + static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size"); + + struct CheatDefinition { + std::array readable_name; + u32 num_opcodes; + std::array opcodes; + }; + static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size"); + + struct CheatEntry { + bool enabled; + u32 cheat_id; + CheatDefinition definition; + }; + static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size"); + static_assert(std::is_trivial_v, "CheatEntry type must be trivially copyable."); + + struct FrozenAddressValue { + u64 value; + u8 width; + }; + static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size"); + + struct FrozenAddressEntry { + u64 address; + FrozenAddressValue value; + }; + static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size"); +} // namespace Service::DMNT diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 636f54ad49..ee2fa25a4d 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -16,6 +16,7 @@ #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/btm/btm.h" #include "core/hle/service/caps/caps.h" +#include "core/hle/service/dmnt/dmnt.h" #include "core/hle/service/erpt/erpt.h" #include "core/hle/service/es/es.h" #include "core/hle/service/eupld/eupld.h" @@ -107,6 +108,7 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"btdrv", &BtDrv::LoopProcess}, {"btm", &BTM::LoopProcess}, {"capsrv", &Capture::LoopProcess}, + {"dmnt", &DMNT::LoopProcess}, {"erpt", &ERPT::LoopProcess}, {"es", &ES::LoopProcess}, {"eupld", &EUPLD::LoopProcess}, diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp deleted file mode 100644 index dcfd23644f..0000000000 --- a/src/core/memory/cheat_engine.cpp +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/hex_util.h" -#include "common/swap.h" -#include "core/arm/debug.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/hle/kernel/k_page_table.h" -#include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/k_process_page_table.h" -#include "core/hle/kernel/svc_types.h" -#include "core/hle/service/hid/hid_server.h" -#include "core/hle/service/sm/sm.h" -#include "core/memory.h" -#include "core/memory/cheat_engine.h" -#include "hid_core/resource_manager.h" -#include "hid_core/resources/npad/npad.h" - -namespace Core::Memory { -namespace { -constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; - -std::string_view ExtractName(std::size_t& out_name_size, std::string_view data, - std::size_t start_index, char match) { - auto end_index = start_index; - while (data[end_index] != match) { - ++end_index; - if (end_index > data.size()) { - return {}; - } - } - - out_name_size = end_index - start_index; - - // Clamp name if it's too big - if (out_name_size > sizeof(CheatDefinition::readable_name)) { - end_index = start_index + sizeof(CheatDefinition::readable_name); - } - - return data.substr(start_index, end_index - start_index); -} -} // Anonymous namespace - -StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_) - : metadata{metadata_}, system{system_} {} - -StandardVmCallbacks::~StandardVmCallbacks() = default; - -void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) { - // Return zero on invalid address - if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { - std::memset(data, 0, size); - return; - } - - system.ApplicationMemory().ReadBlock(address, data, size); -} - -void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) { - // Skip invalid memory write address - if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { - return; - } - - if (system.ApplicationMemory().WriteBlock(address, data, size)) { - Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size); - } -} - -u64 StandardVmCallbacks::HidKeysDown() { - const auto hid = system.ServiceManager().GetService("hid"); - if (hid == nullptr) { - LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!"); - return 0; - } - - const auto applet_resource = hid->GetResourceManager(); - if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) { - LOG_WARNING(CheatEngine, - "Attempted to read input state, but applet resource is not initialized!"); - return 0; - } - - const auto press_state = applet_resource->GetNpad()->GetAndResetPressState(); - return static_cast(press_state & HID::NpadButton::All); -} - -void StandardVmCallbacks::PauseProcess() { - if (system.ApplicationProcess()->IsSuspended()) { - return; - } - system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused); -} - -void StandardVmCallbacks::ResumeProcess() { - if (!system.ApplicationProcess()->IsSuspended()) { - return; - } - system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable); -} - -void StandardVmCallbacks::DebugLog(u8 id, u64 value) { - LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); -} - -void StandardVmCallbacks::CommandLog(std::string_view data) { - LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", - data.back() == '\n' ? data.substr(0, data.size() - 1) : data); -} - -bool StandardVmCallbacks::IsAddressInRange(VAddr in) const { - if ((in < metadata.main_nso_extents.base || - in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && - (in < metadata.heap_extents.base || - in >= metadata.heap_extents.base + metadata.heap_extents.size) && - (in < metadata.alias_extents.base || - in >= metadata.alias_extents.base + metadata.alias_extents.size) && - (in < metadata.aslr_extents.base || - in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) { - LOG_DEBUG(CheatEngine, - "Cheat attempting to access memory at invalid address={:016X}, if this " - "persists, " - "the cheat may be incorrect. However, this may be normal early in execution if " - "the game has not properly set up yet.", - in); - return false; ///< Invalid addresses will hard crash - } - - return true; -} - -CheatParser::~CheatParser() = default; - -TextCheatParser::~TextCheatParser() = default; - -std::vector TextCheatParser::Parse(std::string_view data) const { - std::vector out(1); - std::optional current_entry; - - for (std::size_t i = 0; i < data.size(); ++i) { - if (::isspace(data[i])) { - continue; - } - - if (data[i] == '{') { - current_entry = 0; - - if (out[*current_entry].definition.num_opcodes > 0) { - return {}; - } - - std::size_t name_size{}; - const auto name = ExtractName(name_size, data, i + 1, '}'); - if (name.empty()) { - return {}; - } - - std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), - std::min(out[*current_entry].definition.readable_name.size(), - name.size())); - out[*current_entry] - .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = - '\0'; - - i += name_size + 1; - } else if (data[i] == '[') { - current_entry = out.size(); - out.emplace_back(); - - std::size_t name_size{}; - const auto name = ExtractName(name_size, data, i + 1, ']'); - if (name.empty()) { - return {}; - } - - std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), - std::min(out[*current_entry].definition.readable_name.size(), - name.size())); - out[*current_entry] - .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = - '\0'; - - i += name_size + 1; - } else if (::isxdigit(data[i])) { - if (!current_entry || out[*current_entry].definition.num_opcodes >= - out[*current_entry].definition.opcodes.size()) { - return {}; - } - - const auto hex = std::string(data.substr(i, 8)); - if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { - return {}; - } - - const auto value = static_cast(std::strtoul(hex.c_str(), nullptr, 0x10)); - out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = - value; - - i += 8; - } else { - return {}; - } - } - - out[0].enabled = out[0].definition.num_opcodes > 0; - out[0].cheat_id = 0; - - for (u32 i = 1; i < out.size(); ++i) { - out[i].enabled = out[i].definition.num_opcodes > 0; - out[i].cheat_id = i; - } - - return out; -} - -CheatEngine::CheatEngine(System& system_, std::vector cheats_, - const std::array& build_id_) - : vm{std::make_unique(system_, metadata)}, - cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} { - metadata.main_nso_build_id = build_id_; -} - -CheatEngine::~CheatEngine() { - if (event) - core_timing.UnscheduleEvent(event); - else - LOG_ERROR(CheatEngine, "~CheatEngine before event was registered"); -} - -void CheatEngine::Initialize() { - event = Core::Timing::CreateEvent( - "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), - [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { - FrameCallback(ns_late); - return std::nullopt; - }); - core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event); - - metadata.process_id = system.ApplicationProcess()->GetProcessId(); - metadata.title_id = system.GetApplicationProcessProgramID(); - - const auto& page_table = system.ApplicationProcess()->GetPageTable(); - metadata.heap_extents = { - .base = GetInteger(page_table.GetHeapRegionStart()), - .size = page_table.GetHeapRegionSize(), - }; - metadata.aslr_extents = { - .base = GetInteger(page_table.GetAliasCodeRegionStart()), - .size = page_table.GetAliasCodeRegionSize(), - }; - metadata.alias_extents = { - .base = GetInteger(page_table.GetAliasRegionStart()), - .size = page_table.GetAliasRegionSize(), - }; - - is_pending_reload.exchange(true); -} - -void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { - metadata.main_nso_extents = { - .base = main_region_begin, - .size = main_region_size, - }; -} - -void CheatEngine::Reload(std::vector reload_cheats) { - cheats = std::move(reload_cheats); - is_pending_reload.exchange(true); -} - -void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) { - if (is_pending_reload.exchange(false)) { - vm.LoadProgram(cheats); - } - - if (vm.GetProgramSize() == 0) { - return; - } - - vm.Execute(metadata); -} - -} // namespace Core::Memory diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h deleted file mode 100644 index f52f2be7c3..0000000000 --- a/src/core/memory/cheat_engine.h +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "core/memory/dmnt_cheat_types.h" -#include "core/memory/dmnt_cheat_vm.h" - -namespace Core { -class System; -} - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace Core::Memory { - -class StandardVmCallbacks : public DmntCheatVm::Callbacks { -public: - StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_); - ~StandardVmCallbacks() override; - - void MemoryReadUnsafe(VAddr address, void* data, u64 size) override; - void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override; - u64 HidKeysDown() override; - void PauseProcess() override; - void ResumeProcess() override; - void DebugLog(u8 id, u64 value) override; - void CommandLog(std::string_view data) override; - -private: - bool IsAddressInRange(VAddr address) const; - - const CheatProcessMetadata& metadata; - Core::System& system; -}; - -// Intermediary class that parses a text file or other disk format for storing cheats into a -// CheatList object, that can be used for execution. -class CheatParser { -public: - virtual ~CheatParser(); - - [[nodiscard]] virtual std::vector Parse(std::string_view data) const = 0; -}; - -// CheatParser implementation that parses text files -class TextCheatParser final : public CheatParser { -public: - ~TextCheatParser() override; - - [[nodiscard]] std::vector Parse(std::string_view data) const override; -}; - -// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming -class CheatEngine final { -public: - CheatEngine(System& system_, std::vector cheats_, - const std::array& build_id_); - ~CheatEngine(); - - void Initialize(); - void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); - - void Reload(std::vector reload_cheats); - -private: - void FrameCallback(std::chrono::nanoseconds ns_late); - - DmntCheatVm vm; - CheatProcessMetadata metadata; - - std::vector cheats; - std::atomic_bool is_pending_reload{false}; - - std::shared_ptr event; - Core::Timing::CoreTiming& core_timing; - Core::System& system; -}; - -} // namespace Core::Memory diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h deleted file mode 100644 index 64c072d3de..0000000000 --- a/src/core/memory/dmnt_cheat_types.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/common_types.h" - -namespace Core::Memory { - -struct MemoryRegionExtents { - u64 base{}; - u64 size{}; -}; - -struct CheatProcessMetadata { - u64 process_id{}; - u64 title_id{}; - MemoryRegionExtents main_nso_extents{}; - MemoryRegionExtents heap_extents{}; - MemoryRegionExtents alias_extents{}; - MemoryRegionExtents aslr_extents{}; - std::array main_nso_build_id{}; -}; - -struct CheatDefinition { - std::array readable_name{}; - u32 num_opcodes{}; - std::array opcodes{}; -}; - -struct CheatEntry { - bool enabled{}; - u32 cheat_id{}; - CheatDefinition definition{}; -}; - -} // namespace Core::Memory diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp deleted file mode 100644 index caceeec4fc..0000000000 --- a/src/core/memory/dmnt_cheat_vm.cpp +++ /dev/null @@ -1,1268 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/assert.h" -#include "common/scope_exit.h" -#include "core/memory/dmnt_cheat_types.h" -#include "core/memory/dmnt_cheat_vm.h" - -namespace Core::Memory { - -DmntCheatVm::DmntCheatVm(std::unique_ptr callbacks_) - : callbacks(std::move(callbacks_)) {} - -DmntCheatVm::~DmntCheatVm() = default; - -void DmntCheatVm::DebugLog(u32 log_id, u64 value) { - callbacks->DebugLog(static_cast(log_id), value); -} - -void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { - if (auto store_static = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Store Static"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(store_static->mem_type))); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); - callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); - } else if (auto begin_cond = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Begin Conditional"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(begin_cond->mem_type))); - callbacks->CommandLog( - fmt::format("Cond Type: {:X}", static_cast(begin_cond->cond_type))); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); - callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); - } else if (std::holds_alternative(opcode.opcode)) { - callbacks->CommandLog("Opcode: End Conditional"); - } else if (auto ctrl_loop = std::get_if(&opcode.opcode)) { - if (ctrl_loop->start_loop) { - callbacks->CommandLog("Opcode: Start Loop"); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); - callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); - } else { - callbacks->CommandLog("Opcode: End Loop"); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); - } - } else if (auto ldr_static = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Load Register Static"); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); - callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value)); - } else if (auto ldr_memory = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Load Register Memory"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(ldr_memory->mem_type))); - callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); - } else if (auto str_static = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Store Static to Address"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); - if (str_static->add_offset_reg) { - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index)); - } - callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); - callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value)); - } else if (auto perform_math_static = - std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Perform Static Arithmetic"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); - callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); - callbacks->CommandLog( - fmt::format("Math Type: {:X}", static_cast(perform_math_static->math_type))); - callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); - } else if (auto begin_keypress_cond = - std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Begin Keypress Conditional"); - callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); - } else if (auto perform_math_reg = - std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Perform Register Arithmetic"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); - callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); - callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); - if (perform_math_reg->has_immediate) { - callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); - } else { - callbacks->CommandLog( - fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index)); - } - } else if (auto str_register = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Store Register to Address"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); - callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); - callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); - callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); - switch (str_register->ofs_type) { - case StoreRegisterOffsetType::None: - break; - case StoreRegisterOffsetType::Reg: - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); - break; - case StoreRegisterOffsetType::Imm: - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); - break; - case StoreRegisterOffsetType::MemReg: - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); - break; - case StoreRegisterOffsetType::MemImm: - case StoreRegisterOffsetType::MemImmReg: - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); - break; - } - } else if (auto begin_reg_cond = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Begin Register Conditional"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); - callbacks->CommandLog( - fmt::format("Cond Type: {:X}", static_cast(begin_reg_cond->cond_type))); - callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); - switch (begin_reg_cond->comp_type) { - case CompareRegisterValueType::StaticValue: - callbacks->CommandLog("Comp Type: Static Value"); - callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64)); - break; - case CompareRegisterValueType::OtherRegister: - callbacks->CommandLog("Comp Type: Other Register"); - callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index)); - break; - case CompareRegisterValueType::MemoryRelAddr: - callbacks->CommandLog("Comp Type: Memory Relative Address"); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); - break; - case CompareRegisterValueType::MemoryOfsReg: - callbacks->CommandLog("Comp Type: Memory Offset Register"); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); - break; - case CompareRegisterValueType::RegisterRelAddr: - callbacks->CommandLog("Comp Type: Register Relative Address"); - callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); - break; - case CompareRegisterValueType::RegisterOfsReg: - callbacks->CommandLog("Comp Type: Register Offset Register"); - callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); - break; - } - } else if (auto save_restore_reg = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Save or Restore Register"); - callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); - callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); - callbacks->CommandLog( - fmt::format("Op Type: {:d}", static_cast(save_restore_reg->op_type))); - } else if (auto save_restore_regmask = - std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Save or Restore Register Mask"); - callbacks->CommandLog( - fmt::format("Op Type: {:d}", static_cast(save_restore_regmask->op_type))); - for (std::size_t i = 0; i < NumRegisters; i++) { - callbacks->CommandLog( - fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i])); - } - } else if (auto rw_static_reg = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Read/Write Static Register"); - if (rw_static_reg->static_idx < NumReadableStaticRegisters) { - callbacks->CommandLog("Op Type: ReadStaticRegister"); - } else { - callbacks->CommandLog("Op Type: WriteStaticRegister"); - } - callbacks->CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx)); - callbacks->CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx)); - } else if (auto debug_log = std::get_if(&opcode.opcode)) { - callbacks->CommandLog("Opcode: Debug Log"); - callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); - callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); - callbacks->CommandLog( - fmt::format("Val Type: {:X}", static_cast(debug_log->val_type))); - switch (debug_log->val_type) { - case DebugLogValueType::RegisterValue: - callbacks->CommandLog("Val Type: Register Value"); - callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index)); - break; - case DebugLogValueType::MemoryRelAddr: - callbacks->CommandLog("Val Type: Memory Relative Address"); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); - break; - case DebugLogValueType::MemoryOfsReg: - callbacks->CommandLog("Val Type: Memory Offset Register"); - callbacks->CommandLog( - fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); - break; - case DebugLogValueType::RegisterRelAddr: - callbacks->CommandLog("Val Type: Register Relative Address"); - callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); - callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); - break; - case DebugLogValueType::RegisterOfsReg: - callbacks->CommandLog("Val Type: Register Offset Register"); - callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); - callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); - break; - } - } else if (auto instr = std::get_if(&opcode.opcode)) { - callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(instr->opcode))); - } -} - -DmntCheatVm::Callbacks::~Callbacks() = default; - -bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { - // If we've ever seen a decode failure, return false. - bool valid = decode_success; - CheatVmOpcode opcode = {}; - SCOPE_EXIT { - decode_success &= valid; - if (valid) { - out = opcode; - } - }; - - // Helper function for getting instruction dwords. - const auto GetNextDword = [&] { - if (instruction_ptr >= num_opcodes) { - valid = false; - return static_cast(0); - } - return program[instruction_ptr++]; - }; - - // Helper function for parsing a VmInt. - const auto GetNextVmInt = [&](const u32 bit_width) { - VmInt val{}; - - const u32 first_dword = GetNextDword(); - switch (bit_width) { - case 1: - val.bit8 = static_cast(first_dword); - break; - case 2: - val.bit16 = static_cast(first_dword); - break; - case 4: - val.bit32 = first_dword; - break; - case 8: - val.bit64 = (static_cast(first_dword) << 32ul) | static_cast(GetNextDword()); - break; - } - - return val; - }; - - // Read opcode. - const u32 first_dword = GetNextDword(); - if (!valid) { - return valid; - } - - auto opcode_type = static_cast(((first_dword >> 28) & 0xF)); - if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { - opcode_type = static_cast((static_cast(opcode_type) << 4) | - ((first_dword >> 24) & 0xF)); - } - if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { - opcode_type = static_cast((static_cast(opcode_type) << 4) | - ((first_dword >> 20) & 0xF)); - } - - // detect condition start. - switch (opcode_type) { - case CheatVmOpcodeType::BeginConditionalBlock: - case CheatVmOpcodeType::BeginKeypressConditionalBlock: - case CheatVmOpcodeType::BeginRegisterConditionalBlock: - opcode.begin_conditional_block = true; - break; - default: - opcode.begin_conditional_block = false; - break; - } - - switch (opcode_type) { - case CheatVmOpcodeType::StoreStatic: { - // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) - // Read additional words. - const u32 second_dword = GetNextDword(); - const u32 bit_width = (first_dword >> 24) & 0xF; - - opcode.opcode = StoreStaticOpcode{ - .bit_width = bit_width, - .mem_type = static_cast((first_dword >> 20) & 0xF), - .offset_register = (first_dword >> 16) & 0xF, - .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, - .value = GetNextVmInt(bit_width), - }; - } break; - case CheatVmOpcodeType::BeginConditionalBlock: { - // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) - // Read additional words. - const u32 second_dword = GetNextDword(); - const u32 bit_width = (first_dword >> 24) & 0xF; - - opcode.opcode = BeginConditionalOpcode{ - .bit_width = bit_width, - .mem_type = static_cast((first_dword >> 20) & 0xF), - .cond_type = static_cast((first_dword >> 16) & 0xF), - .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, - .value = GetNextVmInt(bit_width), - }; - } break; - case CheatVmOpcodeType::EndConditionalBlock: { - // 20000000 - opcode.opcode = EndConditionalOpcode{ - .is_else = ((first_dword >> 24) & 0xf) == 1, - }; - } break; - case CheatVmOpcodeType::ControlLoop: { - // 300R0000 VVVVVVVV - // 310R0000 - // Parse register, whether loop start or loop end. - ControlLoopOpcode ctrl_loop{ - .start_loop = ((first_dword >> 24) & 0xF) == 0, - .reg_index = (first_dword >> 20) & 0xF, - .num_iters = 0, - }; - - // Read number of iters if loop start. - if (ctrl_loop.start_loop) { - ctrl_loop.num_iters = GetNextDword(); - } - opcode.opcode = ctrl_loop; - } break; - case CheatVmOpcodeType::LoadRegisterStatic: { - // 400R0000 VVVVVVVV VVVVVVVV - // Read additional words. - opcode.opcode = LoadRegisterStaticOpcode{ - .reg_index = (first_dword >> 16) & 0xF, - .value = (static_cast(GetNextDword()) << 32) | GetNextDword(), - }; - } break; - case CheatVmOpcodeType::LoadRegisterMemory: { - // 5TMRI0AA AAAAAAAA - // Read additional words. - const u32 second_dword = GetNextDword(); - opcode.opcode = LoadRegisterMemoryOpcode{ - .bit_width = (first_dword >> 24) & 0xF, - .mem_type = static_cast((first_dword >> 20) & 0xF), - .reg_index = ((first_dword >> 16) & 0xF), - .load_from_reg = ((first_dword >> 12) & 0xF) != 0, - .rel_address = (static_cast(first_dword & 0xFF) << 32) | second_dword, - }; - } break; - case CheatVmOpcodeType::StoreStaticToAddress: { - // 6T0RIor0 VVVVVVVV VVVVVVVV - // Read additional words. - opcode.opcode = StoreStaticToAddressOpcode{ - .bit_width = (first_dword >> 24) & 0xF, - .reg_index = (first_dword >> 16) & 0xF, - .increment_reg = ((first_dword >> 12) & 0xF) != 0, - .add_offset_reg = ((first_dword >> 8) & 0xF) != 0, - .offset_reg_index = (first_dword >> 4) & 0xF, - .value = (static_cast(GetNextDword()) << 32) | GetNextDword(), - }; - } break; - case CheatVmOpcodeType::PerformArithmeticStatic: { - // 7T0RC000 VVVVVVVV - // Read additional words. - opcode.opcode = PerformArithmeticStaticOpcode{ - .bit_width = (first_dword >> 24) & 0xF, - .reg_index = ((first_dword >> 16) & 0xF), - .math_type = static_cast((first_dword >> 12) & 0xF), - .value = GetNextDword(), - }; - } break; - case CheatVmOpcodeType::BeginKeypressConditionalBlock: { - // 8kkkkkkk - // Just parse the mask. - opcode.opcode = BeginKeypressConditionalOpcode{ - .key_mask = first_dword & 0x0FFFFFFF, - }; - } break; - case CheatVmOpcodeType::PerformArithmeticRegister: { - // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) - PerformArithmeticRegisterOpcode perform_math_reg{ - .bit_width = (first_dword >> 24) & 0xF, - .math_type = static_cast((first_dword >> 20) & 0xF), - .dst_reg_index = (first_dword >> 16) & 0xF, - .src_reg_1_index = (first_dword >> 12) & 0xF, - .src_reg_2_index = 0, - .has_immediate = ((first_dword >> 8) & 0xF) != 0, - .value = {}, - }; - if (perform_math_reg.has_immediate) { - perform_math_reg.src_reg_2_index = 0; - perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); - } else { - perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); - } - opcode.opcode = perform_math_reg; - } break; - case CheatVmOpcodeType::StoreRegisterToAddress: { - // ATSRIOxa (aaaaaaaa) - // A = opcode 10 - // T = bit width - // S = src register index - // R = address register index - // I = 1 if increment address register, 0 if not increment address register - // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, - // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + - // Relative Address - // x = offset register (for offset type 1), memory type (for offset type 3) - // a = relative address (for offset type 2+3) - StoreRegisterToAddressOpcode str_register{ - .bit_width = (first_dword >> 24) & 0xF, - .str_reg_index = (first_dword >> 20) & 0xF, - .addr_reg_index = (first_dword >> 16) & 0xF, - .increment_reg = ((first_dword >> 12) & 0xF) != 0, - .ofs_type = static_cast(((first_dword >> 8) & 0xF)), - .mem_type = MemoryAccessType::MainNso, - .ofs_reg_index = (first_dword >> 4) & 0xF, - .rel_address = 0, - }; - switch (str_register.ofs_type) { - case StoreRegisterOffsetType::None: - case StoreRegisterOffsetType::Reg: - // Nothing more to do - break; - case StoreRegisterOffsetType::Imm: - str_register.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - case StoreRegisterOffsetType::MemReg: - str_register.mem_type = static_cast((first_dword >> 4) & 0xF); - break; - case StoreRegisterOffsetType::MemImm: - case StoreRegisterOffsetType::MemImmReg: - str_register.mem_type = static_cast((first_dword >> 4) & 0xF); - str_register.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - default: - str_register.ofs_type = StoreRegisterOffsetType::None; - break; - } - opcode.opcode = str_register; - } break; - case CheatVmOpcodeType::BeginRegisterConditionalBlock: { - // C0TcSX## - // C0TcS0Ma aaaaaaaa - // C0TcS1Mr - // C0TcS2Ra aaaaaaaa - // C0TcS3Rr - // C0TcS400 VVVVVVVV (VVVVVVVV) - // C0TcS5X0 - // C0 = opcode 0xC0 - // T = bit width - // c = condition type. - // S = source register. - // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset - // register, - // 2 = register with relative offset, 3 = register with offset register, 4 = static - // value, 5 = other register. - // M = memory type. - // R = address register. - // a = relative address. - // r = offset register. - // X = other register. - // V = value. - - BeginRegisterConditionalOpcode begin_reg_cond{ - .bit_width = (first_dword >> 20) & 0xF, - .cond_type = static_cast((first_dword >> 16) & 0xF), - .val_reg_index = (first_dword >> 12) & 0xF, - .comp_type = static_cast((first_dword >> 8) & 0xF), - .mem_type = MemoryAccessType::MainNso, - .addr_reg_index = 0, - .other_reg_index = 0, - .ofs_reg_index = 0, - .rel_address = 0, - .value = {}, - }; - - switch (begin_reg_cond.comp_type) { - case CompareRegisterValueType::StaticValue: - begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); - break; - case CompareRegisterValueType::OtherRegister: - begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); - break; - case CompareRegisterValueType::MemoryRelAddr: - begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); - begin_reg_cond.rel_address = - (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - case CompareRegisterValueType::MemoryOfsReg: - begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); - begin_reg_cond.ofs_reg_index = (first_dword & 0xF); - break; - case CompareRegisterValueType::RegisterRelAddr: - begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; - begin_reg_cond.rel_address = - (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - case CompareRegisterValueType::RegisterOfsReg: - begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF; - begin_reg_cond.ofs_reg_index = first_dword & 0xF; - break; - } - opcode.opcode = begin_reg_cond; - } break; - case CheatVmOpcodeType::SaveRestoreRegister: { - // C10D0Sx0 - // C1 = opcode 0xC1 - // D = destination index. - // S = source index. - // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring - // a register. - // NOTE: If we add more save slots later, current encoding is backwards compatible. - opcode.opcode = SaveRestoreRegisterOpcode{ - .dst_index = (first_dword >> 16) & 0xF, - .src_index = (first_dword >> 8) & 0xF, - .op_type = static_cast((first_dword >> 4) & 0xF), - }; - } break; - case CheatVmOpcodeType::SaveRestoreRegisterMask: { - // C2x0XXXX - // C2 = opcode 0xC2 - // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. - // X = 16-bit bitmask, bit i --> save or restore register i. - SaveRestoreRegisterMaskOpcode save_restore_regmask{ - .op_type = static_cast((first_dword >> 20) & 0xF), - .should_operate = {}, - }; - for (std::size_t i = 0; i < NumRegisters; i++) { - save_restore_regmask.should_operate[i] = (first_dword & (1U << i)) != 0; - } - opcode.opcode = save_restore_regmask; - } break; - case CheatVmOpcodeType::ReadWriteStaticRegister: { - // C3000XXx - // C3 = opcode 0xC3. - // XX = static register index. - // x = register index. - opcode.opcode = ReadWriteStaticRegisterOpcode{ - .static_idx = (first_dword >> 4) & 0xFF, - .idx = first_dword & 0xF, - }; - } break; - case CheatVmOpcodeType::PauseProcess: { - /* FF0????? */ - /* FF0 = opcode 0xFF0 */ - /* Pauses the current process. */ - opcode.opcode = PauseProcessOpcode{}; - } break; - case CheatVmOpcodeType::ResumeProcess: { - /* FF0????? */ - /* FF0 = opcode 0xFF0 */ - /* Pauses the current process. */ - opcode.opcode = ResumeProcessOpcode{}; - } break; - case CheatVmOpcodeType::DebugLog: { - // FFFTIX## - // FFFTI0Ma aaaaaaaa - // FFFTI1Mr - // FFFTI2Ra aaaaaaaa - // FFFTI3Rr - // FFFTI4X0 - // FFF = opcode 0xFFF - // T = bit width. - // I = log id. - // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset - // register, - // 2 = register with relative offset, 3 = register with offset register, 4 = register - // value. - // M = memory type. - // R = address register. - // a = relative address. - // r = offset register. - // X = value register. - DebugLogOpcode debug_log{ - .bit_width = (first_dword >> 16) & 0xF, - .log_id = (first_dword >> 12) & 0xF, - .val_type = static_cast((first_dword >> 8) & 0xF), - .mem_type = MemoryAccessType::MainNso, - .addr_reg_index = 0, - .val_reg_index = 0, - .ofs_reg_index = 0, - .rel_address = 0, - }; - - switch (debug_log.val_type) { - case DebugLogValueType::RegisterValue: - debug_log.val_reg_index = (first_dword >> 4) & 0xF; - break; - case DebugLogValueType::MemoryRelAddr: - debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); - debug_log.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - case DebugLogValueType::MemoryOfsReg: - debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); - debug_log.ofs_reg_index = first_dword & 0xF; - break; - case DebugLogValueType::RegisterRelAddr: - debug_log.addr_reg_index = (first_dword >> 4) & 0xF; - debug_log.rel_address = (static_cast(first_dword & 0xF) << 32) | GetNextDword(); - break; - case DebugLogValueType::RegisterOfsReg: - debug_log.addr_reg_index = (first_dword >> 4) & 0xF; - debug_log.ofs_reg_index = first_dword & 0xF; - break; - } - opcode.opcode = debug_log; - } break; - case CheatVmOpcodeType::ExtendedWidth: - case CheatVmOpcodeType::DoubleExtendedWidth: - default: - // Unrecognized instruction cannot be decoded. - valid = false; - opcode.opcode = UnrecognizedInstruction{opcode_type}; - break; - } - - // End decoding. - return valid; -} - -void DmntCheatVm::SkipConditionalBlock(bool is_if) { - if (condition_depth > 0) { - // We want to continue until we're out of the current block. - const std::size_t desired_depth = condition_depth - 1; - - CheatVmOpcode skip_opcode{}; - while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) { - // Decode instructions until we see end of the current conditional block. - // NOTE: This is broken in gateway's implementation. - // Gateway currently checks for "0x2" instead of "0x20000000" - // In addition, they do a linear scan instead of correctly decoding opcodes. - // This causes issues if "0x2" appears as an immediate in the conditional block... - - // We also support nesting of conditional blocks, and Gateway does not. - if (skip_opcode.begin_conditional_block) { - condition_depth++; - } else if (auto end_cond = std::get_if(&skip_opcode.opcode)) { - if (!end_cond->is_else) { - condition_depth--; - } else if (is_if && condition_depth - 1 == desired_depth) { - break; - } - } - } - } else { - // Skipping, but condition_depth = 0. - // This is an error condition. - // However, I don't actually believe it is possible for this to happen. - // I guess we'll throw a fatal error here, so as to encourage me to fix the VM - // in the event that someone triggers it? I don't know how you'd do that. - UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); - } -} - -u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { - switch (bit_width) { - case 1: - return value.bit8; - case 2: - return value.bit16; - case 4: - return value.bit32; - case 8: - return value.bit64; - default: - // Invalid bit width -> return 0. - return 0; - } -} - -u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, - MemoryAccessType mem_type, u64 rel_address) { - switch (mem_type) { - case MemoryAccessType::MainNso: - default: - return metadata.main_nso_extents.base + rel_address; - case MemoryAccessType::Heap: - return metadata.heap_extents.base + rel_address; - case MemoryAccessType::Alias: - return metadata.alias_extents.base + rel_address; - case MemoryAccessType::Aslr: - return metadata.aslr_extents.base + rel_address; - } -} - -void DmntCheatVm::ResetState() { - registers.fill(0); - saved_values.fill(0); - loop_tops.fill(0); - instruction_ptr = 0; - condition_depth = 0; - decode_success = true; -} - -bool DmntCheatVm::LoadProgram(const std::vector& entries) { - // Reset opcode count. - num_opcodes = 0; - - for (std::size_t i = 0; i < entries.size(); i++) { - if (entries[i].enabled) { - // Bounds check. - if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { - num_opcodes = 0; - return false; - } - - for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { - program[num_opcodes++] = entries[i].definition.opcodes[n]; - } - } - } - - return true; -} - -void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { - CheatVmOpcode cur_opcode{}; - - // Get Keys down. - u64 kDown = callbacks->HidKeysDown(); - - callbacks->CommandLog("Started VM execution."); - callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); - callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); - callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast(kDown & 0x0FFFFFFF))); - - // Clear VM state. - ResetState(); - - // Loop until program finishes. - while (DecodeNextOpcode(cur_opcode)) { - callbacks->CommandLog( - fmt::format("Instruction Ptr: {:04X}", static_cast(instruction_ptr))); - - for (std::size_t i = 0; i < NumRegisters; i++) { - callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); - } - - for (std::size_t i = 0; i < NumRegisters; i++) { - callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); - } - LogOpcode(cur_opcode); - - // Increment conditional depth, if relevant. - if (cur_opcode.begin_conditional_block) { - condition_depth++; - } - - if (auto store_static = std::get_if(&cur_opcode.opcode)) { - // Calculate address, write value to memory. - u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, - store_static->rel_address + - registers[store_static->offset_register]); - u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); - switch (store_static->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryWriteUnsafe(dst_address, &dst_value, store_static->bit_width); - break; - } - } else if (auto begin_cond = std::get_if(&cur_opcode.opcode)) { - // Read value from memory. - u64 src_address = - GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); - u64 src_value = 0; - switch (begin_cond->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryReadUnsafe(src_address, &src_value, begin_cond->bit_width); - break; - } - // Check against condition. - u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); - bool cond_met = false; - switch (begin_cond->cond_type) { - case ConditionalComparisonType::GT: - cond_met = src_value > cond_value; - break; - case ConditionalComparisonType::GE: - cond_met = src_value >= cond_value; - break; - case ConditionalComparisonType::LT: - cond_met = src_value < cond_value; - break; - case ConditionalComparisonType::LE: - cond_met = src_value <= cond_value; - break; - case ConditionalComparisonType::EQ: - cond_met = src_value == cond_value; - break; - case ConditionalComparisonType::NE: - cond_met = src_value != cond_value; - break; - } - // Skip conditional block if condition not met. - if (!cond_met) { - SkipConditionalBlock(true); - } - } else if (auto end_cond = std::get_if(&cur_opcode.opcode)) { - if (end_cond->is_else) { - /* Skip to the end of the conditional block. */ - this->SkipConditionalBlock(false); - } else { - /* Decrement the condition depth. */ - /* We will assume, graciously, that mismatched conditional block ends are a nop. */ - if (condition_depth > 0) { - condition_depth--; - } - } - } else if (auto ctrl_loop = std::get_if(&cur_opcode.opcode)) { - if (ctrl_loop->start_loop) { - // Start a loop. - registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; - loop_tops[ctrl_loop->reg_index] = instruction_ptr; - } else { - // End a loop. - registers[ctrl_loop->reg_index]--; - if (registers[ctrl_loop->reg_index] != 0) { - instruction_ptr = loop_tops[ctrl_loop->reg_index]; - } - } - } else if (auto ldr_static = std::get_if(&cur_opcode.opcode)) { - // Set a register to a static value. - registers[ldr_static->reg_index] = ldr_static->value; - } else if (auto ldr_memory = std::get_if(&cur_opcode.opcode)) { - // Choose source address. - u64 src_address; - if (ldr_memory->load_from_reg) { - src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; - } else { - src_address = - GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); - } - // Read into register. Gateway only reads on valid bitwidth. - switch (ldr_memory->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryReadUnsafe(src_address, ®isters[ldr_memory->reg_index], - ldr_memory->bit_width); - break; - } - } else if (auto str_static = std::get_if(&cur_opcode.opcode)) { - // Calculate address. - u64 dst_address = registers[str_static->reg_index]; - u64 dst_value = str_static->value; - if (str_static->add_offset_reg) { - dst_address += registers[str_static->offset_reg_index]; - } - // Write value to memory. Gateway only writes on valid bitwidth. - switch (str_static->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_static->bit_width); - break; - } - // Increment register if relevant. - if (str_static->increment_reg) { - registers[str_static->reg_index] += str_static->bit_width; - } - } else if (auto perform_math_static = - std::get_if(&cur_opcode.opcode)) { - // Do requested math. - switch (perform_math_static->math_type) { - case RegisterArithmeticType::Addition: - registers[perform_math_static->reg_index] += - static_cast(perform_math_static->value); - break; - case RegisterArithmeticType::Subtraction: - registers[perform_math_static->reg_index] -= - static_cast(perform_math_static->value); - break; - case RegisterArithmeticType::Multiplication: - registers[perform_math_static->reg_index] *= - static_cast(perform_math_static->value); - break; - case RegisterArithmeticType::LeftShift: - registers[perform_math_static->reg_index] <<= - static_cast(perform_math_static->value); - break; - case RegisterArithmeticType::RightShift: - registers[perform_math_static->reg_index] >>= - static_cast(perform_math_static->value); - break; - default: - // Do not handle extensions here. - break; - } - // Apply bit width. - switch (perform_math_static->bit_width) { - case 1: - registers[perform_math_static->reg_index] = - static_cast(registers[perform_math_static->reg_index]); - break; - case 2: - registers[perform_math_static->reg_index] = - static_cast(registers[perform_math_static->reg_index]); - break; - case 4: - registers[perform_math_static->reg_index] = - static_cast(registers[perform_math_static->reg_index]); - break; - case 8: - registers[perform_math_static->reg_index] = - static_cast(registers[perform_math_static->reg_index]); - break; - } - } else if (auto begin_keypress_cond = - std::get_if(&cur_opcode.opcode)) { - // Check for keypress. - if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { - // Keys not pressed. Skip conditional block. - SkipConditionalBlock(true); - } - } else if (auto perform_math_reg = - std::get_if(&cur_opcode.opcode)) { - const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index]; - const u64 operand_2_value = - perform_math_reg->has_immediate - ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) - : registers[perform_math_reg->src_reg_2_index]; - - u64 res_val = 0; - // Do requested math. - switch (perform_math_reg->math_type) { - case RegisterArithmeticType::Addition: - res_val = operand_1_value + operand_2_value; - break; - case RegisterArithmeticType::Subtraction: - res_val = operand_1_value - operand_2_value; - break; - case RegisterArithmeticType::Multiplication: - res_val = operand_1_value * operand_2_value; - break; - case RegisterArithmeticType::LeftShift: - res_val = operand_1_value << operand_2_value; - break; - case RegisterArithmeticType::RightShift: - res_val = operand_1_value >> operand_2_value; - break; - case RegisterArithmeticType::LogicalAnd: - res_val = operand_1_value & operand_2_value; - break; - case RegisterArithmeticType::LogicalOr: - res_val = operand_1_value | operand_2_value; - break; - case RegisterArithmeticType::LogicalNot: - res_val = ~operand_1_value; - break; - case RegisterArithmeticType::LogicalXor: - res_val = operand_1_value ^ operand_2_value; - break; - case RegisterArithmeticType::None: - res_val = operand_1_value; - break; - } - - // Apply bit width. - switch (perform_math_reg->bit_width) { - case 1: - res_val = static_cast(res_val); - break; - case 2: - res_val = static_cast(res_val); - break; - case 4: - res_val = static_cast(res_val); - break; - case 8: - res_val = static_cast(res_val); - break; - } - - // Save to register. - registers[perform_math_reg->dst_reg_index] = res_val; - } else if (auto str_register = - std::get_if(&cur_opcode.opcode)) { - // Calculate address. - u64 dst_value = registers[str_register->str_reg_index]; - u64 dst_address = registers[str_register->addr_reg_index]; - switch (str_register->ofs_type) { - case StoreRegisterOffsetType::None: - // Nothing more to do - break; - case StoreRegisterOffsetType::Reg: - dst_address += registers[str_register->ofs_reg_index]; - break; - case StoreRegisterOffsetType::Imm: - dst_address += str_register->rel_address; - break; - case StoreRegisterOffsetType::MemReg: - dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, - registers[str_register->addr_reg_index]); - break; - case StoreRegisterOffsetType::MemImm: - dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, - str_register->rel_address); - break; - case StoreRegisterOffsetType::MemImmReg: - dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, - registers[str_register->addr_reg_index] + - str_register->rel_address); - break; - } - - // Write value to memory. Write only on valid bitwidth. - switch (str_register->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_register->bit_width); - break; - } - - // Increment register if relevant. - if (str_register->increment_reg) { - registers[str_register->addr_reg_index] += str_register->bit_width; - } - } else if (auto begin_reg_cond = - std::get_if(&cur_opcode.opcode)) { - // Get value from register. - u64 src_value = 0; - switch (begin_reg_cond->bit_width) { - case 1: - src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFul); - break; - case 2: - src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFul); - break; - case 4: - src_value = - static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul); - break; - case 8: - src_value = static_cast(registers[begin_reg_cond->val_reg_index] & - 0xFFFFFFFFFFFFFFFFul); - break; - } - - // Read value from memory. - u64 cond_value = 0; - if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { - cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); - } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { - switch (begin_reg_cond->bit_width) { - case 1: - cond_value = - static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFul); - break; - case 2: - cond_value = - static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFul); - break; - case 4: - cond_value = - static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul); - break; - case 8: - cond_value = static_cast(registers[begin_reg_cond->other_reg_index] & - 0xFFFFFFFFFFFFFFFFul); - break; - } - } else { - u64 cond_address = 0; - switch (begin_reg_cond->comp_type) { - case CompareRegisterValueType::MemoryRelAddr: - cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, - begin_reg_cond->rel_address); - break; - case CompareRegisterValueType::MemoryOfsReg: - cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, - registers[begin_reg_cond->ofs_reg_index]); - break; - case CompareRegisterValueType::RegisterRelAddr: - cond_address = - registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address; - break; - case CompareRegisterValueType::RegisterOfsReg: - cond_address = registers[begin_reg_cond->addr_reg_index] + - registers[begin_reg_cond->ofs_reg_index]; - break; - default: - break; - } - switch (begin_reg_cond->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryReadUnsafe(cond_address, &cond_value, - begin_reg_cond->bit_width); - break; - } - } - - // Check against condition. - bool cond_met = false; - switch (begin_reg_cond->cond_type) { - case ConditionalComparisonType::GT: - cond_met = src_value > cond_value; - break; - case ConditionalComparisonType::GE: - cond_met = src_value >= cond_value; - break; - case ConditionalComparisonType::LT: - cond_met = src_value < cond_value; - break; - case ConditionalComparisonType::LE: - cond_met = src_value <= cond_value; - break; - case ConditionalComparisonType::EQ: - cond_met = src_value == cond_value; - break; - case ConditionalComparisonType::NE: - cond_met = src_value != cond_value; - break; - } - - // Skip conditional block if condition not met. - if (!cond_met) { - SkipConditionalBlock(true); - } - } else if (auto save_restore_reg = - std::get_if(&cur_opcode.opcode)) { - // Save or restore a register. - switch (save_restore_reg->op_type) { - case SaveRestoreRegisterOpType::ClearRegs: - registers[save_restore_reg->dst_index] = 0ul; - break; - case SaveRestoreRegisterOpType::ClearSaved: - saved_values[save_restore_reg->dst_index] = 0ul; - break; - case SaveRestoreRegisterOpType::Save: - saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; - break; - case SaveRestoreRegisterOpType::Restore: - default: - registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; - break; - } - } else if (auto save_restore_regmask = - std::get_if(&cur_opcode.opcode)) { - // Save or restore register mask. - u64* src; - u64* dst; - switch (save_restore_regmask->op_type) { - case SaveRestoreRegisterOpType::ClearSaved: - case SaveRestoreRegisterOpType::Save: - src = registers.data(); - dst = saved_values.data(); - break; - case SaveRestoreRegisterOpType::ClearRegs: - case SaveRestoreRegisterOpType::Restore: - default: - src = saved_values.data(); - dst = registers.data(); - break; - } - for (std::size_t i = 0; i < NumRegisters; i++) { - if (save_restore_regmask->should_operate[i]) { - switch (save_restore_regmask->op_type) { - case SaveRestoreRegisterOpType::ClearSaved: - case SaveRestoreRegisterOpType::ClearRegs: - dst[i] = 0ul; - break; - case SaveRestoreRegisterOpType::Save: - case SaveRestoreRegisterOpType::Restore: - default: - dst[i] = src[i]; - break; - } - } - } - } else if (auto rw_static_reg = - std::get_if(&cur_opcode.opcode)) { - if (rw_static_reg->static_idx < NumReadableStaticRegisters) { - // Load a register with a static register. - registers[rw_static_reg->idx] = static_registers[rw_static_reg->static_idx]; - } else { - // Store a register to a static register. - static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx]; - } - } else if (std::holds_alternative(cur_opcode.opcode)) { - callbacks->PauseProcess(); - } else if (std::holds_alternative(cur_opcode.opcode)) { - callbacks->ResumeProcess(); - } else if (auto debug_log = std::get_if(&cur_opcode.opcode)) { - // Read value from memory. - u64 log_value = 0; - if (debug_log->val_type == DebugLogValueType::RegisterValue) { - switch (debug_log->bit_width) { - case 1: - log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFul); - break; - case 2: - log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFFFul); - break; - case 4: - log_value = - static_cast(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); - break; - case 8: - log_value = static_cast(registers[debug_log->val_reg_index] & - 0xFFFFFFFFFFFFFFFFul); - break; - } - } else { - u64 val_address = 0; - switch (debug_log->val_type) { - case DebugLogValueType::MemoryRelAddr: - val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, - debug_log->rel_address); - break; - case DebugLogValueType::MemoryOfsReg: - val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, - registers[debug_log->ofs_reg_index]); - break; - case DebugLogValueType::RegisterRelAddr: - val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; - break; - case DebugLogValueType::RegisterOfsReg: - val_address = - registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; - break; - default: - break; - } - switch (debug_log->bit_width) { - case 1: - case 2: - case 4: - case 8: - callbacks->MemoryReadUnsafe(val_address, &log_value, debug_log->bit_width); - break; - } - } - - // Log value. - DebugLog(debug_log->log_id, log_value); - } - } -} - -} // namespace Core::Memory diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h deleted file mode 100644 index de5e81add2..0000000000 --- a/src/core/memory/dmnt_cheat_vm.h +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include -#include "common/common_types.h" -#include "core/memory/dmnt_cheat_types.h" - -namespace Core::Memory { - -enum class CheatVmOpcodeType : u32 { - StoreStatic = 0, - BeginConditionalBlock = 1, - EndConditionalBlock = 2, - ControlLoop = 3, - LoadRegisterStatic = 4, - LoadRegisterMemory = 5, - StoreStaticToAddress = 6, - PerformArithmeticStatic = 7, - BeginKeypressConditionalBlock = 8, - - // These are not implemented by Gateway's VM. - PerformArithmeticRegister = 9, - StoreRegisterToAddress = 10, - Reserved11 = 11, - - // This is a meta entry, and not a real opcode. - // This is to facilitate multi-nybble instruction decoding. - ExtendedWidth = 12, - - // Extended width opcodes. - BeginRegisterConditionalBlock = 0xC0, - SaveRestoreRegister = 0xC1, - SaveRestoreRegisterMask = 0xC2, - ReadWriteStaticRegister = 0xC3, - - // This is a meta entry, and not a real opcode. - // This is to facilitate multi-nybble instruction decoding. - DoubleExtendedWidth = 0xF0, - - // Double-extended width opcodes. - PauseProcess = 0xFF0, - ResumeProcess = 0xFF1, - DebugLog = 0xFFF, -}; - -enum class MemoryAccessType : u32 { - MainNso = 0, - Heap = 1, - Alias = 2, - Aslr = 3, -}; - -enum class ConditionalComparisonType : u32 { - GT = 1, - GE = 2, - LT = 3, - LE = 4, - EQ = 5, - NE = 6, -}; - -enum class RegisterArithmeticType : u32 { - Addition = 0, - Subtraction = 1, - Multiplication = 2, - LeftShift = 3, - RightShift = 4, - - // These are not supported by Gateway's VM. - LogicalAnd = 5, - LogicalOr = 6, - LogicalNot = 7, - LogicalXor = 8, - - None = 9, -}; - -enum class StoreRegisterOffsetType : u32 { - None = 0, - Reg = 1, - Imm = 2, - MemReg = 3, - MemImm = 4, - MemImmReg = 5, -}; - -enum class CompareRegisterValueType : u32 { - MemoryRelAddr = 0, - MemoryOfsReg = 1, - RegisterRelAddr = 2, - RegisterOfsReg = 3, - StaticValue = 4, - OtherRegister = 5, -}; - -enum class SaveRestoreRegisterOpType : u32 { - Restore = 0, - Save = 1, - ClearSaved = 2, - ClearRegs = 3, -}; - -enum class DebugLogValueType : u32 { - MemoryRelAddr = 0, - MemoryOfsReg = 1, - RegisterRelAddr = 2, - RegisterOfsReg = 3, - RegisterValue = 4, -}; - -union VmInt { - u8 bit8; - u16 bit16; - u32 bit32; - u64 bit64; -}; - -struct StoreStaticOpcode { - u32 bit_width{}; - MemoryAccessType mem_type{}; - u32 offset_register{}; - u64 rel_address{}; - VmInt value{}; -}; - -struct BeginConditionalOpcode { - u32 bit_width{}; - MemoryAccessType mem_type{}; - ConditionalComparisonType cond_type{}; - u64 rel_address{}; - VmInt value{}; -}; - -struct EndConditionalOpcode { - bool is_else; -}; - -struct ControlLoopOpcode { - bool start_loop{}; - u32 reg_index{}; - u32 num_iters{}; -}; - -struct LoadRegisterStaticOpcode { - u32 reg_index{}; - u64 value{}; -}; - -struct LoadRegisterMemoryOpcode { - u32 bit_width{}; - MemoryAccessType mem_type{}; - u32 reg_index{}; - bool load_from_reg{}; - u64 rel_address{}; -}; - -struct StoreStaticToAddressOpcode { - u32 bit_width{}; - u32 reg_index{}; - bool increment_reg{}; - bool add_offset_reg{}; - u32 offset_reg_index{}; - u64 value{}; -}; - -struct PerformArithmeticStaticOpcode { - u32 bit_width{}; - u32 reg_index{}; - RegisterArithmeticType math_type{}; - u32 value{}; -}; - -struct BeginKeypressConditionalOpcode { - u32 key_mask{}; -}; - -struct PerformArithmeticRegisterOpcode { - u32 bit_width{}; - RegisterArithmeticType math_type{}; - u32 dst_reg_index{}; - u32 src_reg_1_index{}; - u32 src_reg_2_index{}; - bool has_immediate{}; - VmInt value{}; -}; - -struct StoreRegisterToAddressOpcode { - u32 bit_width{}; - u32 str_reg_index{}; - u32 addr_reg_index{}; - bool increment_reg{}; - StoreRegisterOffsetType ofs_type{}; - MemoryAccessType mem_type{}; - u32 ofs_reg_index{}; - u64 rel_address{}; -}; - -struct BeginRegisterConditionalOpcode { - u32 bit_width{}; - ConditionalComparisonType cond_type{}; - u32 val_reg_index{}; - CompareRegisterValueType comp_type{}; - MemoryAccessType mem_type{}; - u32 addr_reg_index{}; - u32 other_reg_index{}; - u32 ofs_reg_index{}; - u64 rel_address{}; - VmInt value{}; -}; - -struct SaveRestoreRegisterOpcode { - u32 dst_index{}; - u32 src_index{}; - SaveRestoreRegisterOpType op_type{}; -}; - -struct SaveRestoreRegisterMaskOpcode { - SaveRestoreRegisterOpType op_type{}; - std::array should_operate{}; -}; - -struct ReadWriteStaticRegisterOpcode { - u32 static_idx{}; - u32 idx{}; -}; - -struct PauseProcessOpcode {}; - -struct ResumeProcessOpcode {}; - -struct DebugLogOpcode { - u32 bit_width{}; - u32 log_id{}; - DebugLogValueType val_type{}; - MemoryAccessType mem_type{}; - u32 addr_reg_index{}; - u32 val_reg_index{}; - u32 ofs_reg_index{}; - u64 rel_address{}; -}; - -struct UnrecognizedInstruction { - CheatVmOpcodeType opcode{}; -}; - -struct CheatVmOpcode { - bool begin_conditional_block{}; - std::variant - opcode{}; -}; - -class DmntCheatVm { -public: - /// Helper Type for DmntCheatVm <=> yuzu Interface - class Callbacks { - public: - virtual ~Callbacks(); - - virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0; - virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0; - - virtual u64 HidKeysDown() = 0; - - virtual void PauseProcess() = 0; - virtual void ResumeProcess() = 0; - - virtual void DebugLog(u8 id, u64 value) = 0; - virtual void CommandLog(std::string_view data) = 0; - }; - - static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; - static constexpr std::size_t NumRegisters = 0x10; - static constexpr std::size_t NumReadableStaticRegisters = 0x80; - static constexpr std::size_t NumWritableStaticRegisters = 0x80; - static constexpr std::size_t NumStaticRegisters = - NumReadableStaticRegisters + NumWritableStaticRegisters; - - explicit DmntCheatVm(std::unique_ptr callbacks_); - ~DmntCheatVm(); - - std::size_t GetProgramSize() const { - return this->num_opcodes; - } - - bool LoadProgram(const std::vector& cheats); - void Execute(const CheatProcessMetadata& metadata); - -private: - std::unique_ptr callbacks; - - std::size_t num_opcodes = 0; - std::size_t instruction_ptr = 0; - std::size_t condition_depth = 0; - bool decode_success = false; - std::array program{}; - std::array registers{}; - std::array saved_values{}; - std::array static_registers{}; - std::array loop_tops{}; - - bool DecodeNextOpcode(CheatVmOpcode& out); - void SkipConditionalBlock(bool is_if); - void ResetState(); - - // For implementing the DebugLog opcode. - void DebugLog(u32 log_id, u64 value); - - void LogOpcode(const CheatVmOpcode& opcode); - - static u64 GetVmInt(VmInt value, u32 bit_width); - static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, - MemoryAccessType mem_type, u64 rel_address); -}; - -}; // namespace Core::Memory diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 65f5b1ee2b..f0522c07d2 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later +set(CMAKE_AUTOMOC ON) + add_library(qt_common STATIC qt_common.h qt_common.cpp @@ -26,7 +28,7 @@ add_library(qt_common STATIC util/mod.h util/mod.cpp abstract/frontend.h abstract/frontend.cpp - abstract/qt_progress_dialog.h abstract/qt_progress_dialog.cpp + abstract/progress.h abstract/progress.cpp qt_string_lookup.h qt_compat.h @@ -93,3 +95,5 @@ if (UNIX AND NOT APPLE) target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate) endif() endif() + +create_target_directory_groups(qt_common) diff --git a/src/qt_common/abstract/frontend.cpp b/src/qt_common/abstract/frontend.cpp index 620256c2d8..de362b253f 100644 --- a/src/qt_common/abstract/frontend.cpp +++ b/src/qt_common/abstract/frontend.cpp @@ -1,75 +1,27 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include #include "frontend.h" -#include "qt_common/qt_common.h" - -#ifdef YUZU_QT_WIDGETS -#include -#endif - -#include -#include namespace QtCommon::Frontend { -StandardButton ShowMessage(Icon icon, const QString& title, const QString& text, - StandardButtons buttons, QObject* parent) { -#ifdef YUZU_QT_WIDGETS - QMessageBox* box = new QMessageBox(icon, title, text, buttons, (QWidget*)parent); - return static_cast(box->exec()); -#endif - // TODO(crueter): If Qt Widgets is disabled... - // need a way to reference icon/buttons too -} - const QString GetOpenFileName(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter, Options options) { -#ifdef YUZU_QT_WIDGETS - return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter, options); -#endif + QString* selectedFilter) { + return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter); } const QStringList GetOpenFileNames(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter, Options options) { -#ifdef YUZU_QT_WIDGETS - return QFileDialog::getOpenFileNames(rootObject, title, dir, filter, selectedFilter, options); -#endif + QString* selectedFilter) { + return QFileDialog::getOpenFileNames(rootObject, title, dir, filter, selectedFilter); } const QString GetSaveFileName(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter, Options options) { -#ifdef YUZU_QT_WIDGETS - return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter, options); -#endif + QString* selectedFilter) { + return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter); } -const QString GetExistingDirectory(const QString& caption, const QString& dir, Options options) { -#ifdef YUZU_QT_WIDGETS - return QFileDialog::getExistingDirectory(rootObject, caption, dir, options); -#endif -} - -int Choice(const QString& title, const QString& caption, const QStringList& options) { - QMessageBox box(rootObject); - box.setText(caption); - box.setWindowTitle(title); - - for (const QString& opt : options) { - box.addButton(opt, QMessageBox::AcceptRole); - } - - box.addButton(QMessageBox::Cancel); - - box.exec(); - auto button = box.clickedButton(); - return options.indexOf(button->text()); -} - -const QString GetTextInput(const QString& title, const QString& caption, - const QString& defaultText) { - return QInputDialog::getText(rootObject, title, caption, QLineEdit::Normal, defaultText); +const QString GetExistingDirectory(const QString& caption, const QString& dir) { + return QFileDialog::getExistingDirectory(rootObject, caption, dir); } } // namespace QtCommon::Frontend diff --git a/src/qt_common/abstract/frontend.h b/src/qt_common/abstract/frontend.h index 40ef80cbb7..9b70f6849e 100644 --- a/src/qt_common/abstract/frontend.h +++ b/src/qt_common/abstract/frontend.h @@ -7,11 +7,7 @@ #include #include "qt_common/qt_common.h" -#ifdef YUZU_QT_WIDGETS #include -#include -#include -#endif /** * manages common functionality e.g. message boxes and such for Qt/QML @@ -20,60 +16,39 @@ namespace QtCommon::Frontend { Q_NAMESPACE -#ifdef YUZU_QT_WIDGETS -using Options = QFileDialog::Options; -using Option = QFileDialog::Option; - -using StandardButton = QMessageBox::StandardButton; -using StandardButtons = QMessageBox::StandardButtons; - -using Icon = QMessageBox::Icon; -#else -enum Option { - ShowDirsOnly = 0x00000001, - DontResolveSymlinks = 0x00000002, - DontConfirmOverwrite = 0x00000004, - DontUseNativeDialog = 0x00000008, - ReadOnly = 0x00000010, - HideNameFilterDetails = 0x00000020, - DontUseCustomDirectoryIcons = 0x00000040 -}; -Q_ENUM_NS(Option) -Q_DECLARE_FLAGS(Options, Option) -Q_FLAG_NS(Options) - enum StandardButton { - // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton - NoButton = 0x00000000, - Ok = 0x00000400, - Save = 0x00000800, - SaveAll = 0x00001000, - Open = 0x00002000, - Yes = 0x00004000, - YesToAll = 0x00008000, - No = 0x00010000, - NoToAll = 0x00020000, - Abort = 0x00040000, - Retry = 0x00080000, - Ignore = 0x00100000, - Close = 0x00200000, - Cancel = 0x00400000, - Discard = 0x00800000, - Help = 0x01000000, - Apply = 0x02000000, - Reset = 0x04000000, - RestoreDefaults = 0x08000000, + // keep this in sync with QDialogButtonBox::StandardButton and + // QPlatformDialogHelper::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, - FirstButton = Ok, // internal - LastButton = RestoreDefaults, // internal + FirstButton = Ok, // internal + LastButton = RestoreDefaults, // internal - YesAll = YesToAll, // obsolete - NoAll = NoToAll, // obsolete + YesAll = YesToAll, // obsolete + NoAll = NoToAll, // obsolete - Default = 0x00000100, // obsolete - Escape = 0x00000200, // obsolete - FlagMask = 0x00000300, // obsolete - ButtonMask = ~FlagMask // obsolete + Default = 0x00000100, // obsolete + Escape = 0x00000200, // obsolete + FlagMask = 0x00000300, // obsolete + ButtonMask = ~FlagMask // obsolete }; Q_ENUM_NS(StandardButton) @@ -83,7 +58,7 @@ typedef StandardButton Button; Q_DECLARE_FLAGS(StandardButtons, StandardButton) Q_FLAG_NS(StandardButtons) -enum Icon { +enum class Icon { // keep this in sync with QMessageDialogOptions::StandardIcon NoIcon = 0, Information = 1, @@ -93,29 +68,26 @@ enum Icon { }; Q_ENUM_NS(Icon) -#endif - -// TODO(crueter) widgets-less impl, choices et al. -StandardButton ShowMessage(Icon icon, - const QString &title, - const QString &text, +StandardButton ShowMessage(Icon icon, const QString& title, const QString& text, StandardButtons buttons = StandardButton::NoButton, - QObject *parent = nullptr); + QObject* parent = nullptr); -#define UTIL_OVERRIDES(level) \ - inline StandardButton level(QObject *parent, \ - const QString &title, \ - const QString &text, \ - StandardButtons buttons = StandardButton::Ok) \ - { \ - return ShowMessage(Icon::level, title, text, buttons, parent); \ - } \ - inline StandardButton level(const QString title, \ - const QString &text, \ - StandardButtons buttons \ - = StandardButton::Ok) \ - { \ - return ShowMessage(Icon::level, title, text, buttons, rootObject); \ +#define UTIL_OVERRIDES(level) \ + inline StandardButton level(QObject* parent, const QString& title, const QString& text, \ + StandardButtons buttons) { \ + return ShowMessage(Icon::level, title, text, buttons, parent); \ + } \ + inline StandardButton level(QObject* parent, const QString& title, const QString& text, \ + int buttons = StandardButton::Ok) { \ + return ShowMessage(Icon::level, title, text, StandardButtons(buttons), parent); \ + } \ + inline StandardButton level(const QString title, const QString& text, \ + StandardButtons buttons) { \ + return ShowMessage(Icon::level, title, text, buttons, rootObject); \ + } \ + inline StandardButton level(const QString& title, const QString& text, \ + int buttons = StandardButton::Ok) { \ + return ShowMessage(Icon::level, title, text, StandardButtons(buttons), rootObject); \ } UTIL_OVERRIDES(Information) @@ -123,27 +95,17 @@ UTIL_OVERRIDES(Warning) UTIL_OVERRIDES(Critical) UTIL_OVERRIDES(Question) -const QString GetOpenFileName(const QString &title, - const QString &dir, - const QString &filter, - QString *selectedFilter = nullptr, - Options options = Options()); +const QString GetOpenFileName(const QString& title, const QString& dir, const QString& filter, + QString* selectedFilter = nullptr); -const QStringList GetOpenFileNames(const QString &title, - const QString &dir, - const QString &filter, - QString *selectedFilter = nullptr, - Options options = Options()); +const QStringList GetOpenFileNames(const QString& title, const QString& dir, const QString& filter, + QString* selectedFilter = nullptr); -const QString GetSaveFileName(const QString &title, - const QString &dir, - const QString &filter, - QString *selectedFilter = nullptr, - Options options = Options()); +const QString GetSaveFileName(const QString& title, const QString& dir, const QString& filter, + QString* selectedFilter = nullptr); -const QString GetExistingDirectory(const QString &caption = QString(), - const QString &dir = QString(), - Options options = Option::ShowDirsOnly); +const QString GetExistingDirectory(const QString& caption = QString(), + const QString& dir = QString()); int Choice(const QString& title = QString(), const QString& caption = QString(), const QStringList& options = {}); diff --git a/src/qt_common/abstract/progress.cpp b/src/qt_common/abstract/progress.cpp new file mode 100644 index 0000000000..2141e41d44 --- /dev/null +++ b/src/qt_common/abstract/progress.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "progress.h" + +namespace QtCommon::Frontend { + +QtProgressDialog::QtProgressDialog(const QString&, const QString&, int, int, QObject* parent, + Qt::WindowFlags) + : QObject(parent) {} + +} // namespace QtCommon::Frontend diff --git a/src/qt_common/abstract/progress.h b/src/qt_common/abstract/progress.h new file mode 100644 index 0000000000..b6f2975c1e --- /dev/null +++ b/src/qt_common/abstract/progress.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace QtCommon::Frontend { + +class QtProgressDialog : public QObject { + Q_OBJECT +public: + QtProgressDialog(const QString& labelText, const QString& cancelButtonText, int minimum, + int maximum, QObject* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + + virtual ~QtProgressDialog() = default; + + virtual bool wasCanceled() const = 0; + virtual void setWindowModality(Qt::WindowModality modality) = 0; + virtual void setMinimumDuration(int durationMs) = 0; + virtual void setAutoClose(bool autoClose) = 0; + virtual void setAutoReset(bool autoReset) = 0; + +public slots: + virtual void setTitle(QString title) = 0; + virtual void setLabelText(QString text) = 0; + virtual void setMinimum(int min) = 0; + virtual void setMaximum(int max) = 0; + virtual void setValue(int value) = 0; + + virtual bool close() = 0; + virtual void show() = 0; +}; + +std::unique_ptr newProgressDialog(const QString& labelText, + const QString& cancelButtonText, int minimum, + int maximum, + Qt::WindowFlags f = Qt::WindowFlags()); + +QtProgressDialog* newProgressDialogPtr(const QString& labelText, const QString& cancelButtonText, + int minimum, int maximum, + Qt::WindowFlags f = Qt::WindowFlags()); + +} // namespace QtCommon::Frontend diff --git a/src/qt_common/abstract/qt_progress_dialog.cpp b/src/qt_common/abstract/qt_progress_dialog.cpp deleted file mode 100644 index b4bf74c8bd..0000000000 --- a/src/qt_common/abstract/qt_progress_dialog.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "qt_progress_dialog.h" diff --git a/src/qt_common/abstract/qt_progress_dialog.h b/src/qt_common/abstract/qt_progress_dialog.h deleted file mode 100644 index 17f6817ffa..0000000000 --- a/src/qt_common/abstract/qt_progress_dialog.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef QT_PROGRESS_DIALOG_H -#define QT_PROGRESS_DIALOG_H - -#include - -#ifdef YUZU_QT_WIDGETS -#include -#endif - -namespace QtCommon::Frontend { -#ifdef YUZU_QT_WIDGETS - -using QtProgressDialog = QProgressDialog; - -// TODO(crueter): QML impl -#else -class QtProgressDialog -{ -public: - QtProgressDialog(const QString &labelText, - const QString &cancelButtonText, - int minimum, - int maximum, - QObject *parent = nullptr, - Qt::WindowFlags f = Qt::WindowFlags()); - - bool wasCanceled() const; - void setWindowModality(Qt::WindowModality modality); - void setMinimumDuration(int durationMs); - void setAutoClose(bool autoClose); - void setAutoReset(bool autoReset); - -public slots: - void setLabelText(QString &text); - void setRange(int min, int max); - void setValue(int progress); - bool close(); - - void show(); -}; -#endif // YUZU_QT_WIDGETS - -} -#endif // QT_PROGRESS_DIALOG_H diff --git a/src/qt_common/config/qt_config.cpp b/src/qt_common/config/qt_config.cpp index c5a8f62745..2ae03ae3b8 100644 --- a/src/qt_common/config/qt_config.cpp +++ b/src/qt_common/config/qt_config.cpp @@ -319,7 +319,7 @@ void QtConfig::ReadUIGamelistValues() { } void QtConfig::ReadUILayoutValues() { - BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList)); + BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout)); ReadCategory(Settings::Category::UiLayout); @@ -578,10 +578,10 @@ void QtConfig::SaveMultiplayerValues() { } std::vector& QtConfig::FindRelevantList(Settings::Category category) { - auto& map = Settings::values.linkage.by_category; - if (map.contains(category)) { - return Settings::values.linkage.by_category[category]; - } + auto& list = Settings::values.linkage.by_category[category]; + if (!list.empty()) + return list; + return UISettings::values.linkage.by_category[category]; } diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index d1ed32134c..a0c4779b73 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -9,24 +9,23 @@ #include "shared_translation.h" +#include +#include +#include #include #include "common/settings.h" #include "common/settings_enums.h" #include "common/settings_setting.h" #include "common/time_zone.h" #include "qt_common/config/uisettings.h" -#include -#include -#include namespace ConfigurationShared { -std::unique_ptr InitializeTranslations(QObject* parent) -{ +std::unique_ptr InitializeTranslations(QObject* parent) { std::unique_ptr translations = std::make_unique(); const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; -#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ +#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) // A setting can be ignored by giving it a blank name @@ -47,10 +46,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, login_share_applet_mode, tr("Login share"), QString()); INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QString()); INSERT(Settings, my_page_applet_mode, tr("My page"), QString()); - INSERT(Settings, - enable_overlay, - tr("Enable Overlay Applet"), - tr("Enables Horizon\'s built-in overlay applet. Press and hold the home button for 1 second to show it.")); + INSERT(Settings, enable_overlay, tr("Enable Overlay Applet"), + tr("Enables Horizon\'s built-in overlay applet. Press and hold the home button for 1 " + "second to show it.")); // Audio INSERT(Settings, sink_id, tr("Output Engine:"), QString()); @@ -62,23 +60,16 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"), QString()); // Core - INSERT( - Settings, - use_multi_core, - tr("Multicore CPU Emulation"), - tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n" - "This is mainly a debug option and shouldn't be disabled.")); - INSERT( - Settings, - memory_layout_mode, - tr("Memory Layout"), - tr("Increases the amount of emulated RAM.\nDoesn't affect performance/stability but may allow HD texture " - "mods to load.")); + INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"), + tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n" + "This is mainly a debug option and shouldn't be disabled.")); + INSERT(Settings, memory_layout_mode, tr("Memory Layout"), + tr("Increases the amount of emulated RAM.\nDoesn't affect performance/stability but may " + "allow HD texture " + "mods to load.")); INSERT(Settings, use_speed_limit, QString(), QString()); INSERT(Settings, current_speed_mode, QString(), QString()); - INSERT(Settings, - speed_limit, - tr("Limit Speed Percent"), + INSERT(Settings, speed_limit, tr("Limit Speed Percent"), tr("Controls the game's maximum rendering speed, but it's up to each game if it runs " "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a " "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the " @@ -91,171 +82,128 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("When the Slow Speed hotkey is pressed, the speed will be limited to this " "percentage.")); - INSERT(Settings, - sync_core_speed, - tr("Synchronize Core Speed"), + INSERT(Settings, sync_core_speed, tr("Synchronize Core Speed"), tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS " "without affecting game speed (animations, physics, etc.).\n" "Can help reduce stuttering at lower framerates.")); // Cpu - INSERT(Settings, - cpu_accuracy, - tr("Accuracy:"), + INSERT(Settings, cpu_accuracy, tr("Accuracy:"), tr("Change the accuracy of the emulated CPU (for debugging only).")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); - INSERT(Settings, - fast_cpu_time, - tr("CPU Overclock"), - tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, " - "and certain games may behave improperly.\nUse Boost (1700MHz) to run at the Switch's highest native " + INSERT(Settings, fast_cpu_time, tr("CPU Overclock"), + tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see " + "reduced performance, " + "and certain games may behave improperly.\nUse Boost (1700MHz) to run at the " + "Switch's highest native " "clock, or Fast (2000MHz) to run at 2x clock.")); INSERT(Settings, use_custom_cpu_ticks, QString(), QString()); - INSERT(Settings, - cpu_ticks, - tr("Custom CPU Ticks"), + INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"), tr("Set a custom value of CPU ticks. Higher values can increase performance, but may " "cause deadlocks. A range of 77-21000 is recommended.")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); - INSERT(Settings, vtable_bouncing, - tr("Virtual Table Bouncing"), - tr("Bounces (by emulating a 0-valued return) any functions that triggers a prefetch abort")); + INSERT(Settings, vtable_bouncing, tr("Virtual Table Bouncing"), + tr("Bounces (by emulating a 0-valued return) any functions that triggers a prefetch " + "abort")); // Cpu Debug // Cpu Unsafe - INSERT(Settings, cpuopt_unsafe_host_mmu, tr("Enable Host MMU Emulation (fastmem)"), - tr("This optimization speeds up memory accesses by the guest program.\nEnabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.\nDisabling this forces all memory accesses to use Software MMU Emulation.")); INSERT( - Settings, - cpuopt_unsafe_unfuse_fma, + Settings, cpuopt_unsafe_host_mmu, tr("Enable Host MMU Emulation (fastmem)"), + tr("This optimization speeds up memory accesses by the guest program.\nEnabling it causes " + "guest memory reads/writes to be done directly into memory and make use of Host's " + "MMU.\nDisabling this forces all memory accesses to use Software MMU Emulation.")); + INSERT( + Settings, cpuopt_unsafe_unfuse_fma, tr("Unfuse FMA (improve performance on CPUs without FMA)"), tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on " "CPUs without native FMA support.")); INSERT( - Settings, - cpuopt_unsafe_reduce_fp_error, - tr("Faster FRSQRTE and FRECPE"), + Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"), tr("This option improves the speed of some approximate floating-point functions by using " "less accurate native approximations.")); - INSERT(Settings, - cpuopt_unsafe_ignore_standard_fpcr, + INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, tr("Faster ASIMD instructions (32 bits only)"), tr("This option improves the speed of 32 bits ASIMD floating-point functions by running " "with incorrect rounding modes.")); - INSERT(Settings, - cpuopt_unsafe_inaccurate_nan, - tr("Inaccurate NaN handling"), + INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"), tr("This option improves speed by removing NaN checking.\nPlease note this also reduces " "accuracy of certain floating-point instructions.")); - INSERT(Settings, - cpuopt_unsafe_fastmem_check, - tr("Disable address space checks"), + INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"), tr("This option improves speed by eliminating a safety check before every memory " "operation.\nDisabling it may allow arbitrary code execution.")); INSERT( - Settings, - cpuopt_unsafe_ignore_global_monitor, - tr("Ignore global monitor"), + Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"), tr("This option improves speed by relying only on the semantics of cmpxchg to ensure " "safety of exclusive access instructions.\nPlease note this may result in deadlocks and " "other race conditions.")); // Renderer - INSERT( - Settings, - renderer_backend, - tr("API:"), - tr("Changes the output graphics API.\nVulkan is recommended.")); - INSERT(Settings, - vulkan_device, - tr("Device:"), + INSERT(Settings, renderer_backend, tr("API:"), + tr("Changes the output graphics API.\nVulkan is recommended.")); + INSERT(Settings, vulkan_device, tr("Device:"), tr("This setting selects the GPU to use (Vulkan only).")); - INSERT(Settings, - resolution_setup, - tr("Resolution:"), + INSERT(Settings, resolution_setup, tr("Resolution:"), tr("Forces to render at a different resolution.\n" "Higher resolutions require more VRAM and bandwidth.\n" "Options lower than 1X can cause artifacts.")); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); - INSERT(Settings, - fsr_sharpening_slider, - tr("FSR Sharpness:"), + INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), tr("Determines how sharpened the image will look using FSR's dynamic contrast.")); - INSERT(Settings, - anti_aliasing, - tr("Anti-Aliasing Method:"), + INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " "can produce a more stable picture in lower resolutions.")); - INSERT(Settings, - fullscreen_mode, - tr("Fullscreen Mode:"), + INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), tr("The method used to render the window in fullscreen.\nBorderless offers the best " "compatibility with the on-screen keyboard that some games request for " "input.\nExclusive " "fullscreen may offer better performance and better Freesync/Gsync support.")); - INSERT(Settings, - aspect_ratio, - tr("Aspect Ratio:"), + INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), tr("Stretches the renderer to fit the specified aspect ratio.\nMost games only support " "16:9, so modifications are required to get other ratios.\nAlso controls the " "aspect ratio of captured screenshots.")); - INSERT(Settings, - use_disk_shader_cache, - tr("Use persistent pipeline cache"), + INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"), tr("Allows saving shaders to storage for faster loading on following game " "boots.\nDisabling it is only intended for debugging.")); - INSERT(Settings, - optimize_spirv_output, - tr("Optimize SPIRV output"), + INSERT(Settings, optimize_spirv_output, tr("Optimize SPIRV output"), tr("Runs an additional optimization pass over generated SPIRV shaders.\n" "Will increase time required for shader compilation.\nMay slightly improve " "performance.\nThis feature is experimental.")); - INSERT(Settings, - nvdec_emulation, - tr("NVDEC emulation:"), + INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " "decoding, or perform no decoding at all (black screen on videos).\n" "In most cases, GPU decoding provides the best performance.")); - INSERT(Settings, - accelerate_astc, - tr("ASTC Decoding Method:"), + INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), tr("This option controls how ASTC textures should be decoded.\n" "CPU: Use the CPU for decoding.\n" "GPU: Use the GPU's compute shaders to decode ASTC textures (recommended).\n" "CPU Asynchronously: Use the CPU to decode ASTC textures on demand. Eliminates" "ASTC decoding\nstuttering but may present artifacts.")); - INSERT( - Settings, - astc_recompression, - tr("ASTC Recompression Method:"), - tr("Most GPUs lack support for ASTC textures and must decompress to an" - "intermediate format: RGBA8.\n" - "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n" - " saving VRAM but degrading image quality.")); + INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"), + tr("Most GPUs lack support for ASTC textures and must decompress to an" + "intermediate format: RGBA8.\n" + "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n" + " saving VRAM but degrading image quality.")); INSERT(Settings, frame_pacing_mode, tr("Frame Pacing Mode (Vulkan only)"), - tr("Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent.")); - INSERT(Settings, - vram_usage_mode, - tr("VRAM Usage Mode:"), - tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may impact performance of other applications such as recording software.")); - INSERT(Settings, - skip_cpu_inner_invalidation, - tr("Skip CPU Inner Invalidation"), + tr("Controls how the emulator manages frame pacing to reduce stuttering and make the " + "frame rate smoother and more consistent.")); + INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), + tr("Selects whether the emulator should prefer to conserve memory or make maximum usage " + "of available video memory for performance.\nAggressive mode may impact performance " + "of other applications such as recording software.")); + INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"), tr("Skips certain cache invalidations during memory updates, reducing CPU usage and " "improving latency. This may cause soft-crashes.")); - INSERT( - Settings, - vsync_mode, - tr("VSync Mode:"), - tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " - "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n" - "Mailbox can have lower latency than FIFO and does not tear but may drop " - "frames.\nImmediate (no synchronization) presents whatever is available and can " - "exhibit tearing.")); + INSERT(Settings, vsync_mode, tr("VSync Mode:"), + tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " + "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n" + "Mailbox can have lower latency than FIFO and does not tear but may drop " + "frames.\nImmediate (no synchronization) presents whatever is available and can " + "exhibit tearing.")); INSERT(Settings, bg_red, QString(), QString()); INSERT(Settings, bg_green, QString(), QString()); INSERT(Settings, bg_blue, QString(), QString()); @@ -264,103 +212,74 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, use_asynchronous_gpu_emulation, QString(), QString()); INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"), - tr("Ensures data consistency between compute and memory operations.\nThis option fixes issues in games, but may degrade performance.\nUnreal Engine 4 games often see the most significant changes thereof.")); - INSERT(Settings, - async_presentation, - tr("Enable asynchronous presentation (Vulkan only)"), + tr("Ensures data consistency between compute and memory operations.\nThis option fixes " + "issues in games, but may degrade performance.\nUnreal Engine 4 games often see the " + "most significant changes thereof.")); + INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), tr("Slightly improves performance by moving presentation to a separate CPU thread.")); INSERT( - Settings, - renderer_force_max_clock, - tr("Force maximum clocks (Vulkan only)"), + Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"), tr("Runs work in the background while waiting for graphics commands to keep the GPU from " "lowering its clock speed.")); - INSERT(Settings, - max_anisotropy, - tr("Anisotropic Filtering:"), - tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on most GPUs.")); - INSERT(Settings, - gpu_accuracy, - tr("GPU Mode:"), - tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced modes, but Accurate is still " + INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), + tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on " + "most GPUs.")); + INSERT(Settings, gpu_accuracy, tr("GPU Mode:"), + tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced " + "modes, but Accurate is still " "required for some.\nParticles tend to only render correctly with Accurate mode.")); - INSERT(Settings, - dma_accuracy, - tr("DMA Accuracy:"), - tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance.")); - INSERT(Settings, - use_asynchronous_shaders, - tr("Enable asynchronous shader compilation"), + INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), + tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but " + "may degrade performance.")); + INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), tr("May reduce shader stutter.")); - INSERT(Settings, - fast_gpu_time, - tr("Fast GPU Time"), + INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), tr("Overclocks the emulated GPU to increase dynamic resolution and render " "distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity.")); - INSERT(Settings, - gpu_unswizzle_enabled, - tr("GPU Unswizzle"), + INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"), tr("Accelerates BCn 3D texture decoding using GPU compute.\n" "Disable if experiencing crashes or graphical glitches.")); - INSERT(Settings, - gpu_unswizzle_texture_size, - tr("GPU Unswizzle Max Texture Size"), + INSERT(Settings, gpu_unswizzle_texture_size, tr("GPU Unswizzle Max Texture Size"), tr("Sets the maximum size (MiB) for GPU-based texture unswizzling.\n" - "While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones.\n" + "While the GPU is faster for medium and large textures, the CPU may be more " + "efficient for very small ones.\n" "Adjust this to find the balance between GPU acceleration and CPU overhead.")); - INSERT(Settings, - gpu_unswizzle_stream_size, - tr("GPU Unswizzle Stream Size"), + INSERT(Settings, gpu_unswizzle_stream_size, tr("GPU Unswizzle Stream Size"), tr("Sets the maximum amount of texture data (in MiB) processed per frame.\n" - "Higher values can reduce stutter during texture loading but may impact frame consistency.")); - INSERT(Settings, - gpu_unswizzle_chunk_size, - tr("GPU Unswizzle Chunk Size"), + "Higher values can reduce stutter during texture loading but may impact frame " + "consistency.")); + INSERT(Settings, gpu_unswizzle_chunk_size, tr("GPU Unswizzle Chunk Size"), tr("Determines the number of depth slices processed in a single dispatch.\n" - "Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware.")); + "Increasing this can improve throughput on high-end GPUs but may cause TDR or driver " + "timeouts on weaker hardware.")); - INSERT(Settings, - use_vulkan_driver_pipeline_cache, - tr("Use Vulkan pipeline cache"), + INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"), tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading " "time significantly in cases where the Vulkan driver does not store pipeline cache " "files internally.")); + INSERT(Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"), + tr("Required by some games.\nThis setting only exists for Intel " + "proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled " + "on all other drivers.")); INSERT( - Settings, - enable_compute_pipelines, - tr("Enable Compute Pipelines (Intel Vulkan Only)"), - tr("Required by some games.\nThis setting only exists for Intel " - "proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled " - "on all other drivers.")); - INSERT( - Settings, - use_reactive_flushing, - tr("Enable Reactive Flushing"), + Settings, use_reactive_flushing, tr("Enable Reactive Flushing"), tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory " "syncing.")); - INSERT(Settings, - use_video_framerate, - tr("Sync to framerate of video playback"), + INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"), tr("Run the game at normal speed during video playback, even when the framerate is " "unlocked.")); - INSERT(Settings, - barrier_feedback_loops, - tr("Barrier feedback loops"), + INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"), tr("Improves rendering of transparency effects in specific games.")); - INSERT(Settings, - enable_buffer_history, - tr("Enable buffer history"), - tr("Enables access to previous buffer states.\nThis option may improve rendering quality and performance consistency in some games.")); - INSERT(Settings, - fix_bloom_effects, - tr("Fix bloom effects"), - tr("Removes bloom in Burnout.")); + INSERT(Settings, enable_buffer_history, tr("Enable buffer history"), + tr("Enables access to previous buffer states.\nThis option may improve rendering " + "quality and performance consistency in some games.")); + INSERT(Settings, fix_bloom_effects, tr("Fix bloom effects"), tr("Removes bloom in Burnout.")); - INSERT(Settings, - rescale_hack, - tr("Enable Legacy Rescale Pass"), - tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n" - "Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3.")); + INSERT(Settings, rescale_hack, tr("Enable Legacy Rescale Pass"), + tr("May fix rescale issues in some games by relying on behavior from the previous " + "implementation.\n" + "Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and " + "grey texture flicker on Nvidia GPUs in Luigis Mansion 3.")); // Renderer (Extensions) INSERT(Settings, dyna_state, tr("Extended Dynamic State"), @@ -368,59 +287,42 @@ std::unique_ptr InitializeTranslations(QObject* parent) "Higher states allow for more features and can increase performance, but may cause " "additional graphical issues.")); - INSERT(Settings, - vertex_input_dynamic_state, - tr("Vertex Input Dynamic State"), + INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"), tr("Enables vertex input dynamic state feature for better quality and performance.")); - INSERT(Settings, - provoking_vertex, - tr("Provoking Vertex"), + INSERT(Settings, provoking_vertex, tr("Provoking Vertex"), tr("Improves lighting and vertex handling in some games.\n" "Only Vulkan 1.0+ devices support this extension.")); - INSERT(Settings, - descriptor_indexing, - tr("Descriptor Indexing"), + INSERT(Settings, descriptor_indexing, tr("Descriptor Indexing"), tr("Improves texture & buffer handling and the Maxwell translation layer.\n" "Some Vulkan 1.1+ and all 1.2+ devices support this extension.")); - INSERT(Settings, - sample_shading, - tr("Sample Shading"), - tr("Allows the fragment shader to execute per sample in a multi-sampled fragment " - "instead of once per fragment. Improves graphics quality at the cost of performance.\n" - "Higher values improve quality but degrade performance.")); + INSERT( + Settings, sample_shading, tr("Sample Shading"), + tr("Allows the fragment shader to execute per sample in a multi-sampled fragment " + "instead of once per fragment. Improves graphics quality at the cost of performance.\n" + "Higher values improve quality but degrade performance.")); // Renderer (Debug) // System - INSERT(Settings, - rng_seed, - tr("RNG Seed"), + INSERT(Settings, rng_seed, tr("RNG Seed"), tr("Controls the seed of the random number generator.\nMainly used for speedrunning.")); INSERT(Settings, rng_seed_enabled, QString(), QString()); INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console.")); - INSERT(Settings, - custom_rtc, - tr("Custom RTC Date:"), + INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), tr("This option allows to change the clock of the console.\n" "Can be used to manipulate time in games.")); INSERT(Settings, custom_rtc_enabled, QString(), QString()); - INSERT(Settings, - custom_rtc_offset, - QStringLiteral(" "), + INSERT(Settings, custom_rtc_offset, QStringLiteral(" "), tr("The number of seconds from the current unix time")); - INSERT(Settings, - language_index, - tr("Language:"), + INSERT(Settings, language_index, tr("Language:"), tr("This option can be overridden when region setting is auto-select")); INSERT(Settings, region_index, tr("Region:"), tr("The region of the console.")); INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the console.")); INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString()); - INSERT(Settings, - use_docked_mode, - tr("Console Mode:"), + INSERT(Settings, use_docked_mode, tr("Console Mode:"), tr("Selects if the console is in Docked or Handheld mode.\nGames will change " "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); @@ -444,31 +346,19 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Ui // Ui General - INSERT(UISettings, - select_user_on_boot, - tr("Prompt for user profile on boot"), + INSERT(UISettings, select_user_on_boot, tr("Prompt for user profile on boot"), tr("Useful if multiple people use the same PC.")); - INSERT(UISettings, - pause_when_in_background, - tr("Pause when not in focus"), + INSERT(UISettings, pause_when_in_background, tr("Pause when not in focus"), tr("Pauses emulation when focusing on other windows.")); - INSERT(UISettings, - confirm_before_stopping, - tr("Confirm before stopping emulation"), + INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"), tr("Overrides prompts asking to confirm stopping the emulation.\nEnabling " "it bypasses such prompts and directly exits the emulation.")); - INSERT(UISettings, - hide_mouse, - tr("Hide mouse on inactivity"), + INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), tr("Hides the mouse after 2.5s of inactivity.")); - INSERT(UISettings, - controller_applet_disabled, - tr("Disable controller applet"), + INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), tr("Forcibly disables the use of the controller applet in emulated programs.\n" - "When a program attempts to open the controller applet, it is immediately closed.")); - INSERT(UISettings, - check_for_updates, - tr("Check for updates"), + "When a program attempts to open the controller applet, it is immediately closed.")); + INSERT(UISettings, check_for_updates, tr("Check for updates"), tr("Whether or not to check for updates upon startup.")); // Linux @@ -489,9 +379,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) return translations; } -std::unique_ptr ComboboxEnumeration(QObject* parent) -{ - std::unique_ptr translations = std::make_unique(); +std::unique_ptr ComboboxEnumeration(QObject* parent) { + std::unique_ptr translations = + std::make_unique(); const auto& tr = [&](const char* text, const char* context = "") { return parent->tr(text, context); }; @@ -537,15 +427,15 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(VramUsageMode, Conservative, tr("Conservative")), PAIR(VramUsageMode, Aggressive, tr("Aggressive")), }}); - translations->insert({Settings::EnumMetadata::Index(), { - PAIR(RendererBackend, Vulkan, tr("Vulkan")), + translations->insert( + {Settings::EnumMetadata::Index(), + {PAIR(RendererBackend, Vulkan, tr("Vulkan")), #ifdef HAS_OPENGL - PAIR(RendererBackend, OpenGL_GLSL, tr("OpenGL GLSL")), - PAIR(RendererBackend, OpenGL_GLASM, tr("OpenGL GLASM (Assembly Shaders, NVIDIA Only)")), - PAIR(RendererBackend, OpenGL_SPIRV, tr("OpenGL SPIR-V (Experimental, AMD/Mesa Only)")), + PAIR(RendererBackend, OpenGL_GLSL, tr("OpenGL GLSL")), + PAIR(RendererBackend, OpenGL_GLASM, tr("OpenGL GLASM (Assembly Shaders, NVIDIA Only)")), + PAIR(RendererBackend, OpenGL_SPIRV, tr("OpenGL SPIR-V (Experimental, AMD/Mesa Only)")), #endif - PAIR(RendererBackend, Null, tr("Null")) - }}); + PAIR(RendererBackend, Null, tr("Null"))}}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(GpuAccuracy, Low, tr("Fast")), @@ -679,58 +569,58 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) translations->insert( {Settings::EnumMetadata::Index(), { - {static_cast(Settings::TimeZone::Auto), - tr("Auto (%1)", "Auto select time zone") - .arg(QString::fromStdString( - Settings::GetTimeZoneString(Settings::TimeZone::Auto)))}, - {static_cast(Settings::TimeZone::Default), - tr("Default (%1)", "Default time zone") - .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, - PAIR(TimeZone, Cet, tr("CET")), - PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")), - PAIR(TimeZone, Cuba, tr("Cuba")), - PAIR(TimeZone, Eet, tr("EET")), - PAIR(TimeZone, Egypt, tr("Egypt")), - PAIR(TimeZone, Eire, tr("Eire")), - PAIR(TimeZone, Est, tr("EST")), - PAIR(TimeZone, Est5Edt, tr("EST5EDT")), - PAIR(TimeZone, Gb, tr("GB")), - PAIR(TimeZone, GbEire, tr("GB-Eire")), - PAIR(TimeZone, Gmt, tr("GMT")), - PAIR(TimeZone, GmtPlusZero, tr("GMT+0")), - PAIR(TimeZone, GmtMinusZero, tr("GMT-0")), - PAIR(TimeZone, GmtZero, tr("GMT0")), - PAIR(TimeZone, Greenwich, tr("Greenwich")), - PAIR(TimeZone, Hongkong, tr("Hongkong")), - PAIR(TimeZone, Hst, tr("HST")), - PAIR(TimeZone, Iceland, tr("Iceland")), - PAIR(TimeZone, Iran, tr("Iran")), - PAIR(TimeZone, Israel, tr("Israel")), - PAIR(TimeZone, Jamaica, tr("Jamaica")), - PAIR(TimeZone, Japan, tr("Japan")), - PAIR(TimeZone, Kwajalein, tr("Kwajalein")), - PAIR(TimeZone, Libya, tr("Libya")), - PAIR(TimeZone, Met, tr("MET")), - PAIR(TimeZone, Mst, tr("MST")), - PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")), - PAIR(TimeZone, Navajo, tr("Navajo")), - PAIR(TimeZone, Nz, tr("NZ")), - PAIR(TimeZone, NzChat, tr("NZ-CHAT")), - PAIR(TimeZone, Poland, tr("Poland")), - PAIR(TimeZone, Portugal, tr("Portugal")), - PAIR(TimeZone, Prc, tr("PRC")), - PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")), - PAIR(TimeZone, Roc, tr("ROC")), - PAIR(TimeZone, Rok, tr("ROK")), - PAIR(TimeZone, Singapore, tr("Singapore")), - PAIR(TimeZone, Turkey, tr("Turkey")), - PAIR(TimeZone, Uct, tr("UCT")), - PAIR(TimeZone, Universal, tr("Universal")), - PAIR(TimeZone, Utc, tr("UTC")), - PAIR(TimeZone, WSu, tr("W-SU")), - PAIR(TimeZone, Wet, tr("WET")), - PAIR(TimeZone, Zulu, tr("Zulu")), - }}); + {static_cast(Settings::TimeZone::Auto), + tr("Auto (%1)", "Auto select time zone") + .arg(QString::fromStdString( + Settings::GetTimeZoneString(Settings::TimeZone::Auto)))}, + {static_cast(Settings::TimeZone::Default), + tr("Default (%1)", "Default time zone") + .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, + PAIR(TimeZone, Cet, tr("CET")), + PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")), + PAIR(TimeZone, Cuba, tr("Cuba")), + PAIR(TimeZone, Eet, tr("EET")), + PAIR(TimeZone, Egypt, tr("Egypt")), + PAIR(TimeZone, Eire, tr("Eire")), + PAIR(TimeZone, Est, tr("EST")), + PAIR(TimeZone, Est5Edt, tr("EST5EDT")), + PAIR(TimeZone, Gb, tr("GB")), + PAIR(TimeZone, GbEire, tr("GB-Eire")), + PAIR(TimeZone, Gmt, tr("GMT")), + PAIR(TimeZone, GmtPlusZero, tr("GMT+0")), + PAIR(TimeZone, GmtMinusZero, tr("GMT-0")), + PAIR(TimeZone, GmtZero, tr("GMT0")), + PAIR(TimeZone, Greenwich, tr("Greenwich")), + PAIR(TimeZone, Hongkong, tr("Hongkong")), + PAIR(TimeZone, Hst, tr("HST")), + PAIR(TimeZone, Iceland, tr("Iceland")), + PAIR(TimeZone, Iran, tr("Iran")), + PAIR(TimeZone, Israel, tr("Israel")), + PAIR(TimeZone, Jamaica, tr("Jamaica")), + PAIR(TimeZone, Japan, tr("Japan")), + PAIR(TimeZone, Kwajalein, tr("Kwajalein")), + PAIR(TimeZone, Libya, tr("Libya")), + PAIR(TimeZone, Met, tr("MET")), + PAIR(TimeZone, Mst, tr("MST")), + PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")), + PAIR(TimeZone, Navajo, tr("Navajo")), + PAIR(TimeZone, Nz, tr("NZ")), + PAIR(TimeZone, NzChat, tr("NZ-CHAT")), + PAIR(TimeZone, Poland, tr("Poland")), + PAIR(TimeZone, Portugal, tr("Portugal")), + PAIR(TimeZone, Prc, tr("PRC")), + PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")), + PAIR(TimeZone, Roc, tr("ROC")), + PAIR(TimeZone, Rok, tr("ROK")), + PAIR(TimeZone, Singapore, tr("Singapore")), + PAIR(TimeZone, Turkey, tr("Turkey")), + PAIR(TimeZone, Uct, tr("UCT")), + PAIR(TimeZone, Universal, tr("Universal")), + PAIR(TimeZone, Utc, tr("UTC")), + PAIR(TimeZone, WSu, tr("W-SU")), + PAIR(TimeZone, Wet, tr("WET")), + PAIR(TimeZone, Zulu, tr("Zulu")), + }}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(AudioMode, Mono, tr("Mono")), @@ -803,9 +693,9 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) }}); translations->insert({Settings::EnumMetadata::Index(), - { - PAIR(GameListMode, TreeView, tr("Tree View")), - PAIR(GameListMode, GridView, tr("Grid View")), + { + PAIR(GameListMode, TreeView, tr("Tree View")), + PAIR(GameListMode, GridView, tr("Grid View")), }}); #undef PAIR diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h index afb18ec435..6529c7bf40 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include #include "common/common_types.h" #include "common/settings_enums.h" @@ -23,7 +23,7 @@ using TranslationMap = std::map>; using ComboboxTranslations = std::vector>; using ComboboxTranslationMap = std::map; -std::unique_ptr InitializeTranslations(QObject *parent); +std::unique_ptr InitializeTranslations(QObject* parent); std::unique_ptr ComboboxEnumeration(QObject* parent); @@ -39,15 +39,15 @@ static const std::map scaling_filter_texts_map {Settings::ScalingFilter::Bilinear, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bilinear"))}, {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bicubic"))}, - {Settings::ScalingFilter::ZeroTangent, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Zero-Tangent"))}, + {Settings::ScalingFilter::ZeroTangent, + QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Zero-Tangent"))}, {Settings::ScalingFilter::BSpline, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "B-Spline"))}, - {Settings::ScalingFilter::Mitchell, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Mitchell"))}, - {Settings::ScalingFilter::Spline1, - QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Spline-1"))}, + {Settings::ScalingFilter::Mitchell, + QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Mitchell"))}, + {Settings::ScalingFilter::Spline1, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Spline-1"))}, {Settings::ScalingFilter::Gaussian, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Gaussian"))}, - {Settings::ScalingFilter::Lanczos, - QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))}, + {Settings::ScalingFilter::Lanczos, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))}, {Settings::ScalingFilter::ScaleForce, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "ScaleForce"))}, {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))}, @@ -68,9 +68,12 @@ static const std::map gpu_accuracy_texts_map = { static const std::map renderer_backend_texts_map = { {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Vulkan"))}, - {Settings::RendererBackend::OpenGL_GLSL, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLSL"))}, - {Settings::RendererBackend::OpenGL_SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL SPIRV"))}, - {Settings::RendererBackend::OpenGL_GLASM, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLASM"))}, + {Settings::RendererBackend::OpenGL_GLSL, + QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLSL"))}, + {Settings::RendererBackend::OpenGL_SPIRV, + QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL SPIRV"))}, + {Settings::RendererBackend::OpenGL_GLASM, + QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLASM"))}, {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Null"))}, }; diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 49205c5b84..c2a14858dd 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -145,14 +145,15 @@ struct Values { // Linux/MinGW may support (requires libdl support) SwitchableSetting enable_gamemode{linkage, #ifndef _MSC_VER - true, + true, #else - false, + false, #endif - "enable_gamemode", Category::UiGeneral}; + "enable_gamemode", Category::UiGeneral}; #ifdef __unix__ SwitchableSetting gui_force_x11{linkage, false, "gui_force_x11", Category::UiGeneral}; - Setting gui_hide_backend_warning{linkage, false, "gui_hide_backend_warning", Category::UiGeneral}; + Setting gui_hide_backend_warning{linkage, false, "gui_hide_backend_warning", + Category::UiGeneral}; #endif // Discord RPC @@ -210,7 +211,8 @@ struct Values { Setting folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList}; Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; - Setting game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList}; + Setting game_list_mode{linkage, Settings::GameListMode::TreeView, + "game_list_mode", Category::UiGameList}; Setting show_game_name{linkage, true, "show_game_name", Category::UiGameList}; std::atomic_bool is_game_list_reload_pending{false}; diff --git a/src/qt_common/discord/discord_impl.cpp b/src/qt_common/discord/discord_impl.cpp index 3bf5544198..37b24cdd57 100644 --- a/src/qt_common/discord/discord_impl.cpp +++ b/src/qt_common/discord/discord_impl.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2018 Citra Emulator Project @@ -70,7 +70,9 @@ std::string DiscordImpl::GetGameString(const std::string& title) { } static constexpr char DEFAULT_DISCORD_TEXT[] = "Eden is an emulator for the Nintendo Switch"; -static constexpr char DEFAULT_DISCORD_IMAGE[] = "https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/eden.png"; +static constexpr char DEFAULT_DISCORD_IMAGE[] = + "https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/" + "eden.png"; void DiscordImpl::UpdateGameStatus(bool use_default) { const std::string url = use_default ? std::string{DEFAULT_DISCORD_IMAGE} : game_url; @@ -120,7 +122,9 @@ void DiscordImpl::Update() { return; } - s64 start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + s64 start_time = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); DiscordRichPresence presence{}; presence.largeImageKey = DEFAULT_DISCORD_IMAGE; presence.largeImageText = DEFAULT_DISCORD_TEXT; diff --git a/src/qt_common/gamemode.cpp b/src/qt_common/gamemode.cpp index 6a3d870aae..b6ce5c2ca8 100644 --- a/src/qt_common/gamemode.cpp +++ b/src/qt_common/gamemode.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -9,9 +9,9 @@ #ifdef __unix__ #include #endif -#include "qt_common/gamemode.h" #include "common/logging/log.h" #include "qt_common/config/uisettings.h" +#include "qt_common/gamemode.h" namespace Common::FeralGamemode { @@ -49,4 +49,4 @@ void Stop() noexcept { } } -} // namespace Common::Linux +} // namespace Common::FeralGamemode diff --git a/src/qt_common/gui_settings.cpp b/src/qt_common/gui_settings.cpp index 982d28bbcb..8b85fe43d6 100644 --- a/src/qt_common/gui_settings.cpp +++ b/src/qt_common/gui_settings.cpp @@ -1,16 +1,17 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "gui_settings.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "gui_settings.h" namespace FS = Common::FS; namespace GraphicsBackend { QString GuiConfigPath() { - return QString::fromStdString(FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "gui_config.ini")); + return QString::fromStdString( + FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "gui_config.ini")); } void SetForceX11(bool state) { diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index af4e4ffa61..e886593780 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -1,21 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "qt_common.h" #include "common/fs/fs.h" -#include "common/fs/ryujinx_compat.h" +#include "qt_common.h" #include #include #include "common/logging/log.h" #include "core/frontend/emu_window.h" -#include "qt_common/abstract/frontend.h" -#include "qt_common/qt_string_lookup.h" #include -#include - #include #if !defined(WIN32) && !defined(__APPLE__) @@ -27,18 +22,13 @@ namespace QtCommon { -#ifdef YUZU_QT_WIDGETS QWidget* rootObject = nullptr; -#else -QObject* rootObject = nullptr; -#endif std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; std::unique_ptr provider = nullptr; -Core::Frontend::WindowSystemType GetWindowSystemType() -{ +Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. QString platform_name = QGuiApplication::platformName(); if (platform_name == QStringLiteral("windows")) @@ -60,8 +50,7 @@ Core::Frontend::WindowSystemType GetWindowSystemType() return Core::Frontend::WindowSystemType::Windows; } // namespace Core::Frontend::WindowSystemType -Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) -{ +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { Core::Frontend::EmuWindow::WindowSystemInfo wsi; wsi.type = GetWindowSystemType(); @@ -69,8 +58,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) // Our Win32 Qt external doesn't have the private API. wsi.render_surface = reinterpret_cast(window->winId()); #elif defined(__APPLE__) - id layer = reinterpret_cast( - objc_msgSend)(reinterpret_cast(window->winId()), sel_registerName("layer")); + id layer = reinterpret_cast(objc_msgSend)( + reinterpret_cast(window->winId()), sel_registerName("layer")); // In Qt 6, the layer of the NSView might be a QContainerLayer. // MoltenVK needs a CAMetalLayer. We search for it in sublayers. @@ -78,15 +67,20 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) id metal_layer = nullptr; if (layer) { - if (reinterpret_cast(objc_msgSend)(layer, sel_registerName("isKindOfClass:"), metal_layer_class)) { + if (reinterpret_cast(objc_msgSend)( + layer, sel_registerName("isKindOfClass:"), metal_layer_class)) { metal_layer = layer; } else { - id sublayers = reinterpret_cast(objc_msgSend)(layer, sel_registerName("sublayers")); + id sublayers = reinterpret_cast(objc_msgSend)( + layer, sel_registerName("sublayers")); if (sublayers) { - unsigned long count = reinterpret_cast(objc_msgSend)(sublayers, sel_registerName("count")); + unsigned long count = reinterpret_cast(objc_msgSend)( + sublayers, sel_registerName("count")); for (unsigned long i = 0; i < count; ++i) { - id sublayer = reinterpret_cast(objc_msgSend)(sublayers, sel_registerName("objectAtIndex:"), i); - if (reinterpret_cast(objc_msgSend)(sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) { + id sublayer = reinterpret_cast(objc_msgSend)( + sublayers, sel_registerName("objectAtIndex:"), i); + if (reinterpret_cast(objc_msgSend)( + sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) { metal_layer = sublayer; break; } @@ -108,30 +102,22 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) return wsi; } -const QString tr(const char* str) -{ +const QString tr(const char* str) { return QGuiApplication::tr(str); } -const QString tr(const std::string& str) -{ +const QString tr(const std::string& str) { return QGuiApplication::tr(str.c_str()); } -#ifdef YUZU_QT_WIDGETS -void Init(QWidget* root) -#else -void Init(QObject* root) -#endif -{ +void Init(QWidget* root) { system = std::make_unique(); rootObject = root; vfs = std::make_unique(); provider = std::make_unique(); } -std::filesystem::path GetEdenCommand() -{ +std::filesystem::path GetEdenCommand() { std::filesystem::path command; // TODO: flatpak? diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index a2700427ab..19c09ef5d0 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -1,24 +1,20 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_COMMON_H #define QT_COMMON_H +#include #include +#include #include "core/core.h" #include "core/file_sys/registered_cache.h" -#include -#include #include namespace QtCommon { -#ifdef YUZU_QT_WIDGETS -extern QWidget *rootObject; -#else -extern QObject *rootObject; -#endif +extern QWidget* rootObject; extern std::unique_ptr system; extern std::shared_ptr vfs; @@ -28,16 +24,12 @@ typedef std::function QtProgressCallback; Core::Frontend::WindowSystemType GetWindowSystemType(); -Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window); +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window); -#ifdef YUZU_QT_WIDGETS -void Init(QWidget *root); -#else -void Init(QObject *root); -#endif +void Init(QWidget* root); -const QString tr(const char *str); -const QString tr(const std::string &str); +const QString tr(const char* str); +const QString tr(const std::string& str); std::filesystem::path GetEdenCommand(); } // namespace QtCommon diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h index 5b7dbd5f0c..daefe20a1a 100644 --- a/src/qt_common/qt_string_lookup.h +++ b/src/qt_common/qt_string_lookup.h @@ -11,8 +11,9 @@ /// Small helper to look up enums. /// res = the result code /// base = the base matching value in the StringKey table -#define LOOKUP_ENUM(res, base) QtCommon::StringLookup::Lookup( \ - QtCommon::StringLookup::StringKey((int) res + (int) QtCommon::StringLookup::base)) +#define LOOKUP_ENUM(res, base) \ + QtCommon::StringLookup::Lookup( \ + QtCommon::StringLookup::StringKey((int)res + (int)QtCommon::StringLookup::base)) namespace QtCommon::StringLookup { @@ -111,16 +112,14 @@ static const constexpr frozen::map strings = { QT_TR_NOOP("Would you like to migrate your data for use in Eden?\n" "Select the corresponding button to migrate data from that emulator.\n" "This may take a while.")}, - {MigrationTooltipClearShader, - QT_TR_NOOP("Clearing shader cache is recommended for all " - "users.\nDo not uncheck unless you know what " - "you're doing.")}, + {MigrationTooltipClearShader, QT_TR_NOOP("Clearing shader cache is recommended for all " + "users.\nDo not uncheck unless you know what " + "you're doing.")}, {MigrationTooltipKeepOld, QT_TR_NOOP("Keeps the old data directory. This is recommended if you aren't\n" "space-constrained and want to keep separate data for the old emulator.")}, - {MigrationTooltipClearOld, - QT_TR_NOOP("Deletes the old data directory.\nThis is recommended on " - "devices with space constraints.")}, + {MigrationTooltipClearOld, QT_TR_NOOP("Deletes the old data directory.\nThis is recommended on " + "devices with space constraints.")}, {MigrationTooltipLinkOld, QT_TR_NOOP("Creates a filesystem link between the old directory and Eden directory.\n" "This is recommended if you want to share data between emulators.")}, @@ -135,8 +134,7 @@ static const constexpr frozen::map strings = { {RyujinxNoSaveId, QT_TR_NOOP("Title %1 not found in Ryujinx title database.")}, }; -static inline const QString Lookup(StringKey key) -{ +static inline const QString Lookup(StringKey key) { return QObject::tr(strings.at(key).data()); } diff --git a/src/qt_common/util/applet.h b/src/qt_common/util/applet.h index 2b48d16698..f88aa066b2 100644 --- a/src/qt_common/util/applet.h +++ b/src/qt_common/util/applet.h @@ -1,11 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_APPLET_UTIL_H #define QT_APPLET_UTIL_H // TODO -namespace QtCommon::Applets { - -} +namespace QtCommon::Applets {} #endif // QT_APPLET_UTIL_H diff --git a/src/qt_common/util/compress.cpp b/src/qt_common/util/compress.cpp index 94dca41151..c86e190bee 100644 --- a/src/qt_common/util/compress.cpp +++ b/src/qt_common/util/compress.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "compress.h" @@ -10,11 +10,8 @@ /** This is a modified version of JlCompress **/ namespace QtCommon::Compress { -bool compressDir(QString fileCompressed, - QString dir, - const Options &options, - QtCommon::QtProgressCallback callback) -{ +bool compressDir(QString fileCompressed, QString dir, const Options& options, + QtCommon::QtProgressCallback callback) { // Create zip QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); @@ -26,8 +23,7 @@ bool compressDir(QString fileCompressed, // See how big the overall fs structure is // good approx. of total progress // TODO(crueter): QDirListing impl... or fs::recursive_dir_iterator - QDirIterator iter(dir, - QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files, + QDirIterator iter(dir, QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files, QDirIterator::Subdirectories); std::size_t total = 0; @@ -54,14 +50,8 @@ bool compressDir(QString fileCompressed, return true; } -bool compressSubDir(QuaZip *zip, - QString dir, - QString origDir, - const Options &options, - std::size_t total, - std::size_t &progress, - QtProgressCallback callback) -{ +bool compressSubDir(QuaZip* zip, QString dir, QString origDir, const Options& options, + std::size_t total, std::size_t& progress, QtProgressCallback callback) { // zip: object where to add the file // dir: current real directory // origDir: original real directory @@ -69,22 +59,20 @@ bool compressSubDir(QuaZip *zip, if (!zip) return false; - if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend - && zip->getMode() != QuaZip::mdAdd) + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend && + zip->getMode() != QuaZip::mdAdd) return false; QDir directory(dir); if (!directory.exists()) return false; - QDir origDirectory(origDir); if (dir != origDir) { QuaZipFile dirZipFile(zip); std::unique_ptr qzni; - qzni = std::make_unique(origDirectory.relativeFilePath(dir) - + QLatin1String("/"), - dir); + qzni = std::make_unique( + origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir); if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) { return false; } @@ -92,18 +80,18 @@ bool compressSubDir(QuaZip *zip, } // For each subfolder - QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot - | QDir::Hidden | QDir::Dirs); - for (const auto &file : std::as_const(subfiles)) { - if (!compressSubDir( - zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) { + QFileInfoList subfiles = + directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Dirs); + for (const auto& file : std::as_const(subfiles)) { + if (!compressSubDir(zip, file.absoluteFilePath(), origDir, options, total, progress, + callback)) { return false; } } // For each file in directory QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files); - for (const auto &file : std::as_const(files)) { + for (const auto& file : std::as_const(files)) { // If it's not a file or it's the compressed file being created if (!file.isFile() || file.absoluteFilePath() == zip->getZipName()) continue; @@ -112,7 +100,8 @@ bool compressSubDir(QuaZip *zip, QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); // Compress the file - if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) { + if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, + callback)) { return false; } } @@ -120,40 +109,26 @@ bool compressSubDir(QuaZip *zip, return true; } -bool compressFile(QuaZip *zip, - QString fileName, - QString fileDest, - const Options &options, - std::size_t total, - std::size_t &progress, - QtCommon::QtProgressCallback callback) -{ +bool compressFile(QuaZip* zip, QString fileName, QString fileDest, const Options& options, + std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback) { // zip: object where to add the file // fileName: real file name // fileDest: file name inside the zip object if (!zip) return false; - if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend - && zip->getMode() != QuaZip::mdAdd) + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend && + zip->getMode() != QuaZip::mdAdd) return false; QuaZipFile outFile(zip); if (options.getDateTime().isNull()) { - if (!outFile.open(QIODevice::WriteOnly, - QuaZipNewInfo(fileDest, fileName), - nullptr, - 0, - options.getCompressionMethod(), - options.getCompressionLevel())) + if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName), nullptr, 0, + options.getCompressionMethod(), options.getCompressionLevel())) return false; } else { - if (!outFile.open(QIODevice::WriteOnly, - QuaZipNewInfo(fileDest, fileName), - nullptr, - 0, - options.getCompressionMethod(), - options.getCompressionLevel())) + if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName), nullptr, 0, + options.getCompressionMethod(), options.getCompressionLevel())) return false; } @@ -171,7 +146,8 @@ bool compressFile(QuaZip *zip, if (!inFile.open(QIODevice::ReadOnly)) { return false; } - if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) { + if (!copyData(inFile, outFile, total, progress, callback) || + outFile.getZipError() != UNZ_OK) { return false; } inFile.close(); @@ -181,12 +157,8 @@ bool compressFile(QuaZip *zip, return outFile.getZipError() == UNZ_OK; } -bool copyData(QIODevice &inFile, - QIODevice &outFile, - std::size_t total, - std::size_t &progress, - QtProgressCallback callback) -{ +bool copyData(QIODevice& inFile, QIODevice& outFile, std::size_t total, std::size_t& progress, + QtProgressCallback callback) { while (!inFile.atEnd()) { char buf[4096]; qint64 readLen = inFile.read(buf, 4096); @@ -203,15 +175,13 @@ bool copyData(QIODevice &inFile, return true; } -QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback) -{ +QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback) { // Open zip QuaZip zip(fileCompressed); return extractDir(zip, dir, callback); } -QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback) -{ +QStringList extractDir(QuaZip& zip, const QString& dir, QtCommon::QtProgressCallback callback) { if (!zip.open(QuaZip::mdUnzip)) { return QStringList(); } @@ -226,7 +196,7 @@ QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCall } std::size_t total = 0; - for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) { + for (const QuaZipFileInfo64& info : zip.getFileInfoList64()) { total += info.uncompressedSize; } @@ -256,13 +226,8 @@ QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCall return extracted; } -bool extractFile(QuaZip *zip, - QString fileName, - QString fileDest, - std::size_t total, - std::size_t &progress, - QtCommon::QtProgressCallback callback) -{ +bool extractFile(QuaZip* zip, QString fileName, QString fileDest, std::size_t total, + std::size_t& progress, QtCommon::QtProgressCallback callback) { // zip: object where to add the file // filename: real file name // fileincompress: file name of the compressed file @@ -334,8 +299,7 @@ bool extractFile(QuaZip *zip, return true; } -bool removeFile(QStringList listFile) -{ +bool removeFile(QStringList listFile) { bool ret = true; // For each file for (int i = 0; i < listFile.count(); i++) { diff --git a/src/qt_common/util/compress.h b/src/qt_common/util/compress.h index 2b3ffd1cbd..640dcad420 100644 --- a/src/qt_common/util/compress.h +++ b/src/qt_common/util/compress.h @@ -1,13 +1,13 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include -#include "qt_common/qt_common.h" #include #include +#include "qt_common/qt_common.h" /** This is a modified version of JlCompress **/ namespace QtCommon::Compress { @@ -15,49 +15,49 @@ namespace QtCommon::Compress { class Options { public: /** - * The enum values refer to the comments in the open function of the quazipfile.h file. - * - * The value is represented by two hexadecimal characters, - * the left character indicating the compression method, - * and the right character indicating the compression level. - * - * method == 0 indicates that the file is not compressed but rather stored as is. - * method == 8(Z_DEFLATED) indicates that zlib compression is used. - * - * A higher value of level indicates a smaller size of the compressed file, - * although it also implies more time consumed during the compression process. - */ - enum CompressionStrategy - { + * The enum values refer to the comments in the open function of the quazipfile.h file. + * + * The value is represented by two hexadecimal characters, + * the left character indicating the compression method, + * and the right character indicating the compression level. + * + * method == 0 indicates that the file is not compressed but rather stored as is. + * method == 8(Z_DEFLATED) indicates that zlib compression is used. + * + * A higher value of level indicates a smaller size of the compressed file, + * although it also implies more time consumed during the compression process. + */ + enum CompressionStrategy { /// Storage without compression - Storage = 0x00, // Z_NO_COMPRESSION 0 - /// The fastest compression speed - Fastest = 0x81, // Z_BEST_SPEED 1 - /// Relatively fast compression speed - Faster = 0x83, + Storage = 0x00, // Z_NO_COMPRESSION 0 + /// The fastest compression speed + Fastest = 0x81, // Z_BEST_SPEED 1 + /// Relatively fast compression speed + Faster = 0x83, /// Standard compression speed and ratio Standard = 0x86, /// Better compression ratio - Better = 0x87, + Better = 0x87, /// The best compression ratio - Best = 0x89, // Z_BEST_COMPRESSION 9 - /// The default compression strategy, according to the open function of quazipfile.h, - /// the value of method is Z_DEFLATED, and the value of level is Z_DEFAULT_COMPRESSION -1 (equals lvl 6) - Default = 0xff + Best = 0x89, // Z_BEST_COMPRESSION 9 + /// The default compression strategy, according to the open function of + /// quazipfile.h, the value of method is Z_DEFLATED, and the value of level is + /// Z_DEFAULT_COMPRESSION -1 (equals lvl 6) + Default = 0xff }; public: - explicit Options(const CompressionStrategy& strategy) - : m_compressionStrategy(strategy) {} + explicit Options(const CompressionStrategy& strategy) : m_compressionStrategy(strategy) {} - explicit Options(const QDateTime& dateTime = QDateTime(), const CompressionStrategy& strategy = Default) + explicit Options(const QDateTime& dateTime = QDateTime(), + const CompressionStrategy& strategy = Default) : m_dateTime(dateTime), m_compressionStrategy(strategy) {} QDateTime getDateTime() const { return m_dateTime; } - void setDateTime(const QDateTime &dateTime) { + void setDateTime(const QDateTime& dateTime) { m_dateTime = dateTime; } @@ -70,10 +70,11 @@ public: } int getCompressionLevel() const { - return m_compressionStrategy != Default ? m_compressionStrategy & 0x0f : Z_DEFAULT_COMPRESSION; + return m_compressionStrategy != Default ? m_compressionStrategy & 0x0f + : Z_DEFAULT_COMPRESSION; } - void setCompressionStrategy(const CompressionStrategy &strategy) { + void setCompressionStrategy(const CompressionStrategy& strategy) { m_compressionStrategy = strategy; } @@ -89,34 +90,21 @@ private: * @param fileCompressed Destination file * @param dir The directory to compress * @param options Compression level, etc - * @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. + * @param callback Callback that takes in two std::size_t (total, progress) and returns false + * if the current operation should be cancelled. */ -bool compressDir(QString fileCompressed, - QString dir, - const Options& options = Options(), +bool compressDir(QString fileCompressed, QString dir, const Options& options = Options(), QtCommon::QtProgressCallback callback = {}); // Internal // -bool compressSubDir(QuaZip *zip, - QString dir, - QString origDir, - const Options &options, - std::size_t total, - std::size_t &progress, +bool compressSubDir(QuaZip* zip, QString dir, QString origDir, const Options& options, + std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback); -bool compressFile(QuaZip *zip, - QString fileName, - QString fileDest, - const Options &options, - std::size_t total, - std::size_t &progress, - QtCommon::QtProgressCallback callback); +bool compressFile(QuaZip* zip, QString fileName, QString fileDest, const Options& options, + std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback); -bool copyData(QIODevice &inFile, - QIODevice &outFile, - std::size_t total, - std::size_t &progress, +bool copyData(QIODevice& inFile, QIODevice& outFile, std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback); // Extract // @@ -125,20 +113,18 @@ bool copyData(QIODevice &inFile, * @brief Extract a zip file and report its progress. * @param fileCompressed Compressed file * @param dir The directory to push the results to - * @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. + * @param callback Callback that takes in two std::size_t (total, progress) and returns false + * if the current operation should be cancelled. */ -QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {}); +QStringList extractDir(QString fileCompressed, QString dir, + QtCommon::QtProgressCallback callback = {}); // Internal // -QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback); +QStringList extractDir(QuaZip& zip, const QString& dir, QtCommon::QtProgressCallback callback); -bool extractFile(QuaZip *zip, - QString fileName, - QString fileDest, - std::size_t total, - std::size_t &progress, - QtCommon::QtProgressCallback callback); +bool extractFile(QuaZip* zip, QString fileName, QString fileDest, std::size_t total, + std::size_t& progress, QtCommon::QtProgressCallback callback); bool removeFile(QStringList listFile); -} +} // namespace QtCommon::Compress diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index 9ba5ab264c..4d9b324608 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -12,63 +12,54 @@ #include "compress.h" #include "qt_common/abstract/frontend.h" -#include "qt_common/abstract/qt_progress_dialog.h" +#include "qt_common/abstract/progress.h" #include "qt_common/qt_common.h" +#include #include #include #include -#include namespace QtCommon::Content { -bool CheckGameFirmware(u64 program_id, QObject* parent) -{ - if (FirmwareManager::GameRequiresFirmware(program_id) - && !FirmwareManager::CheckFirmwarePresence(*system)) { - auto result = QtCommon::Frontend::ShowMessage( - QMessageBox::Warning, +bool CheckGameFirmware(u64 program_id) { + if (FirmwareManager::GameRequiresFirmware(program_id) && + !FirmwareManager::CheckFirmwarePresence(*system)) { + auto result = QtCommon::Frontend::Warning( tr("Game Requires Firmware"), tr("The game you are trying to launch requires firmware to boot or to get past the " "opening menu. Please " "dump and install firmware, or press \"OK\" to launch anyways."), - QMessageBox::Ok | QMessageBox::Cancel, - parent); + QtCommon::Frontend::Ok | QtCommon::Frontend::Cancel); - return result == QMessageBox::Ok; + return result == QtCommon::Frontend::Ok; } return true; } -void InstallFirmware(const QString& location, bool recursive) -{ - QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."), - tr("Cancel"), - 0, - 100, - rootObject); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); - progress.show(); +void InstallFirmware(const QString& location, bool recursive) { + // Initialize a progress dialog. + auto progress = + QtCommon::Frontend::newProgressDialog(tr("Installing Firmware..."), tr("Cancel"), 0, 100); + progress->show(); + + QGuiApplication::processEvents(); // Declare progress callback. auto callback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); + QGuiApplication::processEvents(); + progress->setValue(static_cast((processed_size * 100) / total_size)); + return progress->wasCanceled(); }; QString failedTitle = tr("Firmware Install Failed"); QString successTitle = tr("Firmware Install Succeeded"); - QMessageBox::Icon icon; + QtCommon::Frontend::Icon icon; FirmwareInstallResult result; const auto ShowMessage = [&]() { - QtCommon::Frontend::ShowMessage(icon, - failedTitle, - GetFirmwareInstallResultString(result)); + QtCommon::Frontend::ShowMessage(icon, failedTitle, GetFirmwareInstallResultString(result)); }; LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); @@ -93,28 +84,26 @@ void InstallFirmware(const QString& location, bool recursive) callback(100, 10); if (recursive) { - Common::FS::IterateDirEntriesRecursively(firmware_source_path, - dir_callback, + Common::FS::IterateDirEntriesRecursively(firmware_source_path, dir_callback, Common::FS::DirEntryFilter::File); } else { - Common::FS::IterateDirEntries(firmware_source_path, - dir_callback, + Common::FS::IterateDirEntries(firmware_source_path, dir_callback, Common::FS::DirEntryFilter::File); } if (out.size() <= 0) { result = FirmwareInstallResult::NoNCAs; - icon = QMessageBox::Warning; + icon = QtCommon::Frontend::Icon::Warning; ShowMessage(); return; } // Locate and erase the content of nand/system/Content/registered/*.nca, if any. auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); - if (sysnand_content_vdir->IsWritable() - && !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { + if (sysnand_content_vdir->IsWritable() && + !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { result = FirmwareInstallResult::FailedDelete; - icon = QMessageBox::Critical; + icon = QtCommon::Frontend::Icon::Critical; ShowMessage(); return; } @@ -130,22 +119,20 @@ void InstallFirmware(const QString& location, bool recursive) int i = 0; for (const auto& firmware_src_path : out) { i++; - auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(), - FileSys::OpenMode::Read); - auto firmware_dst_vfile = firmware_vdir->CreateFileRelative( - firmware_src_path.filename().string()); + auto firmware_src_vfile = + vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); + auto firmware_dst_vfile = + firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { - LOG_ERROR(Frontend, - "Failed to copy firmware file {} to {} in registered folder!", - firmware_src_path.generic_string(), - firmware_src_path.filename().string()); + LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!", + firmware_src_path.generic_string(), firmware_src_path.filename().string()); success = false; } if (callback(100, 20 + static_cast(((i) / static_cast(out.size())) * 70.0))) { result = FirmwareInstallResult::FailedCorrupted; - icon = QMessageBox::Warning; + icon = QtCommon::Frontend::Icon::Warning; ShowMessage(); return; } @@ -153,7 +140,7 @@ void InstallFirmware(const QString& location, bool recursive) if (!success) { result = FirmwareInstallResult::FailedCopy; - icon = QMessageBox::Critical; + icon = QtCommon::Frontend::Icon::Critical; ShowMessage(); return; } @@ -162,39 +149,37 @@ void InstallFirmware(const QString& location, bool recursive) system->GetFileSystemController().CreateFactories(*vfs); auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(90 + static_cast((processed_size * 10) / total_size)); - return progress.wasCanceled(); + QGuiApplication::processEvents(); + progress->setValue(90 + static_cast((processed_size * 10) / total_size)); + return progress->wasCanceled(); }; - auto results = ContentManager::VerifyInstalledContents(*QtCommon::system, - *QtCommon::provider, - VerifyFirmwareCallback, - true); + auto results = ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, + VerifyFirmwareCallback, true); if (results.size() > 0) { - const auto failed_names = QString::fromStdString( - fmt::format("{}", fmt::join(results, "\n"))); - progress.close(); + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(results, "\n"))); + progress->close(); QtCommon::Frontend::Critical( tr("Firmware integrity verification failed!"), tr("Verification failed for the following files:\n\n%1").arg(failed_names)); return; } - progress.close(); + progress->close(); + QGuiApplication::processEvents(); const auto pair = FirmwareManager::GetFirmwareVersion(*system); const auto firmware_data = pair.first; const std::string display_version(firmware_data.display_version.data()); result = FirmwareInstallResult::Success; - QtCommon::Frontend::Information(successTitle, - GetFirmwareInstallResultString(result).arg( - QString::fromStdString(display_version))); + QtCommon::Frontend::Information(successTitle, GetFirmwareInstallResultString(result).arg( + QString::fromStdString(display_version))); } -QString UnzipFirmwareToTmp(const QString& location) -{ +QString UnzipFirmwareToTmp(const QString& location) { namespace fs = std::filesystem; fs::path tmp{fs::temp_directory_path()}; @@ -219,59 +204,47 @@ QString UnzipFirmwareToTmp(const QString& location) } // Content // -void VerifyGameContents(const std::string& game_path) -{ - QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), - tr("Cancel"), - 0, - 100, - rootObject); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); +void VerifyGameContents(const std::string& game_path) { + auto progress = + QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100); + progress->show(); + + QGuiApplication::processEvents(); const auto callback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); + QGuiApplication::processEvents(); + progress->setValue(static_cast((processed_size * 100) / total_size)); + return progress->wasCanceled(); }; const auto result = ContentManager::VerifyGameContents(*system, game_path, callback); switch (result) { case ContentManager::GameVerificationResult::Success: - QtCommon::Frontend::Information(rootObject, - tr("Integrity verification succeeded!"), + QtCommon::Frontend::Information(rootObject, tr("Integrity verification succeeded!"), tr("The operation completed successfully.")); break; case ContentManager::GameVerificationResult::Failed: - QtCommon::Frontend::Critical(rootObject, - tr("Integrity verification failed!"), + QtCommon::Frontend::Critical(rootObject, tr("Integrity verification failed!"), tr("File contents may be corrupt or missing.")); break; case ContentManager::GameVerificationResult::NotImplemented: QtCommon::Frontend::Warning( - rootObject, - tr("Integrity verification couldn't be performed"), + rootObject, tr("Integrity verification couldn't be performed"), tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. " "File contents could not be checked for validity.")); } } -void InstallKeys() -{ - const QString key_source_location - = QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"), - {}, - QStringLiteral("Decryption Keys (*.keys)"), - {}, - QtCommon::Frontend::Option::ReadOnly); +void InstallKeys() { + const QString key_source_location = QtCommon::Frontend::GetOpenFileName( + tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {}); if (key_source_location.isEmpty()) return; - FirmwareManager::KeyInstallResult result - = FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); + FirmwareManager::KeyInstallResult result = + FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); system->GetFileSystemController().CreateFactories(*QtCommon::vfs); @@ -286,46 +259,42 @@ void InstallKeys() } } -void VerifyInstalledContents() -{ +void VerifyInstalledContents() { // Initialize a progress dialog. - QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), - tr("Cancel"), - 0, - 100, - rootObject); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); + auto progress = + QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100); + progress->show(); + + QGuiApplication::processEvents(); // Declare progress callback. auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); + QGuiApplication::processEvents(); + progress->setValue(static_cast((processed_size * 100) / total_size)); + return progress->wasCanceled(); }; - const std::vector result - = ContentManager::VerifyInstalledContents(*QtCommon::system, - *QtCommon::provider, - QtProgressCallback); - progress.close(); + const std::vector result = ContentManager::VerifyInstalledContents( + *QtCommon::system, *QtCommon::provider, QtProgressCallback); + + progress->close(); if (result.empty()) { QtCommon::Frontend::Information(tr("Integrity verification succeeded!"), tr("The operation completed successfully.")); } else { - const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); QtCommon::Frontend::Critical( tr("Integrity verification failed!"), tr("Verification failed for the following files:\n\n%1").arg(failed_names)); } } -void FixProfiles() -{ +void FixProfiles() { // Reset user save files after config is initialized and migration is done. - // Doing it at init time causes profiles to read from the wrong place entirely if NAND dir is not default + // Doing it at init time causes profiles to read from the wrong place entirely if NAND dir is + // not default // TODO: better solution system->GetProfileManager().ResetUserSaveFile(); std::vector orphaned = system->GetProfileManager().FindOrphanedProfiles(); @@ -365,79 +334,66 @@ void FixProfiles() "%2

" "Click \"OK\" to open your save folder and fix up your profiles.
" "Hint: copy the contents of the largest or last-modified folder elsewhere, " - "delete all orphaned profiles, and move your copied contents to the good profile.

" - "Still confused? See the help page.
") + "delete all orphaned profiles, and move your copied contents to the good " + "profile.

" + "Still confused? See the help page.
") .arg(qorphaned, qgood)); QtCommon::Game::OpenSaveFolder(); } -void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id) -{ - auto result = QtCommon::Frontend::Warning(tr("Really clear data?"), - tr("Important data may be lost!"), - QMessageBox::Yes | QMessageBox::No); +void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id) { + using namespace QtCommon::Frontend; + auto result = Warning(tr("Really clear data?"), tr("Important data may be lost!"), Yes | No); - if (result != QMessageBox::Yes) + if (result != Yes) return; - result = QtCommon::Frontend::Warning( - tr("Are you REALLY sure?"), - tr("Once deleted, your data will NOT come back!\n" - "Only do this if you're 100% sure you want to delete this data."), - QMessageBox::Yes | QMessageBox::No); + result = Warning(tr("Are you REALLY sure?"), + tr("Once deleted, your data will NOT come back!\n" + "Only do this if you're 100% sure you want to delete this data."), + Yes | No); - if (result != QMessageBox::Yes) + if (result != Yes) return; - QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0); - dialog.show(); + auto dialog = newProgressDialog(tr("Clearing..."), QString(), 0, 0); + dialog->show(); FrontendCommon::DataManager::ClearDir(dir, user_id); - dialog.close(); + dialog->close(); } -void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, - const std::string& user_id, - const QString& name, - std::function callback) -{ +void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::string& user_id, + const QString& name, std::function callback) { using namespace QtCommon::Frontend; const std::string dir = FrontendCommon::DataManager::GetDataDirString(data_dir, user_id); - const QString zip_dump_location = GetSaveFileName(tr("Select Export Location"), - tr("%1.zip").arg(name), - tr("Zipped Archives (*.zip)")); + const QString zip_dump_location = GetSaveFileName( + tr("Select Export Location"), tr("%1.zip").arg(name), tr("Zipped Archives (*.zip)")); if (zip_dump_location.isEmpty()) return; - QtProgressDialog* progress = new QtProgressDialog( - tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100, rootObject); + auto progress = QtCommon::Frontend::newProgressDialogPtr( + tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100); - progress->setWindowTitle(tr("Exporting")); - progress->setWindowModality(Qt::WindowModal); - progress->setMinimumDuration(100); - progress->setAutoClose(false); - progress->setAutoReset(false); + progress->setTitle(tr("Exporting")); progress->show(); - QGuiApplication::processEvents(); - auto progress_callback = [=](size_t total_size, size_t processed_size) { - QMetaObject::invokeMethod(progress, - "setValue", - Qt::DirectConnection, - Q_ARG(int, static_cast((processed_size * 100) / total_size))); + QMetaObject::invokeMethod( + progress, "setValue", Qt::DirectConnection, + Q_ARG(int, static_cast((processed_size * 100) / total_size))); return !progress->wasCanceled(); }; QFuture future = QtConcurrent::run([=]() { - return QtCommon::Compress::compressDir(zip_dump_location, - QString::fromStdString(dir), - QtCommon::Compress::Options(), - progress_callback); + return QtCommon::Compress::compressDir(zip_dump_location, QString::fromStdString(dir), + QtCommon::Compress::Options(), progress_callback); }); QFutureWatcher* watcher = new QFutureWatcher(rootObject); @@ -464,42 +420,32 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, watcher->setFuture(future); } -void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, - const std::string& user_id, - std::function callback) -{ +void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::string& user_id, + std::function callback) { const std::string dir = FrontendCommon::DataManager::GetDataDirString(data_dir, user_id); using namespace QtCommon::Frontend; - const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"), - {}, - tr("Zipped Archives (*.zip)")); + const QString zip_dump_location = + GetOpenFileName(tr("Select Import Location"), {}, tr("Zipped Archives (*.zip)")); if (zip_dump_location.isEmpty()) return; - StandardButton button = Warning( - tr("Import Warning"), - tr("All previous data in this directory will be deleted. Are you sure you wish to " - "proceed?"), - StandardButton::Yes | StandardButton::No); + StandardButton button = + Warning(tr("Import Warning"), + tr("All previous data in this directory will be deleted. Are you sure you wish to " + "proceed?"), + StandardButton::Yes | StandardButton::No); - if (button != QMessageBox::Yes) + if (button != QtCommon::Frontend::Yes) return; - QtProgressDialog* progress = new QtProgressDialog( - tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100, rootObject); + QtProgressDialog* progress = + newProgressDialogPtr(tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100); - progress->setWindowTitle(tr("Importing")); - progress->setWindowModality(Qt::WindowModal); - progress->setMinimumDuration(100); - progress->setAutoClose(false); - progress->setAutoReset(false); + progress->setTitle(tr("Importing")); progress->show(); - progress->setValue(0); - - QGuiApplication::processEvents(); // to prevent GUI mangling we have to run this in a thread as well QFuture delete_future = QtConcurrent::run([=]() { @@ -512,17 +458,14 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, QObject::connect(delete_watcher, &QFutureWatcher::finished, rootObject, [=]() { auto progress_callback = [=](size_t total_size, size_t processed_size) { - QMetaObject::invokeMethod(progress, - "setValue", - Qt::DirectConnection, - Q_ARG(int, - static_cast((processed_size * 100) / total_size))); + QMetaObject::invokeMethod( + progress, "setValue", Qt::DirectConnection, + Q_ARG(int, static_cast((processed_size * 100) / total_size))); return !progress->wasCanceled(); }; QFuture future = QtConcurrent::run([=]() { - return !QtCommon::Compress::extractDir(zip_dump_location, - QString::fromStdString(dir), + return !QtCommon::Compress::extractDir(zip_dump_location, QString::fromStdString(dir), progress_callback) .empty(); }); @@ -553,4 +496,5 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, }); } +// TODO(crueter): Port InstallFirmware et al. from QML Branch } // namespace QtCommon::Content diff --git a/src/qt_common/util/content.h b/src/qt_common/util/content.h index 6e8642083f..20dacf540a 100644 --- a/src/qt_common/util/content.h +++ b/src/qt_common/util/content.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_CONTENT_UTIL_H @@ -13,7 +13,7 @@ namespace QtCommon::Content { // -bool CheckGameFirmware(u64 program_id, QObject *parent); +bool CheckGameFirmware(u64 program_id); enum class FirmwareInstallResult { Success, @@ -23,8 +23,7 @@ enum class FirmwareInstallResult { FailedCorrupted, }; -inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result) -{ +inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result) { return LOOKUP_ENUM(result, FwInstallSuccess); } @@ -33,30 +32,29 @@ inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result * \param result The result code. * \return A string representation of the passed result code. */ -inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult result) -{ +inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult result) { return LOOKUP_ENUM(result, KeyInstallSuccess); } -void InstallFirmware(const QString &location, bool recursive); +void InstallFirmware(const QString& location, bool recursive); -QString UnzipFirmwareToTmp(const QString &location); +QString UnzipFirmwareToTmp(const QString& location); // Keys // void InstallKeys(); // Content // -void VerifyGameContents(const std::string &game_path); +void VerifyGameContents(const std::string& game_path); void VerifyInstalledContents(); -void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = ""); -void ExportDataDir(FrontendCommon::DataManager::DataDir dir, - const std::string &user_id = "", - const QString &name = QStringLiteral("export"), +void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = ""); +void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "", + const QString& name = QStringLiteral("export"), + std::function callback = {}); +void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "", std::function callback = {}); -void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function callback = {}); // Profiles // void FixProfiles(); -} +} // namespace QtCommon::Content #endif // QT_CONTENT_UTIL_H diff --git a/src/qt_common/util/fs.cpp b/src/qt_common/util/fs.cpp index dd105849aa..b5f2844bbf 100644 --- a/src/qt_common/util/fs.cpp +++ b/src/qt_common/util/fs.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -13,8 +13,7 @@ namespace fs = std::filesystem; namespace QtCommon::FS { -void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to) -{ +void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to) { std::error_code ec; // "ignore" errors--if the dir fails to be deleted, error handling later will handle it @@ -25,12 +24,12 @@ void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to) } else { QtCommon::Frontend::Critical( tr("Failed to link save data"), - tr("Could not link directory:\n\t%1\nTo:\n\t%2").arg(QString::fromStdString(from.string()), QString::fromStdString(to.string()))); + tr("Could not link directory:\n\t%1\nTo:\n\t%2") + .arg(QString::fromStdString(from.string()), QString::fromStdString(to.string()))); } } -bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir) -{ +bool CheckUnlink(const fs::path& eden_dir, const fs::path& ryu_dir) { bool eden_link = Common::FS::IsSymlink(eden_dir); bool ryu_link = Common::FS::IsSymlink(ryu_dir); @@ -64,7 +63,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir) // NB: do NOT use remove_all, as Windows treats this as a remove_all to the target, // NOT the junction fs::remove(linked); - } catch (std::exception &e) { + } catch (std::exception& e) { QtCommon::Frontend::Critical( tr("Failed to unlink old directory"), tr("OS returned error: %1").arg(QString::fromStdString(e.what()))); @@ -74,7 +73,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir) // then COPY the other dir try { fs::copy(orig, linked, fs::copy_options::recursive); - } catch (std::exception &e) { + } catch (std::exception& e) { QtCommon::Frontend::Critical( tr("Failed to copy save data"), tr("OS returned error: %1").arg(QString::fromStdString(e.what()))); @@ -87,8 +86,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir) return true; } -const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_id) -{ +const fs::path GetRyujinxSavePath(const fs::path& path_hint, const u64& program_id) { auto ryu_path = path_hint; auto kvdb_path = Common::FS::GetKvdbPath(ryu_path); @@ -99,10 +97,13 @@ const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_ tr("Could not find Ryujinx installation"), tr("Could not find a valid Ryujinx installation. This may typically occur if you are " "using Ryujinx in portable mode.\n\nWould you like to manually select a portable " - "folder to use?"), StandardButton::Yes | StandardButton::No); + "folder to use?"), + StandardButton::Yes | StandardButton::No); if (res == StandardButton::Yes) { - auto selected_path = GetExistingDirectory(tr("Ryujinx Portable Location"), QDir::homePath()).toStdString(); + auto selected_path = + GetExistingDirectory(tr("Ryujinx Portable Location"), QDir::homePath()) + .toStdString(); if (selected_path.empty()) return fs::path{}; @@ -131,7 +132,7 @@ const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_ Common::FS::IMENReadResult res = Common::FS::ReadKvdb(kvdb_path, imens); if (res == Common::FS::IMENReadResult::Success) { - for (const Common::FS::IMEN &imen : imens) { + for (const Common::FS::IMEN& imen : imens) { if (imen.title_id == program_id) return Common::FS::GetRyuSavePath(ryu_path, imen.save_id); } diff --git a/src/qt_common/util/fs.h b/src/qt_common/util/fs.h index 41669e8019..5ab59e2a45 100644 --- a/src/qt_common/util/fs.h +++ b/src/qt_common/util/fs.h @@ -1,19 +1,19 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "common/common_types.h" #include #include +#include "common/common_types.h" #pragma once namespace QtCommon::FS { -void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to); -const std::filesystem::path GetRyujinxSavePath(const std::filesystem::path &path_hint, const u64 &program_id); +void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to); +const std::filesystem::path GetRyujinxSavePath(const std::filesystem::path& path_hint, + const u64& program_id); /// returns FALSE if the dirs are NOT linked -bool CheckUnlink(const std::filesystem::path& eden_dir, - const std::filesystem::path& ryu_dir); +bool CheckUnlink(const std::filesystem::path& eden_dir, const std::filesystem::path& ryu_dir); } // namespace QtCommon::FS diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index 34fbd04ff9..e037fdae6c 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -18,40 +18,34 @@ #include #ifdef _WIN32 -#include "common/scope_exit.h" -#include "common/string_util.h" #include #include +#include "common/scope_exit.h" +#include "common/string_util.h" #else -#include "fmt/ostream.h" #include +#include "fmt/ostream.h" #endif namespace QtCommon::Game { -bool CreateShortcutLink(const std::filesystem::path& shortcut_path, - const std::string& comment, +bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, const std::filesystem::path& icon_path, - const std::filesystem::path& command, - const std::string& arguments, - const std::string& categories, - const std::string& keywords, - const std::string& name) -try { + const std::filesystem::path& command, const std::string& arguments, + const std::string& categories, const std::string& keywords, + const std::string& name) try { #ifdef _WIN32 // Windows HRESULT hr = CoInitialize(nullptr); if (FAILED(hr)) { LOG_ERROR(Frontend, "CoInitialize failed"); return false; } - SCOPE_EXIT - { + SCOPE_EXIT { CoUninitialize(); }; IShellLinkW* ps1 = nullptr; IPersistFile* persist_file = nullptr; - SCOPE_EXIT - { + SCOPE_EXIT { if (persist_file != nullptr) { persist_file->Release(); } @@ -59,10 +53,7 @@ try { ps1->Release(); } }; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IShellLinkW, + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, reinterpret_cast(&ps1)); if (FAILED(hres)) { LOG_ERROR(Frontend, "Failed to create IShellLinkW instance"); @@ -142,10 +133,8 @@ try { return false; } -bool MakeShortcutIcoPath(const u64 program_id, - const std::string_view game_file_name, - std::filesystem::path& out_icon_path) -{ +bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, + std::filesystem::path& out_icon_path) { // Get path to Yuzu icons directory & icon extension std::string ico_extension = "png"; #if defined(_WIN32) @@ -166,46 +155,38 @@ bool MakeShortcutIcoPath(const u64 program_id, return true; } -void OpenEdenFolder(const Common::FS::EdenPath& path) -{ +void OpenEdenFolder(const Common::FS::EdenPath& path) { QDesktopServices::openUrl( QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path)))); } -void OpenRootDataFolder() -{ +void OpenRootDataFolder() { OpenEdenFolder(Common::FS::EdenPath::EdenDir); } -void OpenNANDFolder() -{ +void OpenNANDFolder() { OpenEdenFolder(Common::FS::EdenPath::NANDDir); } -void OpenSaveFolder() -{ - const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) - / "user/save/0000000000000000"; +void OpenSaveFolder() { + const auto path = + Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path.string()))); } -void OpenSDMCFolder() -{ +void OpenSDMCFolder() { OpenEdenFolder(Common::FS::EdenPath::SDMCDir); } -void OpenModFolder() -{ +void OpenModFolder() { OpenEdenFolder(Common::FS::EdenPath::LoadDir); } -void OpenLogFolder() -{ +void OpenLogFolder() { OpenEdenFolder(Common::FS::EdenPath::LogDir); } -static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type) -{ +static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type) { switch (type) { case QtCommon::Game::InstalledEntryType::Game: return tr("Error Removing Contents"); @@ -219,10 +200,9 @@ static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type) } // Game Content // -void RemoveBaseContent(u64 program_id, InstalledEntryType type) -{ - const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(), - program_id); +void RemoveBaseContent(u64 program_id, InstalledEntryType type) { + const auto res = + ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id); if (res) { QtCommon::Frontend::Information(tr("Successfully Removed"), tr("Successfully removed the installed base game.")); @@ -234,8 +214,7 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type) } } -void RemoveUpdateContent(u64 program_id, InstalledEntryType type) -{ +void RemoveUpdateContent(u64 program_id, InstalledEntryType type) { const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); if (res) { QtCommon::Frontend::Information(tr("Successfully Removed"), @@ -246,8 +225,7 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type) } } -void RemoveAddOnContent(u64 program_id, InstalledEntryType type) -{ +void RemoveAddOnContent(u64 program_id, InstalledEntryType type) { const size_t count = ContentManager::RemoveAllDLC(*system, program_id); if (count == 0) { QtCommon::Frontend::Warning(GetGameListErrorRemoving(type), @@ -261,8 +239,7 @@ void RemoveAddOnContent(u64 program_id, InstalledEntryType type) // Global Content // -void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) -{ +void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { const auto target_file_name = [target] { switch (target) { case GameListRemoveTarget::GlShaderCache: @@ -291,8 +268,7 @@ void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) } } -void RemoveVulkanDriverPipelineCache(u64 program_id) -{ +void RemoveVulkanDriverPipelineCache(u64 program_id) { static constexpr std::string_view target_file_name = "vulkan_pipelines.bin"; const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); @@ -308,8 +284,7 @@ void RemoveVulkanDriverPipelineCache(u64 program_id) } } -void RemoveAllTransferableShaderCaches(u64 program_id) -{ +void RemoveAllTransferableShaderCaches(u64 program_id) { const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id); @@ -329,14 +304,13 @@ void RemoveAllTransferableShaderCaches(u64 program_id) } } -void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) -{ +void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) { const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); - const auto config_file_name - = program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") - : fmt::format("{:016X}.ini", program_id); - const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) - / "custom" / config_file_name; + const auto config_file_name = + program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") + : fmt::format("{:016X}.ini", program_id); + const auto custom_config_file_path = + Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) / "custom" / config_file_name; if (!Common::FS::Exists(custom_config_file_path)) { QtCommon::Frontend::Warning(tr("Error Removing Custom Configuration"), @@ -353,20 +327,14 @@ void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) } } -void RemoveCacheStorage(u64 program_id) -{ +void RemoveCacheStorage(u64 program_id) { const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); - auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), - FileSys::OpenMode::Read); + auto vfs_nand_dir = + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); - const auto cache_storage_path - = FileSys::SaveDataFactory::GetFullPath({}, - vfs_nand_dir, - FileSys::SaveDataSpaceId::User, - FileSys::SaveDataType::Cache, - 0 /* program_id */, - {}, - 0); + const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath( + {}, vfs_nand_dir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Cache, + 0 /* program_id */, {}, 0); const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path); @@ -400,32 +368,31 @@ void ResetMetadata(bool show_message) { // Uhhh // // Messages in pre-defined message boxes for less code spaghetti -inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) -{ +inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) { int result = 0; - QMessageBox::StandardButtons buttons; + using namespace QtCommon::Frontend; + int buttons; + switch (imsg) { case ShortcutMessages::Fullscreen: - buttons = QMessageBox::Yes | QMessageBox::No; - result - = QtCommon::Frontend::Information(tr("Create Shortcut"), - tr("Do you want to launch the game in fullscreen?"), - buttons); - return result == QMessageBox::Yes; + buttons = Yes | No; + result = QtCommon::Frontend::Information( + tr("Create Shortcut"), tr("Do you want to launch the game in fullscreen?"), buttons); + return result == Yes; case ShortcutMessages::Success: - QtCommon::Frontend::Information(tr("Shortcut Created"), - tr("Successfully created a shortcut to %1").arg(game_title)); + QtCommon::Frontend::Information( + tr("Shortcut Created"), tr("Successfully created a shortcut to %1").arg(game_title)); return false; case ShortcutMessages::Volatile: - buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel; + buttons = Ok | Cancel; result = QtCommon::Frontend::Warning( tr("Shortcut may be Volatile!"), tr("This will create a shortcut to the current AppImage. This may " "not work well if you update. Continue?"), buttons); - return result == QMessageBox::Ok; + return result == Ok; default: - buttons = QMessageBox::Ok; + buttons = Ok; QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"), tr("Failed to create a shortcut to %1").arg(game_title), buttons); @@ -433,13 +400,9 @@ inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QSt } } -void CreateShortcut(const std::string& game_path, - const u64 program_id, - const std::string& game_title_, - const ShortcutTarget& target, - std::string arguments_, - const bool needs_title) -{ +void CreateShortcut(const std::string& game_path, const u64 program_id, + const std::string& game_title_, const ShortcutTarget& target, + std::string arguments_, const bool needs_title) { // Get path to Eden executable std::filesystem::path command = GetEdenCommand(); @@ -453,13 +416,11 @@ void CreateShortcut(const std::string& game_path, return; } - const FileSys::PatchManager pm{program_id, - QtCommon::system->GetFileSystemController(), + const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(), QtCommon::system->GetContentProvider()}; const auto control = pm.GetControlMetadata(); - const auto loader = Loader::GetLoader(*QtCommon::system, - QtCommon::vfs->OpenFile(game_path, - FileSys::OpenMode::Read)); + const auto loader = Loader::GetLoader( + *QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); std::string game_title{game_title_}; @@ -490,8 +451,8 @@ void CreateShortcut(const std::string& game_path, LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); } - QImage icon_data = QImage::fromData(icon_image_file.data(), - static_cast(icon_image_file.size())); + QImage icon_data = + QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); std::filesystem::path out_icon_path; if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { if (!SaveIconToFile(out_icon_path, icon_data)) { @@ -524,39 +485,32 @@ void CreateShortcut(const std::string& game_path, const std::string categories = "Game;Emulator;Qt;"; const std::string keywords = "Switch;Nintendo;"; - if (QtCommon::Game::CreateShortcutLink(shortcut_path, - comment, - out_icon_path, - command, - arguments, - categories, - keywords, - game_title)) { + if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command, + arguments, categories, keywords, game_title)) { CreateShortcutMessagesGUI(ShortcutMessages::Success, qgame_title); return; } CreateShortcutMessagesGUI(ShortcutMessages::Failed, qgame_title); } -// TODO: You want this to be constexpr? Well too bad, clang19 doesn't believe this is a string literal -std::string GetShortcutPath(ShortcutTarget target) -{ +// TODO: You want this to be constexpr? Well too bad, clang19 doesn't believe this is a string +// literal +std::string GetShortcutPath(ShortcutTarget target) { { std::string shortcut_path{}; if (target == ShortcutTarget::Desktop) { - shortcut_path - = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); + shortcut_path = + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); } else if (target == ShortcutTarget::Applications) { - shortcut_path - = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString(); + shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + .toStdString(); } return shortcut_path; } } -void CreateHomeMenuShortcut(ShortcutTarget target) -{ +void CreateHomeMenuShortcut(ShortcutTarget target) { constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { diff --git a/src/qt_common/util/game.h b/src/qt_common/util/game.h index 2a7c77ef2d..0d7f03fa86 100644 --- a/src/qt_common/util/game.h +++ b/src/qt_common/util/game.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_GAME_UTIL_H @@ -29,27 +29,18 @@ enum class ShortcutTarget { Applications, }; -enum class ShortcutMessages{ - Fullscreen = 0, - Success = 1, - Volatile = 2, - Failed = 3 -}; +enum class ShortcutMessages { Fullscreen = 0, Success = 1, Volatile = 2, Failed = 3 }; -bool CreateShortcutLink(const std::filesystem::path& shortcut_path, - const std::string& comment, +bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, const std::filesystem::path& icon_path, - const std::filesystem::path& command, - const std::string& arguments, - const std::string& categories, - const std::string& keywords, + const std::filesystem::path& command, const std::string& arguments, + const std::string& categories, const std::string& keywords, const std::string& name); -bool MakeShortcutIcoPath(const u64 program_id, - const std::string_view game_file_name, +bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, std::filesystem::path& out_icon_path); -void OpenEdenFolder(const Common::FS::EdenPath &path); +void OpenEdenFolder(const Common::FS::EdenPath& path); void OpenRootDataFolder(); void OpenNANDFolder(); void OpenSaveFolder(); @@ -71,16 +62,13 @@ void RemoveCacheStorage(u64 program_id); void ResetMetadata(bool show_message = true); // Shortcuts // -void CreateShortcut(const std::string& game_path, - const u64 program_id, - const std::string& game_title_, - const ShortcutTarget& target, - std::string arguments_, - const bool needs_title); +void CreateShortcut(const std::string& game_path, const u64 program_id, + const std::string& game_title_, const ShortcutTarget& target, + std::string arguments_, const bool needs_title); std::string GetShortcutPath(ShortcutTarget target); void CreateHomeMenuShortcut(ShortcutTarget target); -} +} // namespace QtCommon::Game #endif // QT_GAME_UTIL_H diff --git a/src/qt_common/util/meta.cpp b/src/qt_common/util/meta.cpp index 4c7f1409e3..4607321285 100644 --- a/src/qt_common/util/meta.cpp +++ b/src/qt_common/util/meta.cpp @@ -1,7 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "qt_common/util/meta.h" #include "common/common_types.h" #include "core/core.h" #include "core/frontend/applets/cabinet.h" @@ -9,11 +8,11 @@ #include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/software_keyboard.h" #include "core/hle/service/am/frontend/applet_web_browser_types.h" +#include "qt_common/util/meta.h" namespace QtCommon::Meta { -void RegisterMetaTypes() -{ +void RegisterMetaTypes() { // Register integral and floating point types qRegisterMetaType("u8"); qRegisterMetaType("u16"); @@ -72,4 +71,4 @@ void RegisterMetaTypes() qRegisterMetaType("Core::SystemResultStatus"); } -} +} // namespace QtCommon::Meta diff --git a/src/qt_common/util/meta.h b/src/qt_common/util/meta.h index c0a37db983..ae6dbc49d8 100644 --- a/src/qt_common/util/meta.h +++ b/src/qt_common/util/meta.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_META_H @@ -11,5 +11,5 @@ namespace QtCommon::Meta { // void RegisterMetaTypes(); -} +} // namespace QtCommon::Meta #endif // QT_META_H diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index 1ab05af9b6..38bc8f5be0 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -43,7 +43,8 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { QString name = QtCommon::Frontend::GetTextInput( tr("Mod Name"), tr("What should this mod be called?"), default_name); - if (name.isEmpty()) return {}; + if (name.isEmpty()) + return {}; // if std_path is empty, frontend_common could not determine mod type and/or name. // so we have to prompt the user and set up the structure ourselves diff --git a/src/qt_common/util/mod.h b/src/qt_common/util/mod.h index 8bdb4bc9dd..2148bed65c 100644 --- a/src/qt_common/util/mod.h +++ b/src/qt_common/util/mod.h @@ -7,8 +7,8 @@ namespace QtCommon::Mod { -QStringList GetModFolders(const QString &root, const QString &fallbackName); +QStringList GetModFolders(const QString& root, const QString& fallbackName); -const QString ExtractMod(const QString &path); +const QString ExtractMod(const QString& path); -} +} // namespace QtCommon::Mod diff --git a/src/qt_common/util/path.cpp b/src/qt_common/util/path.cpp index 73689058c6..a99d3b03ea 100644 --- a/src/qt_common/util/path.cpp +++ b/src/qt_common/util/path.cpp @@ -1,28 +1,24 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "qt_common/util/path.h" #include #include #include +#include #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "qt_common/abstract/frontend.h" -#include +#include "qt_common/util/path.h" namespace QtCommon::Path { -bool OpenShaderCache(u64 program_id, QObject *parent) -{ +bool OpenShaderCache(u64 program_id, QObject* parent) { const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)}; if (!Common::FS::CreateDirs(shader_cache_folder_path)) { - QtCommon::Frontend::ShowMessage(QMessageBox::Warning, - tr("Error Opening Shader Cache"), - tr("Failed to create or open shader cache for this title, " - "ensure your app data directory has write permissions."), - QMessageBox::Ok, - parent); + QtCommon::Frontend::Warning(tr("Error Opening Shader Cache"), + tr("Failed to create or open shader cache for this title, " + "ensure your app data directory has write permissions.")); } const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)}; @@ -30,4 +26,4 @@ bool OpenShaderCache(u64 program_id, QObject *parent) return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); } -} +} // namespace QtCommon::Path diff --git a/src/qt_common/util/path.h b/src/qt_common/util/path.h index 855b06caa9..aff0009f34 100644 --- a/src/qt_common/util/path.h +++ b/src/qt_common/util/path.h @@ -1,12 +1,14 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_PATH_UTIL_H #define QT_PATH_UTIL_H -#include "common/common_types.h" #include +#include "common/common_types.h" -namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); } +namespace QtCommon::Path { +bool OpenShaderCache(u64 program_id, QObject* parent); +} #endif // QT_PATH_UTIL_H diff --git a/src/qt_common/util/rom.cpp b/src/qt_common/util/rom.cpp index 1617548db3..c77fa9530d 100644 --- a/src/qt_common/util/rom.cpp +++ b/src/qt_common/util/rom.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "qt_common/util/rom.h" @@ -7,13 +7,8 @@ namespace QtCommon::ROM { -bool RomFSRawCopy(size_t total_size, - size_t& read_size, - QtProgressCallback callback, - const FileSys::VirtualDir& src, - const FileSys::VirtualDir& dest, - bool full) -{ +bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback, + const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full) { // TODO(crueter) // if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) // return false; @@ -75,4 +70,4 @@ bool RomFSRawCopy(size_t total_size, return true; } -} +} // namespace QtCommon::ROM diff --git a/src/qt_common/util/rom.h b/src/qt_common/util/rom.h index f76b09753d..5ceadaf46b 100644 --- a/src/qt_common/util/rom.h +++ b/src/qt_common/util/rom.h @@ -1,20 +1,16 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef QT_ROM_UTIL_H #define QT_ROM_UTIL_H -#include "qt_common/qt_common.h" #include +#include "qt_common/qt_common.h" namespace QtCommon::ROM { -bool RomFSRawCopy(size_t total_size, - size_t& read_size, - QtProgressCallback callback, - const FileSys::VirtualDir& src, - const FileSys::VirtualDir& dest, - bool full); +bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback, + const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full); } #endif // QT_ROM_UTIL_H diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index e32f21d2ce..71210ffe6e 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -70,10 +70,14 @@ TextureCache

::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag (std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical), DEFAULT_CRITICAL_MEMORY)); minimum_memory = static_cast((device_local_memory - mem_threshold) / 2); + + lowmemorydevice = false; } else { expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB; critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB; minimum_memory = 0; + + lowmemorydevice = true; } const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue(); @@ -118,46 +122,102 @@ void TextureCache

::RunGarbageCollector() { bool aggressive_mode = false; u64 ticks_to_destroy = 0; size_t num_iterations = 0; + const auto Configure = [&](bool allow_aggressive) { high_priority_mode = total_used_memory >= expected_memory; aggressive_mode = allow_aggressive && total_used_memory >= critical_memory; ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL; num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10); }; - const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) { + + const auto Cleanup = [this, &num_iterations, &high_priority_mode, + &aggressive_mode](ImageId image_id) { if (num_iterations == 0) { return true; } --num_iterations; auto& image = slot_images[image_id]; - if (True(image.flags & ImageFlagBits::IsDecoding)) { + + // Never delete recently allocated sparse textures (within 3 frames) + const bool is_recently_allocated = image.allocation_tick >= frame_tick - 3; + if (is_recently_allocated && image.info.is_sparse) { return false; } - const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); - if (must_download && !image.info.is_sparse) { + + if (True(image.flags & ImageFlagBits::IsDecoding)) { + // This image is still being decoded, deleting it will invalidate the slot + // used by the async decoder thread. + return false; + } + + // Prioritize large sparse textures for cleanup + const bool is_large_sparse = lowmemorydevice && + image.info.is_sparse && + image.guest_size_bytes >= 256_MiB; + + if (!aggressive_mode && !is_large_sparse && + True(image.flags & ImageFlagBits::CostlyLoad)) { + return false; + } + + const bool must_download = + image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); + if (!high_priority_mode && !is_large_sparse && must_download) { + return false; + } + + if (must_download && !is_large_sparse) { auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); image.DownloadMemory(map, copies); runtime.Finish(); - SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer); + SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, + swizzle_data_buffer); } + if (True(image.flags & ImageFlagBits::Tracked)) { UntrackImage(image, image_id); } UnregisterImage(image_id); - DeleteImage(image_id, (frame_tick - image.scale_tick) > 5 || aggressive_mode); - if (aggressive_mode && total_used_memory < critical_memory) { - num_iterations >>= 2; - aggressive_mode = false; - } - if (high_priority_mode && total_used_memory < expected_memory) { - num_iterations >>= 1; - high_priority_mode = false; + DeleteImage(image_id, image.scale_tick > frame_tick + 5); + + if (total_used_memory < critical_memory) { + if (aggressive_mode) { + // Sink the aggresiveness. + num_iterations >>= 2; + aggressive_mode = false; + return false; + } + if (high_priority_mode && total_used_memory < expected_memory) { + num_iterations >>= 1; + high_priority_mode = false; + } } return false; }; + + // Aggressively clear massive sparse textures + if (total_used_memory >= expected_memory) { + lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) { + auto& image = slot_images[image_id]; + // Only target sparse textures that are old enough + if (lowmemorydevice && + image.info.is_sparse && + image.guest_size_bytes >= 256_MiB && + image.allocation_tick < frame_tick - 3) { + LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)", + image.gpu_addr, image.guest_size_bytes / (1024 * 1024), + frame_tick - image.allocation_tick); + return Cleanup(image_id); + } + return false; + }); + } + Configure(false); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); + + // If pressure is still too high, prune aggressively. if (total_used_memory >= critical_memory) { Configure(true); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); @@ -1136,6 +1196,9 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { } image.flags &= ~ImageFlagBits::CpuModified; + if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) { + return; + } TrackImage(image, image_id); @@ -1556,6 +1619,39 @@ ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, } } ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); + + // For large sparse textures, aggressively clean up old allocations at same address + if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) { + const auto alloc_it = image_allocs_table.find(gpu_addr); + if (alloc_it != image_allocs_table.end()) { + const ImageAllocId alloc_id = alloc_it->second; + auto& alloc_images = slot_image_allocs[alloc_id].images; + + // Collect old images at this address that were created more than 2 frames ago + boost::container::small_vector to_delete; + for (ImageId old_image_id : alloc_images) { + Image& old_image = slot_images[old_image_id]; + if (old_image.info.is_sparse && + old_image.gpu_addr == gpu_addr && + old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures + to_delete.push_back(old_image_id); + } + } + + // Delete old images immediately + for (ImageId old_id : to_delete) { + Image& old_image = slot_images[old_id]; + LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)", + gpu_addr, old_image.guest_size_bytes / (1024 * 1024)); + if (True(old_image.flags & ImageFlagBits::Tracked)) { + UntrackImage(old_image, old_id); + } + UnregisterImage(old_id); + DeleteImage(old_id, true); + } + } + } + const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different @@ -1571,6 +1667,27 @@ template ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) { ImageInfo new_info = info; const size_t size_bytes = CalculateGuestSizeInBytes(new_info); + + // Proactive cleanup for large sparse texture allocations + if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) { + const u64 estimated_alloc_size = size_bytes; + + if (total_used_memory + estimated_alloc_size >= critical_memory) { + LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. " + "Current memory: {} MiB, Critical: {} MiB", + size_bytes / (1024 * 1024), + total_used_memory / (1024 * 1024), + critical_memory / (1024 * 1024)); + RunGarbageCollector(); + + // If still over threshold after GC, try one more aggressive pass + if (total_used_memory + estimated_alloc_size >= critical_memory) { + LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass"); + RunGarbageCollector(); + } + } + } + const bool broken_views = runtime.HasBrokenTextureViewFormats(); const bool native_bgr = runtime.HasNativeBgr(); join_overlap_ids.clear(); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 47f52c5c99..4b4061f21d 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -478,6 +478,7 @@ private: u64 minimum_memory; u64 expected_memory; u64 critical_memory; + bool lowmemorydevice = false; size_t gpu_unswizzle_maxsize = 0; size_t swizzle_chunk_size = 0; u32 swizzle_slices_per_batch = 0; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 99fb2fec15..982c0eb196 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -244,6 +244,7 @@ add_executable(yuzu configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui + libqt_common.h libqt_common.cpp ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index 9f7597f471..2694f421cb 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -1,29 +1,24 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "yuzu/about_dialog.h" #include +#include #include "common/scm_rev.h" #include "ui_aboutdialog.h" -#include +#include "yuzu/about_dialog.h" AboutDialog::AboutDialog(QWidget* parent) - : QDialog(parent) - , ui{std::make_unique()} -{ + : QDialog(parent), ui{std::make_unique()} { static const std::string build_id = std::string{Common::g_build_id}; - static const std::string yuzu_build = fmt::format("{} | {} | {}", - std::string{Common::g_build_name}, - std::string{Common::g_build_version}, - std::string{Common::g_compiler_id} - ); + static const std::string yuzu_build = + fmt::format("{} | {} | {}", std::string{Common::g_build_name}, + std::string{Common::g_build_version}, std::string{Common::g_compiler_id}); - const auto override_build = fmt::format(fmt::runtime( - std::string(Common::g_title_bar_format_idle)), - build_id); + const auto override_build = + fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; ui->setupUi(this); diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index aa210caf77..e7fa93a793 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -17,8 +17,8 @@ #include "hid_core/hid_core.h" #include "hid_core/hid_types.h" #include "hid_core/resources/npad/npad.h" -#include "ui_qt_controller.h" #include "qt_common/qt_compat.h" +#include "ui_qt_controller.h" #include "yuzu/applets/qt_controller.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_input_profile_dialog.h" @@ -188,14 +188,15 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( CheckIfParametersMet(); }); - connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, [this, i](int state) { - player_groupboxes[i]->setChecked(state == Qt::Checked); - UpdateControllerIcon(i); - UpdateControllerState(i); - UpdateLEDPattern(i); - UpdateBorderColor(i); - CheckIfParametersMet(); - }); + connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, + [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); if (i == 0) { connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index 0b894bf51a..41f0b85e96 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -1499,22 +1499,22 @@ void QtSoftwareKeyboardDialog::StartInputThread() { input_interpreter->PollInput(); HandleButtonPressedOnce< Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X, - Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR, - Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus, - Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, - Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, - Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, - Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft, - Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight, - Core::HID::NpadButton::StickRDown>(); + Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, + Core::HID::NpadButton::StickR, Core::HID::NpadButton::L, Core::HID::NpadButton::R, + Core::HID::NpadButton::Plus, Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, + Core::HID::NpadButton::Right, Core::HID::NpadButton::Down, + Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp, + Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown, + Core::HID::NpadButton::StickRLeft, Core::HID::NpadButton::StickRUp, + Core::HID::NpadButton::StickRRight, Core::HID::NpadButton::StickRDown>(); HandleButtonHold(); + Core::HID::NpadButton::R, Core::HID::NpadButton::Left, + Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, + Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, + Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, + Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft, + Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight, + Core::HID::NpadButton::StickRDown>(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } }); diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 2141b9d0e7..e7c50aa3fb 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -63,8 +63,8 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, : QWebEngineView(parent), input_subsystem{input_subsystem_}, url_interceptor(std::make_unique()), input_interpreter(std::make_unique(system)), - default_profile{QWebEngineProfile::defaultProfile()}, global_settings{ - default_profile->settings()} { + default_profile{QWebEngineProfile::defaultProfile()}, + global_settings{default_profile->settings()} { default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String( Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "qtwebengine"))); @@ -299,21 +299,21 @@ void QtNXWebEngineView::StartInputThread() { while (!stoken.stop_requested()) { input_interpreter->PollInput(); - HandleWindowFooterButtonPressedOnce(); + HandleWindowFooterButtonPressedOnce< + Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X, + Core::HID::NpadButton::Y, Core::HID::NpadButton::L, Core::HID::NpadButton::R>(); HandleWindowKeyButtonPressedOnce< - Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, - Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, - Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, - Core::HID::NpadButton::StickLDown>(); + Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, + Core::HID::NpadButton::Right, Core::HID::NpadButton::Down, + Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp, + Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown>(); HandleWindowKeyButtonHold< - Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right, - Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft, - Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight, - Core::HID::NpadButton::StickLDown>(); + Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, + Core::HID::NpadButton::Right, Core::HID::NpadButton::Down, + Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp, + Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown>(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h index 6a2dd0c8ba..f6869bbb35 100644 --- a/src/yuzu/applets/qt_web_browser.h +++ b/src/yuzu/applets/qt_web_browser.h @@ -169,7 +169,8 @@ private: std::unique_ptr input_interpreter; std::jthread input_thread; std::atomic finished{}; - Service::AM::Frontend::WebExitReason exit_reason{Service::AM::Frontend::WebExitReason::EndButtonPressed}; + Service::AM::Frontend::WebExitReason exit_reason{ + Service::AM::Frontend::WebExitReason::EndButtonPressed}; std::string last_url{"http://localhost/"}; bool is_local{}; QWebEngineProfile* default_profile; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 72a5157fc4..69b3681d0a 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -54,12 +54,12 @@ #include "input_common/drivers/tas_input.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" +#include "qt_common/qt_common.h" #include "video_core/gpu.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main_window.h" -#include "qt_common/qt_common.h" class QObject; class QPaintEngine; @@ -282,8 +282,8 @@ struct NullRenderWidget : public RenderWidget { GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, std::shared_ptr input_subsystem_, Core::System& system_) - : QWidget(parent), - emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, system{system_} { + : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, + system{system_} { setWindowTitle(QStringLiteral("Eden %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -887,13 +887,14 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) { std::unique_ptr GRenderWindow::CreateSharedContext() const { #ifdef HAS_OPENGL - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL - || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM - || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) { + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL || + Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM || + Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) { auto c = static_cast(main_context.get()); // Bind the shared contexts to the main surface in case the backend wants to take over // presentation - return std::make_unique(c->GetShareContext(), child_widget->windowHandle()); + return std::make_unique(c->GetShareContext(), + child_widget->windowHandle()); } #endif return std::make_unique(); @@ -940,9 +941,9 @@ bool GRenderWindow::InitRenderTarget() { OnFramebufferSizeChanged(); BackupGeometry(); - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL - || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM - || Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL || + Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM || + Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) return LoadOpenGL(); return true; } @@ -1050,21 +1051,23 @@ bool GRenderWindow::LoadOpenGL() { } // Display various warnings (but not fatal errors) for missing OpenGL extensions or lack of // OpenGL 4.6 support - const QString renderer = QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); + const QString renderer = + QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); if (!GLAD_GL_VERSION_4_6) { QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"), - tr("Your GPU may not support OpenGL 4.6, or you do not have the " - "latest graphics driver.

GL Renderer:
%1") - .arg(renderer)); + tr("Your GPU may not support OpenGL 4.6, or you do not have the " + "latest graphics driver.

GL Renderer:
%1") + .arg(renderer)); return false; } if (QStringList missing_ext = GetUnsupportedGLExtensions(); !missing_ext.empty()) { QMessageBox::warning( this, tr("Error while initializing OpenGL!"), tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " - "have the latest graphics driver.

GL Renderer:
%1

Unsupported " - "extensions:
%2") - .arg(renderer).arg(missing_ext.join(QStringLiteral("
")))); + "have the latest graphics driver.

GL Renderer:
%1

Unsupported " + "extensions:
%2") + .arg(renderer) + .arg(missing_ext.join(QStringLiteral("
")))); // Non fatal } return true; diff --git a/src/yuzu/configuration/addon/mod_select_dialog.cpp b/src/yuzu/configuration/addon/mod_select_dialog.cpp index e6c361b94f..467de5a764 100644 --- a/src/yuzu/configuration/addon/mod_select_dialog.cpp +++ b/src/yuzu/configuration/addon/mod_select_dialog.cpp @@ -43,7 +43,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent) width = qMax(width, item_model->item(i)->sizeHint().width()); } - width += ui->treeView->contentsMargins().left() * 4 + ui->treeView->contentsMargins().right() * 4; + width += + ui->treeView->contentsMargins().left() * 4 + ui->treeView->contentsMargins().right() * 4; ui->treeView->setMinimumHeight(qMin(height, 600)); ui->treeView->setMinimumWidth(qMin(width, 700)); adjustSize(); diff --git a/src/yuzu/configuration/addon/mod_select_dialog.h b/src/yuzu/configuration/addon/mod_select_dialog.h index d23c435e7a..ab6d4686b0 100644 --- a/src/yuzu/configuration/addon/mod_select_dialog.h +++ b/src/yuzu/configuration/addon/mod_select_dialog.h @@ -14,11 +14,12 @@ class ModSelectDialog : public QDialog { Q_OBJECT public: - explicit ModSelectDialog(const QStringList &mods, QWidget* parent = nullptr); + explicit ModSelectDialog(const QStringList& mods, QWidget* parent = nullptr); ~ModSelectDialog(); signals: - void modsSelected(const QStringList &mods); + void modsSelected(const QStringList& mods); + private: Ui::ModSelectDialog* ui; diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 10eacf39cb..65b8b16301 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -16,12 +16,12 @@ #include "common/settings.h" #include "common/settings_common.h" #include "core/core.h" +#include "qt_common/config/shared_translation.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_audio.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_audio.h" -#include "qt_common/config/shared_translation.h" #include "yuzu/configuration/shared_widget.h" -#include "qt_common/config/uisettings.h" ConfigureAudio::ConfigureAudio(const Core::System& system_, std::shared_ptr> group_, @@ -188,8 +188,8 @@ void ConfigureAudio::SetOutputSinkFromSinkID() { const std::string new_sink_id = []() -> const std::string { const Settings::AudioEngine sink_id = Settings::values.sink_id.GetValue(); - const auto canonicalizations - = Settings::EnumMetadata::Canonicalizations(); + const auto canonicalizations = + Settings::EnumMetadata::Canonicalizations(); for (u32 i = 0; i < canonicalizations.size(); ++i) { const Settings::AudioEngine value = canonicalizations[i].second; @@ -242,8 +242,8 @@ void ConfigureAudio::ApplyConfiguration() { const u32 new_sink_id = [this]() { const std::string sink_id = sink_combo_box->currentText().toStdString(); - const auto canonicalizations - = Settings::EnumMetadata::Canonicalizations(); + const auto canonicalizations = + Settings::EnumMetadata::Canonicalizations(); for (u32 i = 0; i < canonicalizations.size(); ++i) { if (sink_id == canonicalizations[i].first) @@ -291,7 +291,8 @@ void ConfigureAudio::InitializeAudioSinkComboBox() { sink_combo_box->clear(); sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); for (const auto& id : AudioCore::Sink::GetSinkIDs()) - sink_combo_box->addItem(QString::fromStdString(std::string{Settings::CanonicalizeEnum(id)})); + sink_combo_box->addItem( + QString::fromStdString(std::string{Settings::CanonicalizeEnum(id)})); } void ConfigureAudio::RetranslateUI() { diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index bc1140d835..eee1c677b7 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -76,9 +76,9 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } else if (setting->Id() == Settings::values.cpu_backend.Id()) { backend_layout->addWidget(widget); backend_combobox = widget->combobox; - } else if (setting->Id() == Settings::values.fast_cpu_time.Id() - || setting->Id() == Settings::values.vtable_bouncing.Id() - || setting->Id() == Settings::values.cpu_ticks.Id()) { + } else if (setting->Id() == Settings::values.fast_cpu_time.Id() || + setting->Id() == Settings::values.vtable_bouncing.Id() || + setting->Id() == Settings::values.cpu_ticks.Id()) { ui->general_layout->addWidget(widget); } else { // Presently, all other settings here are unsafe checkboxes @@ -93,12 +93,12 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { UpdateGroup(); } -void ConfigureCpu::UpdateGroup() -{ +void ConfigureCpu::UpdateGroup() { const u32 accuracy = accuracy_combobox->currentIndex(); const u32 backend = backend_combobox->currentIndex(); // TODO(crueter): see if this works on NCE - ui->unsafe_group->setVisible(accuracy == (u32) Settings::CpuAccuracy::Unsafe && backend == (u32) Settings::CpuBackend::Dynarmic); + ui->unsafe_group->setVisible(accuracy == (u32)Settings::CpuAccuracy::Unsafe && + backend == (u32)Settings::CpuBackend::Dynarmic); } void ConfigureCpu::ApplyConfiguration() { diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index 461ca7f85e..2b0ae890d7 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -9,8 +9,8 @@ #include #include #include -#include "yuzu/configuration/configuration_shared.h" #include "qt_common/config/shared_translation.h" +#include "yuzu/configuration/configuration_shared.h" class QComboBox; diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index e0bdbaeaa1..287bfbb338 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -13,10 +13,10 @@ #include "common/settings.h" #include "core/core.h" #include "core/crypto/key_manager.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_debug.h" #include "yuzu/configuration/configure_debug.h" #include "yuzu/debugger/console.h" -#include "qt_common/config/uisettings.h" ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) : QScrollArea(parent), ui{std::make_unique()}, system{system_} { @@ -60,7 +60,8 @@ void ConfigureDebug::SetConfiguration() { // Immutable after starting ui->homebrew_args_edit->setEnabled(runtime_lock); - ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args.GetValue())); + ui->homebrew_args_edit->setText( + QString::fromStdString(Settings::values.program_args.GetValue())); ui->toggle_console->setEnabled(runtime_lock); ui->toggle_console->setChecked(UISettings::values.show_console.GetValue()); ui->fs_access_log->setEnabled(runtime_lock); @@ -84,7 +85,8 @@ void ConfigureDebug::SetConfiguration() { ui->disable_macro_hle->setEnabled(runtime_lock); ui->disable_macro_hle->setChecked(Settings::values.disable_macro_hle.GetValue()); ui->disable_loop_safety_checks->setEnabled(runtime_lock); - ui->disable_loop_safety_checks->setChecked(Settings::values.disable_shader_loop_safety_checks.GetValue()); + ui->disable_loop_safety_checks->setChecked( + Settings::values.disable_shader_loop_safety_checks.GetValue()); ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); ui->debug_knobs_spinbox->setValue(Settings::values.debug_knobs.GetValue()); #ifdef YUZU_USE_QT_WEB_ENGINE @@ -118,7 +120,8 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); Settings::values.dump_shaders = ui->dump_shaders->isChecked(); Settings::values.dump_macros = ui->dump_macros->isChecked(); - Settings::values.disable_shader_loop_safety_checks = ui->disable_loop_safety_checks->isChecked(); + Settings::values.disable_shader_loop_safety_checks = + ui->disable_loop_safety_checks->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); Settings::values.disable_macro_hle = ui->disable_macro_hle->isChecked(); Settings::values.extended_logging = ui->extended_logging->isChecked(); diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 1107c77e8c..2e3107d3dc 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -9,6 +9,7 @@ #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" +#include "qt_common/config/uisettings.h" #include "ui_configure.h" #include "vk_device_info.h" #include "yuzu/configuration/configure_applets.h" @@ -30,15 +31,14 @@ #include "yuzu/configuration/configure_ui.h" #include "yuzu/configuration/configure_web.h" #include "yuzu/hotkeys.h" -#include "qt_common/config/uisettings.h" ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, std::vector& vk_device_records, Core::System& system_, bool enable_web_config) - : QDialog(parent), ui{std::make_unique()}, - registry(registry_), system{system_}, builder{std::make_unique( - this, !system_.IsPoweredOn())}, + : QDialog(parent), ui{std::make_unique()}, registry(registry_), + system{system_}, + builder{std::make_unique(this, !system_.IsPoweredOn())}, applets_tab{std::make_unique(system_, nullptr, *builder, this)}, audio_tab{std::make_unique(system_, nullptr, *builder, this)}, cpu_tab{std::make_unique(system_, nullptr, *builder, this)}, @@ -46,9 +46,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, filesystem_tab{std::make_unique(this)}, general_tab{std::make_unique(system_, nullptr, *builder, this)}, graphics_advanced_tab{ - std::make_unique(system_, nullptr, *builder, this)}, + std::make_unique(system_, nullptr, *builder, this)}, graphics_extensions_tab{ - std::make_unique(system_, nullptr, *builder, this)}, + std::make_unique(system_, nullptr, *builder, this)}, ui_tab{std::make_unique(system_, this)}, graphics_tab{std::make_unique( system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, @@ -113,7 +113,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, adjustSize(); ui->selectorList->setCurrentRow(0); - // Selects the leftmost button on the bottom bar (Cancel as of writing) + // Selects the leftmost button on the bottom bar (Cancel as of writing) ui->buttonBox->setFocus(); } @@ -172,16 +172,17 @@ Q_DECLARE_METATYPE(QList); void ConfigureDialog::PopulateSelectionList() { const std::array>, 6> items{ - {{tr("General"), - {general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}}, - {tr("System"), - {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(), - applets_tab.get()}}, - {tr("CPU"), {cpu_tab.get()}}, - {tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get(), graphics_extensions_tab.get()}}, - {tr("Audio"), {audio_tab.get()}}, - {tr("Controls"), input_tab->GetSubTabs()}}, - }; + {{tr("General"), + {general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}}, + {tr("System"), + {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(), + applets_tab.get()}}, + {tr("CPU"), {cpu_tab.get()}}, + {tr("Graphics"), + {graphics_tab.get(), graphics_advanced_tab.get(), graphics_extensions_tab.get()}}, + {tr("Audio"), {audio_tab.get()}}, + {tr("Controls"), input_tab->GetSubTabs()}}, + }; [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 9d79e6f0ac..5c504c9a16 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -10,8 +10,8 @@ #include #include #include "configuration/shared_widget.h" -#include "yuzu/configuration/configuration_shared.h" #include "qt_common/config/shared_translation.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/vk_device_info.h" namespace Core { diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 27af4c8055..ee1e4bbc20 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -4,7 +4,6 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "yuzu/configuration/configure_filesystem.h" #include #include #include @@ -12,10 +11,11 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/settings.h" +#include "qt_common/config/uisettings.h" #include "qt_common/qt_compat.h" #include "qt_common/util/game.h" -#include "qt_common/config/uisettings.h" #include "ui_configure_filesystem.h" +#include "yuzu/configuration/configure_filesystem.h" ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { @@ -26,8 +26,7 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) [this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); }); connect(ui->sdmc_directory_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); }); - connect(ui->save_directory_button, &QToolButton::pressed, this, - [this] { SetSaveDirectory(); }); + connect(ui->save_directory_button, &QToolButton::pressed, this, [this] { SetSaveDirectory(); }); connect(ui->gamecard_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); }); connect(ui->dump_path_button, &QToolButton::pressed, this, @@ -221,9 +220,9 @@ void ConfigureFilesystem::PromptSaveMigration(const QString& from_path, const QS .arg(QString::fromStdString(dest_save_dir.string())); } - QMessageBox::StandardButton reply = QMessageBox::question( - this, tr("Migrate Save Data"), message, - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + QMessageBox::StandardButton reply = + QMessageBox::question(this, tr("Migrate Save Data"), message, + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (reply != QMessageBox::Yes) { return; @@ -249,17 +248,17 @@ void ConfigureFilesystem::PromptSaveMigration(const QString& from_path, const QS progress.close(); if (ec) { - QMessageBox::warning(this, tr("Migration Failed"), - tr("Failed to migrate save data:\n%1") - .arg(QString::fromStdString(ec.message()))); + QMessageBox::warning( + this, tr("Migration Failed"), + tr("Failed to migrate save data:\n%1").arg(QString::fromStdString(ec.message()))); return; } - QMessageBox::StandardButton deleteReply = QMessageBox::question( - this, tr("Migration Complete"), - tr("Save data has been migrated successfully.\n\n" - "Would you like to delete the old save data?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + QMessageBox::StandardButton deleteReply = + QMessageBox::question(this, tr("Migration Complete"), + tr("Save data has been migrated successfully.\n\n" + "Would you like to delete the old save data?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (deleteReply == QMessageBox::Yes) { Common::FS::RemoveDirRecursively(source_save_dir); @@ -278,7 +277,6 @@ void ConfigureFilesystem::UpdateEnabledControls() { !ui->gamecard_current_game->isChecked()); } - void ConfigureFilesystem::RetranslateUI() { ui->retranslateUi(this); } diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index f628abeab3..a311765f2f 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,11 +13,11 @@ #include #include "common/settings.h" #include "core/core.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_general.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_general.h" #include "yuzu/configuration/shared_widget.h" -#include "qt_common/config/uisettings.h" ConfigureGeneral::ConfigureGeneral(const Core::System& system_, std::shared_ptr> group_, diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index b0c63aff4f..475b9b60ac 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -39,12 +39,12 @@ #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" +#include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" #include "ui_configure_graphics.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/shared_widget.h" -#include "qt_common/qt_common.h" -#include "qt_common/config/uisettings.h" #include "yuzu/vk_device_info.h" static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, @@ -91,8 +91,7 @@ ConfigureGraphics::ConfigureGraphics( : ConfigurationShared::Tab(group_, parent), ui{std::make_unique()}, records{records_}, expose_compute_option{expose_compute_option_}, update_aspect_ratio{update_aspect_ratio_}, system{system_}, - combobox_translations{builder.ComboboxTranslations()} -{ + combobox_translations{builder.ComboboxTranslations()} { vulkan_device = Settings::values.vulkan_device.GetValue(); RetrieveVulkanDevices(); @@ -215,9 +214,9 @@ void ConfigureGraphics::PopulateVSyncModeSelection(bool use_setting) { const Settings::VSyncMode global_vsync_mode = Settings::values.vsync_mode.GetValue(true); vsync_restore_global_button->setEnabled( - ((backend == Settings::RendererBackend::OpenGL_GLSL - || backend == Settings::RendererBackend::OpenGL_GLASM - || backend == Settings::RendererBackend::OpenGL_SPIRV) && + ((backend == Settings::RendererBackend::OpenGL_GLSL || + backend == Settings::RendererBackend::OpenGL_GLASM || + backend == Settings::RendererBackend::OpenGL_SPIRV) && (global_vsync_mode == Settings::VSyncMode::Immediate || global_vsync_mode == Settings::VSyncMode::Fifo)) || backend == Settings::RendererBackend::Vulkan); @@ -286,7 +285,9 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { api_combobox = widget->combobox; api_restore_global_button = widget->restore_button; if (!Settings::IsConfiguringGlobal()) { - api_restore_global_button->connect(api_restore_global_button, &QAbstractButton::clicked, [this](bool) { UpdateAPILayout(); }); + api_restore_global_button->connect(api_restore_global_button, + &QAbstractButton::clicked, + [this](bool) { UpdateAPILayout(); }); // Detach API's restore button and place it where we want // Lets us put it on the side, and it will automatically scale if there's a // second combobox (vulkan_device) @@ -312,20 +313,21 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { widget->layout()->addWidget(restore_button); restore_button->connect(restore_button, &QAbstractButton::clicked, - [restore_button, this](bool) { - Settings::values.vsync_mode.SetGlobal(true); - PopulateVSyncModeSelection(true); + [restore_button, this](bool) { + Settings::values.vsync_mode.SetGlobal(true); + PopulateVSyncModeSelection(true); - restore_button->setVisible(false); - }); + restore_button->setVisible(false); + }); std::function set_non_global = [restore_button, this]() { Settings::values.vsync_mode.SetGlobal(false); UpdateVsyncSetting(); restore_button->setVisible(true); }; - widget->combobox->connect(widget->combobox, QOverload::of(&QComboBox::activated), - [set_non_global]() { set_non_global(); }); + widget->combobox->connect(widget->combobox, + QOverload::of(&QComboBox::activated), + [set_non_global]() { set_non_global(); }); vsync_restore_global_button = restore_button; } hold_graphics.emplace(setting->Id(), widget); @@ -364,15 +366,15 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { ui->bg_widget->layout()->addWidget(bg_restore_button); bg_restore_button->connect(bg_restore_button, &QAbstractButton::clicked, - [bg_restore_button, this](bool) { - const int r = Settings::values.bg_red.GetValue(true); - const int g = Settings::values.bg_green.GetValue(true); - const int b = Settings::values.bg_blue.GetValue(true); - UpdateBackgroundColorButton(QColor::fromRgb(r, g, b)); + [bg_restore_button, this](bool) { + const int r = Settings::values.bg_red.GetValue(true); + const int g = Settings::values.bg_green.GetValue(true); + const int b = Settings::values.bg_blue.GetValue(true); + UpdateBackgroundColorButton(QColor::fromRgb(r, g, b)); - bg_restore_button->setVisible(false); - bg_restore_button->setEnabled(false); - }); + bg_restore_button->setVisible(false); + bg_restore_button->setEnabled(false); + }); ui->bg_button->connect(ui->bg_button, &QAbstractButton::clicked, [bg_restore_button](bool) { bg_restore_button->setVisible(true); @@ -397,17 +399,19 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode, Settings::RendererBackend backend) const { switch (mode) { case VK_PRESENT_MODE_IMMEDIATE_KHR: - return (backend == Settings::RendererBackend::OpenGL_GLSL - || backend == Settings::RendererBackend::OpenGL_GLASM - || backend == Settings::RendererBackend::OpenGL_SPIRV) - ? tr("Off") : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); + return (backend == Settings::RendererBackend::OpenGL_GLSL || + backend == Settings::RendererBackend::OpenGL_GLASM || + backend == Settings::RendererBackend::OpenGL_SPIRV) + ? tr("Off") + : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); case VK_PRESENT_MODE_MAILBOX_KHR: return QStringLiteral("Mailbox (%1)").arg(tr("Recommended")); case VK_PRESENT_MODE_FIFO_KHR: - return (backend == Settings::RendererBackend::OpenGL_GLSL - || backend == Settings::RendererBackend::OpenGL_GLASM - || backend == Settings::RendererBackend::OpenGL_SPIRV) - ? tr("On") : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); + return (backend == Settings::RendererBackend::OpenGL_GLSL || + backend == Settings::RendererBackend::OpenGL_GLASM || + backend == Settings::RendererBackend::OpenGL_SPIRV) + ? tr("On") + : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return QStringLiteral("FIFO Relaxed"); default: @@ -416,7 +420,9 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode, } int ConfigureGraphics::FindIndex(u32 enumeration, int value) const { - for (u32 i = 0; enumeration < combobox_translations.size() && i < combobox_translations.at(enumeration).size(); i++) + for (u32 i = 0; enumeration < combobox_translations.size() && + i < combobox_translations.at(enumeration).size(); + i++) if (combobox_translations.at(enumeration)[i].first == u32(value)) return i; return -1; @@ -432,10 +438,14 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.vulkan_device.SetGlobal(true); auto const index = Settings::EnumMetadata::Index(); - if (Settings::IsConfiguringGlobal() || (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) { - auto backend = index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size() - ? Settings::values.renderer_backend.GetValue() - : Settings::RendererBackend(combobox_translations.at(index)[api_combobox->currentIndex()].first); + if (Settings::IsConfiguringGlobal() || + (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) { + auto backend = + index >= combobox_translations.size() || + size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size() + ? Settings::values.renderer_backend.GetValue() + : Settings::RendererBackend( + combobox_translations.at(index)[api_combobox->currentIndex()].first); switch (backend) { case Settings::RendererBackend::Vulkan: Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal()); @@ -506,12 +516,15 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { auto const index = Settings::EnumMetadata::Index(); if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) return Settings::values.renderer_backend.GetValue(true); - return index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size() - ? Settings::values.renderer_backend.GetValue() - : Settings::RendererBackend(combobox_translations.at(index).at(api_combobox->currentIndex()).first); + return index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >= + combobox_translations.at(index).size() + ? Settings::values.renderer_backend.GetValue() + : Settings::RendererBackend( + combobox_translations.at(index).at(api_combobox->currentIndex()).first); }(); - if (selected_backend == Settings::RendererBackend::Vulkan && UISettings::values.has_broken_vulkan) + if (selected_backend == Settings::RendererBackend::Vulkan && + UISettings::values.has_broken_vulkan) return Settings::RendererBackend::OpenGL_GLSL; return selected_backend; } diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 9096e3252d..2ffb3a0b30 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -9,10 +9,10 @@ #include #include "common/settings.h" #include "core/core.h" +#include "qt_common/config/shared_translation.h" #include "ui_configure_graphics_advanced.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_advanced.h" -#include "qt_common/config/shared_translation.h" #include "yuzu/configuration/shared_widget.h" ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced( diff --git a/src/yuzu/configuration/configure_graphics_extensions.cpp b/src/yuzu/configuration/configure_graphics_extensions.cpp index 7334eccc97..8154dfa6ca 100644 --- a/src/yuzu/configuration/configure_graphics_extensions.cpp +++ b/src/yuzu/configuration/configure_graphics_extensions.cpp @@ -1,26 +1,27 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include #include +#include #include +#include #include "common/settings.h" #include "core/core.h" +#include "qt_common/config/shared_translation.h" #include "ui_configure_graphics_extensions.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_extensions.h" -#include "qt_common/config/shared_translation.h" #include "yuzu/configuration/shared_widget.h" ConfigureGraphicsExtensions::ConfigureGraphicsExtensions( const Core::System& system_, std::shared_ptr> group_, const ConfigurationShared::Builder& builder, QWidget* parent) - : Tab(group_, parent), ui{std::make_unique()}, system{system_} { + : Tab(group_, parent), ui{std::make_unique()}, + system{system_} { ui->setupUi(this); @@ -44,8 +45,8 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil if (setting->Id() == Settings::values.sample_shading.Id()) { // TODO(crueter): should support this natively perhaps? return builder.BuildWidget( - setting, apply_funcs, ConfigurationShared::RequestType::Slider, true, - 1.0f, nullptr, tr("%", "Sample Shading percentage (e.g. 50%)")); + setting, apply_funcs, ConfigurationShared::RequestType::Slider, true, 1.0f, + nullptr, tr("%", "Sample Shading percentage (e.g. 50%)")); } else { return builder.BuildWidget(setting, apply_funcs); } @@ -64,7 +65,8 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil #ifdef __APPLE__ if (setting->Id() == Settings::values.dyna_state.Id()) { widget->setEnabled(false); - widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens.")); + widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK " + "compatibility issues that cause black screens.")); } #endif } diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index dfd3727120..9e19cb49bf 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017 Citra Emulator Project @@ -13,10 +13,10 @@ #include "hid_core/hid_core.h" #include "frontend_common/config.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_hotkeys.h" #include "yuzu/configuration/configure_hotkeys.h" #include "yuzu/hotkeys.h" -#include "qt_common/config/uisettings.h" #include "yuzu/util/sequence_dialog/sequence_dialog.h" constexpr int name_column = 0; diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 1a2d36efe6..4473df901e 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project @@ -15,10 +15,10 @@ #include "core/hle/service/sm/sm.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" +#include "qt_common/qt_compat.h" #include "ui_configure_input.h" #include "ui_configure_input_advanced.h" #include "ui_configure_input_player.h" -#include "qt_common/qt_compat.h" #include "yuzu/configuration/configure_camera.h" #include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_input.h" @@ -102,7 +102,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, }; for (std::size_t i = 0; i < player_tabs.size(); ++i) { - QHBoxLayout *tab_layout = new QHBoxLayout(player_tabs[i]); + QHBoxLayout* tab_layout = new QHBoxLayout(player_tabs[i]); tab_layout->addWidget(player_controllers[i]); connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) { // Ensures that connecting a controller changes the number of players @@ -125,10 +125,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, &ConfigureInput::UpdateAllInputDevices); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); - connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, [this, i](int state) { - // Keep activated controllers synced with the "Connected Controllers" checkboxes - player_controllers[i]->ConnectPlayer(state == Qt::Checked); - }); + connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, + [this, i](int state) { + // Keep activated controllers synced with the "Connected Controllers" checkboxes + player_controllers[i]->ConnectPlayer(state == Qt::Checked); + }); // Remove/hide all the elements that exceed max_players, if applicable. if (i >= max_players) { diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index b1c19114bf..d8f0ab7e6c 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -9,8 +9,8 @@ #include "core/core.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" -#include "ui_configure_input_advanced.h" #include "qt_common/qt_compat.h" +#include "ui_configure_input_advanced.h" #include "yuzu/configuration/configure_input_advanced.h" ConfigureInputAdvanced::ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QWidget* parent) diff --git a/src/yuzu/configuration/configure_input_per_game.h b/src/yuzu/configuration/configure_input_per_game.h index a1b6098dca..1e389a7a69 100644 --- a/src/yuzu/configuration/configure_input_per_game.h +++ b/src/yuzu/configuration/configure_input_per_game.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2022 yuzu Emulator Project @@ -10,9 +10,9 @@ #include +#include "qt_common/config/qt_config.h" #include "ui_configure_input_per_game.h" #include "yuzu/configuration/input_profiles.h" -#include "qt_common/config/qt_config.h" class QComboBox; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 6bc37d4347..821825f6e1 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project @@ -14,13 +14,13 @@ #include #include "common/assert.h" #include "common/param_package.h" -#include "qt_common/config/qt_config.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "hid_core/hid_types.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/main.h" +#include "qt_common/config/qt_config.h" #include "ui_configure_input_player.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/configure_input_player.h" @@ -294,11 +294,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i InputCommon::InputSubsystem* input_subsystem_, InputProfiles* profiles_, Core::HID::HIDCore& hid_core_, bool is_powered_on_, bool debug_) - : QWidget(parent), - ui(std::make_unique()), player_index{player_index_}, debug{debug_}, - is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_), - timeout_timer(std::make_unique()), - poll_timer(std::make_unique()), bottom_row{bottom_row_}, hid_core{hid_core_} { + : QWidget(parent), ui(std::make_unique()), + player_index{player_index_}, debug{debug_}, is_powered_on{is_powered_on_}, + input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique()), poll_timer(std::make_unique()), + bottom_row{bottom_row_}, hid_core{hid_core_} { if (player_index == 0) { auto* emulated_controller_p1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); @@ -1215,10 +1215,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { case Core::HID::NpadStyleIndex::Fullkey: case Core::HID::NpadStyleIndex::Handheld: layout_hidden = { - ui->buttonShoulderButtonsSLSRLeft, - ui->buttonShoulderButtonsSLSRRight, - ui->horizontalSpacerShoulderButtonsWidget2, - ui->horizontalSpacerShoulderButtonsWidget4, + ui->buttonShoulderButtonsSLSRLeft, ui->buttonShoulderButtonsSLSRRight, + ui->horizontalSpacerShoulderButtonsWidget2, ui->horizontalSpacerShoulderButtonsWidget4, ui->buttonMiscButtonsScreenshotGroup, }; break; diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 3228b5f17f..790a860002 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -2432,7 +2432,8 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co 1.0 - std::sqrt((offset.x() * offset.x()) + (offset.y() * offset.y())) * 0.1f); const float rotation = - ((offset.x() == 0.f) ? std::atan(1.f) * 2.f : std::atan(offset.y() / offset.x())) * (180.f / (std::atan(1.f) * 4.f)); + ((offset.x() == 0.f) ? std::atan(1.f) * 2.f : std::atan(offset.y() / offset.x())) * + (180.f / (std::atan(1.f) * 4.f)); p.save(); p.translate(offset_center); diff --git a/src/yuzu/configuration/configure_network.cpp b/src/yuzu/configuration/configure_network.cpp index 62a097117e..b1fdd018fd 100644 --- a/src/yuzu/configuration/configure_network.cpp +++ b/src/yuzu/configuration/configure_network.cpp @@ -12,10 +12,7 @@ #include "yuzu/configuration/configure_network.h" ConfigureNetwork::ConfigureNetwork(const Core::System& system_, QWidget* parent) - : QWidget(parent) - , ui(std::make_unique()) - , system{system_} -{ + : QWidget(parent), ui(std::make_unique()), system{system_} { ui->setupUi(this); for (const auto& iface : Network::GetAvailableNetworkInterfaces()) ui->network_interface->addItem(QString::fromStdString(iface.name)); diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 00e0a14aa3..dcf40d678f 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -29,28 +29,28 @@ #include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" #include "frontend_common/config.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_per_game.h" #include "yuzu/configuration/configuration_shared.h" +#include "yuzu/configuration/configure_applets.h" #include "yuzu/configuration/configure_audio.h" #include "yuzu/configuration/configure_cpu.h" #include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/configure_graphics_advanced.h" #include "yuzu/configuration/configure_graphics_extensions.h" #include "yuzu/configuration/configure_input_per_game.h" +#include "yuzu/configuration/configure_network.h" #include "yuzu/configuration/configure_per_game.h" #include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/configuration/configure_system.h" -#include "yuzu/configuration/configure_network.h" -#include "yuzu/configuration/configure_applets.h" -#include "qt_common/config/uisettings.h" #include "yuzu/util/util.h" #include "yuzu/vk_device_info.h" ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, std::vector& vk_device_records, Core::System& system_) - : QDialog(parent), - ui(std::make_unique()), title_id{title_id_}, system{system_}, + : QDialog(parent), ui(std::make_unique()), title_id{title_id_}, + system{system_}, builder{std::make_unique(this, !system_.IsPoweredOn())}, tab_group{std::make_shared>()} { const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index 442daab9cb..ca973db8b0 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -16,10 +16,10 @@ #include "configuration/shared_widget.h" #include "core/file_sys/vfs/vfs_types.h" #include "frontend_common/config.h" -#include "vk_device_info.h" -#include "yuzu/configuration/configuration_shared.h" #include "qt_common/config/qt_config.h" #include "qt_common/config/shared_translation.h" +#include "vk_device_info.h" +#include "yuzu/configuration/configuration_shared.h" namespace Core { class System; diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 1d2d358672..4cdc5a29a5 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -5,6 +5,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include #include @@ -80,7 +82,8 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p connect(ui->folder, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModFolder); connect(ui->zip, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModZip); - connect(tree_view, &QTreeView::customContextMenuRequested, this, &ConfigurePerGameAddons::showContextMenu); + connect(tree_view, &QTreeView::customContextMenuRequested, this, + &ConfigurePerGameAddons::showContextMenu); } ConfigurePerGameAddons::~ConfigurePerGameAddons() = default; @@ -92,10 +95,10 @@ void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) { for (auto* update_item : update_items) { if (update_item != item && update_item->checkState() == Qt::Checked) { disconnect(item_model, &QStandardItemModel::itemChanged, this, - &ConfigurePerGameAddons::OnItemChanged); + &ConfigurePerGameAddons::OnItemChanged); update_item->setCheckState(Qt::Unchecked); connect(item_model, &QStandardItemModel::itemChanged, this, - &ConfigurePerGameAddons::OnItemChanged); + &ConfigurePerGameAddons::OnItemChanged); } } } @@ -105,17 +108,39 @@ void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) { void ConfigurePerGameAddons::ApplyConfiguration() { std::vector disabled_addons; - for (const auto& item : list_items) { - const auto disabled = item.front()->checkState() == Qt::Unchecked; - if (disabled) { - QVariant userData = item.front()->data(Qt::UserRole); - if (userData.isValid() && userData.canConvert() && item.front()->text() == QStringLiteral("Update")) { - quint32 numeric_version = userData.toUInt(); + // Helper function to recursively collect disabled items + std::function collect_disabled = [&](QStandardItem* item) { + if (item == nullptr) { + return; + } + + // Check if this item is disabled + if (item->isCheckable() && item->checkState() == Qt::Unchecked) { + QVariant userData = item->data(Qt::UserRole); + if (userData.isValid() && userData.canConvert() && + item->text() == QStringLiteral("Update")) { + const quint32 numeric_version = userData.toUInt(); disabled_addons.push_back(fmt::format("Update@{}", numeric_version)); } else { - disabled_addons.push_back(item.front()->text().toStdString()); + // Use the stored key from UserRole, falling back to text + const auto key = userData.toString(); + if (!key.isEmpty()) { + disabled_addons.push_back(key.toStdString()); + } else { + disabled_addons.push_back(item->text().toStdString()); + } } } + + // Process children (for cheats under mods) + for (int row = 0; row < item->rowCount(); ++row) { + collect_disabled(item->child(row, 0)); + } + }; + + // Process all root items + for (int row = 0; row < item_model->rowCount(); ++row) { + collect_disabled(item_model->item(row, 0)); } auto current = Settings::values.disabled_addons[title_id]; @@ -164,7 +189,7 @@ void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { } } -void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString &fallbackName) { +void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString& fallbackName) { const auto mods = QtCommon::Mod::GetModFolders(path, fallbackName); if (mods.size() > 1) { @@ -203,8 +228,9 @@ void ConfigurePerGameAddons::InstallModZip() { void ConfigurePerGameAddons::AddonDeleteRequested(QList selected) { QList filtered; - for (const QModelIndex &index : selected) { - if (!index.data(PATCH_LOCATION).toString().isEmpty()) filtered << index; + for (const QModelIndex& index : selected) { + if (!index.data(PATCH_LOCATION).toString().isEmpty()) + filtered << index; } if (filtered.empty()) { @@ -215,10 +241,9 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList selected) { return; } - const auto header = tr("You are about to delete the following installed mods:\n"); QString selected_str; - for (const QModelIndex &index : filtered) { + for (const QModelIndex& index : filtered) { selected_str = selected_str % index.data().toString() % QStringLiteral("\n"); } @@ -231,9 +256,10 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList selected) { QtCommon::Frontend::StandardButton::Yes | QtCommon::Frontend::StandardButton::No); - if (choice == QtCommon::Frontend::StandardButton::No) return; + if (choice == QtCommon::Frontend::StandardButton::No) + return; - for (const QModelIndex &index : filtered) { + for (const QModelIndex& index : filtered) { std::filesystem::remove_all(index.data(PATCH_LOCATION).toString().toStdString()); } @@ -252,17 +278,18 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { auto selected = tree_view->selectionModel()->selectedRows(); if (index.isValid() && selected.empty()) { QModelIndex idx = item_model->index(index.row(), 0); - if (idx.isValid()) selected << idx; + if (idx.isValid()) + selected << idx; } - if (selected.empty()) return; + if (selected.empty()) + return; QMenu menu(this); - QAction *remove = menu.addAction(tr("&Delete")); - connect(remove, &QAction::triggered, this, [this, selected]() { - AddonDeleteRequested(selected); - }); + QAction* remove = menu.addAction(tr("&Delete")); + connect(remove, &QAction::triggered, this, + [this, selected]() { AddonDeleteRequested(selected); }); if (selected.length() == 1) { auto loc = selected.at(0).data(PATCH_LOCATION).toString(); @@ -300,23 +327,40 @@ void ConfigurePerGameAddons::LoadConfiguration() { FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); + // Get the build ID from the main executable for cheat enumeration + const auto build_id = pm.GetBuildID(update_raw); + const auto& disabled = Settings::values.disabled_addons[title_id]; update_items.clear(); list_items.clear(); item_model->removeRows(0, item_model->rowCount()); - std::vector patches = pm.GetPatches(update_raw); + std::vector patches = pm.GetPatches(update_raw, build_id); bool has_enabled_update = false; + // Map to store parent items for mods (for adding cheat children) + std::map mod_items; + for (const auto& patch : patches) { const auto name = QString::fromStdString(patch.name); + // For cheats, we need to use the full key (parent::name) for storage + std::string storage_key; + if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) { + storage_key = patch.parent_name + "::" + patch.name; + } else { + storage_key = patch.name; + } + auto* const first_item = new QStandardItem; first_item->setText(name); first_item->setCheckable(true); + // Store the storage key as user data for later retrieval + first_item->setData(QString::fromStdString(storage_key), Qt::UserRole); + const bool is_external_update = patch.type == FileSys::PatchType::Update && patch.source == FileSys::PatchSource::External && patch.numeric_version != 0; @@ -333,9 +377,11 @@ void ConfigurePerGameAddons::LoadConfiguration() { bool patch_disabled = false; if (is_external_update) { std::string disabled_key = fmt::format("Update@{}", patch.numeric_version); - patch_disabled = std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end(); + patch_disabled = + std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end(); } else { - patch_disabled = std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + patch_disabled = + std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end(); } bool should_enable = !patch_disabled; @@ -353,10 +399,30 @@ void ConfigurePerGameAddons::LoadConfiguration() { first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked); - list_items.push_back(QList{ - first_item, new QStandardItem{QString::fromStdString(patch.version)}}); - item_model->appendRow(list_items.back()); + auto* const version_item = new QStandardItem{QString::fromStdString(patch.version)}; + + if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) { + // This is a cheat - add as child of its parent mod + auto parent_it = mod_items.find(patch.parent_name); + if (parent_it != mod_items.end()) { + parent_it->second->appendRow(QList{first_item, version_item}); + } else { + // Parent not found (shouldn't happen), add to root + list_items.push_back(QList{first_item, version_item}); + item_model->appendRow(list_items.back()); + } + } else { + // This is a top-level item (Update, Mod, DLC) + list_items.push_back(QList{first_item, version_item}); + item_model->appendRow(list_items.back()); + + // Store mod items for later cheat attachment + if (patch.type == FileSys::PatchType::Mod) { + mod_items[patch.name] = first_item; + } + } } + tree_view->expandAll(); tree_view->resizeColumnToContents(1); } diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 20ab39541b..9dd3b06928 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -32,10 +32,7 @@ class ConfigurePerGameAddons : public QWidget { Q_OBJECT public: - enum PatchData { - NUMERIC_VERSION = Qt::UserRole, - PATCH_LOCATION - }; + enum PatchData { NUMERIC_VERSION = Qt::UserRole, PATCH_LOCATION }; explicit ConfigurePerGameAddons(Core::System& system_, QWidget* parent = nullptr); ~ConfigurePerGameAddons() override; @@ -48,7 +45,7 @@ public: void SetTitleId(u64 id); public slots: - void InstallMods(const QStringList &mods); + void InstallMods(const QStringList& mods); void InstallModPath(const QString& path, const QString& fallbackName = {}); void InstallModFolder(); diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index aa19203498..0d86fb5ef9 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -106,7 +106,8 @@ ConfigureProfileManager::ConfigureProfileManager(Core::System& system_, QWidget* ui->scrollArea->setLayout(layout); - connect(tree_view, &QTreeView::customContextMenuRequested, this, &ConfigureProfileManager::showContextMenu); + connect(tree_view, &QTreeView::customContextMenuRequested, this, + &ConfigureProfileManager::showContextMenu); connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser); @@ -229,8 +230,7 @@ void ConfigureProfileManager::showContextMenu(const QPoint& pos) { QAction* edit = menu.addAction(tr("&Edit")); QAction* remove = menu.addAction(tr("&Delete")); - QAction* chosen = - menu.exec(tree_view->viewport()->mapToGlobal(pos)); + QAction* chosen = menu.exec(tree_view->viewport()->mapToGlobal(pos)); if (!chosen) return; @@ -250,7 +250,7 @@ void ConfigureProfileManager::SelectUser(const QModelIndex& index) { } void ConfigureProfileManager::AddUser() { - NewUserDialog *dialog = new NewUserDialog(this); + NewUserDialog* dialog = new NewUserDialog(this); connect(dialog, &NewUserDialog::userAdded, this, [dialog, this](User user) { auto uuid = user.uuid; @@ -285,35 +285,37 @@ void ConfigureProfileManager::EditUser() { std::string username; username.reserve(32); - std::ranges::copy_if(profile.username, std::back_inserter(username), [](u8 byte) { return byte != 0; }); + std::ranges::copy_if(profile.username, std::back_inserter(username), + [](u8 byte) { return byte != 0; }); - NewUserDialog *dialog = new NewUserDialog(uuid.value(), username, tr("Edit User"), this); + NewUserDialog* dialog = new NewUserDialog(uuid.value(), username, tr("Edit User"), this); - connect(dialog, &NewUserDialog::userAdded, this, [dialog, profile, user_idx, uuid, this](User user) mutable { - // TODO: MOVE UUID - // auto new_uuid = user.uuid; - auto new_username = user.username; - auto pixmap = user.pixmap; + connect(dialog, &NewUserDialog::userAdded, this, + [dialog, profile, user_idx, uuid, this](User user) mutable { + // TODO: MOVE UUID + // auto new_uuid = user.uuid; + auto new_username = user.username; + auto pixmap = user.pixmap; - auto const uuid_val = uuid.value(); + auto const uuid_val = uuid.value(); - const auto username_std = new_username.toStdString(); - std::fill(profile.username.begin(), profile.username.end(), '\0'); - std::copy(username_std.begin(), username_std.end(), profile.username.begin()); + const auto username_std = new_username.toStdString(); + std::fill(profile.username.begin(), profile.username.end(), '\0'); + std::copy(username_std.begin(), username_std.end(), profile.username.begin()); - profile_manager.SetProfileBase(uuid_val, profile); - profile_manager.WriteUserSaveFile(); + profile_manager.SetProfileBase(uuid_val, profile); + profile_manager.WriteUserSaveFile(); - item_model->setItem( - user_idx, 0, - new QStandardItem{pixmap, - FormatUserEntryText(QString::fromStdString(username_std), uuid_val)}); + item_model->setItem( + user_idx, 0, + new QStandardItem{pixmap, FormatUserEntryText( + QString::fromStdString(username_std), uuid_val)}); - saveImage(pixmap, uuid_val); - UpdateCurrentUser(); + saveImage(pixmap, uuid_val); + UpdateCurrentUser(); - dialog->deleteLater(); - }); + dialog->deleteLater(); + }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h index 6e8c762452..48cd5d215f 100644 --- a/src/yuzu/configuration/configure_profile_manager.h +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -47,8 +47,7 @@ public: explicit ConfigureProfileManagerDeleteDialog(QWidget* parent); ~ConfigureProfileManagerDeleteDialog(); - void SetInfo(const QString& username, const Common::UUID& uuid, - int index); + void SetInfo(const QString& username, const Common::UUID& uuid, int index); signals: void deleteUser(int index); @@ -71,7 +70,7 @@ public: private slots: void saveImage(QPixmap pixmap, Common::UUID uuid); - void showContextMenu(const QPoint &pos); + void showContextMenu(const QPoint& pos); void DeleteUser(const int index); private: diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index dfb1540584..4c842bd069 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -11,12 +11,12 @@ #include #include -#include "qt_common/config/qt_config.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/main.h" +#include "qt_common/config/qt_config.h" #include "ui_configure_ringcon.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/configure_ringcon.h" diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 56e3e29f90..36920b1ff9 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project @@ -19,8 +19,8 @@ #include "common/settings.h" #include "core/core.h" -#include "ui_configure_system.h" #include "qt_common/qt_compat.h" +#include "ui_configure_system.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_system.h" #include "yuzu/configuration/shared_widget.h" @@ -87,7 +87,8 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, connect(combo_language, qOverload(&QComboBox::currentIndexChanged), this, locale_check); connect(combo_region, qOverload(&QComboBox::currentIndexChanged), this, locale_check); - connect(checkbox_rtc, qOverload(&QCheckBox::STATE_CHANGED), this, update_rtc_date); + connect(checkbox_rtc, qOverload(&QCheckBox::STATE_CHANGED), this, + update_rtc_date); connect(date_rtc_offset, qOverload(&QSpinBox::valueChanged), this, update_rtc_date); connect(date_rtc, &QDateTimeEdit::dateTimeChanged, this, update_date_offset); diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 75d5a5eeaf..d737b46d2c 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -9,9 +9,9 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/settings.h" +#include "qt_common/config/uisettings.h" #include "ui_configure_tas.h" #include "yuzu/configuration/configure_tas.h" -#include "qt_common/config/uisettings.h" ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique()) { @@ -35,7 +35,8 @@ void ConfigureTasDialog::LoadConfiguration() { ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue()); ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue()); ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue()); - ui->tas_show_recording_dialog->setChecked(Settings::values.tas_show_recording_dialog.GetValue()); + ui->tas_show_recording_dialog->setChecked( + Settings::values.tas_show_recording_dialog.GetValue()); } void ConfigureTasDialog::ApplyConfiguration() { diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp index 2a4ae3bc89..bbd52e4bfa 100644 --- a/src/yuzu/configuration/configure_touch_from_button.cpp +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -548,9 +548,8 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { } const auto mouse_event = static_cast(event); if (!drag_state.active) { - drag_state.active = - (mouse_event->globalPosition().toPoint() - drag_state.start_pos).manhattanLength() >= - QApplication::startDragDistance(); + drag_state.active = (mouse_event->globalPosition().toPoint() - drag_state.start_pos) + .manhattanLength() >= QApplication::startDragDistance(); if (!drag_state.active) { break; } diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 0e91a1a9fd..af8d9fecce 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -26,8 +26,8 @@ #include "core/frontend/framebuffer_layout.h" #include "ui_configure_ui.h" -#include "qt_common/qt_compat.h" #include "qt_common/config/uisettings.h" +#include "qt_common/qt_compat.h" namespace { diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index c5e9256a48..987de5989a 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -1,12 +1,12 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "yuzu/configuration/configure_web.h" #include #include +#include "yuzu/configuration/configure_web.h" #if QT_VERSION_MAJOR >= 6 #include @@ -16,28 +16,25 @@ #include #include "common/settings.h" -#include "ui_configure_web.h" #include "qt_common/config/uisettings.h" +#include "ui_configure_web.h" ConfigureWeb::ConfigureWeb(QWidget* parent) - : QWidget(parent) - , ui(std::make_unique()) - , m_rng{QRandomGenerator::system()} -{ + : QWidget(parent), ui(std::make_unique()), m_rng{QRandomGenerator::system()} { ui->setupUi(this); QString user_regex = QStringLiteral(".{4,20}"); QString token_regex = QStringLiteral("[a-z]{48}"); #if QT_VERSION_MAJOR >= 6 - QRegularExpressionValidator *username_validator = new QRegularExpressionValidator(this); - QRegularExpressionValidator *token_validator = new QRegularExpressionValidator(this); + QRegularExpressionValidator* username_validator = new QRegularExpressionValidator(this); + QRegularExpressionValidator* token_validator = new QRegularExpressionValidator(this); username_validator->setRegularExpression(QRegularExpression(user_regex)); token_validator->setRegularExpression(QRegularExpression(token_regex)); #else - QRegExpValidator *username_validator = new QRegExpValidator(this); - QRegExpValidator *token_validator = new QRegExpValidator(this); + QRegExpValidator* username_validator = new QRegExpValidator(this); + QRegExpValidator* token_validator = new QRegExpValidator(this); username_validator->setRegExp(QRegExp(user_regex)); token_validator->setRegExp(QRegExp(token_regex)); @@ -121,7 +118,8 @@ void ConfigureWeb::VerifyLogin() { ui->label_token_verified->setToolTip(tr("All Good", "Tooltip")); } else { ui->label_token_verified->setPixmap(failed); - ui->label_token_verified->setToolTip(tr("Must be 48 characters, and lowercase a-z", "Tooltip")); + ui->label_token_verified->setToolTip( + tr("Must be 48 characters, and lowercase a-z", "Tooltip")); } } diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index 3da7e5eccc..d37db5c020 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017 Citra Emulator Project @@ -8,8 +8,8 @@ #include -#include #include +#include namespace Ui { class ConfigureWeb; @@ -32,7 +32,7 @@ private: void SetConfiguration(); std::unique_ptr ui; - QRandomGenerator *m_rng; + QRandomGenerator* m_rng; private slots: void GenerateToken(); diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp index b663ac51ee..3b425e7200 100644 --- a/src/yuzu/configuration/shared_widget.cpp +++ b/src/yuzu/configuration/shared_widget.cpp @@ -45,8 +45,8 @@ #include "common/logging/log.h" #include "common/settings.h" #include "common/settings_common.h" -#include "qt_common/qt_compat.h" #include "qt_common/config/shared_translation.h" +#include "qt_common/qt_compat.h" namespace ConfigurationShared { @@ -170,7 +170,7 @@ QWidget* Widget::CreateCombobox(std::function& serializer, if (!Settings::IsConfiguringGlobal()) { combobox->connect(combobox, QOverload::of(&QComboBox::activated), - [touch]() { touch(); }); + [touch]() { touch(); }); } return combobox; @@ -413,13 +413,13 @@ QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix, }; if (!Settings::IsConfiguringGlobal()) { - double_spinbox->connect(double_spinbox, QOverload::of(&QDoubleSpinBox::valueChanged), - [this, touch]() { - if (double_spinbox->value() != - std::strtod(setting.ToStringGlobal().c_str(), nullptr)) { - touch(); - } - }); + double_spinbox->connect( + double_spinbox, QOverload::of(&QDoubleSpinBox::valueChanged), [this, touch]() { + if (double_spinbox->value() != + std::strtod(setting.ToStringGlobal().c_str(), nullptr)) { + touch(); + } + }); } return double_spinbox; @@ -491,11 +491,11 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict, if (!Settings::IsConfiguringGlobal()) { date_time_edit->connect(date_time_edit, &QDateTimeEdit::editingFinished, - [this, get_clear_val, touch]() { - if (date_time_edit->dateTime() != get_clear_val()) { - touch(); - } - }); + [this, get_clear_val, touch]() { + if (date_time_edit->dateTime() != get_clear_val()) { + touch(); + } + }); } return date_time_edit; @@ -570,7 +570,8 @@ void Widget::SetupComponent(const QString& label, std::function& load_fu } if (require_checkbox) { - QWidget* lhs = CreateCheckBox(other_setting, label, checkbox_serializer, checkbox_restore_func, touch); + QWidget* lhs = + CreateCheckBox(other_setting, label, checkbox_serializer, checkbox_restore_func, touch); layout->addWidget(lhs, 1); } else if (type_id != "bool") { QLabel* qt_label = CreateLabel(label); @@ -665,16 +666,16 @@ void Widget::SetupComponent(const QString& label, std::function& load_fu layout->addWidget(restore_button); restore_button->connect(restore_button, &QAbstractButton::clicked, - [this, restore_func, checkbox_restore_func](bool) { - LOG_DEBUG(Frontend, "Restore global state for \"{}\"", - setting.GetLabel()); + [this, restore_func, checkbox_restore_func](bool) { + LOG_DEBUG(Frontend, "Restore global state for \"{}\"", + setting.GetLabel()); - restore_button->setEnabled(false); - restore_button->setVisible(false); + restore_button->setEnabled(false); + restore_button->setVisible(false); - checkbox_restore_func(); - restore_func(); - }); + checkbox_restore_func(); + restore_func(); + }); load_func = [this, serializer, require_checkbox, checkbox_serializer, other_setting]() { bool using_global = !restore_button->isEnabled(); @@ -766,8 +767,8 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati Builder::Builder(QWidget* parent_, bool runtime_lock_) : translations{InitializeTranslations(parent_)}, - combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_}, runtime_lock{ - runtime_lock_} {} + combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_}, + runtime_lock{runtime_lock_} {} Builder::~Builder() = default; diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h index b07804ac00..f687d0b20c 100644 --- a/src/yuzu/configuration/shared_widget.h +++ b/src/yuzu/configuration/shared_widget.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -10,10 +10,10 @@ #include #include #include +#include #include #include #include -#include #include #include "qt_common/config/shared_translation.h" diff --git a/src/yuzu/configuration/system/new_user_dialog.h b/src/yuzu/configuration/system/new_user_dialog.h index 656d8d7290..e81007aefe 100644 --- a/src/yuzu/configuration/system/new_user_dialog.h +++ b/src/yuzu/configuration/system/new_user_dialog.h @@ -20,15 +20,15 @@ struct User { QPixmap pixmap; }; -class NewUserDialog : public QDialog -{ +class NewUserDialog : public QDialog { Q_OBJECT Q_PROPERTY(bool isDefaultAvatar READ isDefaultAvatar WRITE setIsDefaultAvatar NOTIFY isDefaultAvatarChanged FINAL) public: - explicit NewUserDialog(QWidget *parent = nullptr); - explicit NewUserDialog(Common::UUID uuid, const std::string &username, const QString &title, QWidget *parent = nullptr); + explicit NewUserDialog(QWidget* parent = nullptr); + explicit NewUserDialog(Common::UUID uuid, const std::string& username, const QString& title, + QWidget* parent = nullptr); ~NewUserDialog(); bool isDefaultAvatar() const; @@ -39,8 +39,8 @@ public: static QPixmap DefaultAvatar(); private: - Ui::NewUserDialog *ui; - QGraphicsScene *m_scene; + Ui::NewUserDialog* ui; + QGraphicsScene* m_scene; QPixmap m_pixmap; ProfileAvatarDialog* avatar_dialog; @@ -48,12 +48,12 @@ private: bool m_isDefaultAvatar = true; bool m_editing = false; - void setup(Common::UUID uuid, const std::string &username, const QString &title); + void setup(Common::UUID uuid, const std::string& username, const QString& title); bool LoadAvatarData(); std::vector DecompressYaz0(const FileSys::VirtualFile& file); public slots: - void setImage(const QPixmap &pixmap); + void setImage(const QPixmap& pixmap); void selectImage(); void setAvatar(); diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index 8caa362bdf..bbe5d43e05 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "data_dialog.h" #include "frontend_common/data_manager.h" -#include "qt_common/util/content.h" #include "qt_common/qt_string_lookup.h" +#include "qt_common/util/content.h" #include "ui_data_dialog.h" #include "util/util.h" @@ -18,18 +18,14 @@ #include -DataDialog::DataDialog(QWidget *parent) - : QDialog(parent) - , ui(std::make_unique()) -{ +DataDialog::DataDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique()) { ui->setupUi(this); // TODO: Should we make this a single widget that pulls data from a model? -#define WIDGET(label, name) \ - ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ - QtCommon::StringLookup::DataManager##name##Tooltip, \ - QStringLiteral(#name), \ - this)); \ +#define WIDGET(label, name) \ + ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ + QtCommon::StringLookup::DataManager##name##Tooltip, \ + QStringLiteral(#name), this)); \ ui->labels->addItem(label); WIDGET(tr("Shaders"), Shaders) @@ -53,14 +49,10 @@ DataDialog::DataDialog(QWidget *parent) DataDialog::~DataDialog() = default; DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, - QtCommon::StringLookup::StringKey tooltip, - const QString &exportName, - QWidget *parent) - : QWidget(parent) - , ui(std::make_unique()) - , m_dir(data_dir) - , m_exportName(exportName) -{ + QtCommon::StringLookup::StringKey tooltip, const QString& exportName, + QWidget* parent) + : QWidget(parent), ui(std::make_unique()), m_dir(data_dir), + m_exportName(exportName) { ui->setupUi(this); ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip)); @@ -78,28 +70,24 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, scan(); } -void DataWidget::clear() -{ +void DataWidget::clear() { std::string user_id = selectProfile(); QtCommon::Content::ClearDataDir(m_dir, user_id); scan(); } -void DataWidget::open() -{ +void DataWidget::open() { std::string user_id = selectProfile(); QDesktopServices::openUrl(QUrl::fromLocalFile( QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id)))); } -void DataWidget::upload() -{ +void DataWidget::upload() { std::string user_id = selectProfile(); QtCommon::Content::ExportDataDir(m_dir, user_id, m_exportName); } -void DataWidget::download() -{ +void DataWidget::download() { std::string user_id = selectProfile(); QtCommon::Content::ImportDataDir(m_dir, user_id, std::bind(&DataWidget::scan, this)); } @@ -107,7 +95,7 @@ void DataWidget::download() void DataWidget::scan() { ui->size->setText(tr("Calculating...")); - QFutureWatcher *watcher = new QFutureWatcher(this); + QFutureWatcher* watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, [=, this]() { u64 size = watcher->result(); @@ -120,8 +108,7 @@ void DataWidget::scan() { QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); } -std::string DataWidget::selectProfile() -{ +std::string DataWidget::selectProfile() { std::string user_id{}; if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { user_id = GetProfileIDString(); diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h index 70714be7cb..7f988f6047 100644 --- a/src/yuzu/data_dialog.h +++ b/src/yuzu/data_dialog.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef DATA_DIALOG_H @@ -14,26 +14,23 @@ namespace Ui { class DataDialog; } -class DataDialog : public QDialog -{ +class DataDialog : public QDialog { Q_OBJECT public: - explicit DataDialog(QWidget *parent = nullptr); + explicit DataDialog(QWidget* parent = nullptr); ~DataDialog(); private: std::unique_ptr ui; }; -class DataWidget : public QWidget -{ +class DataWidget : public QWidget { Q_OBJECT public: explicit DataWidget(FrontendCommon::DataManager::DataDir data_dir, - QtCommon::StringLookup::StringKey tooltip, - const QString &exportName, - QWidget *parent = nullptr); + QtCommon::StringLookup::StringKey tooltip, const QString& exportName, + QWidget* parent = nullptr); public slots: void clear(); diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index e93a8b9265..44d4e1db7b 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -11,8 +11,8 @@ #endif #include "common/logging/backend.h" -#include "yuzu/debugger/console.h" #include "qt_common/config/uisettings.h" +#include "yuzu/debugger/console.h" namespace Debugger { void ToggleConsole() { diff --git a/src/yuzu/deps_dialog.cpp b/src/yuzu/deps_dialog.cpp index ffbd0a0e74..e490bfc163 100644 --- a/src/yuzu/deps_dialog.cpp +++ b/src/yuzu/deps_dialog.cpp @@ -1,24 +1,21 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "yuzu/deps_dialog.h" #include #include #include #include #include #include +#include #include "dep_hashes.h" #include "ui_deps_dialog.h" -#include +#include "yuzu/deps_dialog.h" -DepsDialog::DepsDialog(QWidget* parent) - : QDialog(parent) - , ui{std::make_unique()} -{ +DepsDialog::DepsDialog(QWidget* parent) : QDialog(parent), ui{std::make_unique()} { ui->setupUi(this); - constexpr int rows = (int) Common::dep_hashes.size(); + constexpr int rows = (int)Common::dep_hashes.size(); ui->tableDeps->setRowCount(rows); QStringList labels; @@ -36,8 +33,8 @@ DepsDialog::DepsDialog(QWidget* parent) std::string dependency = fmt::format("{}", url, name); - QTableWidgetItem *nameItem = new QTableWidgetItem(QString::fromStdString(dependency)); - QTableWidgetItem *shaItem = new QTableWidgetItem(QString::fromStdString(sha)); + QTableWidgetItem* nameItem = new QTableWidgetItem(QString::fromStdString(dependency)); + QTableWidgetItem* shaItem = new QTableWidgetItem(QString::fromStdString(sha)); ui->tableDeps->setItem(i, 0, nameItem); ui->tableDeps->setItem(i, 1, shaItem); @@ -48,14 +45,10 @@ DepsDialog::DepsDialog(QWidget* parent) DepsDialog::~DepsDialog() = default; -LinkItemDelegate::LinkItemDelegate(QObject *parent) - : QStyledItemDelegate(parent) -{} +LinkItemDelegate::LinkItemDelegate(QObject* parent) : QStyledItemDelegate(parent) {} -void LinkItemDelegate::paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ +void LinkItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const { auto options = option; initStyleOption(&options, index); @@ -71,8 +64,8 @@ void LinkItemDelegate::paint(QPainter *painter, painter->restore(); } -QSize LinkItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const -{ +QSize LinkItemDelegate::sizeHint(const QStyleOptionViewItem& option, + const QModelIndex& index) const { QStyleOptionViewItem options = option; initStyleOption(&options, index); @@ -82,13 +75,10 @@ QSize LinkItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMode return QSize(doc.idealWidth(), doc.size().height()); } -bool LinkItemDelegate::editorEvent(QEvent *event, - QAbstractItemModel *model, - const QStyleOptionViewItem &option, - const QModelIndex &index) -{ +bool LinkItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, + const QStyleOptionViewItem& option, const QModelIndex& index) { if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); + QMouseEvent* mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { QString html = index.data(Qt::DisplayRole).toString(); QTextDocument doc; @@ -96,7 +86,7 @@ bool LinkItemDelegate::editorEvent(QEvent *event, doc.setTextWidth(option.rect.width()); // this is kinda silly but it werks - QAbstractTextDocumentLayout *layout = doc.documentLayout(); + QAbstractTextDocumentLayout* layout = doc.documentLayout(); QPoint pos = mouseEvent->pos() - option.rect.topLeft(); int charPos = layout->hitTest(pos, Qt::ExactHit); diff --git a/src/yuzu/deps_dialog.h b/src/yuzu/deps_dialog.h index f8e7f1d987..fab2f0b1ca 100644 --- a/src/yuzu/deps_dialog.h +++ b/src/yuzu/deps_dialog.h @@ -1,41 +1,38 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once +#include #include #include #include -#include -namespace Ui { class DepsDialog; } +namespace Ui { +class DepsDialog; +} -class DepsDialog : public QDialog -{ +class DepsDialog : public QDialog { Q_OBJECT public: - explicit DepsDialog(QWidget *parent); + explicit DepsDialog(QWidget* parent); ~DepsDialog() override; private: std::unique_ptr ui; }; -class LinkItemDelegate : public QStyledItemDelegate -{ +class LinkItemDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit LinkItemDelegate(QObject *parent = 0); + explicit LinkItemDelegate(QObject* parent = 0); protected: - void paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - bool editorEvent(QEvent *event, - QAbstractItemModel *model, - const QStyleOptionViewItem &option, - const QModelIndex &index) override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, + const QModelIndex& index) override; }; diff --git a/src/yuzu/game/game_card.cpp b/src/yuzu/game/game_card.cpp index 26ab99faaf..ba60b54fcc 100644 --- a/src/yuzu/game/game_card.cpp +++ b/src/yuzu/game/game_card.cpp @@ -73,9 +73,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->restore(); } else { // if there is no icon just draw a blank rect - iconRect = QRect(cardRect.left() + padding, - cardRect.top() + padding, - _iconsize, _iconsize); + iconRect = QRect(cardRect.left() + padding, cardRect.top() + padding, _iconsize, _iconsize); } if (UISettings::values.show_game_name.GetValue()) { diff --git a/src/yuzu/game/game_card.h b/src/yuzu/game/game_card.h index 9fd0ec081a..86387452b6 100644 --- a/src/yuzu/game/game_card.h +++ b/src/yuzu/game/game_card.h @@ -14,12 +14,10 @@ class GameCard : public QStyledItemDelegate { public: explicit GameCard(QObject* parent = nullptr); - void paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setSize(const QSize& newSize, const int padding); private: diff --git a/src/yuzu/game/game_list.cpp b/src/yuzu/game/game_list.cpp index c57198848f..344cff445c 100644 --- a/src/yuzu/game/game_list.cpp +++ b/src/yuzu/game/game_list.cpp @@ -218,11 +218,13 @@ void GameList::OnTextChanged(const QString& new_text) { for (int i = 0; i < row_count; ++i) { QStandardItem* item = item_model->item(i, 0); - if (!item) continue; + if (!item) + continue; children_total++; - const QString file_path = item->data(GameListItemPath::FullPathRole).toString().toLower(); + const QString file_path = + item->data(GameListItemPath::FullPathRole).toString().toLower(); const QString file_title = item->data(GameListItemPath::TitleRole).toString().toLower(); const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title; @@ -236,7 +238,8 @@ void GameList::OnTextChanged(const QString& new_text) { } search_field->setFilterResult(result_count, children_total); } else if (edit_filter_text.isEmpty()) { - hide(0, UISettings::values.favorited_ids.size() == 0, item_model->invisibleRootItem()->index()); + hide(0, UISettings::values.favorited_ids.size() == 0, + item_model->invisibleRootItem()->index()); for (int i = 1; i < item_model->rowCount() - 1; ++i) { folder = item_model->item(i, 0); const QModelIndex folder_index = folder->index(); @@ -362,7 +365,8 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid external_watcher = new QFileSystemWatcher(this); ResetExternalWatcher(); - connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshExternalContent); + connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, + &GameList::RefreshExternalContent); this->main_window = parent; layout = new QVBoxLayout; @@ -471,7 +475,7 @@ bool GameList::IsTreeMode() { } void GameList::ResetViewMode() { - auto &setting = UISettings::values.game_list_mode; + auto& setting = UISettings::values.game_list_mode; bool newTreeMode = false; switch (setting.GetValue()) { @@ -678,7 +682,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { return; QMenu blank_menu; - QAction *addGameDirAction = blank_menu.addAction(tr("&Add New Game Directory")); + QAction* addGameDirAction = blank_menu.addAction(tr("&Add New Game Directory")); connect(addGameDirAction, &QAction::triggered, this, &GameList::AddDirectory); blank_menu.exec(m_currentView->viewport()->mapToGlobal(menu_location)); @@ -1113,8 +1117,7 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; -void GameList::RefreshGameDirectory() -{ +void GameList::RefreshGameDirectory() { // Reset the externals watcher whenever the game list is reloaded, // primarily ensures that new titles and external dirs are caught. ResetExternalWatcher(); @@ -1142,7 +1145,7 @@ void GameList::ResetExternalWatcher() { external_watcher->removePaths(watch_dirs); } - for (const std::string &dir : Settings::values.external_content_dirs) { + for (const std::string& dir : Settings::values.external_content_dirs) { external_watcher->addPath(QString::fromStdString(dir)); } } @@ -1286,8 +1289,8 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { horizontal_scroll_target = m_currentView->horizontalScrollBar()->value(); horizontal_scroll_target -= deltaX; - horizontal_scroll_target = - qBound(0, horizontal_scroll_target, m_currentView->horizontalScrollBar()->maximum()); + horizontal_scroll_target = qBound(0, horizontal_scroll_target, + m_currentView->horizontalScrollBar()->maximum()); horizontal_scroll->stop(); horizontal_scroll->setStartValue(m_currentView->horizontalScrollBar()->value()); diff --git a/src/yuzu/game/game_list.h b/src/yuzu/game/game_list.h index 7de622b714..42efcbdc3b 100644 --- a/src/yuzu/game/game_list.h +++ b/src/yuzu/game/game_list.h @@ -21,10 +21,10 @@ #include "common/common_types.h" #include "core/core.h" +#include "frontend_common/play_time_manager.h" #include "qt_common/config/uisettings.h" #include "qt_common/util/game.h" #include "yuzu/compatibility_list.h" -#include "frontend_common/play_time_manager.h" class QVariantAnimation; @@ -124,7 +124,7 @@ signals: void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); - void LinkToRyujinxRequested(const u64 &program_id); + void LinkToRyujinxRequested(const u64& program_id); void OpenDirectory(const QString& directory); void AddDirectory(); void ShowList(bool show); @@ -170,8 +170,8 @@ private: QVBoxLayout* layout = nullptr; QTreeView* tree_view = nullptr; - QListView *list_view = nullptr; - GameCard *m_gameCard = nullptr; + QListView* list_view = nullptr; + GameCard* m_gameCard = nullptr; QStandardItemModel* item_model = nullptr; std::unique_ptr current_worker; @@ -194,7 +194,7 @@ private: Core::System& system; bool m_isTreeMode = true; - QAbstractItemView *m_currentView = tree_view; + QAbstractItemView* m_currentView = tree_view; }; class GameListPlaceholder : public QWidget { diff --git a/src/yuzu/game/game_list_p.h b/src/yuzu/game/game_list_p.h index 9bae5b9e01..004f8917ad 100644 --- a/src/yuzu/game/game_list_p.h +++ b/src/yuzu/game/game_list_p.h @@ -14,10 +14,10 @@ #include #include #include +#include #include #include #include -#include #include "common/common_types.h" #include "common/logging/log.h" @@ -77,7 +77,7 @@ public: GameListItemPath() = default; GameListItemPath(const QString& game_path, const std::vector& picture_data, const QString& game_name, const QString& game_type, u64 program_id, - u64 play_time, const QString &patch_versions) { + u64 play_time, const QString& patch_versions) { setData(type(), TypeRole); setData(game_path, FullPathRole); setData(game_name, TitleRole); @@ -85,14 +85,15 @@ public: setData(game_type, FileTypeRole); const auto readable_play_time = - play_time > 0 - ? QObject::tr("Play Time: %1").arg(QString::fromStdString(PlayTime::PlayTimeManager::GetReadablePlayTime(play_time))) - : QObject::tr("Never Played"); + play_time > 0 ? QObject::tr("Play Time: %1") + .arg(QString::fromStdString( + PlayTime::PlayTimeManager::GetReadablePlayTime(play_time))) + : QObject::tr("Never Played"); const auto enabled_update = [patch_versions]() -> QString { const QStringList lines = patch_versions.split(QLatin1Char('\n')); const QRegularExpression regex{QStringLiteral(R"(^Update \(([0-9\.]+)\))")}; - for (const QString &line : std::as_const(lines)) { + for (const QString& line : std::as_const(lines)) { const auto match = regex.match(line); if (match.hasMatch() && match.hasCaptured(1)) return QObject::tr("Version: %1").arg(match.captured(1)); @@ -100,9 +101,7 @@ public: return QObject::tr("Version: 1.0.0"); }(); - const auto tooltip = QStringLiteral("%1\n%2").arg( - readable_play_time, - enabled_update); + const auto tooltip = QStringLiteral("%1\n%2").arg(readable_play_time, enabled_update); setData(tooltip, Qt::ToolTipRole); @@ -145,7 +144,7 @@ public: return row1.toLower(); } - // None + // None if (row2_id == 4) { return row1; } @@ -163,7 +162,6 @@ public: default: break; } - } return GameListItem::data(role); diff --git a/src/yuzu/game/game_list_worker.cpp b/src/yuzu/game/game_list_worker.cpp index 81012e4374..777122e135 100644 --- a/src/yuzu/game/game_list_worker.cpp +++ b/src/yuzu/game/game_list_worker.cpp @@ -30,11 +30,11 @@ #include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" +#include "qt_common/config/uisettings.h" #include "yuzu/compatibility_list.h" #include "yuzu/game/game_list.h" #include "yuzu/game/game_list_p.h" #include "yuzu/game/game_list_worker.h" -#include "qt_common/config/uisettings.h" namespace { @@ -198,26 +198,24 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, return out; } -QList MakeGameListEntry(const std::string& path, - const std::string& name, - const std::size_t size, - const std::vector& icon, - Loader::AppLoader& loader, - u64 program_id, +QList MakeGameListEntry(const std::string& path, const std::string& name, + const std::size_t size, const std::vector& icon, + Loader::AppLoader& loader, u64 program_id, const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager, - const FileSys::PatchManager& patch) -{ + const FileSys::PatchManager& patch) { auto const it = FindMatchingCompatibilityEntry(compatibility_list, program_id); // The game list uses 99 as compatibility number for untested games - QString compatibility = it != compatibility_list.end() ? it->second.first : QStringLiteral("99"); + QString compatibility = + it != compatibility_list.end() ? it->second.first : QStringLiteral("99"); auto const file_type = loader.GetFileType(); auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); - QString patch_versions = GetGameListCachedObject(fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { - return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); - }); + QString patch_versions = GetGameListCachedObject( + fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { + return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); + }); u64 play_time = play_time_manager.GetPlayTime(program_id); return QList{ @@ -238,13 +236,9 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_) - : vfs{std::move(vfs_)} - , provider{provider_} - , game_dirs{game_dirs_} - , compatibility_list{compatibility_list_} - , play_time_manager{play_time_manager_} - , system{system_} -{ + : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, + compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, + system{system_} { // We want the game list to manage our lifetime. setAutoDelete(false); } @@ -338,15 +332,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { GetMetadataFromControlNCA(patch, *control, icon, name); } - auto entry = MakeGameListEntry(file->GetFullPath(), - name, - file->GetSize(), - icon, - *loader, - program_id, - compatibility_list, - play_time_manager, - patch); + auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } @@ -424,15 +411,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()}; - auto entry = MakeGameListEntry(physical_name, - name, - Common::FS::GetSize(physical_name), - icon, - *loader, - id, - compatibility_list, - play_time_manager, - patch); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + id, compatibility_list, play_time_manager, patch); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); @@ -447,15 +428,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; - auto entry = MakeGameListEntry(physical_name, - name, - Common::FS::GetSize(physical_name), - icon, - *loader, - program_id, - compatibility_list, - play_time_manager, - patch); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); diff --git a/src/yuzu/game/game_list_worker.h b/src/yuzu/game/game_list_worker.h index 76153f7917..bf67585fe6 100644 --- a/src/yuzu/game/game_list_worker.h +++ b/src/yuzu/game/game_list_worker.h @@ -18,9 +18,9 @@ #include "common/thread.h" #include "core/file_sys/registered_cache.h" +#include "frontend_common/play_time_manager.h" #include "qt_common/config/uisettings.h" #include "yuzu/compatibility_list.h" -#include "frontend_common/play_time_manager.h" namespace Core { class System; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 10e407402b..5ca437a597 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -10,8 +10,8 @@ #include #include "hid_core/frontend/emulated_controller.h" -#include "yuzu/hotkeys.h" #include "qt_common/config/uisettings.h" +#include "yuzu/hotkeys.h" HotkeyRegistry::HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default; diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 1b772a0eba..de42297618 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -10,8 +10,8 @@ #include #include #include -#include "yuzu/install_dialog.h" #include "qt_common/config/uisettings.h" +#include "yuzu/install_dialog.h" InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) { file_list = new QListWidget(this); diff --git a/src/yuzu/libqt_common.cpp b/src/yuzu/libqt_common.cpp new file mode 100644 index 0000000000..198d1b66be --- /dev/null +++ b/src/yuzu/libqt_common.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "libqt_common.h" +#include "qt_common/abstract/frontend.h" +#include "qt_common/abstract/progress.h" +#include "qt_common/qt_common.h" + +namespace QtCommon::Frontend { + +StandardButton ShowMessage(Icon icon, const QString& title, const QString& text, + StandardButtons buttons, QObject* parent) { + QMessageBox* box = + new QMessageBox(QMessageBox::Icon(int(icon)), title, text, + QMessageBox::StandardButtons(int(buttons)), (QWidget*)parent); + return StandardButton(box->exec()); +} + +WidgetsProgressDialog::WidgetsProgressDialog(const QString& labelText, + const QString& cancelButtonText, int minimum, + int maximum, QWidget* parent, Qt::WindowFlags f) + : QtProgressDialog(labelText, cancelButtonText, minimum, maximum, parent, f), + m_dialog(new QProgressDialog(labelText, cancelButtonText, minimum, maximum, parent, f)) { + m_dialog->setAutoClose(false); + m_dialog->setAutoReset(false); + m_dialog->setMinimumDuration(100); + m_dialog->setWindowModality(Qt::WindowModal); +} + +bool WidgetsProgressDialog::wasCanceled() const { + return m_dialog->wasCanceled(); +} + +void WidgetsProgressDialog::setWindowModality(Qt::WindowModality modality) { + m_dialog->setWindowModality(modality); +} + +void WidgetsProgressDialog::setMinimumDuration(int durationMs) { + m_dialog->setMinimumDuration(durationMs); +} + +void WidgetsProgressDialog::setAutoClose(bool autoClose) { + m_dialog->setAutoClose(autoClose); +} + +void WidgetsProgressDialog::setAutoReset(bool autoReset) { + m_dialog->setAutoReset(autoReset); +} + +void WidgetsProgressDialog::setTitle(QString title) { + m_dialog->setWindowTitle(title); +} + +void WidgetsProgressDialog::setLabelText(QString text) { + m_dialog->setLabelText(text); +} + +void WidgetsProgressDialog::setMinimum(int min) { + m_dialog->setMinimum(min); +} + +void WidgetsProgressDialog::setMaximum(int max) { + m_dialog->setMaximum(max); +} + +void WidgetsProgressDialog::setValue(int value) { + m_dialog->setValue(value); +} + +bool WidgetsProgressDialog::close() { + m_dialog->close(); + return true; +} + +void WidgetsProgressDialog::show() { + m_dialog->show(); +} + +std::unique_ptr newProgressDialog(const QString& labelText, + const QString& cancelButtonText, int minimum, + int maximum, Qt::WindowFlags f) { + return std::make_unique(labelText, cancelButtonText, minimum, maximum, + (QWidget*)rootObject, f); +} + +QtProgressDialog* newProgressDialogPtr(const QString& labelText, const QString& cancelButtonText, + int minimum, int maximum, Qt::WindowFlags f) { + return new WidgetsProgressDialog(labelText, cancelButtonText, minimum, maximum, + (QWidget*)rootObject, f); +} + +int Choice(const QString& title, const QString& caption, const QStringList& options) { + QMessageBox box(rootObject); + box.setText(caption); + box.setWindowTitle(title); + + for (const QString& opt : options) { + box.addButton(opt, QMessageBox::AcceptRole); + } + + box.addButton(QMessageBox::Cancel); + + box.exec(); + auto button = box.clickedButton(); + return options.indexOf(button->text()); +} + +const QString GetTextInput(const QString& title, const QString& caption, + const QString& defaultText) { + return QInputDialog::getText(rootObject, title, caption, QLineEdit::Normal, defaultText); +} + +} // namespace QtCommon::Frontend diff --git a/src/yuzu/libqt_common.h b/src/yuzu/libqt_common.h new file mode 100644 index 0000000000..356fd65d2b --- /dev/null +++ b/src/yuzu/libqt_common.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "qt_common/abstract/progress.h" + +#include + +namespace QtCommon::Frontend { + +class WidgetsProgressDialog : public QtProgressDialog { + Q_OBJECT +public: + WidgetsProgressDialog(const QString& labelText, const QString& cancelButtonText, int minimum, + int maximum, QWidget* parent = nullptr, Qt::WindowFlags f = {}); + + bool wasCanceled() const override; + void setWindowModality(Qt::WindowModality modality) override; + void setMinimumDuration(int durationMs) override; + void setAutoClose(bool autoClose) override; + void setAutoReset(bool autoReset) override; + +public slots: + void setTitle(QString title) override; + void setLabelText(QString text) override; + void setMinimum(int min) override; + void setMaximum(int max) override; + void setValue(int value) override; + bool close() override; + void show() override; + +private: + QProgressDialog* m_dialog; +}; + +} // namespace QtCommon::Frontend diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index d6550a2c81..d67667a91d 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -4,7 +4,6 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include #include "core/frontend/framebuffer_layout.h" #include "core/loader/loader.h" #include "ui_loading_screen.h" diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index c6b4fde83e..f8528d9671 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -5,8 +5,8 @@ #include "startup_checks.h" #if YUZU_ROOM -#include "dedicated_room/yuzu_room.h" #include +#include "dedicated_room/yuzu_room.h" #endif #include @@ -62,9 +62,8 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() { // Get the lower of the 2 ratios and truncate, this is the maximum integer scale. const qreal max_ratio = std::trunc(std::min(width_ratio, height_ratio)); - return max_ratio > real_ratio - ? Qt::HighDpiScaleFactorRoundingPolicy::Round - : Qt::HighDpiScaleFactorRoundingPolicy::Floor; + return max_ratio > real_ratio ? Qt::HighDpiScaleFactorRoundingPolicy::Round + : Qt::HighDpiScaleFactorRoundingPolicy::Floor; #else // Other OSes should be better than Windows at fractional scaling. return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough; @@ -174,7 +173,7 @@ int main(int argc, char* argv[]) { main_window.show(); app.connect(&app, &QGuiApplication::applicationStateChanged, &main_window, - &MainWindow::OnAppFocusStateChanged); + &MainWindow::OnAppFocusStateChanged); int result = app.exec(); detached_tasks.WaitForAllTasks(); diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 399b3bd976..a251451bea 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -19,8 +19,8 @@ #include "ui_main.h" // Other Yuzu stuff // -#include "debugger/controller.h" #include "debugger/console.h" +#include "debugger/controller.h" #include "about_dialog.h" #include "data_dialog.h" @@ -28,18 +28,18 @@ #include "install_dialog.h" #include "bootmanager.h" -#include "yuzu/game/game_list.h" #include "loading_screen.h" #include "ryujinx_dialog.h" #include "set_play_time_dialog.h" #include "util/util.h" #include "vk_device_info.h" +#include "yuzu/game/game_list.h" #include "applets/qt_amiibo_settings.h" #include "applets/qt_controller.h" +#include "applets/qt_error.h" #include "applets/qt_profile_select.h" #include "applets/qt_software_keyboard.h" -#include "applets/qt_error.h" #include "applets/qt_web_browser.h" #include "configuration/configure_dialog.h" @@ -48,8 +48,8 @@ #include "configuration/configure_tas.h" #include "util/clickable_label.h" -#include "util/overlay_dialog.h" #include "util/controller_navigation.h" +#include "util/overlay_dialog.h" #include "multiplayer/state.h" @@ -60,6 +60,7 @@ #include #endif +#include #include #include #include @@ -68,32 +69,32 @@ #include #include #include +#include #include #include #include #include #include #include -#include -#include // Qt Common // -#include "qt_common/config/uisettings.h" #include "qt_common/config/shared_translation.h" +#include "qt_common/config/uisettings.h" #include "qt_common/abstract/frontend.h" #include "qt_common/qt_common.h" -#include "qt_common/util/path.h" -#include "qt_common/util/meta.h" #include "qt_common/util/content.h" #include "qt_common/util/fs.h" +#include "qt_common/util/meta.h" #include "qt_common/util/mod.h" +#include "qt_common/util/path.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. -static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path, FileSys::OpenMode mode) { +static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path, + FileSys::OpenMode mode) { return QtCommon::vfs->CreateDirectory(path, mode); } @@ -122,14 +123,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #endif // Core // -#include "core/frontend/applets/software_keyboard.h" -#include "core/frontend/applets/mii_edit.h" #include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/frontend/applet_web_browser_types.h" -#include "core/hle/kernel/k_process.h" #include "core/file_sys/card_image.h" #include "core/file_sys/romfs.h" @@ -142,8 +143,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/crypto/key_manager.h" // Input // -#include "hid_core/hid_core.h" #include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" #include "input_common/drivers/virtual_amiibo.h" // Video Core // @@ -165,17 +166,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #ifdef __unix__ #include +#include #include #include "qt_common/gui_settings.h" -#include #endif #include "qt_common/gamemode.h" #ifdef _WIN32 -#include "core/core_timing.h" #include "common/windows/timer_resolution.h" +#include "core/core_timing.h" #include #include @@ -381,7 +382,7 @@ static QString PrettyProductName() { namespace { -constexpr std::array, 5> default_game_icon_sizes{ +constexpr std::array, 5> default_game_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("MainWindow", "None")), std::make_pair(32, QT_TRANSLATE_NOOP("MainWindow", "Small (32x32)")), std::make_pair(64, QT_TRANSLATE_NOOP("MainWindow", "Standard (64x64)")), @@ -393,7 +394,7 @@ QString GetTranslatedGameIconSize(size_t index) { return QCoreApplication::translate("MainWindow", default_game_icon_sizes[index].second); } -} +} // namespace #ifndef _WIN32 // TODO(crueter): carboxyl does this, is it needed in qml? @@ -412,7 +413,7 @@ inline static bool isDarkMode() { MainWindow::MainWindow(bool has_broken_vulkan) : ui{std::make_unique()}, - input_subsystem{std::make_shared()}, user_data_migrator{this} { + input_subsystem{std::make_shared()}, user_data_migrator{this} { QtCommon::Init(this); Common::FS::CreateEdenPaths(); @@ -423,17 +424,14 @@ MainWindow::MainWindow(bool has_broken_vulkan) using namespace Common::FS; - static constexpr const std::array paths = {EdenPath::NANDDir, - EdenPath::SDMCDir, - EdenPath::DumpDir, - EdenPath::LoadDir}; + static constexpr const std::array paths = { + EdenPath::NANDDir, EdenPath::SDMCDir, EdenPath::DumpDir, EdenPath::LoadDir}; for (const EdenPath& path : paths) { std::string str_path = Common::FS::GetEdenPathString(path); if (str_path.starts_with(user_data_migrator.selected_emu.get_user_dir())) { - boost::replace_all(str_path, - user_data_migrator.selected_emu.lower_name().toStdString(), - "eden"); + boost::replace_all( + str_path, user_data_migrator.selected_emu.lower_name().toStdString(), "eden"); Common::FS::SetEdenPath(path, str_path); } } @@ -531,7 +529,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) std::chrono::duration_cast>( Common::Windows::SetCurrentTimerResolutionToMaximum()) .count()); - QtCommon::system->CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); + QtCommon::system->CoreTiming().SetTimerResolutionNs( + Common::Windows::GetCurrentTimerResolution()); #endif UpdateWindowTitle(); @@ -539,9 +538,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) #ifdef ENABLE_UPDATE_CHECKER if (UISettings::values.check_for_updates) { - update_future = QtConcurrent::run([]() -> std::optional { - return UpdateChecker::GetUpdate(); - }); + update_future = QtConcurrent::run( + []() -> std::optional { return UpdateChecker::GetUpdate(); }); update_watcher.connect(&update_watcher, &QFutureWatcher::finished, this, &MainWindow::OnEmulatorUpdateAvailable); update_watcher.setFuture(update_future); @@ -590,7 +588,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) if (has_broken_vulkan) { UISettings::values.has_broken_vulkan = true; - QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"), tr("Vulkan initialization failed during boot.")); + QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"), + tr("Vulkan initialization failed during boot.")); #ifdef HAS_OPENGL Settings::values.renderer_backend = Settings::RendererBackend::OpenGL_GLSL; #else @@ -652,7 +651,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) if (!argument_ok) { // try to look it up by username, only finds the first username that matches. std::string const user_arg_str = args[user_arg_idx].toStdString(); - auto const user_idx = QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str); + auto const user_idx = + QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str); if (user_idx != std::nullopt) { selected_user = user_idx.value(); } else { @@ -711,7 +711,7 @@ MainWindow::~MainWindow() { } void MainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr nfp_device) { + std::shared_ptr nfp_device) { cabinet_applet = new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device); SCOPE_EXIT { @@ -915,7 +915,7 @@ void MainWindow::SoftwareKeyboardExit() { } void MainWindow::WebBrowserOpenWebPage(const std::string& main_url, - const std::string& additional_args, bool is_local) { + const std::string& additional_args, bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE // Raw input breaks with the web applet, Disable web applets if enabled @@ -1072,7 +1072,8 @@ void MainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); render_window->hide(); - game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, *QtCommon::system, this); + game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, + *QtCommon::system, this); ui->horizontalLayout->addWidget(game_list); game_list_placeholder = new GameListPlaceholder(this); @@ -1229,8 +1230,7 @@ void MainWindow::InitializeWidgets() { filter_status_button = new QPushButton(); filter_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); filter_status_button->setFocusPolicy(Qt::NoFocus); - connect(filter_status_button, &QPushButton::clicked, this, - &MainWindow::OnToggleAdaptingFilter); + connect(filter_status_button, &QPushButton::clicked, this, &MainWindow::OnToggleAdaptingFilter); UpdateFilterText(); filter_status_button->setCheckable(true); filter_status_button->setChecked(true); @@ -1369,7 +1369,7 @@ void MainWindow::InitializeRecentFileMenuActions() { } void MainWindow::LinkActionShortcut(QAction* action, const QString& action_name, - const bool tas_allowed) { + const bool tas_allowed) { static const auto main_window = std::string("Main Window"); action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name.toStdString())); action->setShortcutContext( @@ -1378,7 +1378,8 @@ void MainWindow::LinkActionShortcut(QAction* action, const QString& action_name, this->addAction(action); - auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(main_window, action_name.toStdString(), controller); connect( @@ -1404,17 +1405,16 @@ void MainWindow::InitializeHotkeys() { LinkActionShortcut(ui->action_Stop, QStringLiteral("Stop Emulation")); LinkActionShortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar")); LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); - LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay")); + LinkActionShortcut(ui->action_Show_Performance_Overlay, + QStringLiteral("Toggle Performance Overlay")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true); LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true); - LinkActionShortcut(ui->action_View_Lobby, - QStringLiteral("Browse Public Game Lobby")); + LinkActionShortcut(ui->action_View_Lobby, QStringLiteral("Browse Public Game Lobby")); LinkActionShortcut(ui->action_Start_Room, QStringLiteral("Create Room")); - LinkActionShortcut(ui->action_Connect_To_Room, - QStringLiteral("Direct Connect to Room")); + LinkActionShortcut(ui->action_Connect_To_Room, QStringLiteral("Direct Connect to Room")); LinkActionShortcut(ui->action_Show_Room, QStringLiteral("Show Current Room")); LinkActionShortcut(ui->action_Leave_Room, QStringLiteral("Leave Room")); LinkActionShortcut(ui->action_Configure, QStringLiteral("Configure")); @@ -1424,7 +1424,8 @@ void MainWindow::InitializeHotkeys() { const auto connect_shortcut = [&](const QString& action_name, const Fn& function) { const auto* hotkey = hotkey_registry.GetHotkey(main_window.toStdString(), action_name.toStdString(), this); - auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); const auto* controller_hotkey = hotkey_registry.GetControllerHotkey( main_window.toStdString(), action_name.toStdString(), controller); connect(hotkey, &QShortcut::activated, this, function); @@ -1438,8 +1439,7 @@ void MainWindow::InitializeHotkeys() { ToggleFullscreen(); } }); - connect_shortcut(QStringLiteral("Change Adapting Filter"), - &MainWindow::OnToggleAdaptingFilter); + connect_shortcut(QStringLiteral("Change Adapting Filter"), &MainWindow::OnToggleAdaptingFilter); connect_shortcut(QStringLiteral("Change Docked Mode"), &MainWindow::OnToggleDockedMode); connect_shortcut(QStringLiteral("Change GPU Mode"), &MainWindow::OnToggleGpuAccuracy); connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &MainWindow::OnMute); @@ -1513,8 +1513,10 @@ void MainWindow::RestoreUIState() { ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue()); statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); - ui->action_Show_Performance_Overlay->setChecked(UISettings::values.show_perf_overlay.GetValue()); - if (perf_overlay) perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); + ui->action_Show_Performance_Overlay->setChecked( + UISettings::values.show_perf_overlay.GetValue()); + if (perf_overlay) + perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); Debugger::ToggleConsole(); } @@ -1554,16 +1556,14 @@ void MainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &MainWindow::OnGameListLoadFile); connect(game_list, &GameList::OpenDirectory, this, &MainWindow::OnGameListOpenDirectory); connect(game_list, &GameList::OpenFolderRequested, this, &MainWindow::OnGameListOpenFolder); - connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, [this](u64 program_id) { - QtCommon::Path::OpenShaderCache(program_id, this); - }); + connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, + [this](u64 program_id) { QtCommon::Path::OpenShaderCache(program_id, this); }); connect(game_list, &GameList::RemoveInstalledEntryRequested, this, &MainWindow::OnGameListRemoveInstalledEntry); connect(game_list, &GameList::RemoveFileRequested, this, &MainWindow::OnGameListRemoveFile); connect(game_list, &GameList::RemovePlayTimeRequested, this, &MainWindow::OnGameListRemovePlayTimeData); - connect(game_list, &GameList::SetPlayTimeRequested, this, - &MainWindow::OnGameListSetPlayTime); + connect(game_list, &GameList::SetPlayTimeRequested, this, &MainWindow::OnGameListSetPlayTime); connect(game_list, &GameList::DumpRomFSRequested, this, &MainWindow::OnGameListDumpRomFS); connect(game_list, &GameList::VerifyIntegrityRequested, this, &MainWindow::OnGameListVerifyIntegrity); @@ -1581,11 +1581,9 @@ void MainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &MainWindow::OnGameListOpenPerGameProperties); - connect(game_list, &GameList::LinkToRyujinxRequested, this, - &MainWindow::OnLinkToRyujinx); + connect(game_list, &GameList::LinkToRyujinxRequested, this, &MainWindow::OnLinkToRyujinx); - connect(this, &MainWindow::UpdateInstallProgress, this, - &MainWindow::IncrementInstallProgress); + connect(this, &MainWindow::UpdateInstallProgress, this, &MainWindow::IncrementInstallProgress); connect(this, &MainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); @@ -1652,10 +1650,11 @@ void MainWindow::ConnectMenuEvents() { for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { const auto current_size = UISettings::values.game_icon_size.GetValue(); const auto size = default_game_icon_sizes[i].first; - QAction *action = ui->menuGame_Icon_Size->addAction(GetTranslatedGameIconSize(i)); + QAction* action = ui->menuGame_Icon_Size->addAction(GetTranslatedGameIconSize(i)); action->setCheckable(true); - if (current_size == size) action->setChecked(true); + if (current_size == size) + action->setChecked(true); game_size_actions->addAction(action); @@ -1687,35 +1686,38 @@ void MainWindow::ConnectMenuEvents() { connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &MainWindow::OnSaveConfig); // Tools - connect_menu(ui->action_Launch_PhotoViewer, [this]{ + connect_menu(ui->action_Launch_PhotoViewer, [this] { LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::PhotoViewer), std::nullopt); }); - connect_menu(ui->action_Launch_MiiEdit, [this]{ + connect_menu(ui->action_Launch_MiiEdit, [this] { LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::MiiEdit), std::nullopt); }); - connect_menu(ui->action_Launch_Controller, [this]{ + connect_menu(ui->action_Launch_Controller, [this] { LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Controller), std::nullopt); }); - connect_menu(ui->action_Launch_QLaunch, [this]{ + connect_menu(ui->action_Launch_QLaunch, [this] { LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); }); // Tools (cabinet) - connect_menu(ui->action_Launch_Cabinet_Nickname_Owner, [this]{ - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartNicknameAndOwnerSettings}); + connect_menu(ui->action_Launch_Cabinet_Nickname_Owner, [this] { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), + {Service::NFP::CabinetMode::StartNicknameAndOwnerSettings}); }); - connect_menu(ui->action_Launch_Cabinet_Eraser, [this]{ - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartGameDataEraser}); + connect_menu(ui->action_Launch_Cabinet_Eraser, [this] { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), + {Service::NFP::CabinetMode::StartGameDataEraser}); }); - connect_menu(ui->action_Launch_Cabinet_Restorer, [this]{ - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartRestorer}); + connect_menu(ui->action_Launch_Cabinet_Restorer, [this] { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), + {Service::NFP::CabinetMode::StartRestorer}); }); - connect_menu(ui->action_Launch_Cabinet_Formatter, [this]{ - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), {Service::NFP::CabinetMode::StartFormatter}); + connect_menu(ui->action_Launch_Cabinet_Formatter, [this] { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Cabinet), + {Service::NFP::CabinetMode::StartFormatter}); }); connect_menu(ui->action_Desktop, &MainWindow::OnCreateHomeMenuDesktopShortcut); - connect_menu(ui->action_Application_Menu, - &MainWindow::OnCreateHomeMenuApplicationMenuShortcut); + connect_menu(ui->action_Application_Menu, &MainWindow::OnCreateHomeMenuApplicationMenuShortcut); connect_menu(ui->action_Capture_Screenshot, &MainWindow::OnCaptureScreenshot); // TAS @@ -1754,15 +1756,10 @@ void MainWindow::UpdateMenuState() { }; const std::array applet_actions{ - ui->action_Launch_PhotoViewer, - ui->action_Launch_Cabinet_Nickname_Owner, - ui->action_Launch_Cabinet_Eraser, - ui->action_Launch_Cabinet_Restorer, - ui->action_Launch_Cabinet_Formatter, - ui->action_Launch_MiiEdit, - ui->action_Launch_QLaunch, - ui->action_Launch_Controller - }; + ui->action_Launch_PhotoViewer, ui->action_Launch_Cabinet_Nickname_Owner, + ui->action_Launch_Cabinet_Eraser, ui->action_Launch_Cabinet_Restorer, + ui->action_Launch_Cabinet_Formatter, ui->action_Launch_MiiEdit, + ui->action_Launch_QLaunch, ui->action_Launch_Controller}; for (QAction* action : running_actions) { action->setEnabled(emulation_running); @@ -1795,17 +1792,16 @@ void MainWindow::SetupPrepareForSleep() { const auto dbus_logind_service = QStringLiteral("org.freedesktop.login1"); const auto dbus_logind_path = QStringLiteral("/org/freedesktop/login1"); const auto dbus_logind_manager_if = QStringLiteral("org.freedesktop.login1.Manager"); - //const auto dbus_logind_session_if = QStringLiteral("org.freedesktop.login1.Session"); + // const auto dbus_logind_session_if = QStringLiteral("org.freedesktop.login1.Session"); #else const auto dbus_logind_service = QStringLiteral("org.freedesktop.ConsoleKit"); const auto dbus_logind_path = QStringLiteral("/org/freedesktop/ConsoleKit/Manager"); const auto dbus_logind_manager_if = QStringLiteral("org.freedesktop.ConsoleKit.Manager"); - //const auto dbus_logind_session_if = QStringLiteral("org.freedesktop.ConsoleKit.Session"); + // const auto dbus_logind_session_if = QStringLiteral("org.freedesktop.ConsoleKit.Session"); #endif - const bool success = bus.connect( - dbus_logind_service, dbus_logind_path, - dbus_logind_manager_if, QStringLiteral("PrepareForSleep"), - QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool))); + const bool success = bus.connect(dbus_logind_service, dbus_logind_path, + dbus_logind_manager_if, QStringLiteral("PrepareForSleep"), + QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool))); if (!success) LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal"); } else { @@ -1929,19 +1925,19 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar /** firmware check */ - if (!QtCommon::Content::CheckGameFirmware(params.program_id, this)) { + if (!QtCommon::Content::CheckGameFirmware(params.program_id)) return false; - } /** Exec */ const Core::SystemResultStatus result{ - QtCommon::system->Load(*render_window, filename.toStdString(), params)}; + QtCommon::system->Load(*render_window, filename.toStdString(), params)}; const auto drd_callout = (UISettings::values.callout_flags.GetValue() & static_cast(CalloutFlag::DRDDeprecation)) == 0; if (result == Core::SystemResultStatus::Success && - QtCommon::system->GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory && + QtCommon::system->GetAppLoader().GetFileType() == + Loader::FileType::DeconstructedRomDirectory && drd_callout) { UISettings::values.callout_flags = UISettings::values.callout_flags.GetValue() | static_cast(CalloutFlag::DRDDeprecation); @@ -2048,7 +2044,7 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { } void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, - StartGameType type) { + StartGameType type) { LOG_INFO(Frontend, "Eden starting..."); if (params.program_id == 0 || @@ -2067,7 +2063,8 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa ConfigureFilesystemProvider(filename.toStdString()); const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); - const auto loader = Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); + const auto loader = + Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && type == StartGameType::Normal) { @@ -2086,11 +2083,11 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa if (UISettings::values.select_user_on_boot && !user_flag_cmd_line) { const Core::Frontend::ProfileSelectParameters parameters{ - .mode = Service::AM::Frontend::UiMode::UserSelector, - .invalid_uid_list = {}, - .display_options = {}, - .purpose = Service::AM::Frontend::UserSelectionPurpose::General, - }; + .mode = Service::AM::Frontend::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Frontend::UserSelectionPurpose::General, + }; if (SelectAndSetCurrentUser(parameters) == false) { return; } @@ -2247,8 +2244,9 @@ bool MainWindow::OnShutdownBegin() { } void MainWindow::OnShutdownBeginDialog() { - shutdown_dialog = new OverlayDialog(this, *QtCommon::system, QString{}, tr("Closing software..."), - QString{}, QString{}, Qt::AlignHCenter | Qt::AlignVCenter); + shutdown_dialog = + new OverlayDialog(this, *QtCommon::system, QString{}, tr("Closing software..."), QString{}, + QString{}, Qt::AlignHCenter | Qt::AlignVCenter); shutdown_dialog->open(); } @@ -2385,7 +2383,7 @@ void MainWindow::OnGameListLoadFile(QString game_path, u64 program_id) { // TODO(crueter): Common profile selector void MainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, - const std::string& game_path) { + const std::string& game_path) { std::filesystem::path path; QString open_target; @@ -2415,8 +2413,8 @@ void MainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, case GameListOpenTarget::SaveData: { open_target = tr("Save Data"); const auto save_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::SaveDir); - auto vfs_save_dir = - QtCommon::vfs->OpenDirectory(Common::FS::PathToUTF8String(save_dir), FileSys::OpenMode::Read); + auto vfs_save_dir = QtCommon::vfs->OpenDirectory(Common::FS::PathToUTF8String(save_dir), + FileSys::OpenMode::Read); if (has_user_save) { // User save data @@ -2531,7 +2529,8 @@ static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& // TODO(crueter): All this can be transfered to qt_common // Aldoe I need to decide re: message boxes for QML // translations_common? strings_common? qt_strings? who knows -void MainWindow::OnGameListRemoveInstalledEntry(u64 program_id, QtCommon::Game::InstalledEntryType type) { +void MainWindow::OnGameListRemoveInstalledEntry(u64 program_id, + QtCommon::Game::InstalledEntryType type) { const QString entry_question = [type] { switch (type) { case QtCommon::Game::InstalledEntryType::Game: @@ -2571,7 +2570,7 @@ void MainWindow::OnGameListRemoveInstalledEntry(u64 program_id, QtCommon::Game:: } void MainWindow::OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListRemoveTarget target, - const std::string& game_path) { + const std::string& game_path) { const QString question = [target] { switch (target) { case QtCommon::Game::GameListRemoveTarget::GlShaderCache: @@ -2589,8 +2588,8 @@ void MainWindow::OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListRe } }(); - if (!MainWindow::question(this, tr("Remove File"), question, - QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { + if (!MainWindow::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, + QMessageBox::No)) { return; } @@ -2625,7 +2624,6 @@ void MainWindow::OnGameListSetPlayTime(u64 program_id) { } } - void MainWindow::OnGameListRemovePlayTimeData(u64 program_id) { if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"), QMessageBox::Yes | QMessageBox::No, @@ -2638,15 +2636,15 @@ void MainWindow::OnGameListRemovePlayTimeData(u64 program_id) { } void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, - DumpRomFSTarget target) { + DumpRomFSTarget target) { const auto failed = [this] { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), tr("There was an error copying the RomFS files or the user " "cancelled the operation.")); }; - const auto loader = - Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); + const auto loader = Loader::GetLoader( + *QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); if (loader == nullptr) { failed(); return; @@ -2687,7 +2685,8 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); - const FileSys::PatchManager pm{title_id, QtCommon::system->GetFileSystemController(), installed}; + const FileSys::PatchManager pm{title_id, QtCommon::system->GetFileSystemController(), + installed}; auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); const auto out = VfsFilesystemCreateDirectoryWrapper(path, FileSys::OpenMode::ReadWrite); @@ -2763,7 +2762,7 @@ void MainWindow::OnGameListCopyTID(u64 program_id) { } void MainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, - const CompatibilityList& compatibility_list) { + const CompatibilityList& compatibility_list) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); QString directory; @@ -2771,11 +2770,14 @@ void MainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, directory = it->second.second; } - QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.emuready.com/listings?emulatorIds=43bfc023-ec22-422d-8324-048a8ec9f28f") + directory)); + QDesktopServices::openUrl(QUrl( + QStringLiteral( + "https://www.emuready.com/listings?emulatorIds=43bfc023-ec22-422d-8324-048a8ec9f28f") + + directory)); } void MainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - const QtCommon::Game::ShortcutTarget target) { + const QtCommon::Game::ShortcutTarget target) { // Create shortcu std::string arguments = fmt::format("-g \"{:s}\"", game_path); @@ -2847,8 +2849,7 @@ void MainWindow::OnGameListOpenPerGameProperties(const std::string& file) { OpenPerGameConfiguration(title_id, file); } -void MainWindow::OnLinkToRyujinx(const u64& program_id) -{ +void MainWindow::OnLinkToRyujinx(const u64& program_id) { namespace fs = std::filesystem; fs::path ryu_dir; @@ -2863,10 +2864,12 @@ void MainWindow::OnLinkToRyujinx(const u64& program_id) // this function also prompts the user to manually specify a portable location ryu_dir = QtCommon::FS::GetRyujinxSavePath(existing_path, program_id); - if (ryu_dir.empty()) return; + if (ryu_dir.empty()) + return; const std::string user_id = GetProfileIDString(); - if (user_id.empty()) return; + if (user_id.empty()) + return; const std::string hex_program = fmt::format("{:016X}", program_id); @@ -2874,7 +2877,6 @@ void MainWindow::OnLinkToRyujinx(const u64& program_id) FrontendCommon::DataManager::DataDir::Saves, user_id) / hex_program; - // CheckUnlink basically just checks to see if one or both are linked, and prompts the user to // unlink if this is the case. // If it returns false, neither dir is linked so it's fine to continue @@ -3009,8 +3011,8 @@ void MainWindow::OnMenuInstallToNAND() { return false; }; future = QtConcurrent::run([&file, progress_callback] { - return ContentManager::InstallNSP(*QtCommon::system, *QtCommon::vfs, file.toStdString(), - progress_callback); + return ContentManager::InstallNSP(*QtCommon::system, *QtCommon::vfs, + file.toStdString(), progress_callback); }); while (!future.isFinished()) { @@ -3221,9 +3223,8 @@ void MainWindow::OnLoadComplete() { perf_overlay = new PerformanceOverlay(this); perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); - connect(perf_overlay, &PerformanceOverlay::closed, perf_overlay, [this]() { - ui->action_Show_Performance_Overlay->setChecked(false); - }); + connect(perf_overlay, &PerformanceOverlay::closed, perf_overlay, + [this]() { ui->action_Show_Performance_Overlay->setChecked(false); }); } void MainWindow::OnExecuteProgram(std::size_t program_index) { @@ -3245,8 +3246,8 @@ void MainWindow::OnSaveConfig() { } void MainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { - error_applet = new OverlayDialog(render_window, *QtCommon::system, error_code, error_text, QString{}, - tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); + error_applet = new OverlayDialog(render_window, *QtCommon::system, error_code, error_text, + QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); SCOPE_EXIT { error_applet->deleteLater(); error_applet = nullptr; @@ -3267,37 +3268,41 @@ void MainWindow::OnMenuReportCompatibility() { tr("Function Disabled"), tr("Compatibility list reporting is currently disabled. Check back later!")); -// #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__) -// const auto& caps = Common::GetCPUCaps(); -// const bool has_fma = caps.fma || caps.fma4; -// const auto processor_count = std::thread::hardware_concurrency(); -// const bool has_4threads = processor_count == 0 || processor_count >= 4; -// const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; -// const bool has_broken_vulkan = UISettings::values.has_broken_vulkan; + // #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__) + // const auto& caps = Common::GetCPUCaps(); + // const bool has_fma = caps.fma || caps.fma4; + // const auto processor_count = std::thread::hardware_concurrency(); + // const bool has_4threads = processor_count == 0 || processor_count >= 4; + // const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; + // const bool has_broken_vulkan = UISettings::values.has_broken_vulkan; -// if (!has_fma || !has_4threads || !has_8gb_ram || has_broken_vulkan) { -// QMessageBox::critical(this, tr("Hardware requirements not met"), -// tr("Your system does not meet the recommended hardware requirements. " -// "Compatibility reporting has been disabled.")); -// return; -// } + // if (!has_fma || !has_4threads || !has_8gb_ram || has_broken_vulkan) { + // QMessageBox::critical(this, tr("Hardware requirements not met"), + // tr("Your system does not meet the recommended hardware + // requirements. " + // "Compatibility reporting has been disabled.")); + // return; + // } -// if (!Settings::values.eden_token.GetValue().empty() && -// !Settings::values.eden_username.GetValue().empty()) { -// } else { -// QMessageBox::critical( -// this, tr("Missing yuzu Account"), -// tr("In order to submit a game compatibility test case, you must set up your web token " -// "and " -// "username.

To link your eden account, go to Emulation > Configuration " -// "> " -// "Web.")); -// } -// #else -// QMessageBox::critical(this, tr("Hardware requirements not met"), -// tr("Your system does not meet the recommended hardware requirements. " -// "Compatibility reporting has been disabled.")); -// #endif + // if (!Settings::values.eden_token.GetValue().empty() && + // !Settings::values.eden_username.GetValue().empty()) { + // } else { + // QMessageBox::critical( + // this, tr("Missing yuzu Account"), + // tr("In order to submit a game compatibility test case, you must set up your web + // token " + // "and " + // "username.

To link your eden account, go to Emulation > + // Configuration " + // "> " + // "Web.")); + // } + // #else + // QMessageBox::critical(this, tr("Hardware requirements not met"), + // tr("Your system does not meet the recommended hardware + // requirements. " + // "Compatibility reporting has been disabled.")); + // #endif } void MainWindow::OpenURL(const QUrl& url) { @@ -3432,7 +3437,8 @@ void MainWindow::ToggleWindowMode() { } void MainWindow::ResetWindowSize(u32 width, u32 height) { - const auto aspect_ratio = Layout::EmulationAspectRatio(Settings::values.aspect_ratio.GetValue(), float(height) / width); + const auto aspect_ratio = Layout::EmulationAspectRatio(Settings::values.aspect_ratio.GetValue(), + float(height) / width); if (!ui->action_Single_Window_Mode->isChecked()) { render_window->resize(height / aspect_ratio, height); } else { @@ -3489,7 +3495,8 @@ void MainWindow::CheckIconSize() { for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { const auto current_size = newSize; const auto size = default_game_icon_sizes[i].first; - if (current_size == size) actions.at(i)->setChecked(true); + if (current_size == size) + actions.at(i)->setChecked(true); } // Update this if you add anything before None. @@ -3500,7 +3507,7 @@ void MainWindow::CheckIconSize() { } void MainWindow::ToggleShowGameName() { - auto &setting = UISettings::values.show_game_name; + auto& setting = UISettings::values.show_game_name; const bool newValue = !setting.GetValue(); ui->action_Show_Game_Name->setChecked(newValue); setting.SetValue(newValue); @@ -3649,7 +3656,8 @@ void MainWindow::OnTasStartStop() { } // Disable system buttons to prevent TAS from executing a hotkey - auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); controller->ResetSystemButtons(); input_subsystem->GetTas()->StartStop(); @@ -3665,7 +3673,8 @@ void MainWindow::OnTasRecord() { } // Disable system buttons to prevent TAS from recording a hotkey - auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); controller->ResetSystemButtons(); const bool is_recording = input_subsystem->GetTas()->Record(); @@ -3691,8 +3700,10 @@ void MainWindow::OnTasReset() { void MainWindow::OnToggleDockedMode() { const bool is_docked = Settings::IsDockedMode(); - auto* player_1 = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); - auto* handheld = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + auto* player_1 = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* handheld = + QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); if (!is_docked && handheld->IsConnected()) { QMessageBox::warning(this, tr("Invalid config detected"), @@ -3862,8 +3873,8 @@ void MainWindow::OnLoadAmiibo() { // TODO(crueter): does this need to be ported to QML? bool MainWindow::question(QWidget* parent, const QString& title, const QString& text, - QMessageBox::StandardButtons buttons, - QMessageBox::StandardButton defaultButton) { + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) { QMessageBox* box_dialog = new QMessageBox(parent); box_dialog->setWindowTitle(title); box_dialog->setText(text); @@ -3915,23 +3926,19 @@ void MainWindow::OnOpenRootDataFolder() { QtCommon::Game::OpenRootDataFolder(); } -void MainWindow::OnOpenNANDFolder() -{ +void MainWindow::OnOpenNANDFolder() { QtCommon::Game::OpenNANDFolder(); } -void MainWindow::OnOpenSDMCFolder() -{ +void MainWindow::OnOpenSDMCFolder() { QtCommon::Game::OpenSDMCFolder(); } -void MainWindow::OnOpenModFolder() -{ +void MainWindow::OnOpenModFolder() { QtCommon::Game::OpenModFolder(); } -void MainWindow::OnOpenLogFolder() -{ +void MainWindow::OnOpenLogFolder() { QtCommon::Game::OpenLogFolder(); } @@ -3994,7 +4001,8 @@ void MainWindow::OnInstallFirmwareFromZIP() { if (!qCacheDir.isEmpty()) { InstallFirmware(qCacheDir, true); std::error_code ec; - std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", ec); + std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", + ec); if (ec) { QMessageBox::warning(this, tr("Firmware cleanup failed"), @@ -4062,13 +4070,15 @@ void MainWindow::OnGameListRefresh() { SetFirmwareVersion(); } -void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optional cabinet_mode) { +void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, + std::optional cabinet_mode) { auto const program_id = Service::AM::AppletProgramId(raw_program_id); auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); using namespace QtCommon::StringLookup; switch (result) { case FirmwareManager::ErrorFirmwareMissing: - QMessageBox::warning(this, tr("No firmware available"), Lookup(FwCheckErrorFirmwareMissing)); + QMessageBox::warning(this, tr("No firmware available"), + Lookup(FwCheckErrorFirmwareMissing)); return; case FirmwareManager::ErrorFirmwareCorrupted: QMessageBox::warning(this, tr("Firmware Corrupted"), Lookup(FwCheckErrorFirmwareCorrupted)); @@ -4077,33 +4087,58 @@ void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optionalGetFileSystemController().GetSystemNANDContents(); - if (auto applet_nca = bis_system->GetEntry(u64(program_id), FileSys::ContentRecordType::Program); applet_nca) { - if (auto const applet_id = [program_id] { - using namespace Service::AM; - switch (program_id) { - case AppletProgramId::OverlayDisplay: return AppletId::OverlayDisplay; - case AppletProgramId::QLaunch: return AppletId::QLaunch; - case AppletProgramId::Starter: return AppletId::Starter; - case AppletProgramId::Auth: return AppletId::Auth; - case AppletProgramId::Cabinet: return AppletId::Cabinet; - case AppletProgramId::Controller: return AppletId::Controller; - case AppletProgramId::DataErase: return AppletId::DataErase; - case AppletProgramId::Error: return AppletId::Error; - case AppletProgramId::NetConnect: return AppletId::NetConnect; - case AppletProgramId::ProfileSelect: return AppletId::ProfileSelect; - case AppletProgramId::SoftwareKeyboard: return AppletId::SoftwareKeyboard; - case AppletProgramId::MiiEdit: return AppletId::MiiEdit; - case AppletProgramId::Web: return AppletId::Web; - case AppletProgramId::Shop: return AppletId::Shop; - case AppletProgramId::PhotoViewer: return AppletId::PhotoViewer; - case AppletProgramId::Settings: return AppletId::Settings; - case AppletProgramId::OfflineWeb: return AppletId::OfflineWeb; - case AppletProgramId::LoginShare: return AppletId::LoginShare; - case AppletProgramId::WebAuth: return AppletId::WebAuth; - case AppletProgramId::MyPage: return AppletId::MyPage; - default: return AppletId::None; - } - }(); applet_id != Service::AM::AppletId::None) { + if (auto applet_nca = + bis_system->GetEntry(u64(program_id), FileSys::ContentRecordType::Program); + applet_nca) { + if (auto const applet_id = + [program_id] { + using namespace Service::AM; + switch (program_id) { + case AppletProgramId::OverlayDisplay: + return AppletId::OverlayDisplay; + case AppletProgramId::QLaunch: + return AppletId::QLaunch; + case AppletProgramId::Starter: + return AppletId::Starter; + case AppletProgramId::Auth: + return AppletId::Auth; + case AppletProgramId::Cabinet: + return AppletId::Cabinet; + case AppletProgramId::Controller: + return AppletId::Controller; + case AppletProgramId::DataErase: + return AppletId::DataErase; + case AppletProgramId::Error: + return AppletId::Error; + case AppletProgramId::NetConnect: + return AppletId::NetConnect; + case AppletProgramId::ProfileSelect: + return AppletId::ProfileSelect; + case AppletProgramId::SoftwareKeyboard: + return AppletId::SoftwareKeyboard; + case AppletProgramId::MiiEdit: + return AppletId::MiiEdit; + case AppletProgramId::Web: + return AppletId::Web; + case AppletProgramId::Shop: + return AppletId::Shop; + case AppletProgramId::PhotoViewer: + return AppletId::PhotoViewer; + case AppletProgramId::Settings: + return AppletId::Settings; + case AppletProgramId::OfflineWeb: + return AppletId::OfflineWeb; + case AppletProgramId::LoginShare: + return AppletId::LoginShare; + case AppletProgramId::WebAuth: + return AppletId::WebAuth; + case AppletProgramId::MyPage: + return AppletId::MyPage; + default: + return AppletId::None; + } + }(); + applet_id != Service::AM::AppletId::None) { QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(applet_id); if (cabinet_mode) QtCommon::system->GetFrontendAppletHolder().SetCabinetMode(*cabinet_mode); @@ -4112,10 +4147,12 @@ void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optionalname))); + update_prompt.setText(tr("Download %1?").arg(QString::fromStdString(version->name))); update_prompt.exec(); if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) { - auto const full_url = fmt::format("{}/{}/releases/tag/", - std::string{Common::g_build_auto_update_website}, - std::string{Common::g_build_auto_update_repo} - ); + auto const full_url = + fmt::format("{}/{}/releases/tag/", std::string{Common::g_build_auto_update_website}, + std::string{Common::g_build_auto_update_repo}); QDesktopServices::openUrl(QUrl(QString::fromStdString(full_url + version->tag))); } } #endif -void MainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version, std::string_view gpu_vendor) { +void MainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version, + std::string_view gpu_vendor) { static const std::string build_id = std::string{Common::g_build_id}; - static const std::string yuzu_title = fmt::format("{} | {} | {}", - std::string{Common::g_build_name}, - std::string{Common::g_build_version}, - std::string{Common::g_compiler_id} - ); + static const std::string yuzu_title = + fmt::format("{} | {} | {}", std::string{Common::g_build_name}, + std::string{Common::g_build_version}, std::string{Common::g_compiler_id}); const auto override_title = fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); @@ -4302,7 +4336,8 @@ void MainWindow::UpdateStatusBar() { } QString fpsText = tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0); - if (!m_fpsSuffix.isEmpty()) fpsText = fpsText % QStringLiteral(" (%1)").arg(m_fpsSuffix); + if (!m_fpsSuffix.isEmpty()) + fpsText = fpsText % QStringLiteral(" (%1)").arg(m_fpsSuffix); game_fps_label->setText(fpsText); @@ -4445,7 +4480,9 @@ void MainWindow::OnCheckGraphicsBackend() { if (platformName == QStringLiteral("xcb") || qtPlatform == "xcb") return; - const bool isWayland = platformName.startsWith(QStringLiteral("wayland"), Qt::CaseInsensitive) || qtPlatform.startsWith("wayland"); + const bool isWayland = + platformName.startsWith(QStringLiteral("wayland"), Qt::CaseInsensitive) || + qtPlatform.startsWith("wayland"); if (!isWayland) return; @@ -4455,9 +4492,10 @@ void MainWindow::OnCheckGraphicsBackend() { QMessageBox msgbox(this); msgbox.setWindowTitle(tr("Wayland Detected!")); - msgbox.setText(tr("Wayland is known to have significant performance issues and mysterious bugs.\n" - "It's recommended to use X11 instead.\n\n" - "Would you like to force it for future launches?")); + msgbox.setText( + tr("Wayland is known to have significant performance issues and mysterious bugs.\n" + "It's recommended to use X11 instead.\n\n" + "Would you like to force it for future launches?")); msgbox.setIcon(QMessageBox::Warning); QPushButton* okButton = msgbox.addButton(tr("Use X11"), QMessageBox::AcceptRole); @@ -4478,8 +4516,7 @@ void MainWindow::OnCheckGraphicsBackend() { if (msgbox.clickedButton() == okButton) { UISettings::values.gui_force_x11.SetValue(true); GraphicsBackend::SetForceX11(true); - QMessageBox::information(this, - tr("Restart Required"), + QMessageBox::information(this, tr("Restart Required"), tr("Restart Eden to apply the X11 backend.")); } } @@ -4531,7 +4568,7 @@ void MainWindow::SetFPSSuffix() { } bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, - u64* selected_title_id, u8* selected_content_record_type) { + u64* selected_title_id, u8* selected_content_record_type) { using ContentInfo = std::tuple; boost::container::flat_set available_title_ids; @@ -4810,7 +4847,7 @@ void MainWindow::OnLanguageChanged(const QString& locale) { qApp->removeTranslator(&translator); } - QList actions = game_size_actions->actions(); + QList actions = game_size_actions->actions(); for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { actions.at(i)->setText(GetTranslatedGameIconSize(i)); } diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index bb58191951..e85aadaa3d 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -24,15 +24,15 @@ #include "input_common/drivers/tas_input.h" #include "qt_common/config/qt_config.h" #include "qt_common/util/game.h" -#include "yuzu/user_data_migration.h" #include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" +#include "yuzu/user_data_migration.h" #ifdef __unix__ #include +#include #include #include -#include #endif #ifdef ENABLE_UPDATE_CHECKER @@ -218,9 +218,10 @@ signals: void WebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason, std::string last_url); void SigInterrupt(); - void sizeChanged(const QSize &size); - void positionChanged(const QPoint &pos); - void statsUpdated(const Core::PerfStatsResults &results, const VideoCore::ShaderNotify &shaders); + void sizeChanged(const QSize& size); + void positionChanged(const QPoint& pos); + void statsUpdated(const Core::PerfStatsResults& results, + const VideoCore::ShaderNotify& shaders); public slots: void OnLoadComplete(); @@ -317,8 +318,8 @@ private: void RequestGameExit(); void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; - void resizeEvent(QResizeEvent *event) override; - void moveEvent(QMoveEvent *event) override; + void resizeEvent(QResizeEvent* event) override; + void moveEvent(QMoveEvent* event) override; std::string CreateTASFramesString( std::array frames) const; @@ -360,8 +361,7 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); - void OnGameListCreateShortcut(u64 program_id, - const std::string& game_path, + void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, const QtCommon::Game::ShortcutTarget target); void OnGameListOpenDirectory(const QString& directory); void OnGameListAddDirectory(); @@ -483,7 +483,7 @@ private: */ bool question(QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons = - QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); std::unique_ptr ui; @@ -504,7 +504,7 @@ private: LoadingScreen* loading_screen = nullptr; QTimer shutdown_timer; OverlayDialog* shutdown_dialog{}; - PerformanceOverlay *perf_overlay = nullptr; + PerformanceOverlay* perf_overlay = nullptr; GameListPlaceholder* game_list_placeholder = nullptr; @@ -551,7 +551,7 @@ private: QString startup_icon_theme; - QActionGroup *game_size_actions; + QActionGroup* game_size_actions; // Debugger panes ControllerDialog* controller_dialog = nullptr; @@ -595,12 +595,9 @@ private: std::filesystem::path GetEdenCommand(); - void CreateShortcut(const std::string& game_path, - const u64 program_id, - const std::string& game_title, - QtCommon::Game::ShortcutTarget target, - std::string arguments, - const bool needs_title); + void CreateShortcut(const std::string& game_path, const u64 program_id, + const std::string& game_title, QtCommon::Game::ShortcutTarget target, + std::string arguments, const bool needs_title); void InstallFirmware(const QString& location, bool recursive = false); diff --git a/src/yuzu/migration_dialog.cpp b/src/yuzu/migration_dialog.cpp index 9cc40841e8..ba6a14b156 100644 --- a/src/yuzu/migration_dialog.cpp +++ b/src/yuzu/migration_dialog.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #include "migration_dialog.h" #include @@ -6,11 +9,8 @@ #include #include -MigrationDialog::MigrationDialog( - QWidget *parent) - : QDialog(parent) -{ - QVBoxLayout *layout = new QVBoxLayout(this); +MigrationDialog::MigrationDialog(QWidget* parent) : QDialog(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); m_text = new QLabel(this); m_boxes = new QVBoxLayout; @@ -26,23 +26,16 @@ MigrationDialog::~MigrationDialog() { m_buttons->deleteLater(); } -void MigrationDialog::setText( - const QString &text) -{ +void MigrationDialog::setText(const QString& text) { m_text->setText(text); } -void MigrationDialog::addBox( - QWidget *box) -{ +void MigrationDialog::addBox(QWidget* box) { m_boxes->addWidget(box); - } -QAbstractButton *MigrationDialog::addButton( - const QString &text, const bool reject) -{ - QAbstractButton *button = new QPushButton(this); +QAbstractButton* MigrationDialog::addButton(const QString& text, const bool reject) { + QAbstractButton* button = new QPushButton(this); button->setText(text); m_buttons->addWidget(button, 1); @@ -58,7 +51,6 @@ QAbstractButton *MigrationDialog::addButton( return button; } -QAbstractButton *MigrationDialog::clickedButton() const -{ +QAbstractButton* MigrationDialog::clickedButton() const { return m_clickedButton; } diff --git a/src/yuzu/migration_dialog.h b/src/yuzu/migration_dialog.h index 7123fd6b87..a2a2c80358 100644 --- a/src/yuzu/migration_dialog.h +++ b/src/yuzu/migration_dialog.h @@ -1,30 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #ifndef MIGRATION_DIALOG_H #define MIGRATION_DIALOG_H -#include -#include #include +#include #include +#include class MigrationDialog : public QDialog { Q_OBJECT public: - MigrationDialog(QWidget *parent = nullptr); + MigrationDialog(QWidget* parent = nullptr); virtual ~MigrationDialog(); - void setText(const QString &text); - void addBox(QWidget *box); - QAbstractButton *addButton(const QString &text, const bool reject = false); + void setText(const QString& text); + void addBox(QWidget* box); + QAbstractButton* addButton(const QString& text, const bool reject = false); - QAbstractButton *clickedButton() const; + QAbstractButton* clickedButton() const; private: - QLabel *m_text; - QVBoxLayout *m_boxes; - QHBoxLayout *m_buttons; + QLabel* m_text; + QVBoxLayout* m_boxes; + QHBoxLayout* m_buttons; - QAbstractButton *m_clickedButton; + QAbstractButton* m_clickedButton; }; #endif // MIGRATION_DIALOG_H diff --git a/src/yuzu/migration_worker.cpp b/src/yuzu/migration_worker.cpp index 0149db4149..ba5e50e187 100644 --- a/src/yuzu/migration_worker.cpp +++ b/src/yuzu/migration_worker.cpp @@ -1,27 +1,22 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "migration_worker.h" #include "common/fs/symlink.h" +#include "migration_worker.h" +#include #include #include #include -#include #include "common/fs/path_util.h" -MigrationWorker::MigrationWorker(const Emulator selected_emu_, - const bool clear_shader_cache_, +MigrationWorker::MigrationWorker(const Emulator selected_emu_, const bool clear_shader_cache_, const MigrationStrategy strategy_) - : QObject() - , selected_emu(selected_emu_) - , clear_shader_cache(clear_shader_cache_) - , strategy(strategy_) -{} + : QObject(), selected_emu(selected_emu_), clear_shader_cache(clear_shader_cache_), + strategy(strategy_) {} -void MigrationWorker::process() -{ +void MigrationWorker::process() { namespace fs = std::filesystem; constexpr auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive; @@ -42,7 +37,7 @@ void MigrationWorker::process() try { fs::remove_all(eden_dir); - } catch (fs::filesystem_error &_) { + } catch (fs::filesystem_error& _) { // ignore because linux does stupid crap sometimes } @@ -53,7 +48,7 @@ void MigrationWorker::process() // Windows 11 has random permission nonsense to deal with. try { Common::FS::CreateSymlink(legacy_user_dir, eden_dir); - } catch (const fs::filesystem_error &e) { + } catch (const fs::filesystem_error& e) { emit error(tr("Linking the old directory failed. You may need to re-run with " "administrative privileges on Windows.\nOS gave error: %1") .arg(tr(e.what()))); @@ -74,8 +69,7 @@ void MigrationWorker::process() success_text.append(tr("\n\nNote that your configuration and data will be shared with %1.\n" "If this is not desirable, delete the following files:\n%2\n%3\n%4") - .arg(selected_emu.name(), - QString::fromStdString(eden_dir.string()), + .arg(selected_emu.name(), QString::fromStdString(eden_dir.string()), QString::fromStdString(config_dir.string()), QString::fromStdString(cache_dir.string()))); diff --git a/src/yuzu/migration_worker.h b/src/yuzu/migration_worker.h index 2db05570ea..42abedb9c8 100644 --- a/src/yuzu/migration_worker.h +++ b/src/yuzu/migration_worker.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef MIGRATION_WORKER_H @@ -8,7 +8,7 @@ #include "common/fs/path_util.h" typedef struct Emulator { - const char *m_name; + const char* m_name; Common::FS::EmuPath e_user_dir; Common::FS::EmuPath e_config_dir; @@ -26,14 +26,20 @@ typedef struct Emulator { return Common::FS::GetLegacyPath(e_cache_dir).string(); } - const QString name() const { return QObject::tr(m_name); + const QString name() const { + return QObject::tr(m_name); } - const QString lower_name() const { return name().toLower(); + const QString lower_name() const { + return name().toLower(); } } Emulator; -#define STRUCT_EMU(name, enumName) Emulator{name, Common::FS::enumName##Dir, Common::FS::enumName##ConfigDir, Common::FS::enumName##CacheDir} +#define STRUCT_EMU(name, enumName) \ + Emulator { \ + name, Common::FS::enumName##Dir, Common::FS::enumName##ConfigDir, \ + Common::FS::enumName##CacheDir \ + } static constexpr std::array legacy_emus = { STRUCT_EMU(QT_TR_NOOP("Citron"), Citron), @@ -42,8 +48,7 @@ static constexpr std::array legacy_emus = { STRUCT_EMU(QT_TR_NOOP("Yuzu"), Yuzu), }; -class MigrationWorker : public QObject -{ +class MigrationWorker : public QObject { Q_OBJECT public: enum class MigrationStrategy { @@ -52,16 +57,15 @@ public: Link, }; - MigrationWorker(const Emulator selected_emu, - const bool clear_shader_cache, + MigrationWorker(const Emulator selected_emu, const bool clear_shader_cache, const MigrationStrategy strategy); public slots: void process(); signals: - void finished(const QString &success_text, const std::string &user_dir); - void error(const QString &error_message); + void finished(const QString& success_text, const std::string& user_dir); + void error(const QString& error_message); private: Emulator selected_emu; diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h index a94683ef58..44ec8952d1 100644 --- a/src/yuzu/multiplayer/chat_room.h +++ b/src/yuzu/multiplayer/chat_room.h @@ -6,11 +6,11 @@ #pragma once #include -#include #include #include #include #include +#include #include "network/network.h" namespace Ui { diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 486ab28213..eea906f16d 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project @@ -14,6 +14,7 @@ #include "core/core.h" #include "core/internal_network/network_interface.h" #include "network/network.h" +#include "qt_common/config/uisettings.h" #include "ui_direct_connect.h" #include "yuzu/main_window.h" #include "yuzu/multiplayer/client_room.h" @@ -21,7 +22,6 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "qt_common/config/uisettings.h" enum class ConnectionType : u8 { TraversalServer, IP }; diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 3e5e42e442..65afcc3b7c 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -18,6 +18,7 @@ #include "core/core.h" #include "core/internal_network/network_interface.h" #include "network/announce_multiplayer_session.h" +#include "qt_common/config/uisettings.h" #include "ui_host_room.h" #include "yuzu/game/game_list_p.h" #include "yuzu/main_window.h" @@ -25,7 +26,6 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "qt_common/config/uisettings.h" #ifdef ENABLE_WEB_SERVICE #include "web_service/verify_user_jwt.h" #endif @@ -34,8 +34,7 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, std::shared_ptr session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique()), - announce_multiplayer_session(session), system{system_} { + ui(std::make_unique()), announce_multiplayer_session(session), system{system_} { ui->setupUi(this); // set up validation for all of the fields diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index f28374f75f..8c435af8b6 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -13,6 +13,7 @@ #include "core/hle/service/acc/profile_manager.h" #include "core/internal_network/network_interface.h" #include "network/network.h" +#include "qt_common/config/uisettings.h" #include "ui_lobby.h" #include "yuzu/game/game_list_p.h" #include "yuzu/main_window.h" @@ -22,7 +23,6 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "qt_common/config/uisettings.h" #ifdef ENABLE_WEB_SERVICE #include "web_service/web_backend.h" #endif @@ -30,8 +30,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, std::shared_ptr session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique()), - announce_multiplayer_session(session), system{system_} { + ui(std::make_unique()), announce_multiplayer_session(session), system{system_} { ui->setupUi(this); // setup the watcher for background connections diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index c344bcb8a3..b5f2326b44 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -11,6 +11,7 @@ #include "common/announce_multiplayer_room.h" #include "common/logging/log.h" #include "core/core.h" +#include "qt_common/config/uisettings.h" #include "yuzu/game/game_list.h" #include "yuzu/multiplayer/client_room.h" #include "yuzu/multiplayer/direct_connect.h" @@ -18,7 +19,6 @@ #include "yuzu/multiplayer/lobby.h" #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" -#include "qt_common/config/uisettings.h" #include "yuzu/util/clickable_label.h" MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, diff --git a/src/yuzu/render/performance_overlay.h b/src/yuzu/render/performance_overlay.h index 1d7fb46f26..a0325a10a3 100644 --- a/src/yuzu/render/performance_overlay.h +++ b/src/yuzu/render/performance_overlay.h @@ -34,13 +34,13 @@ protected: void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent* event) override; private: void resetPosition(const QPoint& pos); - void updateStats(const Core::PerfStatsResults &results, const VideoCore::ShaderNotify &shaders); + void updateStats(const Core::PerfStatsResults& results, const VideoCore::ShaderNotify& shaders); - MainWindow *m_mainWindow = nullptr; + MainWindow* m_mainWindow = nullptr; Ui::PerformanceOverlay* ui; // colors @@ -62,11 +62,11 @@ private: QPoint m_drag_start_pos; // fps chart - QLineSeries *m_fpsSeries = nullptr; - QChart *m_fpsChart = nullptr; - QChartView *m_fpsChartView = nullptr; - QValueAxis *m_fpsX = nullptr; - QValueAxis *m_fpsY = nullptr; + QLineSeries* m_fpsSeries = nullptr; + QChart* m_fpsChart = nullptr; + QChartView* m_fpsChartView = nullptr; + QValueAxis* m_fpsX = nullptr; + QValueAxis* m_fpsY = nullptr; signals: void closed(); diff --git a/src/yuzu/ryujinx_dialog.cpp b/src/yuzu/ryujinx_dialog.cpp index 563714f6be..f52f37b297 100644 --- a/src/yuzu/ryujinx_dialog.cpp +++ b/src/yuzu/ryujinx_dialog.cpp @@ -1,20 +1,16 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "qt_common/abstract/frontend.h" -#include "ryujinx_dialog.h" -#include "qt_common/util/fs.h" -#include "ui_ryujinx_dialog.h" #include +#include "qt_common/abstract/frontend.h" +#include "qt_common/util/fs.h" +#include "ryujinx_dialog.h" +#include "ui_ryujinx_dialog.h" -RyujinxDialog::RyujinxDialog(std::filesystem::path eden_path, - std::filesystem::path ryu_path, - QWidget *parent) - : QDialog(parent) - , ui(new Ui::RyujinxDialog) - , m_eden(eden_path.make_preferred()) - , m_ryu(ryu_path.make_preferred()) -{ +RyujinxDialog::RyujinxDialog(std::filesystem::path eden_path, std::filesystem::path ryu_path, + QWidget* parent) + : QDialog(parent), ui(new Ui::RyujinxDialog), m_eden(eden_path.make_preferred()), + m_ryu(ryu_path.make_preferred()) { ui->setupUi(this); connect(ui->eden, &QPushButton::clicked, this, &RyujinxDialog::fromEden); @@ -22,13 +18,11 @@ RyujinxDialog::RyujinxDialog(std::filesystem::path eden_path, connect(ui->cancel, &QPushButton::clicked, this, &RyujinxDialog::reject); } -RyujinxDialog::~RyujinxDialog() -{ +RyujinxDialog::~RyujinxDialog() { delete ui; } -void RyujinxDialog::fromEden() -{ +void RyujinxDialog::fromEden() { accept(); // Workaround: Ryujinx deletes and re-creates its directory structure??? @@ -38,17 +32,17 @@ void RyujinxDialog::fromEden() fs::remove_all(m_ryu); fs::create_directories(m_ryu); fs::copy(m_eden, m_ryu, fs::copy_options::recursive); - } catch (std::exception &e) { - QtCommon::Frontend::Critical(tr("Failed to link save data"), - tr("OS returned error: %1").arg(QString::fromStdString(e.what()))); + } catch (std::exception& e) { + QtCommon::Frontend::Critical( + tr("Failed to link save data"), + tr("OS returned error: %1").arg(QString::fromStdString(e.what()))); } // ?ploo QtCommon::FS::LinkRyujinx(m_ryu, m_eden); } -void RyujinxDialog::fromRyujinx() -{ +void RyujinxDialog::fromRyujinx() { accept(); QtCommon::FS::LinkRyujinx(m_ryu, m_eden); } diff --git a/src/yuzu/ryujinx_dialog.h b/src/yuzu/ryujinx_dialog.h index 63cebe483c..c067499cc1 100644 --- a/src/yuzu/ryujinx_dialog.h +++ b/src/yuzu/ryujinx_dialog.h @@ -1,22 +1,22 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #ifndef RYUJINX_DIALOG_H #define RYUJINX_DIALOG_H -#include #include +#include namespace Ui { class RyujinxDialog; } -class RyujinxDialog : public QDialog -{ +class RyujinxDialog : public QDialog { Q_OBJECT public: - explicit RyujinxDialog(std::filesystem::path eden_path, std::filesystem::path ryu_path, QWidget *parent = nullptr); + explicit RyujinxDialog(std::filesystem::path eden_path, std::filesystem::path ryu_path, + QWidget* parent = nullptr); ~RyujinxDialog(); private slots: @@ -24,7 +24,7 @@ private slots: void fromRyujinx(); private: - Ui::RyujinxDialog *ui; + Ui::RyujinxDialog* ui; std::filesystem::path m_eden; std::filesystem::path m_ryu; }; diff --git a/src/yuzu/set_play_time_dialog.cpp b/src/yuzu/set_play_time_dialog.cpp index c0f1f0be22..38876abd6d 100644 --- a/src/yuzu/set_play_time_dialog.cpp +++ b/src/yuzu/set_play_time_dialog.cpp @@ -1,20 +1,23 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "yuzu/set_play_time_dialog.h" #include "frontend_common/play_time_manager.h" #include "ui_set_play_time_dialog.h" +#include "yuzu/set_play_time_dialog.h" SetPlayTimeDialog::SetPlayTimeDialog(QWidget* parent, u64 current_play_time) : QDialog(parent), ui{std::make_unique()} { ui->setupUi(this); ui->hoursSpinBox->setValue( - QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeHours(current_play_time)).toInt()); + QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeHours(current_play_time)) + .toInt()); ui->minutesSpinBox->setValue( - QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeMinutes(current_play_time)).toInt()); + QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeMinutes(current_play_time)) + .toInt()); ui->secondsSpinBox->setValue( - QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeSeconds(current_play_time)).toInt()); + QString::fromStdString(PlayTime::PlayTimeManager::GetPlayTimeSeconds(current_play_time)) + .toInt()); connect(ui->hoursSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &SetPlayTimeDialog::OnValueChanged); diff --git a/src/yuzu/set_play_time_dialog.h b/src/yuzu/set_play_time_dialog.h index 75513539e5..a66ceb7ced 100644 --- a/src/yuzu/set_play_time_dialog.h +++ b/src/yuzu/set_play_time_dialog.h @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once -#include #include +#include #include "common/common_types.h" namespace Ui { diff --git a/src/yuzu/user_data_migration.cpp b/src/yuzu/user_data_migration.cpp index bc31c99e30..da8b0847c7 100644 --- a/src/yuzu/user_data_migration.cpp +++ b/src/yuzu/user_data_migration.cpp @@ -1,42 +1,41 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "user_data_migration.h" #include #include #include #include #include "common/fs/path_util.h" #include "qt_common/qt_string_lookup.h" +#include "user_data_migration.h" #include "yuzu/migration_dialog.h" // Needs to be included at the end due to https://bugreports.qt.io/browse/QTBUG-73263 +#include #include #include #include #include #include #include -#include -UserDataMigrator::UserDataMigrator(QMainWindow *main_window) -{ +UserDataMigrator::UserDataMigrator(QMainWindow* main_window) { // NOTE: Logging is not initialized yet, do not produce logs here. // Check migration if config directory does not exist - // TODO: ProfileManager messes with us a bit here, and force-creates the /nand/system/save/8000000000000010/su/avators/profiles.dat - // file. Find a way to reorder operations and have it create after this guy runs. + // TODO: ProfileManager messes with us a bit here, and force-creates the + // /nand/system/save/8000000000000010/su/avators/profiles.dat file. Find a way to reorder + // operations and have it create after this guy runs. if (!std::filesystem::is_directory(Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir))) { ShowMigrationPrompt(main_window); } } -void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window) -{ +void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { namespace fs = std::filesystem; using namespace QtCommon::StringLookup; @@ -44,35 +43,36 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window) migration_prompt.setWindowTitle(QObject::tr("Migration")); // mutually exclusive - QButtonGroup *group = new QButtonGroup(&migration_prompt); + QButtonGroup* group = new QButtonGroup(&migration_prompt); // MACRO MADNESS -#define BUTTON(clazz, name, text, tooltip, checkState) \ - clazz *name = new clazz(&migration_prompt); \ - name->setText(text); \ - name->setToolTip(Lookup(tooltip)); \ - name->setChecked(checkState); \ +#define BUTTON(clazz, name, text, tooltip, checkState) \ + clazz* name = new clazz(&migration_prompt); \ + name->setText(text); \ + name->setToolTip(Lookup(tooltip)); \ + name->setChecked(checkState); \ migration_prompt.addBox(name); - BUTTON(QCheckBox, clear_shaders, QObject::tr("Clear Shader Cache"), MigrationTooltipClearShader, true) + BUTTON(QCheckBox, clear_shaders, QObject::tr("Clear Shader Cache"), MigrationTooltipClearShader, + true) u32 id = 0; -#define RADIO(name, text, tooltip, checkState) \ - BUTTON(QRadioButton, name, text, tooltip, checkState) \ +#define RADIO(name, text, tooltip, checkState) \ + BUTTON(QRadioButton, name, text, tooltip, checkState) \ group->addButton(name, ++id); - RADIO(keep_old, QObject::tr("Keep Old Data"), MigrationTooltipKeepOld, true) + RADIO(keep_old, QObject::tr("Keep Old Data"), MigrationTooltipKeepOld, true) RADIO(clear_old, QObject::tr("Clear Old Data"), MigrationTooltipClearOld, false) - RADIO(link_old, QObject::tr("Link Old Directory"), MigrationTooltipLinkOld, false) + RADIO(link_old, QObject::tr("Link Old Directory"), MigrationTooltipLinkOld, false) #undef RADIO #undef BUTTON std::vector found{}; - for (const Emulator &emu : legacy_emus) + for (const Emulator& emu : legacy_emus) if (fs::is_directory(emu.get_user_dir())) found.emplace_back(emu); @@ -86,10 +86,10 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window) QString prompt_text = Lookup(MigrationPromptPrefix); // natural language processing is a nightmare - for (const Emulator &emu : found) { + for (const Emulator& emu : found) { prompt_text = prompt_text % QStringLiteral("\n ") % emu.name(); - QAbstractButton *button = migration_prompt.addButton(emu.name()); + QAbstractButton* button = migration_prompt.addButton(emu.name()); // This is cursed, but it's actually the most efficient way by a mile button->setProperty("emulator", QVariant::fromValue(emu)); @@ -103,26 +103,22 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window) migration_prompt.exec(); - QAbstractButton *button = migration_prompt.clickedButton(); + QAbstractButton* button = migration_prompt.clickedButton(); if (button->text() == QObject::tr("No")) { return ShowMigrationCancelledMessage(main_window); } - MigrationWorker::MigrationStrategy strategy = static_cast( - group->checkedId()); + MigrationWorker::MigrationStrategy strategy = + static_cast(group->checkedId()); selected_emu = button->property("emulator").value(); - MigrateUserData(main_window, - clear_shaders->isChecked(), - strategy); + MigrateUserData(main_window, clear_shaders->isChecked(), strategy); } -void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow *main_window) -{ - QMessageBox::information(main_window, - QObject::tr("Migration"), +void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow* main_window) { + QMessageBox::information(main_window, QObject::tr("Migration"), QObject::tr("You can manually re-trigger this prompt by deleting the " "new config directory:\n%1") .arg(QString::fromStdString(Common::FS::GetEdenPathString( @@ -130,33 +126,27 @@ void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow *main_window) QMessageBox::Ok); } -void UserDataMigrator::MigrateUserData(QMainWindow *main_window, - const bool clear_shader_cache, - const MigrationWorker::MigrationStrategy strategy) -{ +void UserDataMigrator::MigrateUserData(QMainWindow* main_window, const bool clear_shader_cache, + const MigrationWorker::MigrationStrategy strategy) { // Create a dialog to let the user know it's migrating - QProgressDialog *progress = new QProgressDialog(main_window); + QProgressDialog* progress = new QProgressDialog(main_window); progress->setWindowTitle(QObject::tr("Migrating")); progress->setLabelText(QObject::tr("Migrating, this may take a while...")); progress->setRange(0, 0); progress->setCancelButton(nullptr); progress->setWindowModality(Qt::WindowModality::ApplicationModal); - QThread *thread = new QThread(main_window); - MigrationWorker *worker = new MigrationWorker(selected_emu, clear_shader_cache, strategy); + QThread* thread = new QThread(main_window); + MigrationWorker* worker = new MigrationWorker(selected_emu, clear_shader_cache, strategy); worker->moveToThread(thread); thread->connect(thread, &QThread::started, worker, &MigrationWorker::process); - thread->connect(worker, - &MigrationWorker::finished, - progress, - [=, this](const QString &success_text, const std::string &path) { + thread->connect(worker, &MigrationWorker::finished, progress, + [=, this](const QString& success_text, const std::string& path) { progress->close(); - QMessageBox::information(main_window, - QObject::tr("Migration"), - success_text, - QMessageBox::Ok); + QMessageBox::information(main_window, QObject::tr("Migration"), + success_text, QMessageBox::Ok); migrated = true; thread->quit(); diff --git a/src/yuzu/user_data_migration.h b/src/yuzu/user_data_migration.h index 1cfeda3bca..df8057eaa5 100644 --- a/src/yuzu/user_data_migration.h +++ b/src/yuzu/user_data_migration.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // Copyright Citra Emulator Project / Azahar Emulator Project @@ -21,7 +21,6 @@ public: private: void ShowMigrationPrompt(QMainWindow* main_window); void ShowMigrationCancelledMessage(QMainWindow* main_window); - void MigrateUserData(QMainWindow* main_window, - const bool clear_shader_cache, + void MigrateUserData(QMainWindow* main_window, const bool clear_shader_cache, const MigrationWorker::MigrationStrategy strategy); }; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index 307f121a49..3e9ebc2ad2 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2015 Citra Emulator Project @@ -157,11 +157,11 @@ const std::optional GetProfileID() { const auto select_profile = [] { const Core::Frontend::ProfileSelectParameters parameters{ - .mode = Service::AM::Frontend::UiMode::UserSelector, - .invalid_uid_list = {}, - .display_options = {}, - .purpose = Service::AM::Frontend::UserSelectionPurpose::General, - }; + .mode = Service::AM::Frontend::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Frontend::UserSelectionPurpose::General, + }; QtProfileSelectionDialog dialog(*QtCommon::system, QtCommon::rootObject, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp index 45faecb95d..2b87ccd3ca 100644 --- a/src/yuzu/vk_device_info.cpp +++ b/src/yuzu/vk_device_info.cpp @@ -62,12 +62,13 @@ void PopulateRecords(std::vector& records, QWindow* window) try { const auto driverID = driver_properties.driverID; - bool has_broken_compute{Vulkan::Device::CheckBrokenCompute( - driverID, properties.properties.driverVersion)}; + bool has_broken_compute{ + Vulkan::Device::CheckBrokenCompute(driverID, properties.properties.driverVersion)}; std::string driver_string = Vulkan::vk::GetDriverName(driver_properties); - if (driver_string.empty()) driver_string = "Unknown"; + if (driver_string.empty()) + driver_string = "Unknown"; name = fmt::format("{} ({})", name, driver_string); diff --git a/tools/clang-format.sh b/tools/clang-format.sh index 2deb0a3ade..e2857d9723 100755 --- a/tools/clang-format.sh +++ b/tools/clang-format.sh @@ -1,6 +1,7 @@ #! /bin/sh -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later -exec find src -iname "*.h" -o -iname "*.cpp" | xargs clang-format -i -style=file:src/.clang-format +# Only clang-formats Qt stuff. :) +find src/qt_common src/yuzu -iname "*.h" -o -iname "*.cpp" | xargs clang-format -i -style=file:src/.clang-format