// ===================================================================================== // Copyright (c) 2021-2025 Dave Bernazzani (wavemotion-dave) // // Copying and distribution of this emulator, its source code and associated // readme files, with or without modification, are permitted in any medium without // royalty provided the this copyright notice is used and wavemotion-dave (NINTV-DS) // and Kyle Davis (BLISS) are thanked profusely. // // The NINTV-DS emulator is offered as-is, without any warranty. // ===================================================================================== #include #include #include #include #include #include "nintv-ds.h" #include "savestate.h" #include "CRC32.h" #include "bgBottom.h" #include "config.h" #include "bgMenu-Green.h" #include "bgMenu-White.h" #include "Emulator.h" #include "Rip.h" #include "overlay.h" #include "loadgame.h" #include "printf.h" // ------------------------------------------------------------------------- // Configuration structures - we keep configuration at two levels: // Global - things that apply to all games (or defaults for new games) // Game - things that apply only to one game - indexed by game CRC32 // We use a master config struct to hold one global config and up to 625 // individual game configs... and we save them out with version number // and CRC to the SD card. The version number is critical because we can // change that to force a reset to defaults if we change our master // configration defaults in code... // ------------------------------------------------------------------------- struct Config_t myConfig __attribute__((section(".dtcm"))); struct GlobalConfig_t myGlobalConfig; struct AllConfig_t allConfigs; extern AudioMixer *audioMixer; extern Rip *currentRip; // 0 = Global Options // 1,2 = Game Options UINT8 options_shown = 0; // Start with Global config... can toggle to game options as needed UINT32 last_crc = 0x00000000; UINT8 bConfigWasFound = FALSE; short int display_options_list(bool); // -------------------------------------------- // Set the global configuration defaults... // -------------------------------------------- static void SetDefaultGlobalConfig(void) { memset(myGlobalConfig.last_path, 0x00, 256); memset(myGlobalConfig.last_game, 0x00, 256); memset(myGlobalConfig.exec_bios_filename, 0x00, 256); memset(myGlobalConfig.grom_bios_filename, 0x00, 256); memset(myGlobalConfig.ivoice_bios_filename, 0x00, 256); memset(myGlobalConfig.favorites, 0x00, 256); for (int i=0; i<64; i++) { myGlobalConfig.reserved32[i] = 0; } // -------------------------------------------------------------------- // Look for some common directories and set these as global defaults // -------------------------------------------------------------------- DIR* bdir = opendir("/roms/bios"); // See if directory exists if (bdir) { closedir(bdir); myGlobalConfig.bios_dir = 1; // in /roms/bios } else { DIR* bdir = opendir("/data/bios"); // See if directory exists if (bdir) { closedir(bdir); myGlobalConfig.bios_dir = 3; // in /data/bios } else { myGlobalConfig.bios_dir = 0; // Same as ROMS } } DIR* dir = opendir("/roms/intv"); // See if directory exists if (dir) { closedir(dir); myGlobalConfig.save_dir = 2; myGlobalConfig.rom_dir = 2; myGlobalConfig.man_dir = 2; myGlobalConfig.ovl_dir = 2; } else //Same as ROMS { DIR* dir = opendir("/roms"); // See if directory exists if (dir) { myGlobalConfig.save_dir = 1; myGlobalConfig.rom_dir = 1; myGlobalConfig.man_dir = 1; myGlobalConfig.ovl_dir = 1; } else // Set 'Same as ROMs' which is a sensible fall-back default { myGlobalConfig.save_dir = 0; myGlobalConfig.rom_dir = 0; myGlobalConfig.man_dir = 0; myGlobalConfig.ovl_dir = 0; } } myGlobalConfig.show_fps = 0; myGlobalConfig.erase_saves = 0; myGlobalConfig.key_START_map_default = OVL_META_DISC; myGlobalConfig.key_SELECT_map_default = OVL_META_MENU; myGlobalConfig.def_sound_quality = (isDSiMode() ? 2:0); myGlobalConfig.def_palette = 0; myGlobalConfig.brightness = 0; myGlobalConfig.menu_color = 1; myGlobalConfig.spare1 = 0; myGlobalConfig.spare2 = 0; myGlobalConfig.spare3 = 0; myGlobalConfig.spare4 = 0; myGlobalConfig.spare5 = 0; myGlobalConfig.frame_skip = (isDSiMode() ? 0:1); memset(myGlobalConfig.reserved, 0x00, 256); } // -------------------------------------------- // Set one game-specific default. We track // this in the myConfig global data struct. // -------------------------------------------- static void SetDefaultGameConfig(UINT32 crc) { myConfig.game_crc = 0x00000000; myConfig.frame_skip = myGlobalConfig.frame_skip; myConfig.spare1 = 0; myConfig.key_A_map = OVL_BTN_FIRE; myConfig.key_B_map = OVL_BTN_FIRE; myConfig.key_X_map = OVL_BTN_R_ACT; myConfig.key_Y_map = OVL_BTN_L_ACT; myConfig.key_L_map = OVL_KEY_1; myConfig.key_R_map = OVL_KEY_2; myConfig.key_START_map = myGlobalConfig.key_START_map_default; myConfig.key_SELECT_map = myGlobalConfig.key_SELECT_map_default; myConfig.key_AX_map = OVL_BTN_FIRE; myConfig.key_XY_map = OVL_BTN_FIRE; myConfig.key_YB_map = OVL_BTN_FIRE; myConfig.key_BA_map = OVL_BTN_FIRE; myConfig.controller_type = CONTROLLER_P1; myConfig.sound_quality = myGlobalConfig.def_sound_quality; myConfig.dpad_config = DPAD_NORMAL; myConfig.target_fps = 0; myConfig.load_options = 0x00; myConfig.palette = myGlobalConfig.def_palette; myConfig.stretch_x = ((160 / 256) << 8) | (160 % 256); myConfig.offset_x = 0; myConfig.bLatched = 0; myConfig.fudgeTiming = 0; myConfig.key_click = 0; myConfig.bSkipBlanks = 0; myConfig.gramSize = GRAM_512B; // Normal 512 bytes myConfig.spare6 = 0; myConfig.spare7 = 0; myConfig.spare8 = 0; myConfig.spare9 = 0; myConfig.spare10 = 0; myConfig.spare11 = 0; myConfig.spare12 = 0; myConfig.spare13 = 0; myConfig.spare14 = 0; myConfig.spare15 = 0; myConfig.spare16 = 0; myConfig.spare17 = 1; myConfig.spare18 = 1; myConfig.spare19 = 1; myConfig.spare20 = 0x0000; // ----------------------------------------------------------------------------------------- // Now patch up some of the more common games that work best with non-default settings... // ----------------------------------------------------------------------------------------- if (crc == 0x2DEACD15) myConfig.bLatched = true; // Stampede must have latched backtab access if (crc == 0x573B9B6D) myConfig.bLatched = true; // Masters of the Universe must have latched backtab access if (crc == 0x5F6E1AF6) myConfig.fudgeTiming = 2; // Motocross needs some fudge timing to run... known race condition... if (crc == 0xfab2992c) myConfig.controller_type = CONTROLLER_DUAL_ACTION_B; // Astrosmash is best with Dual Action B if (crc == 0xd0f83698) myConfig.controller_type = CONTROLLER_DUAL_ACTION_B; // Astrosmash (competition) is best with Dual Action B if (crc == 0x2a1e0c1c) myConfig.controller_type = CONTROLLER_DUAL_ACTION_B; // Buzz Bombers is best with Dual Action B if (crc == 0x2a1e0c1c) myConfig.controller_type = CONTROLLER_DUAL_ACTION_B; // Buzz Bombers is best with Dual Action B if (crc == 0xc047d487) myConfig.controller_type = CONTROLLER_DUAL_ACTION_B; // Beauty and the Beast is best with Dual Action B if (crc == 0x10D64E48) myConfig.controller_type = CONTROLLER_P2; // MLB Baseball and Super Pro Baseball use the P2 controller if (crc == 0xDAB36628) myConfig.controller_type = CONTROLLER_P2; // MLB Baseball and Super Pro Baseball use the P2 controller if (crc == 0x650fc1b4) myConfig.controller_type = CONTROLLER_P2; // MLB Baseball and Super Pro Baseball use the P2 controller if (crc == 0x275F3512) myConfig.controller_type = CONTROLLER_P2; // Turbo uses the P2 controller if (crc == 0x15E88FCE) myConfig.key_START_map = OVL_META_SWITCH; // Swords and Serpents wants to map the START button to swap controllers if (crc == 0xB35C1101) myConfig.key_START_map = OVL_META_SWITCH; // Auto Racing wants to map the START button to swap controllers if (crc == 0x515E1D7E) myConfig.key_START_map = OVL_META_SWITCH; // Body Slam - Super Pro Wrestling wants to map the START button to swap controllers if (crc == 0xc047d487) myConfig.dpad_config = DPAD_STRICT_4WAY; // Beauty and the Beast is best with Strict 4-way if (crc == 0xD1D352A0) myConfig.dpad_config = DPAD_STRICT_4WAY; // Tower of Doom is best with Strict 4-way if (crc == 0xD8C9856A) myConfig.dpad_config = DPAD_DIAGONALS; // Q-Bert is best with diagonal if (crc == 0x8AD19AB3) myConfig.bSkipBlanks = true; // B-17 Bomber needs to skip rendering blanks or the screen 'flashes' if (crc == 0xc0c84a13) myConfig.gramSize = GRAM_2K; // Chippi-Plus is best with 2K Expanded GRAM if (crc == 0xb8417889) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x51bd4fed) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x1deeee3e) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x3acd33e4) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x39eec5c7) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x788b440d) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x1a6b9100) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x52bb127b) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x2c0edc53) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x490f1348) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x3f490bd2) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0xccb7fe6d) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0xb91a1636) myConfig.gramSize = GRAM_2K; // Studio Vision games are best with 2K Expanded GRAM if (crc == 0x59f5fa32) myConfig.load_options = LOAD_WITH_TUTORVISION; // Map Mazes is a Tutorvision game if (crc == 0x714ecd51) myConfig.load_options = LOAD_WITH_TUTORVISION; // Shapes in Space is a Tutorvision game if (crc == 0x63a87259) myConfig.load_options = LOAD_WITH_TUTORVISION | LOAD_WITH_JLP; // Little Man Computer is a Tutorvision game with JLP last_crc = crc; } static void ToggleXYABasDirections(void) { if (myConfig.key_A_map == OVL_KEY_6) // If we were AD&D map, go to TARMIN map { myConfig.key_A_map = OVL_KEY_7; myConfig.key_B_map = OVL_KEY_4; myConfig.key_X_map = OVL_KEY_6; myConfig.key_Y_map = OVL_KEY_1; myConfig.key_AX_map = OVL_BTN_FIRE; myConfig.key_XY_map = OVL_BTN_FIRE; myConfig.key_YB_map = OVL_BTN_FIRE; myConfig.key_BA_map = OVL_BTN_FIRE; myConfig.controller_type = CONTROLLER_P1; myConfig.key_L_map = OVL_KEY_3; myConfig.key_R_map = OVL_BTN_FIRE; myConfig.key_START_map = OVL_KEY_5; dsPrintValue(24,0,0, (char*)"TARMIN"); WAITVBL;WAITVBL;WAITVBL;WAITVBL; dsPrintValue(24,0,0, (char*)" "); } else if (myConfig.key_A_map == OVL_BTN_FIRE) // If we were normal map, go to AD&D/TRON map { myConfig.key_A_map = OVL_KEY_6; myConfig.key_B_map = OVL_KEY_8; myConfig.key_X_map = OVL_KEY_2; myConfig.key_Y_map = OVL_KEY_4; myConfig.key_AX_map = OVL_KEY_3; myConfig.key_XY_map = OVL_KEY_1; myConfig.key_YB_map = OVL_KEY_7; myConfig.key_BA_map = OVL_KEY_9; myConfig.controller_type = CONTROLLER_DUAL_ACTION_A; myConfig.key_L_map = OVL_BTN_FIRE; myConfig.key_R_map = OVL_BTN_FIRE; myConfig.key_START_map = myGlobalConfig.key_START_map_default; dsPrintValue(23,0,0, (char*)"AD&D/TRON"); WAITVBL;WAITVBL;WAITVBL;WAITVBL; dsPrintValue(23,0,0, (char*)" "); } else // Just cycle back to normal key map { myConfig.key_A_map = OVL_BTN_FIRE; myConfig.key_B_map = OVL_BTN_FIRE; myConfig.key_X_map = OVL_BTN_R_ACT; myConfig.key_Y_map = OVL_BTN_L_ACT; myConfig.key_AX_map = OVL_BTN_FIRE; myConfig.key_XY_map = OVL_BTN_FIRE; myConfig.key_YB_map = OVL_BTN_FIRE; myConfig.key_BA_map = OVL_BTN_FIRE; myConfig.controller_type = CONTROLLER_P1; myConfig.key_L_map = OVL_KEY_1; myConfig.key_R_map = OVL_KEY_2; myConfig.key_START_map = myGlobalConfig.key_START_map_default; dsPrintValue(24,0,0, (char*)"DEFAULT"); WAITVBL;WAITVBL;WAITVBL;WAITVBL; dsPrintValue(24,0,0, (char*)" "); } } // --------------------------------------------------------------------------- // Write out the NINTV-DS.DAT configuration file to capture the settings for // each game. This one file contains global settings + 300 game settings. // --------------------------------------------------------------------------- void SaveConfig(UINT32 crc, bool bShow) { FILE *fp; int slot = 0; if (bShow) dsPrintValue(0,23,0, (char*)" SAVING CONFIGURATION "); // Set the global configuration version number... allConfigs.config_ver = CONFIG_VER; // Copy in the global configuration memcpy(&allConfigs.global_config, &myGlobalConfig, sizeof(struct GlobalConfig_t)); // If there is a game loaded, save that into a slot... re-use the same slot if it exists if (crc != 0x00000000) { myConfig.game_crc = crc; // Find the slot we should save into... for (slot=0; slotresetProcessor(); // Set the screen scaling options for the game selected REG_BG3PA = myConfig.stretch_x; REG_BG3X = (myConfig.offset_x) << 8; } // ------------------------------------------------------------------ // Display the current list of options: either the global list // or the individual game list of options... // ------------------------------------------------------------------ short int display_options_list(bool bFullDisplay) { short int len=0; if (bFullDisplay) { Current_Option_Table = (options_t *)Option_Table[options_shown]; while (true) { sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[len].label, Current_Option_Table[len].option[*(Current_Option_Table[len].option_val)]); dsPrintValue(1,2+len, (len==0 ? 1:0), strBuf); len++; if (Current_Option_Table[len].label == NULL) break; } // Blank out rest of the screen... option menus are of different lengths... for (int i=len; i<22; i++) { dsPrintValue(1,2+i, 0, (char *)" "); } } dsPrintValue(0,22, 0, (char *)"D-PAD TOGGLE. B=EXIT, START=SAVE"); dsPrintValue(0,23, 0, (char *)"X=SWAP KEYS. SELECT=MORE OPTIONS"); return len; } // ----------------------------------------------------------------------------- // Allows the user to move the cursor up and down through the various table // enties above to select options for the game they wish to play. // ----------------------------------------------------------------------------- void dsChooseOptions(int global) { short int optionHighlighted; short int idx; UINT8 bDone=false; int keys_pressed; short int last_keys_pressed = 999; options_shown = global; // Always start with GAME options unless told otherwise // Show the Options background... dsShowBannerScreen(); idx=display_options_list(true); optionHighlighted = 0; while (!bDone) { keys_pressed = keysCurrent(); if (keys_pressed != last_keys_pressed) { last_keys_pressed = keys_pressed; if (keysCurrent() & KEY_UP) // Previous option { sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,0, strBuf); if (optionHighlighted > 0) optionHighlighted--; else optionHighlighted=(idx-1); sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,1, strBuf); } if (keysCurrent() & KEY_DOWN) // Next option { sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,0, strBuf); if (optionHighlighted < (idx-1)) optionHighlighted++; else optionHighlighted=0; sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,1, strBuf); } if (keysCurrent() & KEY_RIGHT) // Toggle option clockwise { *(Current_Option_Table[optionHighlighted].option_val) = (*(Current_Option_Table[optionHighlighted].option_val) + 1) % Current_Option_Table[optionHighlighted].option_max; sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,1, strBuf); } if (keysCurrent() & KEY_LEFT) // Toggle option counterclockwise { if ((*(Current_Option_Table[optionHighlighted].option_val)) == 0) *(Current_Option_Table[optionHighlighted].option_val) = Current_Option_Table[optionHighlighted].option_max -1; else *(Current_Option_Table[optionHighlighted].option_val) = (*(Current_Option_Table[optionHighlighted].option_val) - 1) % Current_Option_Table[optionHighlighted].option_max; sprintf(strBuf, " %-11s : %-15s", Current_Option_Table[optionHighlighted].label, Current_Option_Table[optionHighlighted].option[*(Current_Option_Table[optionHighlighted].option_val)]); dsPrintValue(1,2+optionHighlighted,1, strBuf); } if (keysCurrent() & KEY_START) // Save Options { SaveConfig(currentRip ? currentRip->GetCRC():0x00000000, TRUE); } if (keysCurrent() & (KEY_SELECT | KEY_L | KEY_R)) // Toggle Between Option Pages { options_shown = (options_shown + 1) % 3; idx=display_options_list(true); optionHighlighted = 0; } if ((keysCurrent() & KEY_B) || (keysCurrent() & KEY_A)) // Exit options { break; } if (keysCurrent() & KEY_X) // Toggle the various ABXY button options (so we can quickly map into AD&D or Tron Deadly Discs, etc) { ToggleXYABasDirections(); idx=display_options_list(true); optionHighlighted = 0; WAITVBL;WAITVBL;WAITVBL; } } swiWaitForVBlank(); } ApplyOptions(); // Restore original bottom graphic dsShowScreenMain(false, false); // Give a third of a second time delay... for (int i=0; i<20; i++) { swiWaitForVBlank(); } return; } // End of Line