dmnt-cheat: Add support for saving/restoring cheat toggle state

This commit is contained in:
Michael Scire 2019-03-25 10:35:08 -07:00
parent 20ba6432b9
commit f4950ff26e
3 changed files with 333 additions and 157 deletions

View file

@ -12,3 +12,7 @@ power_menu_reboot_function = str!payload
; Controls whether dmnt cheats should be toggled on or off by
; default. 1 = toggled on by default, 0 = toggled off by default.
dmnt_cheats_enabled_by_default = u8!0x1
; Controls whether dmnt should always save cheat toggle state
; for restoration on new game launch. 1 = always save toggles,
; 0 = only save toggles if toggle file exists.
dmnt_always_save_cheat_toggles = u8!0x0

View file

@ -33,6 +33,8 @@ static Handle g_cheat_process_debug_hnd = 0;
/* Should we enable cheats by default? */
static bool g_enable_cheats_by_default = true;
static bool g_always_save_cheat_toggles = false;
static bool g_should_save_cheat_toggles = false;
/* For debug event thread management. */
static HosMutex g_debug_event_thread_lock, g_attach_lock;
@ -83,6 +85,14 @@ void DmntCheatManager::CloseActiveCheatProcess() {
/* Close process resources. */
svcCloseHandle(g_cheat_process_debug_hnd);
g_cheat_process_debug_hnd = 0;
/* Save cheat toggles. */
if (g_always_save_cheat_toggles || g_should_save_cheat_toggles) {
SaveCheatToggles(g_cheat_process_metadata.title_id);
g_should_save_cheat_toggles = false;
}
/* Clear metadata. */
g_cheat_process_metadata = (CheatProcessMetadata){0};
/* Clear cheat list. */
@ -278,6 +288,17 @@ CheatEntry *DmntCheatManager::GetCheatEntryById(size_t i) {
return nullptr;
}
CheatEntry *DmntCheatManager::GetCheatEntryByReadableName(const char *readable_name) {
/* Check all non-master cheats for match. */
for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) {
if (strcmp(g_cheat_entries[i].definition.readable_name, readable_name) == 0) {
return &g_cheat_entries[i];
}
}
return nullptr;
}
bool DmntCheatManager::ParseCheats(const char *s, size_t len) {
size_t i = 0;
CheatEntry *cur_entry = NULL;
@ -427,6 +448,145 @@ bool DmntCheatManager::LoadCheats(u64 title_id, const u8 *build_id) {
return ParseCheats(cht_txt, strlen(cht_txt));
}
bool DmntCheatManager::ParseCheatToggles(const char *s, size_t len) {
size_t i = 0;
char cur_cheat_name[sizeof(CheatEntry::definition.readable_name)];
char toggle[8];
while (i < len) {
if (isspace(s[i])) {
/* Just ignore space. */
i++;
} else if (s[i] == '[') {
/* Extract name bounds. */
size_t j = i + 1;
while (s[j] != ']') {
j++;
if (j >= len || (j - i - 1) >= sizeof(cur_cheat_name)) {
return false;
}
}
/* s[i+1:j] is cheat name. */
const size_t cheat_name_len = (j - i - 1);
memcpy(cur_cheat_name, &s[i+1], cheat_name_len);
cur_cheat_name[cheat_name_len] = 0;
/* Skip onwards. */
i = j + 1;
/* Skip whitespace. */
while (isspace(s[i])) {
i++;
}
/* Parse whether to toggle. */
j = i + 1;
while (!isspace(s[j])) {
j++;
if (j >= len || (j - i) >= sizeof(toggle)) {
return false;
}
}
/* s[i:j] is toggle. */
const size_t toggle_len = (j - i);
memcpy(toggle, &s[i], toggle_len);
toggle[toggle_len] = 0;
/* Allow specifying toggle for not present cheat. */
CheatEntry *entry = GetCheatEntryByReadableName(cur_cheat_name);
if (entry != nullptr) {
if (strcasecmp(toggle, "1") == 0 || strcasecmp(toggle, "true") == 0 || strcasecmp(toggle, "on") == 0) {
entry->enabled = true;
} else if (strcasecmp(toggle, "0") == 0 || strcasecmp(toggle, "false") == 0 || strcasecmp(toggle, "off") == 0) {
entry->enabled = false;
}
}
/* Skip onwards. */
i = j + 1;
} else {
/* Unexpected character encountered. */
return false;
}
}
return true;
}
bool DmntCheatManager::LoadCheatToggles(u64 title_id) {
FILE *f_tg = NULL;
/* Open the file for title id. */
{
char path[FS_MAX_PATH+1] = {0};
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", title_id);
f_tg = fopen(path, "rb");
}
/* Unless we successfully parse, don't save toggles on close. */
g_should_save_cheat_toggles = false;
/* Check for NULL, which is allowed. */
if (f_tg == NULL) {
return true;
}
ON_SCOPE_EXIT { fclose(f_tg); };
/* Get file size. */
fseek(f_tg, 0L, SEEK_END);
const size_t tg_sz = ftell(f_tg);
fseek(f_tg, 0L, SEEK_SET);
/* Allocate toggle txt buffer. */
char *tg_txt = (char *)malloc(tg_sz + 1);
if (tg_txt == NULL) {
return false;
}
ON_SCOPE_EXIT { free(tg_txt); };
/* Read toggles into buffer. */
if (fread(tg_txt, 1, tg_sz, f_tg) != tg_sz) {
return false;
}
tg_txt[tg_sz] = 0;
/* Parse toggle buffer. */
g_should_save_cheat_toggles = ParseCheatToggles(tg_txt, strlen(tg_txt));
return g_should_save_cheat_toggles;
}
void DmntCheatManager::SaveCheatToggles(u64 title_id) {
FILE *f_tg = NULL;
/* Open the file for title id. */
{
char path[FS_MAX_PATH+1] = {0};
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", title_id);
f_tg = fopen(path, "wb");
}
if (f_tg == NULL) {
return;
}
ON_SCOPE_EXIT { fclose(f_tg); };
/* Save all non-master cheats. */
for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) {
if (g_cheat_entries[i].definition.num_opcodes != 0) {
fprintf(f_tg, "[%s]\n", g_cheat_entries[i].definition.readable_name);
if (g_cheat_entries[i].enabled) {
fprintf(f_tg, "true\n");
} else {
fprintf(f_tg, "false\n");
}
}
}
}
Result DmntCheatManager::GetCheatCount(u64 *out_count) {
std::scoped_lock<HosMutex> lk(g_cheat_lock);
@ -752,6 +912,9 @@ Result DmntCheatManager::ForceOpenCheatProcess() {
/* This is allowed to fail. We may not have any cheats. */
LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id);
/* Load saved toggles, if present. */
LoadCheatToggles(g_cheat_process_metadata.title_id);
/* Open a debug handle. */
if (R_FAILED((rc = svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)))) {
return rc;
@ -837,7 +1000,7 @@ void DmntCheatManager::OnNewApplicationLaunch() {
}
/* Read cheats off the SD. */
if (!LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id)) {
if (!LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id) || !LoadCheatToggles(g_cheat_process_metadata.title_id)) {
/* If we don't have cheats, or cheats are malformed, don't attach. */
StartDebugProcess(g_cheat_process_metadata.process_id);
g_cheat_process_metadata.process_id = 0;
@ -955,6 +1118,10 @@ void DmntCheatManager::InitializeCheatManager() {
if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", &en, sizeof(en)))) {
g_enable_cheats_by_default = (en != 0);
}
if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_always_save_cheat_toggles", &en, sizeof(en)))) {
g_always_save_cheat_toggles = (en != 0);
}
}
/* Initialize debug events manager. */

View file

@ -42,9 +42,14 @@ class DmntCheatManager {
static void ResetAllCheatEntries();
static CheatEntry *GetFreeCheatEntry();
static CheatEntry *GetCheatEntryById(size_t i);
static CheatEntry *GetCheatEntryByReadableName(const char *readable_name);
static bool ParseCheats(const char *cht_txt, size_t len);
static bool LoadCheats(u64 title_id, const u8 *build_id);
static bool ParseCheatToggles(const char *s, size_t len);
static bool LoadCheatToggles(u64 title_id);
static void SaveCheatToggles(u64 title_id);
static void ResetFrozenAddresses();
public:
static bool GetHasActiveCheatProcess();