mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-30 11:15:51 +00:00
[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:
@@ -45,12 +45,13 @@ Another option would be to launch the game in Eden and close it - then go into t
|
|||||||
***INFO***: Make sure you select the correct directory or it will not work, the screen should show the **cheats** sub-directory.
|
***INFO***: Make sure you select the correct directory or it will not work, the screen should show the **cheats** sub-directory.
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
2. You should now see the cheat appear in the **Add-ons** screen.
|
|
||||||
3. Launch the game and confirm that the cheat is applied.
|
9. You should now see the cheat appear in the **Add-ons** screen.
|
||||||
|
10. Launch the game and confirm that the cheat is applied.
|
||||||
|
|
||||||
### Step 2: Multiple Cheats
|
### Step 2: Multiple Cheats
|
||||||
|
|
||||||
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
|
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory.
|
||||||
|
|
||||||
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ Another option would be to launch the game in Eden and close it - then go into t
|
|||||||
|
|
||||||
### Step 2: Multiple Cheats
|
### Step 2: Multiple Cheats
|
||||||
|
|
||||||
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
|
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory.
|
||||||
|
|
||||||
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
|
||||||
|
|
||||||
@@ -154,3 +155,11 @@ Cheats are enabled by default, but can be disabled so they don't affect gameplay
|
|||||||
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
|
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
|
||||||
4. Click **OK** to close the window.
|
4. Click **OK** to close the window.
|
||||||
5. Launch the game to confirm the cheat is/is not active.
|
5. Launch the game to confirm the cheat is/is not active.
|
||||||
|
|
||||||
|
### Runtime Cheat Controls
|
||||||
|
|
||||||
|
* While a game is running, open **Cheats** from the menu bar to toggle individual entries from the loaded cheat files. The menu stays open while toggling entries and is hidden when the running game has no cheats. Runtime changes apply to the current game session only.
|
||||||
|
|
||||||
|
* Use **Configure > System > Core > Enable cheats by default** to choose whether regular cheats start enabled. The setting can also be overridden for an individual game from its **System** settings.
|
||||||
|
|
||||||
|
* Master Code entries (`{}` blocks) are always enabled and appear as disabled and checked items in the runtime menu.
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
|
|||||||
* with the native side of the Yuzu code.
|
* with the native side of the Yuzu code.
|
||||||
*/
|
*/
|
||||||
object NativeLibrary {
|
object NativeLibrary {
|
||||||
|
@Keep
|
||||||
|
class RuntimeCheat(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val enabled: Boolean,
|
||||||
|
val isMaster: Boolean
|
||||||
|
)
|
||||||
@Keep
|
@Keep
|
||||||
data class UpdateResult(
|
data class UpdateResult(
|
||||||
var tag: String = "",
|
var tag: String = "",
|
||||||
@@ -197,6 +204,9 @@ object NativeLibrary {
|
|||||||
*/
|
*/
|
||||||
external fun isPaused(): Boolean
|
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
|
* Returns the performance stats for the current game
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
FASTMEM("cpuopt_fastmem"),
|
FASTMEM("cpuopt_fastmem"),
|
||||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
|
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
|
||||||
CORE_SYNC_CORE_SPEED("sync_core_speed"),
|
CORE_SYNC_CORE_SPEED("sync_core_speed"),
|
||||||
|
ENABLE_CHEATS_BY_DEFAULT("enable_cheats_by_default"),
|
||||||
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
|
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
|
||||||
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
|
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
|
||||||
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
||||||
|
|||||||
+7
@@ -316,6 +316,13 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.use_sync_core_description
|
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(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.REGION_INDEX,
|
IntSetting.REGION_INDEX,
|
||||||
|
|||||||
+1
@@ -510,6 +510,7 @@ class SettingsFragmentPresenter(
|
|||||||
add(IntSetting.FAST_CPU_TIME.key)
|
add(IntSetting.FAST_CPU_TIME.key)
|
||||||
add(IntSetting.FAST_GPU_TIME.key)
|
add(IntSetting.FAST_GPU_TIME.key)
|
||||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||||
|
add(BooleanSetting.ENABLE_CHEATS_BY_DEFAULT.key)
|
||||||
|
|
||||||
add(IntSetting.MEMORY_LAYOUT.key)
|
add(IntSetting.MEMORY_LAYOUT.key)
|
||||||
add(BooleanSetting.USE_CUSTOM_CPU_TICKS.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 =
|
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
||||||
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
||||||
|
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
|
||||||
|
|
||||||
binding.pausedIcon.setOnClickListener {
|
binding.pausedIcon.setOnClickListener {
|
||||||
if (this::emulationState.isInitialized && emulationState.isPaused) {
|
if (this::emulationState.isInitialized && emulationState.isPaused) {
|
||||||
@@ -817,6 +818,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
R.id.menu_load_amiibo -> handleLoadAmiiboSelection()
|
R.id.menu_load_amiibo -> handleLoadAmiiboSelection()
|
||||||
|
|
||||||
|
R.id.menu_cheats -> {
|
||||||
|
binding.drawerLayout.closeDrawer(binding.inGameMenu)
|
||||||
|
showRuntimeCheatDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_controls -> {
|
R.id.menu_controls -> {
|
||||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
null,
|
null,
|
||||||
@@ -962,6 +969,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
emulationState.updateSurface()
|
emulationState.updateSurface()
|
||||||
|
|
||||||
|
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible =
|
||||||
|
NativeLibrary.getRuntimeCheats().isNotEmpty()
|
||||||
updateShowStatsOverlay()
|
updateShowStatsOverlay()
|
||||||
updateSocOverlay()
|
updateSocOverlay()
|
||||||
|
|
||||||
@@ -987,6 +996,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
||||||
if (it) {
|
if (it) {
|
||||||
|
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
|
||||||
binding.loadingText.setText(R.string.shutting_down)
|
binding.loadingText.setText(R.string.shutting_down)
|
||||||
ViewUtils.showView(binding.loadingIndicator)
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
ViewUtils.hideView(binding.inputContainer)
|
ViewUtils.hideView(binding.inputContainer)
|
||||||
@@ -1119,6 +1129,45 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
NativePostProcessing.persist()
|
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() {
|
private fun addQuickSettings() {
|
||||||
binding.quickSettingsSheet.apply {
|
binding.quickSettingsSheet.apply {
|
||||||
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ extern "C" {
|
|||||||
#include "core/file_sys/submission_package.h"
|
#include "core/file_sys/submission_package.h"
|
||||||
#include "core/file_sys/vfs/vfs.h"
|
#include "core/file_sys/vfs/vfs.h"
|
||||||
#include "core/file_sys/vfs/vfs_real.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/cabinet.h"
|
||||||
#include "core/frontend/applets/controller.h"
|
#include "core/frontend/applets/controller.h"
|
||||||
#include "core/frontend/applets/error.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());
|
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) {
|
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
|
||||||
using namespace VideoCore::Capture;
|
using namespace VideoCore::Capture;
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@
|
|||||||
android:icon="@drawable/ic_nfc"
|
android:icon="@drawable/ic_nfc"
|
||||||
android:title="@string/load_amiibo" />
|
android:title="@string/load_amiibo" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_cheats"
|
||||||
|
android:icon="@drawable/ic_code"
|
||||||
|
android:title="@string/cheats" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_overlay_controls"
|
android:id="@+id/menu_overlay_controls"
|
||||||
|
|||||||
@@ -840,6 +840,12 @@
|
|||||||
<string name="preferences_graphics">Graphics</string>
|
<string name="preferences_graphics">Graphics</string>
|
||||||
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
|
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
|
||||||
<string name="quick_settings">Quick Settings</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">Enable Quick Settings</string>
|
||||||
<string name="enable_quick_settings_description">Allow access to quick settings menu via swipe and menu button</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>
|
<string name="preferences_audio">Audio</string>
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ void SetGlobalFilter(const Filter& filter) {
|
|||||||
logging_instance->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) {
|
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||||
if (logging_instance)
|
if (logging_instance)
|
||||||
logging_instance->color_console_backend.enabled = enabled;
|
logging_instance->color_console_backend.enabled = enabled;
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ void Stop();
|
|||||||
|
|
||||||
/// The global filter will prevent any messages from even being processed if they are filtered.
|
/// The global filter will prevent any messages from even being processed if they are filtered.
|
||||||
void SetGlobalFilter(const Filter& filter);
|
void SetGlobalFilter(const Filter& filter);
|
||||||
|
[[nodiscard]] bool IsMessageEnabled(Class log_class, Level log_level);
|
||||||
void SetColorConsoleBackendEnabled(bool enabled);
|
void SetColorConsoleBackendEnabled(bool enabled);
|
||||||
|
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ struct Values {
|
|||||||
|
|
||||||
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core,
|
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core,
|
||||||
Specialization::Default};
|
Specialization::Default};
|
||||||
|
SwitchableSetting<bool> enable_cheats_by_default{linkage, true, "enable_cheats_by_default", Category::Core};
|
||||||
|
|
||||||
// Cpu
|
// Cpu
|
||||||
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
|
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
|
||||||
|
|||||||
@@ -786,6 +786,17 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
|||||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
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) {
|
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||||
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
|
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ enum class ResultStatus : u16;
|
|||||||
|
|
||||||
namespace Core::Memory {
|
namespace Core::Memory {
|
||||||
struct CheatEntry;
|
struct CheatEntry;
|
||||||
|
struct RuntimeCheatInfo;
|
||||||
class Memory;
|
class Memory;
|
||||||
} // namespace Core::Memory
|
} // namespace Core::Memory
|
||||||
|
|
||||||
@@ -342,6 +343,8 @@ public:
|
|||||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||||
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
|
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
|
||||||
u64 main_region_size);
|
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);
|
void SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set);
|
||||||
|
|
||||||
|
|||||||
@@ -503,10 +503,11 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
|
|||||||
for (const auto& subdir : patch_dirs) {
|
for (const auto& subdir : patch_dirs) {
|
||||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
||||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
||||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
|
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));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/arm/debug.h"
|
#include "core/arm/debug.h"
|
||||||
#include "core/core.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::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||||
std::size_t start_index, char match) {
|
std::size_t start_index, char match) {
|
||||||
auto end_index = start_index;
|
auto end_index = start_index;
|
||||||
while (data[end_index] != match) {
|
while (end_index < data.size() && data[end_index] != match) {
|
||||||
++end_index;
|
++end_index;
|
||||||
if (end_index > data.size()) {
|
}
|
||||||
return {};
|
if (end_index == data.size()) {
|
||||||
}
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
out_name_size = end_index - start_index;
|
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);
|
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 {
|
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||||
if ((in < metadata.main_nso_extents.base ||
|
if ((in < metadata.main_nso_extents.base ||
|
||||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
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] == '{') {
|
if (data[i] == '{') {
|
||||||
current_entry = 0;
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,32 +212,33 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
|
|||||||
'\0';
|
'\0';
|
||||||
|
|
||||||
i += name_size + 1;
|
i += name_size + 1;
|
||||||
} else if (::isxdigit(data[i])) {
|
} else if (std::isxdigit(static_cast<unsigned char>(data[i]))) {
|
||||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
if (!current_entry || data.size() - i < 8) {
|
||||||
out[*current_entry].definition.opcodes.size()) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto hex = std::string(data.substr(i, 8));
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
out[*current_entry].definition.opcodes.push_back(value);
|
||||||
value;
|
|
||||||
|
|
||||||
i += 8;
|
i += 7;
|
||||||
} else {
|
} else {
|
||||||
return {};
|
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;
|
out[0].cheat_id = 0;
|
||||||
|
|
||||||
for (u32 i = 1; i < out.size(); ++i) {
|
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;
|
out[i].cheat_id = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +250,10 @@ CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
|||||||
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
|
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
|
||||||
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
|
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
|
||||||
metadata.main_nso_build_id = build_id_;
|
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() {
|
CheatEngine::~CheatEngine() {
|
||||||
@@ -286,12 +300,56 @@ void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_regi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
|
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) {
|
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||||
if (is_pending_reload.exchange(false)) {
|
if (is_pending_reload.exchange(false)) {
|
||||||
|
std::scoped_lock lock{cheats_mutex};
|
||||||
vm.LoadProgram(cheats);
|
vm.LoadProgram(cheats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/memory/dmnt_cheat_types.h"
|
#include "core/memory/dmnt_cheat_types.h"
|
||||||
@@ -41,6 +42,7 @@ public:
|
|||||||
void ResumeProcess() override;
|
void ResumeProcess() override;
|
||||||
void DebugLog(u8 id, u64 value) override;
|
void DebugLog(u8 id, u64 value) override;
|
||||||
void CommandLog(std::string_view data) override;
|
void CommandLog(std::string_view data) override;
|
||||||
|
[[nodiscard]] bool IsCommandLogEnabled() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool IsAddressInRange(VAddr address) const;
|
bool IsAddressInRange(VAddr address) const;
|
||||||
@@ -81,6 +83,8 @@ public:
|
|||||||
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
|
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
|
||||||
|
|
||||||
void Reload(std::vector<CheatEntry> reload_cheats);
|
void Reload(std::vector<CheatEntry> reload_cheats);
|
||||||
|
[[nodiscard]] std::vector<RuntimeCheatInfo> GetCheats() const;
|
||||||
|
bool SetCheatEnabled(u32 cheat_id, bool enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||||
@@ -89,6 +93,7 @@ private:
|
|||||||
CheatProcessMetadata metadata;
|
CheatProcessMetadata metadata;
|
||||||
|
|
||||||
std::vector<CheatEntry> cheats;
|
std::vector<CheatEntry> cheats;
|
||||||
|
mutable std::mutex cheats_mutex;
|
||||||
std::atomic_bool is_pending_reload{false};
|
std::atomic_bool is_pending_reload{false};
|
||||||
|
|
||||||
std::shared_ptr<Core::Timing::EventType> event;
|
std::shared_ptr<Core::Timing::EventType> event;
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Core::Memory {
|
namespace Core::Memory {
|
||||||
@@ -24,14 +30,21 @@ struct CheatProcessMetadata {
|
|||||||
|
|
||||||
struct CheatDefinition {
|
struct CheatDefinition {
|
||||||
std::array<char, 0x40> readable_name{};
|
std::array<char, 0x40> readable_name{};
|
||||||
u32 num_opcodes{};
|
std::vector<u32> opcodes;
|
||||||
std::array<u32, 0x100> opcodes{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CheatEntry {
|
struct CheatEntry {
|
||||||
bool enabled{};
|
bool enabled{};
|
||||||
|
bool is_master{};
|
||||||
u32 cheat_id{};
|
u32 cheat_id{};
|
||||||
CheatDefinition definition{};
|
CheatDefinition definition{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RuntimeCheatInfo {
|
||||||
|
u32 id{};
|
||||||
|
std::string name;
|
||||||
|
bool enabled{};
|
||||||
|
bool is_master{};
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Core::Memory
|
} // namespace Core::Memory
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -233,7 +236,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
|
|||||||
|
|
||||||
// Helper function for getting instruction dwords.
|
// Helper function for getting instruction dwords.
|
||||||
const auto GetNextDword = [&] {
|
const auto GetNextDword = [&] {
|
||||||
if (instruction_ptr >= num_opcodes) {
|
if (instruction_ptr >= program.size()) {
|
||||||
valid = false;
|
valid = false;
|
||||||
return static_cast<u32>(0);
|
return static_cast<u32>(0);
|
||||||
}
|
}
|
||||||
@@ -709,20 +712,20 @@ void DmntCheatVm::ResetState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
|
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
|
||||||
// Reset opcode count.
|
program.clear();
|
||||||
num_opcodes = 0;
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < entries.size(); i++) {
|
std::size_t program_size{};
|
||||||
if (entries[i].enabled) {
|
for (const auto& entry : entries) {
|
||||||
// Bounds check.
|
if (entry.enabled || entry.is_master) {
|
||||||
if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
|
program_size += entry.definition.opcodes.size();
|
||||||
num_opcodes = 0;
|
}
|
||||||
return false;
|
}
|
||||||
}
|
program.reserve(program_size);
|
||||||
|
|
||||||
for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
|
for (const auto& entry : entries) {
|
||||||
program[num_opcodes++] = entries[i].definition.opcodes[n];
|
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.
|
// Get Keys down.
|
||||||
u64 kDown = callbacks->HidKeysDown();
|
u64 kDown = callbacks->HidKeysDown();
|
||||||
|
|
||||||
callbacks->CommandLog("Started VM execution.");
|
const bool command_log_enabled = callbacks->IsCommandLogEnabled();
|
||||||
callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
|
if (command_log_enabled) {
|
||||||
callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
|
callbacks->CommandLog("Started VM execution.");
|
||||||
callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
|
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.
|
// Clear VM state.
|
||||||
ResetState();
|
ResetState();
|
||||||
|
|
||||||
// Loop until program finishes.
|
// Loop until program finishes.
|
||||||
while (DecodeNextOpcode(cur_opcode)) {
|
while (DecodeNextOpcode(cur_opcode)) {
|
||||||
callbacks->CommandLog(
|
if (command_log_enabled) {
|
||||||
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
|
callbacks->CommandLog(
|
||||||
|
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
|
||||||
|
|
||||||
for (std::size_t i = 0; i < NumRegisters; i++) {
|
for (std::size_t i = 0; i < NumRegisters; i++) {
|
||||||
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
|
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i,
|
||||||
}
|
registers[i]));
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t i = 0; i < NumRegisters; i++) {
|
for (std::size_t i = 0; i < NumRegisters; i++) {
|
||||||
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
|
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i,
|
||||||
|
saved_values[i]));
|
||||||
|
}
|
||||||
|
LogOpcode(cur_opcode);
|
||||||
}
|
}
|
||||||
LogOpcode(cur_opcode);
|
|
||||||
|
|
||||||
// Increment conditional depth, if relevant.
|
// Increment conditional depth, if relevant.
|
||||||
if (cur_opcode.begin_conditional_block) {
|
if (cur_opcode.begin_conditional_block) {
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
@@ -281,9 +281,9 @@ public:
|
|||||||
|
|
||||||
virtual void DebugLog(u8 id, u64 value) = 0;
|
virtual void DebugLog(u8 id, u64 value) = 0;
|
||||||
virtual void CommandLog(std::string_view data) = 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 NumRegisters = 0x10;
|
||||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||||
@@ -294,7 +294,7 @@ public:
|
|||||||
~DmntCheatVm();
|
~DmntCheatVm();
|
||||||
|
|
||||||
std::size_t GetProgramSize() const {
|
std::size_t GetProgramSize() const {
|
||||||
return this->num_opcodes;
|
return program.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoadProgram(const std::vector<CheatEntry>& cheats);
|
bool LoadProgram(const std::vector<CheatEntry>& cheats);
|
||||||
@@ -303,11 +303,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::unique_ptr<Callbacks> callbacks;
|
std::unique_ptr<Callbacks> callbacks;
|
||||||
|
|
||||||
std::size_t num_opcodes = 0;
|
|
||||||
std::size_t instruction_ptr = 0;
|
std::size_t instruction_ptr = 0;
|
||||||
std::size_t condition_depth = 0;
|
std::size_t condition_depth = 0;
|
||||||
bool decode_success = false;
|
bool decode_success = false;
|
||||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
std::vector<u32> program;
|
||||||
std::array<u64, NumRegisters> registers{};
|
std::array<u64, NumRegisters> registers{};
|
||||||
std::array<u64, NumRegisters> saved_values{};
|
std::array<u64, NumRegisters> saved_values{};
|
||||||
std::array<u64, NumStaticRegisters> static_registers{};
|
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 "
|
tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS "
|
||||||
"without affecting game speed (animations, physics, etc.).\n"
|
"without affecting game speed (animations, physics, etc.).\n"
|
||||||
"Can help reduce stuttering at lower framerates."));
|
"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
|
// Cpu
|
||||||
INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
|
INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
|
||||||
|
|||||||
@@ -91,6 +91,14 @@
|
|||||||
<addaction name="action_Configure"/>
|
<addaction name="action_Configure"/>
|
||||||
<addaction name="action_Configure_Current_Game"/>
|
<addaction name="action_Configure_Current_Game"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menu_Cheats">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>&Cheats</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
<widget class="QMenu" name="menu_View">
|
<widget class="QMenu" name="menu_View">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&View</string>
|
<string>&View</string>
|
||||||
@@ -241,6 +249,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<addaction name="menu_File"/>
|
<addaction name="menu_File"/>
|
||||||
<addaction name="menu_Emulation"/>
|
<addaction name="menu_Emulation"/>
|
||||||
|
<addaction name="menu_Cheats"/>
|
||||||
<addaction name="menu_View"/>
|
<addaction name="menu_View"/>
|
||||||
<addaction name="menu_Tools"/>
|
<addaction name="menu_Tools"/>
|
||||||
<addaction name="menu_Multiplayer"/>
|
<addaction name="menu_Multiplayer"/>
|
||||||
|
|||||||
@@ -78,10 +78,12 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QMouseEvent>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
#include <QSignalBlocker>
|
||||||
#include <QStatusBar>
|
#include <QStatusBar>
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
@@ -134,6 +136,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Core //
|
// Core //
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/frontend/applets/general.h"
|
#include "core/frontend/applets/general.h"
|
||||||
#include "core/frontend/applets/mii_edit.h"
|
#include "core/frontend/applets/mii_edit.h"
|
||||||
#include "core/frontend/applets/software_keyboard.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/common_funcs.h"
|
||||||
#include "core/file_sys/romfs.h"
|
#include "core/file_sys/romfs.h"
|
||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
|
#include "core/memory/dmnt_cheat_types.h"
|
||||||
|
|
||||||
#include "core/tools/renderdoc.h"
|
#include "core/tools/renderdoc.h"
|
||||||
|
|
||||||
@@ -1434,6 +1438,9 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ConnectWidgetEvents() {
|
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::BootGame, this, &MainWindow::BootGameFromList);
|
||||||
connect(game_list, &GameList::GameChosen, this, &MainWindow::OnGameListLoadFile);
|
connect(game_list, &GameList::GameChosen, this, &MainWindow::OnGameListLoadFile);
|
||||||
connect(game_list, &GameList::OpenDirectory, this, &MainWindow::OnGameListOpenDirectory);
|
connect(game_list, &GameList::OpenDirectory, this, &MainWindow::OnGameListOpenDirectory);
|
||||||
@@ -1468,6 +1475,8 @@ void MainWindow::ConnectWidgetEvents() {
|
|||||||
// Software Keyboard Applet
|
// Software Keyboard Applet
|
||||||
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit);
|
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit);
|
||||||
connect(this, &MainWindow::EmulationStopping, 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);
|
connect(&status_bar_update_timer, &QTimer::timeout, this, &MainWindow::UpdateStatusBar);
|
||||||
|
|
||||||
@@ -1475,6 +1484,68 @@ void MainWindow::ConnectWidgetEvents() {
|
|||||||
&MultiplayerState::UpdateThemedIcons);
|
&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() {
|
void MainWindow::ConnectMenuEvents() {
|
||||||
const auto connect_menu = [&]<typename Fn>(QAction* action, const Fn& event_fn) {
|
const auto connect_menu = [&]<typename Fn>(QAction* action, const Fn& event_fn) {
|
||||||
connect(action, &QAction::triggered, this, event_fn);
|
connect(action, &QAction::triggered, this, event_fn);
|
||||||
|
|||||||
@@ -176,6 +176,9 @@ public:
|
|||||||
|
|
||||||
std::filesystem::path GetShortcutPath(QtCommon::Game::ShortcutTarget target);
|
std::filesystem::path GetShortcutPath(QtCommon::Game::ShortcutTarget target);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,6 +267,8 @@ private:
|
|||||||
void ConnectWidgetEvents();
|
void ConnectWidgetEvents();
|
||||||
void ConnectMenuEvents();
|
void ConnectMenuEvents();
|
||||||
void UpdateMenuState();
|
void UpdateMenuState();
|
||||||
|
void RefreshRuntimeCheats();
|
||||||
|
void ClearRuntimeCheats();
|
||||||
|
|
||||||
void SetupPrepareForSleep();
|
void SetupPrepareForSleep();
|
||||||
|
|
||||||
@@ -552,6 +557,7 @@ private:
|
|||||||
QString startup_icon_theme;
|
QString startup_icon_theme;
|
||||||
|
|
||||||
QActionGroup* game_size_actions;
|
QActionGroup* game_size_actions;
|
||||||
|
std::vector<QAction*> runtime_cheat_actions;
|
||||||
|
|
||||||
// Debugger panes
|
// Debugger panes
|
||||||
ControllerDialog* controller_dialog = nullptr;
|
ControllerDialog* controller_dialog = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user