#include #include #include #include #include #include #include #include #include #include "hardware.h" #include "file_io.h" #include "str_util.h" #include "user_io.h" #include "fpga_io.h" #include "miniz.h" #include "osd.h" #include "cheats.h" #include "support.h" struct cheat_rec_t { bool enabled; char name[256]; int cheatSize; char *cheatData; cheat_rec_t() { this->enabled = false; this->cheatData = NULL; this->cheatSize = 0; memset(name, 0, sizeof(name)); } cheat_rec_t(const cheat_rec_t& other) { memcpy(this->name, other.name, sizeof(other.name)); this->enabled = other.enabled; this->cheatSize = other.cheatSize; if (other.cheatData) { this->cheatData = new char [this->cheatSize]; memcpy(this->cheatData, other.cheatData, this->cheatSize); } else { this->cheatData = nullptr; } } ~cheat_rec_t() { if (this->cheatData) { delete[] this->cheatData; } } }; typedef std::vector CheatVector; static CheatVector cheats; #define CHEAT_SIZE (128*16) // 128 codes max static int iSelectedEntry = 0; static int iFirstEntry = 0; static int loaded = 0; static int cheat_unit_size = 16; static int cheat_max_active = 128; struct CheatComp { bool operator()(const cheat_rec_t& ce1, const cheat_rec_t& ce2) { int len1 = strlen(ce1.name); int len2 = strlen(ce2.name); int len = (len1 < len2) ? len1 : len2; int ret = strncasecmp(ce1.name, ce2.name, len); if (!ret) { return len1 < len2; } return ret < 0; } }; static char cheat_zip[1024] = {}; static int find_by_crc(uint32_t romcrc) { if (!romcrc) return 0; sprintf(cheat_zip, "%s/cheats/%s", getRootDir(), CoreName2); DIR *d = opendir(cheat_zip); if (!d) { printf("Couldn't open dir: %s\n", cheat_zip); return 0; } struct dirent *de; while ((de = readdir(d))) { if (de->d_type == DT_REG) { int len = strlen(de->d_name); if (len >= 14 && de->d_name[len - 14] == '[' && !strcasecmp(de->d_name + len - 5, "].zip")) { uint32_t crc = 0; if (sscanf(de->d_name + len - 14, "[%X].zip", &crc) == 1) { if (crc == romcrc) { strcat(cheat_zip, "/"); strcat(cheat_zip, de->d_name); closedir(d); return 1; } } } } } closedir(d); return 0; } static int find_in_same_dir(const char *name) { sprintf(cheat_zip, "%s/%s", getRootDir(), name); char *p = strrchr(cheat_zip, '/'); //impossible to fail *p = 0; DIR *d = opendir(cheat_zip); if (!d) { printf("Couldn't open dir: %s\n", cheat_zip); return 0; } struct dirent *de; while ((de = readdir(d))) { if (de->d_type == DT_REG) { int len = strlen(de->d_name); if (len >= 4 && !strcasecmp(de->d_name + len - 4, ".zip")) { strcat(cheat_zip, "/"); strcat(cheat_zip, de->d_name); closedir(d); return 1; } } } closedir(d); return 0; } bool cheat_init_psx(mz_zip_archive* _z, const char *rom_path) { // lookup based on file name const char *rom_name = strrchr(rom_path, '/'); if (rom_name) { sprintf(cheat_zip, "%s/cheats/%s%s", getRootDir(), CoreName2, rom_name); char *p = strrchr(cheat_zip, '.'); if (p) *p = 0; strcat(cheat_zip, ".zip"); printf("Trying cheat file: %s\n", cheat_zip); memset(_z, 0, sizeof(mz_zip_archive)); if (mz_zip_reader_init_file(_z, cheat_zip, 0)) return true; } // lookup based on game ID const char *game_id = psx_get_game_id(); if (game_id && game_id[0]) { sprintf(cheat_zip, "%s/cheats/%s/%s.zip", getRootDir(), CoreName2, psx_get_game_id()); printf("Trying cheat file: %s\n", cheat_zip); memset(_z, 0, sizeof(mz_zip_archive)); if (mz_zip_reader_init_file(_z, cheat_zip, 0)) return true; } return false; } void cheats_init_arcade(int unit_size, int max_active) { cheats.clear(); loaded = 0; cheat_unit_size = unit_size > 0 ? unit_size : 16; cheat_max_active = max_active > 0 ? max_active : 128; if ((cheat_max_active * cheat_unit_size) > CHEAT_SIZE) { cheat_max_active = CHEAT_SIZE / cheat_unit_size; } cheat_zip[0] = 0; } void cheats_add_arcade(const char *name, const char *cheatData, int cheatSize) { cheat_rec_t cheat = {}; if ((cheatSize % cheat_unit_size) != 0) { printf("Arcade cheat \'%s\' has incorrect length %d -> skipping.\n", name, cheatSize); return; } strcpyz(cheat.name, name); cheat.cheatSize = cheatSize; cheat.cheatData = new char [cheatSize]; memcpy(cheat.cheatData, cheatData, cheatSize); cheats.push_back(cheat); } void cheats_finalize_arcade() { printf("MRA cheats: %d\n", cheats_available()); cheats_scan(SCANF_INIT); } void cheats_init(const char *rom_path, uint32_t romcrc) { cheats.clear(); loaded = 0; cheat_unit_size = 16; cheat_max_active = 128; cheat_zip[0] = 0; // reset cheats if (!is_n64()) { user_io_set_index(255); user_io_set_download(1); user_io_file_tx_data((const uint8_t*)&loaded, 2); user_io_set_download(0); } if (!strcasestr(rom_path, ".zip")) { sprintf(cheat_zip, "%s/%s", getRootDir(), rom_path); char *p = strrchr(cheat_zip, '.'); if (p) *p = 0; strcat(cheat_zip, ".zip"); } mz_zip_archive _z = {}; if (is_psx() && !mz_zip_reader_init_file(&_z, cheat_zip, 0)) { if (!cheat_init_psx(&_z, rom_path)) { printf("no cheat file found\n"); return; } } else if (!mz_zip_reader_init_file(&_z, cheat_zip, 0)) { memset(&_z, 0, sizeof(_z)); if (!(pcecd_using_cd() || is_megacd()) || !find_in_same_dir(rom_path) || !mz_zip_reader_init_file(&_z, cheat_zip, 0)) { memset(&_z, 0, sizeof(_z)); const char *rom_name = strrchr(rom_path, '/'); if (rom_name) { sprintf(cheat_zip, "%s/cheats/%s%s%s", getRootDir(), CoreName2, pcecd_using_cd() ? "CD" : "", rom_name); char *p = strrchr(cheat_zip, '.'); if (p) *p = 0; if (pcecd_using_cd() || is_megacd()) strcat(cheat_zip, " []"); strcat(cheat_zip, ".zip"); if (!mz_zip_reader_init_file(&_z, cheat_zip, 0)) { memset(&_z, 0, sizeof(_z)); if (!find_by_crc(romcrc) || !mz_zip_reader_init_file(&_z, cheat_zip, 0)) { printf("no cheat file found\n"); return; } } } else { if (!find_by_crc(romcrc) || !mz_zip_reader_init_file(&_z, cheat_zip, 0)) { printf("no cheat file found\n"); return; } } } } printf("Using cheat file: %s\n", cheat_zip); mz_zip_archive *z = new mz_zip_archive(_z); for (size_t i = 0; i < mz_zip_reader_get_num_files(z); i++) { cheat_rec_t ch = {}; mz_zip_reader_get_filename(z, i, ch.name, sizeof(ch.name)); if (mz_zip_reader_is_file_a_directory(z, i)) { continue; } cheats.push_back(ch); } mz_zip_reader_end(z); delete z; std::sort(cheats.begin(), cheats.end(), CheatComp()); printf("cheats: %d\n", cheats_available()); cheats_scan(SCANF_INIT); } int cheats_available() { return cheats.size(); } void cheats_scan(int mode) { if (mode == SCANF_INIT) { iFirstEntry = 0; iSelectedEntry = 0; } else { if (!cheats_available()) return; if (mode == SCANF_END || (mode == SCANF_PREV && iSelectedEntry <= 0)) { iSelectedEntry = cheats_available() - 1; iFirstEntry = iSelectedEntry - OsdGetSize() + 1; if (iFirstEntry < 0) iFirstEntry = 0; } else if (mode == SCANF_NEXT) { if (iSelectedEntry + 1 < cheats_available()) // scroll within visible items { iSelectedEntry++; if (iSelectedEntry > iFirstEntry + OsdGetSize() - 1) iFirstEntry = iSelectedEntry - OsdGetSize() + 1; } else { // jump to first visible item iFirstEntry = 0; iSelectedEntry = 0; } } else if (mode == SCANF_PREV) { if (iSelectedEntry > 0) // scroll within visible items { iSelectedEntry--; if (iSelectedEntry < iFirstEntry) iFirstEntry = iSelectedEntry; } } else if (mode == SCANF_NEXT_PAGE) { if (iSelectedEntry < iFirstEntry + OsdGetSize() - 1) { iSelectedEntry = iFirstEntry + OsdGetSize() - 1; if (iSelectedEntry >= cheats_available()) iSelectedEntry = cheats_available() - 1; } else { iSelectedEntry += OsdGetSize(); iFirstEntry += OsdGetSize(); if (iSelectedEntry >= cheats_available()) { iSelectedEntry = cheats_available() - 1; iFirstEntry = iSelectedEntry - OsdGetSize() + 1; if (iFirstEntry < 0) iFirstEntry = 0; } else if (iFirstEntry + OsdGetSize() > cheats_available()) { iFirstEntry = cheats_available() - OsdGetSize(); } } } else if (mode == SCANF_PREV_PAGE) { if (iSelectedEntry != iFirstEntry) { iSelectedEntry = iFirstEntry; } else { iFirstEntry -= OsdGetSize(); if (iFirstEntry < 0) iFirstEntry = 0; iSelectedEntry = iFirstEntry; } } } } void cheats_scroll_name() { // this function is called periodically when file selection window is displayed // it checks if predefined period of time has elapsed and scrolls the name if necessary int len; int max_len; static char name[256 + 4]; name[0] = 32; name[1] = cheats[iSelectedEntry].enabled ? 0x1a : 0x1b; name[2] = 32; strcpy(name + 3, cheats[iSelectedEntry].name); len = strlen(name); // get name length if (len > 3 && !strncasecmp(name + len - 3, ".gg", 3)) len -= 3; max_len = 30; ScrollText(iSelectedEntry - iFirstEntry, name, 3, len, max_len, 1); } void cheats_print() { int k; int len; static char s[256 + 4]; ScrollReset(); for (int i = 0; i < OsdGetSize(); i++) { char leftchar = 0; if (i < cheats_available()) { k = iFirstEntry + i; s[0] = 32; s[1] = cheats[k].enabled ? 0x1a : 0x1b; s[2] = 32; strcpy(s + 3, cheats[k].name); len = strlen(s); // get name length if (len > 3 && !strncasecmp(s + len - 3, ".gg", 3)) len -= 3; s[len] = 0; if (len > 28) { len = 27; // trim display length if longer than 30 characters s[28] = 22; } s[29] = 0; if (!i && k) leftchar = 17; if ((i == OsdGetSize() - 1) && (k < cheats_available() - 1)) leftchar = 16; } else { memset(s, ' ', 32); } OsdWriteOffset(i, s, i == (iSelectedEntry - iFirstEntry), 0, 0, leftchar); } } static void cheats_send() { static uint8_t buff[CHEAT_SIZE]; int pos = 0; for (int i = 0; i < cheats_available(); i++) { if (cheats[i].enabled) { if (cheats[i].cheatData) { memcpy(&buff[pos], cheats[i].cheatData, cheats[i].cheatSize); pos += cheats[i].cheatSize; } else { printf("Consistency error, memory for cheat not allocated, but cheat was enabled -> disable.\n"); cheats[i].cheatSize = 0; cheats[i].enabled = false; } } } loaded = pos / cheat_unit_size; printf("Cheat codes: %d\n", loaded); if (is_n64()) { n64_cheats_send(buff, loaded); } else { user_io_set_index(255); user_io_set_download(1); user_io_file_tx_data(buff, pos ? pos : 2); user_io_set_download(0); } } void cheats_toggle() { bool changedCheats = false; if (cheats[iSelectedEntry].enabled == true) { cheats[iSelectedEntry].enabled = false; changedCheats = true; } else { /* enabled cheat, load data */ static char filename[1024]; fileTYPE f = {}; /* lazy load cheat data */ if (cheats[iSelectedEntry].cheatData == NULL) { snprintf(filename, sizeof(filename), "%s/%s", cheat_zip, cheats[iSelectedEntry].name); if (FileOpen(&f, filename)) { int len = f.size; if (!len || (len % cheat_unit_size)) { printf("Cheat file %s has incorrect length %d -> skipping.\n", filename, len); } else if (((len / cheat_unit_size) + cheats_loaded()) <= cheat_max_active) { cheats[iSelectedEntry].cheatData = new char[len]; if (cheats[iSelectedEntry].cheatData) { if (FileReadAdv(&f, cheats[iSelectedEntry].cheatData, len) == len) { cheats[iSelectedEntry].cheatSize = len; cheats[iSelectedEntry].enabled = true; changedCheats = true; } else { printf("Cannot read cheat file %s.\n", filename); delete[] cheats[iSelectedEntry].cheatData; cheats[iSelectedEntry].cheatData = NULL; cheats[iSelectedEntry].cheatSize = 0; } } else { printf("Could not allocate required memory (%d) for cheat file %s.\n", len, filename); } } else { printf("No more room in current selection for cheat file %s.\n", filename); } FileClose(&f); } else { printf("Cannot open cheat file %s.\n", filename); } } if (cheats[iSelectedEntry].cheatData) { cheats[iSelectedEntry].enabled = true; changedCheats = true; } } if (changedCheats) { cheats_send(); } } int cheats_loaded() { return loaded; }