[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
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
24 changed files with 365 additions and 55 deletions

View file

@ -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
*/

View file

@ -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"),

View file

@ -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,

View file

@ -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)

View file

@ -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)

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;

View file

@ -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"

View file

@ -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>