From 1457123bccd79b5bcf23c00fd487b8d0e93f08f3 Mon Sep 17 00:00:00 2001 From: DraVee Date: Sun, 8 Mar 2026 23:26:18 -0300 Subject: [PATCH 1/6] [cheat] add dmnt, indiviual cheats, etc. --- .../yuzu/yuzu_emu/adapters/AddonAdapter.kt | 5 +- .../yuzu/yuzu_emu/fragments/AddonsFragment.kt | 2 +- .../org/yuzu/yuzu_emu/model/AddonViewModel.kt | 36 +- .../java/org/yuzu/yuzu_emu/model/Patch.kt | 19 + .../java/org/yuzu/yuzu_emu/model/PatchType.kt | 6 +- src/android/app/src/main/jni/native.cpp | 8 +- src/common/android/id_cache.cpp | 2 +- src/core/CMakeLists.txt | 17 +- src/core/core.cpp | 149 +- src/core/core.h | 12 +- src/core/file_sys/patch_manager.cpp | 239 +++- src/core/file_sys/patch_manager.h | 17 +- src/core/hle/service/dmnt/cheat_interface.cpp | 238 ++++ src/core/hle/service/dmnt/cheat_interface.h | 88 ++ src/core/hle/service/dmnt/cheat_parser.cpp | 121 ++ src/core/hle/service/dmnt/cheat_parser.h | 26 + .../service/dmnt/cheat_process_manager.cpp | 599 ++++++++ .../hle/service/dmnt/cheat_process_manager.h | 126 ++ .../service/dmnt/cheat_virtual_machine.cpp | 1269 +++++++++++++++++ .../hle/service/dmnt/cheat_virtual_machine.h | 323 +++++ src/core/hle/service/dmnt/dmnt.cpp | 26 + src/core/hle/service/dmnt/dmnt.h | 15 + src/core/hle/service/dmnt/dmnt_results.h | 26 + src/core/hle/service/dmnt/dmnt_types.h | 55 + src/core/hle/service/services.cpp | 4 +- src/core/memory/cheat_engine.cpp | 288 ---- src/core/memory/cheat_engine.h | 88 -- src/core/memory/dmnt_cheat_types.h | 37 - src/core/memory/dmnt_cheat_vm.cpp | 1268 ---------------- src/core/memory/dmnt_cheat_vm.h | 330 ----- .../configure_per_game_addons.cpp | 84 +- 31 files changed, 3404 insertions(+), 2119 deletions(-) create mode 100644 src/core/hle/service/dmnt/cheat_interface.cpp create mode 100644 src/core/hle/service/dmnt/cheat_interface.h create mode 100644 src/core/hle/service/dmnt/cheat_parser.cpp create mode 100644 src/core/hle/service/dmnt/cheat_parser.h create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.cpp create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.h create mode 100644 src/core/hle/service/dmnt/cheat_virtual_machine.cpp create mode 100644 src/core/hle/service/dmnt/cheat_virtual_machine.h create mode 100644 src/core/hle/service/dmnt/dmnt.cpp create mode 100644 src/core/hle/service/dmnt/dmnt.h create mode 100644 src/core/hle/service/dmnt/dmnt_results.h create mode 100644 src/core/hle/service/dmnt/dmnt_types.h delete mode 100644 src/core/memory/cheat_engine.cpp delete mode 100644 src/core/memory/cheat_engine.h delete mode 100644 src/core/memory/dmnt_cheat_types.h delete mode 100644 src/core/memory/dmnt_cheat_vm.cpp delete mode 100644 src/core/memory/dmnt_cheat_vm.h 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..21f7b1a96f 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,7 +150,9 @@ 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 -> {} } + _patchList.value.clear() refreshAddons(force = true) } @@ -179,7 +185,7 @@ class AddonViewModel : ViewModel() { it.name } } else { - it.name + it.getStorageKey() } } }.toTypedArray() @@ -201,4 +207,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..183e97e373 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 @@ -18,6 +18,7 @@ data class Patch( val titleId: String, val numericVersion: Long = 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/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/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 1d2d358672..ec116f2656 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 @@ -105,17 +107,38 @@ 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]; @@ -300,23 +323,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; @@ -335,7 +375,7 @@ void ConfigurePerGameAddons::LoadConfiguration() { std::string disabled_key = fmt::format("Update@{}", patch.numeric_version); 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 +393,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); } From 07e3a2aa46d1b8e9e28fdc3b306d1628e5c92f3e Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 10 Mar 2026 05:36:12 +0100 Subject: [PATCH 2/6] [settings] Disable fastmem on Linux systems with non-4kb page sizes (#3669) Asahi, etc Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3669 Reviewed-by: Lizzie Reviewed-by: CamilleLaVey --- src/common/settings.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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; From 0ff1d215c81bda45e84c45d0dddb1f7ffb81f7b1 Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 10 Mar 2026 05:37:45 +0100 Subject: [PATCH 3/6] [desktop] Port some QtCommon changes from QML branch (#3703) - Linker now resolves implementation differences - Remove unneeded ifdefs - Better abstractions overall Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3703 Reviewed-by: Lizzie Reviewed-by: CamilleLaVey --- .ci/license-header.sh | 4 +- src/CMakeLists.txt | 1 - src/qt_common/CMakeLists.txt | 6 +- src/qt_common/abstract/frontend.cpp | 64 +------ src/qt_common/abstract/frontend.h | 150 ++++++---------- src/qt_common/abstract/progress.cpp | 17 ++ src/qt_common/abstract/progress.h | 45 +++++ src/qt_common/abstract/qt_progress_dialog.cpp | 4 - src/qt_common/abstract/qt_progress_dialog.h | 47 ----- src/qt_common/config/qt_config.cpp | 10 +- src/qt_common/config/shared_translation.h | 2 +- src/qt_common/discord/discord_impl.cpp | 2 +- src/qt_common/gamemode.cpp | 2 +- src/qt_common/gui_settings.cpp | 2 +- src/qt_common/qt_common.cpp | 13 -- src/qt_common/qt_common.h | 10 +- src/qt_common/util/applet.h | 2 +- src/qt_common/util/compress.cpp | 2 +- src/qt_common/util/compress.h | 2 +- src/qt_common/util/content.cpp | 162 ++++++++---------- src/qt_common/util/content.h | 4 +- src/qt_common/util/fs.cpp | 2 +- src/qt_common/util/fs.h | 2 +- src/qt_common/util/game.cpp | 24 +-- src/qt_common/util/game.h | 2 +- src/qt_common/util/meta.cpp | 2 +- src/qt_common/util/meta.h | 2 +- src/qt_common/util/path.cpp | 20 +-- src/qt_common/util/path.h | 2 +- src/qt_common/util/rom.cpp | 2 +- src/qt_common/util/rom.h | 2 +- src/yuzu/CMakeLists.txt | 1 + src/yuzu/libqt_common.cpp | 119 +++++++++++++ src/yuzu/libqt_common.h | 37 ++++ src/yuzu/main_window.cpp | 3 +- tools/clang-format.sh | 5 +- 36 files changed, 405 insertions(+), 371 deletions(-) create mode 100644 src/qt_common/abstract/progress.cpp create mode 100644 src/qt_common/abstract/progress.h delete mode 100644 src/qt_common/abstract/qt_progress_dialog.cpp delete mode 100644 src/qt_common/abstract/qt_progress_dialog.h create mode 100644 src/yuzu/libqt_common.cpp create mode 100644 src/yuzu/libqt_common.h 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/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..05eb550095 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..c9185a756d --- /dev/null +++ b/src/qt_common/abstract/progress.cpp @@ -0,0 +1,17 @@ +// 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) +{} + +} 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.h b/src/qt_common/config/shared_translation.h index afb18ec435..6fbeb1df6e 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 diff --git a/src/qt_common/discord/discord_impl.cpp b/src/qt_common/discord/discord_impl.cpp index 3bf5544198..82f805b888 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 diff --git a/src/qt_common/gamemode.cpp b/src/qt_common/gamemode.cpp index 6a3d870aae..dae6676fee 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 diff --git a/src/qt_common/gui_settings.cpp b/src/qt_common/gui_settings.cpp index 982d28bbcb..4a1506261a 100644 --- a/src/qt_common/gui_settings.cpp +++ b/src/qt_common/gui_settings.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 "gui_settings.h" diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index af4e4ffa61..8954bf09f2 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -3,19 +3,14 @@ #include "qt_common.h" #include "common/fs/fs.h" -#include "common/fs/ryujinx_compat.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,11 +22,7 @@ namespace QtCommon { -#ifdef YUZU_QT_WIDGETS QWidget* rootObject = nullptr; -#else -QObject* rootObject = nullptr; -#endif std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; @@ -118,11 +109,7 @@ 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 { system = std::make_unique(); rootObject = root; diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index a2700427ab..9c7816a2ed 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.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_COMMON_H @@ -14,11 +14,7 @@ namespace QtCommon { -#ifdef YUZU_QT_WIDGETS extern QWidget *rootObject; -#else -extern QObject *rootObject; -#endif extern std::unique_ptr system; extern std::shared_ptr vfs; @@ -30,11 +26,7 @@ Core::Frontend::WindowSystemType GetWindowSystemType(); Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window); -#ifdef YUZU_QT_WIDGETS void Init(QWidget *root); -#else -void Init(QObject *root); -#endif const QString tr(const char *str); const QString tr(const std::string &str); diff --git a/src/qt_common/util/applet.h b/src/qt_common/util/applet.h index 2b48d16698..34183274ff 100644 --- a/src/qt_common/util/applet.h +++ b/src/qt_common/util/applet.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_APPLET_UTIL_H diff --git a/src/qt_common/util/compress.cpp b/src/qt_common/util/compress.cpp index 94dca41151..527640cb98 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" diff --git a/src/qt_common/util/compress.h b/src/qt_common/util/compress.h index 2b3ffd1cbd..a16c6bff1a 100644 --- a/src/qt_common/util/compress.h +++ b/src/qt_common/util/compress.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 #pragma once diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index 9ba5ab264c..dd095a40a4 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -12,7 +12,7 @@ #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 @@ -22,20 +22,18 @@ namespace QtCommon::Content { -bool CheckGameFirmware(u64 program_id, QObject* parent) +bool CheckGameFirmware(u64 program_id) { if (FirmwareManager::GameRequiresFirmware(program_id) && !FirmwareManager::CheckFirmwarePresence(*system)) { - auto result = QtCommon::Frontend::ShowMessage( - QMessageBox::Warning, + 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; @@ -43,26 +41,23 @@ bool CheckGameFirmware(u64 program_id, QObject* parent) 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(); + // 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 = [&]() { @@ -104,7 +99,7 @@ void InstallFirmware(const QString& location, bool recursive) if (out.size() <= 0) { result = FirmwareInstallResult::NoNCAs; - icon = QMessageBox::Warning; + icon = QtCommon::Frontend::Icon::Warning; ShowMessage(); return; } @@ -114,7 +109,7 @@ void InstallFirmware(const QString& location, bool recursive) if (sysnand_content_vdir->IsWritable() && !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { result = FirmwareInstallResult::FailedDelete; - icon = QMessageBox::Critical; + icon = QtCommon::Frontend::Icon::Critical; ShowMessage(); return; } @@ -145,7 +140,7 @@ void InstallFirmware(const QString& location, bool recursive) 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 +148,7 @@ void InstallFirmware(const QString& location, bool recursive) if (!success) { result = FirmwareInstallResult::FailedCopy; - icon = QMessageBox::Critical; + icon = QtCommon::Frontend::Icon::Critical; ShowMessage(); return; } @@ -162,8 +157,9 @@ 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, @@ -174,14 +170,15 @@ void InstallFirmware(const QString& location, bool recursive) if (results.size() > 0) { const auto failed_names = QString::fromStdString( fmt::format("{}", fmt::join(results, "\n"))); - progress.close(); + 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; @@ -221,19 +218,16 @@ 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); + 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); @@ -260,12 +254,8 @@ void VerifyGameContents(const std::string& game_path) void InstallKeys() { - const QString key_source_location - = QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"), - {}, - QStringLiteral("Decryption Keys (*.keys)"), - {}, - QtCommon::Frontend::Option::ReadOnly); + const QString key_source_location = QtCommon::Frontend::GetOpenFileName( + tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {}); if (key_source_location.isEmpty()) return; @@ -289,27 +279,22 @@ void InstallKeys() 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!"), @@ -374,28 +359,29 @@ void FixProfiles() 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); + 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( + 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."), - QMessageBox::Yes | QMessageBox::No); + 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, @@ -413,18 +399,12 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, 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", @@ -485,23 +465,16 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, "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 + // to prevent GUI mangling we have to run this in a thread as well QFuture delete_future = QtConcurrent::run([=]() { FrontendCommon::DataManager::ClearDir(data_dir, user_id); return !progress->wasCanceled(); @@ -532,7 +505,7 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, QObject::connect(watcher, &QFutureWatcher::finished, rootObject, [=]() { progress->close(); - // this sucks + // this sucks if (watcher->result()) { Information(tr("Imported Successfully"), tr("Data was imported successfully.")); } else if (progress->wasCanceled()) { @@ -553,4 +526,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..c1e2e7791d 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, diff --git a/src/qt_common/util/fs.cpp b/src/qt_common/util/fs.cpp index dd105849aa..e171b8eaf5 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 diff --git a/src/qt_common/util/fs.h b/src/qt_common/util/fs.h index 41669e8019..ef56a361dd 100644 --- a/src/qt_common/util/fs.h +++ b/src/qt_common/util/fs.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 #include "common/common_types.h" diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index 34fbd04ff9..3e07b9d6b5 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -403,29 +403,29 @@ void ResetMetadata(bool show_message) { 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); diff --git a/src/qt_common/util/game.h b/src/qt_common/util/game.h index 2a7c77ef2d..16a1b7340a 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 diff --git a/src/qt_common/util/meta.cpp b/src/qt_common/util/meta.cpp index 4c7f1409e3..ead090b7a4 100644 --- a/src/qt_common/util/meta.cpp +++ b/src/qt_common/util/meta.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/meta.h" diff --git a/src/qt_common/util/meta.h b/src/qt_common/util/meta.h index c0a37db983..53501591e0 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 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..7a1d74b354 100644 --- a/src/qt_common/util/path.h +++ b/src/qt_common/util/path.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_PATH_UTIL_H diff --git a/src/qt_common/util/rom.cpp b/src/qt_common/util/rom.cpp index 1617548db3..ef4d4e1ae4 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" diff --git a/src/qt_common/util/rom.h b/src/qt_common/util/rom.h index f76b09753d..5dd372e450 100644 --- a/src/qt_common/util/rom.h +++ b/src/qt_common/util/rom.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_ROM_UTIL_H 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/libqt_common.cpp b/src/yuzu/libqt_common.cpp new file mode 100644 index 0000000000..6da75033af --- /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..9fb0add154 --- /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; +}; + +} diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 399b3bd976..aaac46bffb 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -1929,9 +1929,8 @@ 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{ 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 From 769edbfea31e0300137bb1214dd2822fceaa09ab Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 10 Mar 2026 05:44:51 +0100 Subject: [PATCH 4/6] [video_core] Revert "Simplify TextureCache GC and remove redundant code" (#3652) (#3704) regr. Steam Deck Please, for the love of God, stop saying "YOLO good to merge" after testers report performance regressions (and promptly get brushed to the side). Seriously, what the hell? This reverts commit f8ea09fa0f06572b32df1ead2fb38a64098e312e. Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3704 Reviewed-by: Lizzie Reviewed-by: DraVee --- src/video_core/texture_cache/texture_cache.h | 143 ++++++++++++++++-- .../texture_cache/texture_cache_base.h | 1 + 2 files changed, 131 insertions(+), 13 deletions(-) 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; From 8678cb06eb28a6416a4c6911458fc04113ad8ed4 Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 10 Mar 2026 06:51:08 +0100 Subject: [PATCH 5/6] [meta] clang-format literally all of the Qt code (#3706) I'm tired of dealing with this tbh Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3706 Reviewed-by: Lizzie --- src/qt_common/abstract/frontend.cpp | 6 +- src/qt_common/abstract/progress.cpp | 11 +- src/qt_common/config/shared_translation.cpp | 502 +++++++----------- src/qt_common/config/shared_translation.h | 25 +- src/qt_common/config/uisettings.h | 12 +- src/qt_common/discord/discord_impl.cpp | 8 +- src/qt_common/gamemode.cpp | 4 +- src/qt_common/gui_settings.cpp | 5 +- src/qt_common/qt_common.cpp | 39 +- src/qt_common/qt_common.h | 14 +- src/qt_common/qt_string_lookup.h | 20 +- src/qt_common/util/applet.h | 4 +- src/qt_common/util/compress.cpp | 106 ++-- src/qt_common/util/compress.h | 110 ++-- src/qt_common/util/content.cpp | 188 +++---- src/qt_common/util/content.h | 24 +- src/qt_common/util/fs.cpp | 25 +- src/qt_common/util/fs.h | 10 +- src/qt_common/util/game.cpp | 168 +++--- src/qt_common/util/game.h | 32 +- src/qt_common/util/meta.cpp | 7 +- src/qt_common/util/meta.h | 2 +- src/qt_common/util/mod.cpp | 3 +- src/qt_common/util/mod.h | 6 +- src/qt_common/util/path.h | 6 +- src/qt_common/util/rom.cpp | 11 +- src/qt_common/util/rom.h | 10 +- src/yuzu/about_dialog.cpp | 23 +- src/yuzu/applets/qt_controller.cpp | 21 +- src/yuzu/applets/qt_software_keyboard.cpp | 30 +- src/yuzu/applets/qt_web_browser.cpp | 26 +- src/yuzu/applets/qt_web_browser.h | 3 +- src/yuzu/bootmanager.cpp | 37 +- .../configuration/addon/mod_select_dialog.cpp | 3 +- .../configuration/addon/mod_select_dialog.h | 5 +- src/yuzu/configuration/configure_audio.cpp | 17 +- src/yuzu/configuration/configure_cpu.cpp | 14 +- src/yuzu/configuration/configure_cpu.h | 4 +- src/yuzu/configuration/configure_debug.cpp | 11 +- src/yuzu/configuration/configure_dialog.cpp | 35 +- src/yuzu/configuration/configure_dialog.h | 2 +- .../configuration/configure_filesystem.cpp | 30 +- src/yuzu/configuration/configure_general.cpp | 2 +- src/yuzu/configuration/configure_graphics.cpp | 93 ++-- .../configure_graphics_advanced.cpp | 4 +- .../configure_graphics_extensions.cpp | 18 +- src/yuzu/configuration/configure_hotkeys.cpp | 4 +- src/yuzu/configuration/configure_input.cpp | 15 +- .../configure_input_advanced.cpp | 2 +- .../configuration/configure_input_per_game.h | 4 +- .../configuration/configure_input_player.cpp | 20 +- .../configure_input_player_widget.cpp | 5 +- src/yuzu/configuration/configure_network.cpp | 5 +- src/yuzu/configuration/configure_per_game.cpp | 10 +- src/yuzu/configuration/configure_per_game.h | 4 +- .../configure_per_game_addons.cpp | 44 +- .../configuration/configure_per_game_addons.h | 7 +- .../configure_profile_manager.cpp | 52 +- .../configuration/configure_profile_manager.h | 5 +- src/yuzu/configuration/configure_ringcon.cpp | 4 +- src/yuzu/configuration/configure_system.cpp | 7 +- src/yuzu/configuration/configure_tas.cpp | 5 +- .../configure_touch_from_button.cpp | 7 +- src/yuzu/configuration/configure_ui.cpp | 2 +- src/yuzu/configuration/configure_web.cpp | 22 +- src/yuzu/configuration/configure_web.h | 6 +- src/yuzu/configuration/shared_widget.cpp | 51 +- src/yuzu/configuration/shared_widget.h | 4 +- .../configuration/system/new_user_dialog.h | 16 +- src/yuzu/data_dialog.cpp | 47 +- src/yuzu/data_dialog.h | 15 +- src/yuzu/debugger/console.cpp | 4 +- src/yuzu/deps_dialog.cpp | 42 +- src/yuzu/deps_dialog.h | 31 +- src/yuzu/game/game_card.cpp | 4 +- src/yuzu/game/game_card.h | 8 +- src/yuzu/game/game_list.cpp | 25 +- src/yuzu/game/game_list.h | 10 +- src/yuzu/game/game_list_p.h | 20 +- src/yuzu/game/game_list_worker.cpp | 69 +-- src/yuzu/game/game_list_worker.h | 2 +- src/yuzu/hotkeys.cpp | 4 +- src/yuzu/install_dialog.cpp | 4 +- src/yuzu/libqt_common.cpp | 20 +- src/yuzu/libqt_common.h | 2 +- src/yuzu/loading_screen.cpp | 2 +- src/yuzu/main.cpp | 9 +- src/yuzu/main_window.cpp | 490 +++++++++-------- src/yuzu/main_window.h | 33 +- src/yuzu/migration_dialog.cpp | 28 +- src/yuzu/migration_dialog.h | 25 +- src/yuzu/migration_worker.cpp | 26 +- src/yuzu/migration_worker.h | 26 +- src/yuzu/multiplayer/chat_room.h | 2 +- src/yuzu/multiplayer/direct_connect.cpp | 4 +- src/yuzu/multiplayer/host_room.cpp | 5 +- src/yuzu/multiplayer/lobby.cpp | 5 +- src/yuzu/multiplayer/state.cpp | 2 +- src/yuzu/render/performance_overlay.h | 16 +- src/yuzu/ryujinx_dialog.cpp | 38 +- src/yuzu/ryujinx_dialog.h | 12 +- src/yuzu/set_play_time_dialog.cpp | 13 +- src/yuzu/set_play_time_dialog.h | 4 +- src/yuzu/user_data_migration.cpp | 86 ++- src/yuzu/user_data_migration.h | 5 +- src/yuzu/util/util.cpp | 12 +- src/yuzu/vk_device_info.cpp | 7 +- 107 files changed, 1457 insertions(+), 1737 deletions(-) diff --git a/src/qt_common/abstract/frontend.cpp b/src/qt_common/abstract/frontend.cpp index 05eb550095..de362b253f 100644 --- a/src/qt_common/abstract/frontend.cpp +++ b/src/qt_common/abstract/frontend.cpp @@ -6,17 +6,17 @@ namespace QtCommon::Frontend { const QString GetOpenFileName(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter) { + QString* selectedFilter) { return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter); } const QStringList GetOpenFileNames(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter) { + QString* selectedFilter) { return QFileDialog::getOpenFileNames(rootObject, title, dir, filter, selectedFilter); } const QString GetSaveFileName(const QString& title, const QString& dir, const QString& filter, - QString* selectedFilter) { + QString* selectedFilter) { return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter); } diff --git a/src/qt_common/abstract/progress.cpp b/src/qt_common/abstract/progress.cpp index c9185a756d..2141e41d44 100644 --- a/src/qt_common/abstract/progress.cpp +++ b/src/qt_common/abstract/progress.cpp @@ -5,13 +5,8 @@ namespace QtCommon::Frontend { -QtProgressDialog::QtProgressDialog(const QString&, - const QString&, - int, - int, - QObject* parent, +QtProgressDialog::QtProgressDialog(const QString&, const QString&, int, int, QObject* parent, Qt::WindowFlags) - : QObject(parent) -{} + : QObject(parent) {} -} +} // namespace QtCommon::Frontend 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 6fbeb1df6e..6529c7bf40 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -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 82f805b888..37b24cdd57 100644 --- a/src/qt_common/discord/discord_impl.cpp +++ b/src/qt_common/discord/discord_impl.cpp @@ -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 dae6676fee..b6ce5c2ca8 100644 --- a/src/qt_common/gamemode.cpp +++ b/src/qt_common/gamemode.cpp @@ -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 4a1506261a..8b85fe43d6 100644 --- a/src/qt_common/gui_settings.cpp +++ b/src/qt_common/gui_settings.cpp @@ -1,16 +1,17 @@ // 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 8954bf09f2..e886593780 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -1,8 +1,8 @@ // 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 "qt_common.h" #include #include @@ -28,8 +28,7 @@ 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")) @@ -51,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(); @@ -60,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. @@ -69,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; } @@ -99,26 +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()); } -void Init(QWidget* root) -{ +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 9c7816a2ed..19c09ef5d0 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -4,17 +4,17 @@ #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 { -extern QWidget *rootObject; +extern QWidget* rootObject; extern std::unique_ptr system; extern std::shared_ptr vfs; @@ -24,12 +24,12 @@ typedef std::function QtProgressCallback; Core::Frontend::WindowSystemType GetWindowSystemType(); -Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window); +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window); -void Init(QWidget *root); +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 34183274ff..f88aa066b2 100644 --- a/src/qt_common/util/applet.h +++ b/src/qt_common/util/applet.h @@ -5,7 +5,5 @@ #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 527640cb98..c86e190bee 100644 --- a/src/qt_common/util/compress.cpp +++ b/src/qt_common/util/compress.cpp @@ -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 a16c6bff1a..640dcad420 100644 --- a/src/qt_common/util/compress.h +++ b/src/qt_common/util/compress.h @@ -5,9 +5,9 @@ #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 dd095a40a4..4d9b324608 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -15,17 +15,16 @@ #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) -{ - if (FirmwareManager::GameRequiresFirmware(program_id) - && !FirmwareManager::CheckFirmwarePresence(*system)) { +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 " @@ -39,11 +38,10 @@ bool CheckGameFirmware(u64 program_id) return true; } -void InstallFirmware(const QString& location, bool recursive) -{ +void InstallFirmware(const QString& location, bool recursive) { // Initialize a progress dialog. - auto progress = QtCommon::Frontend::newProgressDialog(tr("Installing Firmware..."), - tr("Cancel"), 0, 100); + auto progress = + QtCommon::Frontend::newProgressDialog(tr("Installing Firmware..."), tr("Cancel"), 0, 100); progress->show(); QGuiApplication::processEvents(); @@ -61,9 +59,7 @@ void InstallFirmware(const QString& location, bool recursive) 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()); @@ -88,12 +84,10 @@ 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); } @@ -106,8 +100,8 @@ void InstallFirmware(const QString& location, bool recursive) // 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 = QtCommon::Frontend::Icon::Critical; ShowMessage(); @@ -125,16 +119,14 @@ 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; } @@ -162,14 +154,12 @@ void InstallFirmware(const QString& location, bool recursive) 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"))); + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(results, "\n"))); progress->close(); QtCommon::Frontend::Critical( tr("Firmware integrity verification failed!"), @@ -185,13 +175,11 @@ void InstallFirmware(const QString& location, bool recursive) 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()}; @@ -216,8 +204,7 @@ QString UnzipFirmwareToTmp(const QString& location) } // Content // -void VerifyGameContents(const std::string& game_path) -{ +void VerifyGameContents(const std::string& game_path) { auto progress = QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100); progress->show(); @@ -234,34 +221,30 @@ void VerifyGameContents(const std::string& game_path) 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() -{ +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); @@ -276,11 +259,10 @@ void InstallKeys() } } -void VerifyInstalledContents() -{ +void VerifyInstalledContents() { // Initialize a progress dialog. - auto progress = QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), - tr("Cancel"), 0, 100); + auto progress = + QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100); progress->show(); QGuiApplication::processEvents(); @@ -289,7 +271,8 @@ void VerifyInstalledContents() auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { QGuiApplication::processEvents(); progress->setValue(static_cast((processed_size * 100) / total_size)); - return progress->wasCanceled(); }; + return progress->wasCanceled(); + }; const std::vector result = ContentManager::VerifyInstalledContents( *QtCommon::system, *QtCommon::provider, QtProgressCallback); @@ -300,17 +283,18 @@ void VerifyInstalledContents() 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(); @@ -350,28 +334,27 @@ 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) -{ +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); + auto result = Warning(tr("Really clear data?"), tr("Important data may be lost!"), Yes | No); if (result != Yes) return; - 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); + 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 != Yes) return; @@ -384,17 +367,13 @@ void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& u 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; @@ -406,18 +385,15 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, progress->show(); 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); @@ -444,37 +420,34 @@ 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 != QtCommon::Frontend::Yes) return; - QtProgressDialog* progress = newProgressDialogPtr( - tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100); + QtProgressDialog* progress = + newProgressDialogPtr(tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100); progress->setTitle(tr("Importing")); progress->show(); - // to prevent GUI mangling we have to run this in a thread as well + // to prevent GUI mangling we have to run this in a thread as well QFuture delete_future = QtConcurrent::run([=]() { FrontendCommon::DataManager::ClearDir(data_dir, user_id); return !progress->wasCanceled(); @@ -485,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(); }); @@ -505,7 +475,7 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, QObject::connect(watcher, &QFutureWatcher::finished, rootObject, [=]() { progress->close(); - // this sucks + // this sucks if (watcher->result()) { Information(tr("Imported Successfully"), tr("Data was imported successfully.")); } else if (progress->wasCanceled()) { diff --git a/src/qt_common/util/content.h b/src/qt_common/util/content.h index c1e2e7791d..20dacf540a 100644 --- a/src/qt_common/util/content.h +++ b/src/qt_common/util/content.h @@ -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 e171b8eaf5..b5f2844bbf 100644 --- a/src/qt_common/util/fs.cpp +++ b/src/qt_common/util/fs.cpp @@ -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 ef56a361dd..5ab59e2a45 100644 --- a/src/qt_common/util/fs.h +++ b/src/qt_common/util/fs.h @@ -1,19 +1,19 @@ // 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 3e07b9d6b5..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,8 +368,7 @@ 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; using namespace QtCommon::Frontend; int 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 16a1b7340a..0d7f03fa86 100644 --- a/src/qt_common/util/game.h +++ b/src/qt_common/util/game.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 ead090b7a4..4607321285 100644 --- a/src/qt_common/util/meta.cpp +++ b/src/qt_common/util/meta.cpp @@ -1,7 +1,6 @@ // 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 53501591e0..ae6dbc49d8 100644 --- a/src/qt_common/util/meta.h +++ b/src/qt_common/util/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.h b/src/qt_common/util/path.h index 7a1d74b354..aff0009f34 100644 --- a/src/qt_common/util/path.h +++ b/src/qt_common/util/path.h @@ -4,9 +4,11 @@ #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 ef4d4e1ae4..c77fa9530d 100644 --- a/src/qt_common/util/rom.cpp +++ b/src/qt_common/util/rom.cpp @@ -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 5dd372e450..5ceadaf46b 100644 --- a/src/qt_common/util/rom.h +++ b/src/qt_common/util/rom.h @@ -4,17 +4,13 @@ #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/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..7d0e15accc 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -80,7 +80,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 +93,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); } } } @@ -109,7 +110,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { 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")) { + if (userData.isValid() && userData.canConvert() && + item.front()->text() == QStringLiteral("Update")) { quint32 numeric_version = userData.toUInt(); disabled_addons.push_back(fmt::format("Update@{}", numeric_version)); } else { @@ -164,7 +166,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 +205,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 +218,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 +233,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 +255,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(); @@ -333,9 +337,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(), name.toStdString()) != disabled.end(); } bool should_enable = !patch_disabled; 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 index 6da75033af..198d1b66be 100644 --- a/src/yuzu/libqt_common.cpp +++ b/src/yuzu/libqt_common.cpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include #include -#include #include "libqt_common.h" #include "qt_common/abstract/frontend.h" @@ -14,10 +14,11 @@ 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); +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()); } @@ -81,15 +82,15 @@ void WidgetsProgressDialog::show() { m_dialog->show(); } -std::unique_ptr newProgressDialog(const QString& labelText, const QString& cancelButtonText, - int minimum, int maximum, Qt::WindowFlags f) { +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) { + int minimum, int maximum, Qt::WindowFlags f) { return new WidgetsProgressDialog(labelText, cancelButtonText, minimum, maximum, (QWidget*)rootObject, f); } @@ -115,5 +116,4 @@ const QString GetTextInput(const QString& title, const QString& caption, 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 index 9fb0add154..356fd65d2b 100644 --- a/src/yuzu/libqt_common.h +++ b/src/yuzu/libqt_common.h @@ -34,4 +34,4 @@ 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 aaac46bffb..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 { @@ -1934,13 +1930,14 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar /** 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); @@ -2047,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 || @@ -2066,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) { @@ -2085,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; } @@ -2246,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(); } @@ -2384,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; @@ -2414,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 @@ -2530,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: @@ -2570,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: @@ -2588,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; } @@ -2624,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, @@ -2637,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; @@ -2686,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); @@ -2762,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; @@ -2770,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); @@ -2846,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; @@ -2862,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); @@ -2873,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 @@ -3008,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()) { @@ -3220,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) { @@ -3244,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; @@ -3266,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) { @@ -3431,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 { @@ -3488,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. @@ -3499,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); @@ -3648,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(); @@ -3664,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(); @@ -3690,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"), @@ -3861,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); @@ -3914,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(); } @@ -3993,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"), @@ -4061,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)); @@ -4076,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); @@ -4111,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); @@ -4301,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); @@ -4444,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; @@ -4454,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); @@ -4477,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.")); } } @@ -4530,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; @@ -4809,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); From 1c57cb5603f4224b5abeb0783989db55aa7abb0c Mon Sep 17 00:00:00 2001 From: DraVee Date: Tue, 10 Mar 2026 11:09:34 -0300 Subject: [PATCH 6/6] [cheat] add dmnt, indiviual cheats, etc. --- .../yuzu/yuzu_emu/adapters/AddonAdapter.kt | 5 +- .../yuzu/yuzu_emu/fragments/AddonsFragment.kt | 2 +- .../org/yuzu/yuzu_emu/model/AddonViewModel.kt | 35 +- .../java/org/yuzu/yuzu_emu/model/Patch.kt | 21 +- .../java/org/yuzu/yuzu_emu/model/PatchType.kt | 6 +- src/android/app/src/main/jni/native.cpp | 8 +- src/common/android/id_cache.cpp | 2 +- src/core/CMakeLists.txt | 17 +- src/core/core.cpp | 149 +- src/core/core.h | 12 +- src/core/file_sys/patch_manager.cpp | 239 +++- src/core/file_sys/patch_manager.h | 17 +- src/core/hle/service/dmnt/cheat_interface.cpp | 238 ++++ src/core/hle/service/dmnt/cheat_interface.h | 88 ++ src/core/hle/service/dmnt/cheat_parser.cpp | 121 ++ src/core/hle/service/dmnt/cheat_parser.h | 26 + .../service/dmnt/cheat_process_manager.cpp | 599 ++++++++ .../hle/service/dmnt/cheat_process_manager.h | 126 ++ .../service/dmnt/cheat_virtual_machine.cpp | 1269 +++++++++++++++++ .../hle/service/dmnt/cheat_virtual_machine.h | 323 +++++ src/core/hle/service/dmnt/dmnt.cpp | 26 + src/core/hle/service/dmnt/dmnt.h | 15 + src/core/hle/service/dmnt/dmnt_results.h | 26 + src/core/hle/service/dmnt/dmnt_types.h | 55 + src/core/hle/service/services.cpp | 4 +- src/core/memory/cheat_engine.cpp | 288 ---- src/core/memory/cheat_engine.h | 88 -- src/core/memory/dmnt_cheat_types.h | 37 - src/core/memory/dmnt_cheat_vm.cpp | 1268 ---------------- src/core/memory/dmnt_cheat_vm.h | 330 ----- .../configure_per_game_addons.cpp | 84 +- 31 files changed, 3404 insertions(+), 2120 deletions(-) create mode 100644 src/core/hle/service/dmnt/cheat_interface.cpp create mode 100644 src/core/hle/service/dmnt/cheat_interface.h create mode 100644 src/core/hle/service/dmnt/cheat_parser.cpp create mode 100644 src/core/hle/service/dmnt/cheat_parser.h create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.cpp create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.h create mode 100644 src/core/hle/service/dmnt/cheat_virtual_machine.cpp create mode 100644 src/core/hle/service/dmnt/cheat_virtual_machine.h create mode 100644 src/core/hle/service/dmnt/dmnt.cpp create mode 100644 src/core/hle/service/dmnt/dmnt.h create mode 100644 src/core/hle/service/dmnt/dmnt_results.h create mode 100644 src/core/hle/service/dmnt/dmnt_types.h delete mode 100644 src/core/memory/cheat_engine.cpp delete mode 100644 src/core/memory/cheat_engine.h delete mode 100644 src/core/memory/dmnt_cheat_types.h delete mode 100644 src/core/memory/dmnt_cheat_vm.cpp delete mode 100644 src/core/memory/dmnt_cheat_vm.h 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/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/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 7d0e15accc..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 @@ -106,18 +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); + // 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.front()->text() == QStringLiteral("Update")) { - quint32 numeric_version = userData.toUInt(); + 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]; @@ -304,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; @@ -341,7 +381,7 @@ void ConfigurePerGameAddons::LoadConfiguration() { std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end(); } else { patch_disabled = - std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end(); } bool should_enable = !patch_disabled; @@ -359,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); }