[cheats] add runtime cheats toggles with master code support (#4334)

add per-cheat runtime toggles (thread-safe).
add master code handling (master code cannot be disabled, as noted in Atmosphere's implementation).
add configurable global / per-game initial states for cheat entries.
remove fixed opcode limits and do some fixes to text parsing / file loading.
do not run debug log while cheat logging is not enabled, to reduce performance impacts.
update the `Using Cheats` document to reflect the changes.

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4334
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: John <john@eden-emu.dev>
This commit is contained in:
Soar Qin
2026-10-30 03:47:38 +01:00
committed by crueter
parent b4d5760014
commit 69665063a9
24 changed files with 365 additions and 55 deletions
@@ -34,6 +34,13 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
* with the native side of the Yuzu code.
*/
object NativeLibrary {
@Keep
class RuntimeCheat(
val id: Int,
val name: String,
val enabled: Boolean,
val isMaster: Boolean
)
@Keep
data class UpdateResult(
var tag: String = "",
@@ -197,6 +204,9 @@ object NativeLibrary {
*/
external fun isPaused(): Boolean
external fun getRuntimeCheats(): Array<RuntimeCheat>
external fun setRuntimeCheatEnabled(id: Int, enabled: Boolean): Boolean
/**
* Returns the performance stats for the current game
*/
@@ -13,6 +13,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
FASTMEM("cpuopt_fastmem"),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
CORE_SYNC_CORE_SPEED("sync_core_speed"),
ENABLE_CHEATS_BY_DEFAULT("enable_cheats_by_default"),
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
@@ -316,6 +316,13 @@ abstract class SettingsItem(
descriptionId = R.string.use_sync_core_description
)
)
put(
SwitchSetting(
BooleanSetting.ENABLE_CHEATS_BY_DEFAULT,
titleId = R.string.enable_cheats_by_default,
descriptionId = R.string.enable_cheats_by_default_description
)
)
put(
SingleChoiceSetting(
IntSetting.REGION_INDEX,
@@ -510,6 +510,7 @@ class SettingsFragmentPresenter(
add(IntSetting.FAST_CPU_TIME.key)
add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
add(BooleanSetting.ENABLE_CHEATS_BY_DEFAULT.key)
add(IntSetting.MEMORY_LAYOUT.key)
add(BooleanSetting.USE_CUSTOM_CPU_TICKS.key)
@@ -734,6 +734,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
binding.pausedIcon.setOnClickListener {
if (this::emulationState.isInitialized && emulationState.isPaused) {
@@ -817,6 +818,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_load_amiibo -> handleLoadAmiiboSelection()
R.id.menu_cheats -> {
binding.drawerLayout.closeDrawer(binding.inGameMenu)
showRuntimeCheatDialog()
true
}
R.id.menu_controls -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
@@ -962,6 +969,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface()
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible =
NativeLibrary.getRuntimeCheats().isNotEmpty()
updateShowStatsOverlay()
updateSocOverlay()
@@ -987,6 +996,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
if (it) {
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer)
@@ -1119,6 +1129,45 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
NativePostProcessing.persist()
}
private fun showRuntimeCheatDialog() {
val cheats = NativeLibrary.getRuntimeCheats()
if (cheats.isEmpty()) return
val names = cheats.map { it.name.ifEmpty { getString(R.string.unnamed_cheat) } }.toTypedArray()
lateinit var dialog: androidx.appcompat.app.AlertDialog
dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.cheats)
.setMultiChoiceItems(names, cheats.map { it.enabled }.toBooleanArray()) { _, which, enabled ->
if (cheats[which].isMaster) {
dialog.listView.setItemChecked(which, true)
} else if (!NativeLibrary.setRuntimeCheatEnabled(cheats[which].id, enabled)) {
dialog.listView.setItemChecked(which, !enabled)
}
}
.setNegativeButton(R.string.disable_all_cheats) { _, _ ->
cheats.forEach {
if (!it.isMaster) NativeLibrary.setRuntimeCheatEnabled(it.id, false)
}
}
.setNeutralButton(R.string.enable_all_cheats) { _, _ ->
cheats.forEach {
if (!it.isMaster) NativeLibrary.setRuntimeCheatEnabled(it.id, true)
}
}
.setPositiveButton(R.string.done, null)
.create()
dialog.setOnShowListener {
val listView = dialog.listView
fun updateMasterVisuals() = repeat(listView.childCount) { index ->
listView.getChildAt(index).alpha =
if (cheats[listView.firstVisiblePosition + index].isMaster) 0.38f else 1f
}
listView.setOnScrollChangeListener { _, _, _, _, _ -> updateMasterVisuals() }
updateMasterVisuals()
}
dialog.show()
}
private fun addQuickSettings() {
binding.quickSettingsSheet.apply {
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
+26
View File
@@ -67,6 +67,7 @@ extern "C" {
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_real.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
@@ -946,6 +947,31 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getRuntimeCheats(JNIEnv* env, jclass clazz) {
const auto cheats = EmulationSession::GetInstance().System().GetRuntimeCheats();
const auto runtime_cheat_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$RuntimeCheat");
const auto constructor = env->GetMethodID(runtime_cheat_class, "<init>", "(ILjava/lang/String;ZZ)V");
auto result = env->NewObjectArray(static_cast<jsize>(cheats.size()), runtime_cheat_class, nullptr);
for (jsize i = 0; i < static_cast<jsize>(cheats.size()); ++i) {
const auto& cheat = cheats[i];
const auto name = Common::Android::ToJString(env, cheat.name);
const auto item = env->NewObject(runtime_cheat_class, constructor, static_cast<jint>(cheat.id), name,
static_cast<jboolean>(cheat.enabled),
static_cast<jboolean>(cheat.is_master));
env->SetObjectArrayElement(result, i, item);
env->DeleteLocalRef(name);
env->DeleteLocalRef(item);
}
env->DeleteLocalRef(runtime_cheat_class);
return result;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setRuntimeCheatEnabled(JNIEnv* env, jclass clazz,
jint id, jboolean enabled) {
return static_cast<jboolean>(EmulationSession::GetInstance().System().SetCheatEnabled(
static_cast<u32>(id), static_cast<bool>(enabled)));
}
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
using namespace VideoCore::Capture;
@@ -43,6 +43,10 @@
android:icon="@drawable/ic_nfc"
android:title="@string/load_amiibo" />
<item
android:id="@+id/menu_cheats"
android:icon="@drawable/ic_code"
android:title="@string/cheats" />
<item
android:id="@+id/menu_overlay_controls"
@@ -840,6 +840,12 @@
<string name="preferences_graphics">Graphics</string>
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
<string name="quick_settings">Quick Settings</string>
<string name="cheats">Cheats</string>
<string name="enable_all_cheats">Enable All Cheats</string>
<string name="disable_all_cheats">Disable All Cheats</string>
<string name="unnamed_cheat">Unnamed Cheat</string>
<string name="enable_cheats_by_default">Enable cheats by default</string>
<string name="enable_cheats_by_default_description">Enable regular cheats when a game starts</string>
<string name="enable_quick_settings">Enable Quick Settings</string>
<string name="enable_quick_settings_description">Allow access to quick settings menu via swipe and menu button</string>
<string name="preferences_audio">Audio</string>
+4
View File
@@ -418,6 +418,10 @@ void SetGlobalFilter(const Filter& filter) {
logging_instance->filter = filter;
}
bool IsMessageEnabled(Class log_class, Level log_level) {
return logging_instance && logging_instance->filter.CheckMessage(log_class, log_level);
}
void SetColorConsoleBackendEnabled(bool enabled) {
if (logging_instance)
logging_instance->color_console_backend.enabled = enabled;
+1
View File
@@ -138,6 +138,7 @@ void Stop();
/// The global filter will prevent any messages from even being processed if they are filtered.
void SetGlobalFilter(const Filter& filter);
[[nodiscard]] bool IsMessageEnabled(Class log_class, Level log_level);
void SetColorConsoleBackendEnabled(bool enabled);
} // namespace Common::Log
+1
View File
@@ -247,6 +247,7 @@ struct Values {
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core,
Specialization::Default};
SwitchableSetting<bool> enable_cheats_by_default{linkage, true, "enable_cheats_by_default", Category::Core};
// Cpu
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
+11
View File
@@ -786,6 +786,17 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
}
std::vector<Memory::RuntimeCheatInfo> System::GetRuntimeCheats() const {
if (!impl->cheat_engine) {
return {};
}
return impl->cheat_engine->GetCheats();
}
bool System::SetCheatEnabled(u32 cheat_id, bool enabled) {
return impl->cheat_engine && impl->cheat_engine->SetCheatEnabled(cheat_id, enabled);
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
}
+3
View File
@@ -46,6 +46,7 @@ enum class ResultStatus : u16;
namespace Core::Memory {
struct CheatEntry;
struct RuntimeCheatInfo;
class Memory;
} // namespace Core::Memory
@@ -342,6 +343,8 @@ public:
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
u64 main_region_size);
[[nodiscard]] std::vector<Memory::RuntimeCheatInfo> GetRuntimeCheats() const;
bool SetCheatEnabled(u32 cheat_id, bool enabled);
void SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set);
+4 -3
View File
@@ -503,10 +503,11 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
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))
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
} else if (auto const lower_res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
std::copy(lower_res->begin(), lower_res->end(), std::back_inserter(out));
}
}
}
}
+74 -16
View File
@@ -4,8 +4,11 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cctype>
#include <locale>
#include "common/hex_util.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/arm/debug.h"
#include "core/core.h"
@@ -28,11 +31,11 @@ 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) {
while (end_index < data.size() && data[end_index] != match) {
++end_index;
if (end_index > data.size()) {
return {};
}
}
if (end_index == data.size()) {
return {};
}
out_name_size = end_index - start_index;
@@ -130,6 +133,11 @@ void StandardVmCallbacks::CommandLog(std::string_view data) {
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
}
bool StandardVmCallbacks::IsCommandLogEnabled() const {
return Common::Log::IsMessageEnabled(Common::Log::Class::CheatEngine,
Common::Log::Level::Debug);
}
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
if ((in < metadata.main_nso_extents.base ||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
@@ -166,8 +174,9 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
if (data[i] == '{') {
current_entry = 0;
out[*current_entry].is_master = true;
if (out[*current_entry].definition.num_opcodes > 0) {
if (!out[*current_entry].definition.opcodes.empty()) {
return {};
}
@@ -203,32 +212,33 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
'\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()) {
} else if (std::isxdigit(static_cast<unsigned char>(data[i]))) {
if (!current_entry || data.size() - i < 8) {
return {};
}
const auto hex = std::string(data.substr(i, 8));
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
if (!std::all_of(hex.begin(), hex.end(), [](char value) {
return std::isxdigit(static_cast<unsigned char>(value));
})) {
return {};
}
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
out[*current_entry].definition.opcodes.push_back(value);
i += 8;
i += 7;
} else {
return {};
}
}
out[0].enabled = out[0].definition.num_opcodes > 0;
out[0].enabled = out[0].is_master && !out[0].definition.opcodes.empty();
out[0].cheat_id = 0;
for (u32 i = 1; i < out.size(); ++i) {
out[i].enabled = out[i].definition.num_opcodes > 0;
out[i].enabled = Settings::values.enable_cheats_by_default.GetValue() &&
!out[i].definition.opcodes.empty();
out[i].cheat_id = i;
}
@@ -240,6 +250,10 @@ CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
metadata.main_nso_build_id = build_id_;
for (u32 i = 0; i < cheats.size(); ++i) {
cheats[i].cheat_id = i;
cheats[i].enabled = cheats[i].is_master || cheats[i].enabled;
}
}
CheatEngine::~CheatEngine() {
@@ -286,12 +300,56 @@ void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_regi
}
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
cheats = std::move(reload_cheats);
is_pending_reload.exchange(true);
{
std::scoped_lock lock{cheats_mutex};
cheats = std::move(reload_cheats);
for (u32 i = 0; i < cheats.size(); ++i) {
cheats[i].cheat_id = i;
cheats[i].enabled = cheats[i].is_master || cheats[i].enabled;
}
}
is_pending_reload.store(true);
}
std::vector<RuntimeCheatInfo> CheatEngine::GetCheats() const {
std::scoped_lock lock{cheats_mutex};
std::vector<RuntimeCheatInfo> result;
result.reserve(cheats.size());
for (const auto& cheat : cheats) {
if (cheat.definition.opcodes.empty()) {
continue;
}
result.push_back({
.id = cheat.cheat_id,
.name = cheat.definition.readable_name.data(),
.enabled = cheat.enabled || cheat.is_master,
.is_master = cheat.is_master,
});
}
return result;
}
bool CheatEngine::SetCheatEnabled(u32 cheat_id, bool enabled) {
std::scoped_lock lock{cheats_mutex};
const auto entry = std::find_if(cheats.begin(), cheats.end(), [cheat_id](const auto& cheat) {
return cheat.cheat_id == cheat_id && !cheat.definition.opcodes.empty();
});
if (entry == cheats.end()) {
return false;
}
if (entry->is_master) {
return enabled;
}
if (entry->enabled != enabled) {
entry->enabled = enabled;
is_pending_reload.store(true);
}
return true;
}
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
std::scoped_lock lock{cheats_mutex};
vm.LoadProgram(cheats);
}
+5
View File
@@ -9,6 +9,7 @@
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <vector>
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"
@@ -41,6 +42,7 @@ public:
void ResumeProcess() override;
void DebugLog(u8 id, u64 value) override;
void CommandLog(std::string_view data) override;
[[nodiscard]] bool IsCommandLogEnabled() const override;
private:
bool IsAddressInRange(VAddr address) const;
@@ -81,6 +83,8 @@ public:
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
void Reload(std::vector<CheatEntry> reload_cheats);
[[nodiscard]] std::vector<RuntimeCheatInfo> GetCheats() const;
bool SetCheatEnabled(u32 cheat_id, bool enabled);
private:
void FrameCallback(std::chrono::nanoseconds ns_late);
@@ -89,6 +93,7 @@ private:
CheatProcessMetadata metadata;
std::vector<CheatEntry> cheats;
mutable std::mutex cheats_mutex;
std::atomic_bool is_pending_reload{false};
std::shared_ptr<Core::Timing::EventType> event;
+15 -2
View File
@@ -1,8 +1,14 @@
// 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 <string>
#include <vector>
#include "common/common_types.h"
namespace Core::Memory {
@@ -24,14 +30,21 @@ struct CheatProcessMetadata {
struct CheatDefinition {
std::array<char, 0x40> readable_name{};
u32 num_opcodes{};
std::array<u32, 0x100> opcodes{};
std::vector<u32> opcodes;
};
struct CheatEntry {
bool enabled{};
bool is_master{};
u32 cheat_id{};
CheatDefinition definition{};
};
struct RuntimeCheatInfo {
u32 id{};
std::string name;
bool enabled{};
bool is_master{};
};
} // namespace Core::Memory
+36 -25
View File
@@ -1,3 +1,6 @@
// 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
@@ -233,7 +236,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// Helper function for getting instruction dwords.
const auto GetNextDword = [&] {
if (instruction_ptr >= num_opcodes) {
if (instruction_ptr >= program.size()) {
valid = false;
return static_cast<u32>(0);
}
@@ -709,20 +712,20 @@ void DmntCheatVm::ResetState() {
}
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
// Reset opcode count.
num_opcodes = 0;
program.clear();
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;
}
std::size_t program_size{};
for (const auto& entry : entries) {
if (entry.enabled || entry.is_master) {
program_size += entry.definition.opcodes.size();
}
}
program.reserve(program_size);
for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
program[num_opcodes++] = entries[i].definition.opcodes[n];
}
for (const auto& entry : entries) {
if (entry.enabled || entry.is_master) {
program.insert(program.end(), entry.definition.opcodes.begin(),
entry.definition.opcodes.end());
}
}
@@ -735,27 +738,35 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
// 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<u32>(kDown & 0x0FFFFFFF)));
const bool command_log_enabled = callbacks->IsCommandLogEnabled();
if (command_log_enabled) {
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<u32>(kDown & 0x0FFFFFFF)));
}
// Clear VM state.
ResetState();
// Loop until program finishes.
while (DecodeNextOpcode(cur_opcode)) {
callbacks->CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
if (command_log_enabled) {
callbacks->CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(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("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]));
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i,
saved_values[i]));
}
LogOpcode(cur_opcode);
}
LogOpcode(cur_opcode);
// Increment conditional depth, if relevant.
if (cur_opcode.begin_conditional_block) {
+4 -5
View File
@@ -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 2019 yuzu Emulator Project
@@ -281,9 +281,9 @@ public:
virtual void DebugLog(u8 id, u64 value) = 0;
virtual void CommandLog(std::string_view data) = 0;
[[nodiscard]] virtual bool IsCommandLogEnabled() const = 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;
@@ -294,7 +294,7 @@ public:
~DmntCheatVm();
std::size_t GetProgramSize() const {
return this->num_opcodes;
return program.size();
}
bool LoadProgram(const std::vector<CheatEntry>& cheats);
@@ -303,11 +303,10 @@ public:
private:
std::unique_ptr<Callbacks> 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<u32, MaximumProgramOpcodeCount> program{};
std::vector<u32> program;
std::array<u64, NumRegisters> registers{};
std::array<u64, NumRegisters> saved_values{};
std::array<u64, NumStaticRegisters> static_registers{};
@@ -86,6 +86,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
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."));
INSERT(Settings, enable_cheats_by_default, tr("Enable cheats by default"),
tr("Enables regular cheats when a game starts. Master Code entries are always enabled. "
"Individual cheats can still be toggled from Cheats in the menu bar while the game is "
"running."));
// Cpu
INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
+9
View File
@@ -91,6 +91,14 @@
<addaction name="action_Configure"/>
<addaction name="action_Configure_Current_Game"/>
</widget>
<widget class="QMenu" name="menu_Cheats">
<property name="visible">
<bool>false</bool>
</property>
<property name="title">
<string>&amp;Cheats</string>
</property>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
@@ -241,6 +249,7 @@
</widget>
<addaction name="menu_File"/>
<addaction name="menu_Emulation"/>
<addaction name="menu_Cheats"/>
<addaction name="menu_View"/>
<addaction name="menu_Tools"/>
<addaction name="menu_Multiplayer"/>
+71
View File
@@ -78,10 +78,12 @@
#include <QGuiApplication>
#include <QInputDialog>
#include <QMimeData>
#include <QMouseEvent>
#include <QPalette>
#include <QProgressDialog>
#include <QScreen>
#include <QShortcut>
#include <QSignalBlocker>
#include <QStatusBar>
#include <QtConcurrentRun>
@@ -134,6 +136,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#endif
// Core //
#include "core/core.h"
#include "core/frontend/applets/general.h"
#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/software_keyboard.h"
@@ -147,6 +150,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/tools/renderdoc.h"
@@ -1434,6 +1438,9 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
}
void MainWindow::ConnectWidgetEvents() {
ui->menu_Cheats->installEventFilter(this);
ui->menu_Cheats->menuAction()->setVisible(false);
connect(game_list, &GameList::BootGame, this, &MainWindow::BootGameFromList);
connect(game_list, &GameList::GameChosen, this, &MainWindow::OnGameListLoadFile);
connect(game_list, &GameList::OpenDirectory, this, &MainWindow::OnGameListOpenDirectory);
@@ -1468,6 +1475,8 @@ void MainWindow::ConnectWidgetEvents() {
// Software Keyboard Applet
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit);
connect(this, &MainWindow::EmulationStopping, this, &MainWindow::SoftwareKeyboardExit);
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::RefreshRuntimeCheats);
connect(this, &MainWindow::EmulationStopping, this, &MainWindow::ClearRuntimeCheats);
connect(&status_bar_update_timer, &QTimer::timeout, this, &MainWindow::UpdateStatusBar);
@@ -1475,6 +1484,68 @@ void MainWindow::ConnectWidgetEvents() {
&MultiplayerState::UpdateThemedIcons);
}
void MainWindow::RefreshRuntimeCheats() {
ClearRuntimeCheats();
const auto cheats = QtCommon::system->GetRuntimeCheats();
auto* disable_all = ui->menu_Cheats->addAction(tr("Disable All Cheats"));
connect(disable_all, &QAction::triggered, this, [this] {
for (auto* action : runtime_cheat_actions) {
if (action->isEnabled()) {
action->setChecked(false);
}
}
});
auto* enable_all = ui->menu_Cheats->addAction(tr("Enable All Cheats"));
connect(enable_all, &QAction::triggered, this, [this] {
for (auto* action : runtime_cheat_actions) {
if (action->isEnabled()) {
action->setChecked(true);
}
}
});
ui->menu_Cheats->addSeparator();
runtime_cheat_actions.reserve(cheats.size());
for (const auto& cheat : cheats) {
const QString name = cheat.name.empty() ? tr("Unnamed Cheat")
: QString::fromUtf8(cheat.name);
auto* action = ui->menu_Cheats->addAction(name);
action->setCheckable(true);
action->setChecked(cheat.enabled || cheat.is_master);
action->setEnabled(!cheat.is_master);
connect(action, &QAction::toggled, this, [action, id = cheat.id](bool enabled) {
if (!QtCommon::system->SetCheatEnabled(id, enabled)) {
const QSignalBlocker blocker{action};
action->setChecked(!enabled);
}
});
runtime_cheat_actions.push_back(action);
}
ui->menu_Cheats->menuAction()->setVisible(!runtime_cheat_actions.empty());
}
void MainWindow::ClearRuntimeCheats() {
ui->menu_Cheats->clear();
runtime_cheat_actions.clear();
ui->menu_Cheats->menuAction()->setVisible(false);
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
if (watched == ui->menu_Cheats && event->type() == QEvent::MouseButtonRelease) {
const auto* mouse_event = static_cast<QMouseEvent*>(event);
auto* action = ui->menu_Cheats->actionAt(mouse_event->pos());
if (mouse_event->button() == Qt::LeftButton && action && action->isEnabled() &&
action->isCheckable()) {
action->trigger();
return true;
}
}
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::ConnectMenuEvents() {
const auto connect_menu = [&]<typename Fn>(QAction* action, const Fn& event_fn) {
connect(action, &QAction::triggered, this, event_fn);
+6
View File
@@ -176,6 +176,9 @@ public:
std::filesystem::path GetShortcutPath(QtCommon::Game::ShortcutTarget target);
protected:
bool eventFilter(QObject* watched, QEvent* event) override;
signals:
/**
@@ -264,6 +267,8 @@ private:
void ConnectWidgetEvents();
void ConnectMenuEvents();
void UpdateMenuState();
void RefreshRuntimeCheats();
void ClearRuntimeCheats();
void SetupPrepareForSleep();
@@ -552,6 +557,7 @@ private:
QString startup_icon_theme;
QActionGroup* game_size_actions;
std::vector<QAction*> runtime_cheat_actions;
// Debugger panes
ControllerDialog* controller_dialog = nullptr;