Commit: aa37a4b1de7c6f1bf74ecd39933ddc5188d1296e Parent: d57dc5451bd20669c94eb855067aa3758d50321d Author: Vi Grey Date: 2023-08-07 04:30 UTC Summary: Add -g/--game_genie flag to inlretro command host/scripts/inlretro2.lua | 14 ++++++++++++++ host/scripts/nes/dragonwarrior.lua | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- host/source/inlprog.c | 17 ++++++++++------- 3 files changed, 79 insertions(+), 34 deletions(-) diff --git a/host/scripts/inlretro2.lua b/host/scripts/inlretro2.lua index 6cc9eae..8037013 100644 --- a/host/scripts/inlretro2.lua +++ b/host/scripts/inlretro2.lua @@ -99,6 +99,17 @@ function nes_exec(process_opts, console_opts) else -- Attempt requested operations with hardware! + if console_opts["game_genie"] == 1 then + dict.nes("NES_CPU_WR", 0xFFF0, 0x00) + dict.nes("NES_CPU_WR", 0xFFF1, 0x00) + dict.nes("NES_CPU_WR", 0xFFF0, 0x00) + dict.nes("NES_CPU_WR", 0x8000, 0x00) + dict.nes("NES_CPU_WR", 0xFFF0, 0x00) + dict.nes("NES_CPU_WR", 0xFFF1, 0x00) + dict.nes("NES_CPU_WR", 0xFFF0, 0x00) + dict.nes("NES_CPU_WR", 0x8000, 0x80) + end + -- TODO: Do plumbing for interacting with RAM. m.process(process_opts, console_opts) end @@ -141,6 +152,8 @@ function main() -- end -- Always test! + + local do_test = true -- If a dump filename was provided, dump data from cartridge to a file. @@ -205,6 +218,7 @@ function main() rom_size_kbyte = rom_size_kbyte, console_process_script = console_process_script, mapper = mapper_name, + game_genie = game_genie, } console_exec(process_opts, console_opts) end diff --git a/host/scripts/nes/dragonwarrior.lua b/host/scripts/nes/dragonwarrior.lua index e93b7e6..f7ae797 100644 --- a/host/scripts/nes/dragonwarrior.lua +++ b/host/scripts/nes/dragonwarrior.lua @@ -4,36 +4,22 @@ local dragonwarrior = {} -- import required modules local dict = require "scripts.app.dict" --- file constants -local mapname = "DRAGONWARRIOR" - - -local function unset_game_mode() - dict.nes("NES_CPU_WR", 0xFFF0, 0x00) - dict.nes("NES_CPU_WR", 0xFFF1, 0x00) - dict.nes("NES_CPU_WR", 0xFFF0, 0x00) - dict.nes("NES_CPU_WR", 0x8000, 0x00) -end - -local function set_game_mode() - dict.nes("NES_CPU_WR", 0xFFF0, 0x00) - dict.nes("NES_CPU_WR", 0xFFF1, 0x00) - dict.nes("NES_CPU_WR", 0xFFF0, 0x00) - dict.nes("NES_CPU_WR", 0x8000, 0x01) -end - +-- Disable Dragon Warrior cartridge SRAM for safety (Maybe not needed?) local function disable_sram() dict.nes("NES_CPU_WR", 0xE000, 0x80) end +-- Enable Dragon Warrior cartridge SRAM local function enable_sram() dict.nes("NES_CPU_WR", 0xE000, 0x00) end +-- Set the mirroring of local function set_mirror_vert() dict.nes("NES_CPU_WR", 0x8000, 0x02) end +-- Get 8KB SRAM Data from cartridge and returns that data local function read_sram(file) local content = "" print("START") @@ -44,14 +30,54 @@ local function read_sram(file) return content end -local function sanity_check(content) +-- Checks SRAM bytes 0x35-0x38 to make sure they're valid "slot usage" bytes +local function check_slot_usage(slot_usage) + slot_bits = 0 + for i = 0, 2, 1 do + slot_byte = string.byte(string.sub(slot_usage, i + 1, i + 1)) + if slot_byte == 0xC8 then + slot_bits = slot_bits + (1 << i) + elseif slot_byte ~= 0 then + return false + end + end + if string.byte(string.sub(slot_usage, 4, 4)) ~= slot_bits then + return false + end + return true +end + +-- Checks save slot data to make sure all bytes are valid bytes +local function check_valid_slot_data(slot_data) + -- Name characters check + for i = 0x0E, 0x15, 1 do + name_char = string.byte(string.sub(slot_data, i + 1, i + 1)) + if name_char > 0x4F and name_char ~= 0x60 then + return false + end + end + -- Message speed check + if string.byte(string.sub(slot_data, 0x16 + 1, 0x16 + 1)) > 3 then + return false + end + -- 0xC8 check + for i = 0x1A, 0x1D, 1 do + if string.byte(string.sub(slot_data, i + 1, i + 1)) ~= 0xC8 then + return false + end + end + return true +end + +-- Checksum check of slot data +local function sanity_check(slot_data) local checksum_low = 0x1D local checksum_high = 0x1D local carry = 0 local al = 0 local bl = 0 for i = 0x1D, 0, -1 do - bl = string.byte(string.sub(content, i + 1, i + 1)) + bl = string.byte(string.sub(slot_data, i + 1, i + 1)) for x = 0, 7, 1 do al = bl ~ checksum_high carry = (checksum_low & 0x80) >> 7 @@ -66,8 +92,8 @@ local function sanity_check(content) end end end - if checksum_low == string.byte(string.sub(content, 31, 31)) then - if checksum_high == string.byte(string.sub(content, 32, 32)) then + if checksum_low == string.byte(string.sub(slot_data, 31, 31)) then + if checksum_high == string.byte(string.sub(slot_data, 32, 32)) then return true end end @@ -87,7 +113,8 @@ local function check_save_slots(file, content) local c_start = 0x69 + (i * 0x140) + (x * 0x20) local slot_content = string.sub(content, c_start, c_start + 0x1F) local sc = sanity_check(slot_content) - if sc then + local good_slot = check_valid_slot_data(slot_content) + if sc == true and good_slot == true then for y = 0, 9, 1 do if i == 0 then slot_1 = slot_1 .. slot_content @@ -126,15 +153,16 @@ local function process(process_opts, console_opts) if dumpram then print("\nDumping WRAM...") file = assert(io.open(ramdumpfile, "wb")) - unset_game_mode() - set_game_mode() - set_mirror_vert() enable_sram() local content = read_sram(file) disable_sram() local slot_1, slot_1_valid, slot_2, slot_2_valid, slot_3, slot_3_valid = check_save_slots(file, content) file:write(string.rep("\x00", 0x35)) - file:write(string.sub(content, 0x36, 0x39)) + if check_slot_usage(string.sub(content, 0x35 + 1, 0x38 + 1)) == false then + print("[!] Corrupted slot usage data. Saving slot usage data anyways.") + print("[!] Dragon Warrior cartridge or Game Genie may need to be adjusted.") + end + file:write(string.sub(content, 0x35 + 1, 0x38 + 1)) file:write(string.rep("\x00", 0x02)) file:write("KEN MASUTA") file:write(string.rep("\x00", 0x23)) diff --git a/host/source/inlprog.c b/host/source/inlprog.c index c0c183e..9928955 100644 --- a/host/source/inlprog.c +++ b/host/source/inlprog.c @@ -25,6 +25,7 @@ const char *HELP = "Usage: inlretro [options]\n\n"\ " --console=console, -c console\t\t\tConsole port, {GBA,GENESIS,N64,NES}\n"\ " --dump_filename=filename, -d filename\t\tIf provided, dump cartridge ROMs to this filename\n"\ " --dump_ram_filename=filename, -a filename\tIf provided write ram to this filename\n"\ + " --game_genie, -g\t\t\t\tGame Genie between cartridge and cartridge slot\n"\ " --help, -h\t\t\t\t\tDisplays this message.\n"\ " --lua_filename=filename, -s filename\t\tIf provided, use this script for main application logic\n"\ " --mapper=mapper, -m mapper\t\t\tNES, SNES, GB consoles only, mapper ASIC on cartridge\n"\ @@ -47,6 +48,7 @@ typedef struct { char *console_name; char *mapper_name; int display_help; + int game_genie; // NES Functionality int chr_rom_size_kb; @@ -82,6 +84,7 @@ INLOptions* parseOptions(int argc, char *argv[]) { // Declare command line flags/options. struct option longopts[] = { + {"game_genie", optional_argument, NULL, 'g'}, {"help", optional_argument, NULL, 'h'}, {"console", required_argument, NULL, 'c'}, {"mapper", optional_argument, NULL, 'm'}, @@ -100,7 +103,7 @@ INLOptions* parseOptions(int argc, char *argv[]) { }; // FLAG_FORMAT must be kept in sync with any short options used in longopts. - const char *FLAG_FORMAT = "a:b:hc:d:k:m:p:s:v:w:x:y:z:"; + const char *FLAG_FORMAT = "a:b:hc:d:gk:m:p:s:v:w:x:y:z:"; int index = 0; int rv = 0; int kbyte = 0; @@ -121,14 +124,13 @@ INLOptions* parseOptions(int argc, char *argv[]) { //string of possible args : denotes 1 required additional arg //:: denotes optional additional arg follows while((rv = getopt_long(argc, argv, FLAG_FORMAT, longopts, NULL)) != -1) { - switch(rv) { - case 'a': opts->ramdump_filename = optarg; break; case 'b': opts->ramwrite_filename = optarg; break; case 'h': opts->display_help = 1; break; case 'c': opts->console_name = optarg; break; case 'd': opts->dump_filename = optarg; break; + case 'g': opts->game_genie = 1; break; case 'k': opts->rom_size_kbyte = atoi(optarg); break; case 'm': opts->mapper_name = optarg; break; case 'p': opts->program_filename = optarg; break; @@ -154,7 +156,6 @@ INLOptions* parseOptions(int argc, char *argv[]) { ){ log_err("Option -%c requires an argument.", optopt); return NULL; - } else if ( isprint(optopt)) { log_err("Unknown option -%c .", optopt); return NULL; @@ -162,18 +163,17 @@ INLOptions* parseOptions(int argc, char *argv[]) { log_err("Unknown option character '\\x%x'", optopt); return NULL; } - log_err("Improper arguements passed"); + log_err("Improper arguments passed"); return NULL; break; default: printf("getopt failed to catch all arg cases"); return 0; } - } for( index = optind; index < argc; index++) { - log_err("Non-option arguement: %s \n", argv[index]); + log_err("Non-option argument: %s \n", argv[index]); } if (opts->display_help) { @@ -230,6 +230,9 @@ lua_State *lua_init(INLOptions *opts) { lua_pushstring(L, opts->mapper_name); lua_setglobal(L, "mapper_name"); + lua_pushinteger(L, opts->game_genie); + lua_setglobal(L, "game_genie"); + lua_pushstring(L, opts->dump_filename); lua_setglobal(L, "dump_filename");