/* * Nestopia UE * * Copyright (C) 2007-2008 R. Belmont * Copyright (C) 2012-2014 R. Danbrook * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #ifdef _MINGW #include #endif #ifndef _MINGW #include #include #endif #include "core/api/NstApiEmulator.hpp" #include "core/api/NstApiVideo.hpp" #include "core/api/NstApiSound.hpp" #include "core/api/NstApiInput.hpp" #include "core/api/NstApiMachine.hpp" #include "core/api/NstApiUser.hpp" #include "core/api/NstApiFds.hpp" #include "core/api/NstApiDipSwitches.hpp" #include "core/api/NstApiRewinder.hpp" #include "core/api/NstApiCartridge.hpp" #include "core/api/NstApiMovie.hpp" #include "main.h" #include "cli.h" #include "audio.h" #include "video.h" #include "input.h" #include "config.h" #include "cheats.h" #ifdef _GTK #include "gtkui/gtkui.h" #include "gtkui/gtkui_archive.h" #endif using namespace Nes::Api; // base class, all interfaces derives from this Emulator emulator; bool loaded = false; bool playing = false; bool updateok = false; bool nst_pal = false; static int nst_quit = 0; nstpaths_t nstpaths; static Video::Output *cNstVideo; static Sound::Output *cNstSound; static Input::Controllers *cNstPads; static Cartridge::Database::Entry dbentry; static std::ifstream *nstdb; static std::ifstream *fdsbios; static std::ifstream *moviefile; static std::fstream *movierecfile; extern settings_t conf; extern bool altspeed; // ******************* // emulation callbacks // ******************* // called right before Nestopia is about to write pixels static bool NST_CALLBACK VideoLock(void* userData, Video::Output& video) { video.pitch = video_lock_screen(video.pixels); return true; // true=lock success, false=lock failed (Nestopia will carry on but skip video) } // called right after Nestopia has finished writing pixels (not called if previous lock failed) static void NST_CALLBACK VideoUnlock(void* userData, Video::Output& video) { video_unlock_screen(video.pixels); } static bool NST_CALLBACK SoundLock(void* userData, Sound::Output& sound) { return true; } static void NST_CALLBACK SoundUnlock(void* userData, Sound::Output& sound) { // Do Nothing } static void NST_CALLBACK nst_cb_event(void *userData, User::Event event, const void* data) { // Handle special events switch (event) { case User::EVENT_CPU_JAM: fprintf(stderr, "Cpu: Jammed\n"); break; case User::EVENT_CPU_UNOFFICIAL_OPCODE: fprintf(stderr, "Cpu: Unofficial Opcode %s\n", (const char*)data); break; case User::EVENT_DISPLAY_TIMER: fprintf(stderr, "\r%s", (const char*)data); break; default: break; } } static void NST_CALLBACK nst_cb_log(void *userData, const char *string, unsigned long int length) { // Print logging information to stderr fprintf(stderr, "%s", string); } static void NST_CALLBACK nst_cb_file(void *userData, User::File& file) { unsigned char *compbuffer; int compsize, compoffset; char *filename; switch (file.GetAction()) { case User::File::LOAD_ROM: // Nothing here for now break; case User::File::LOAD_SAMPLE: case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU: case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU_88: case User::File::LOAD_SAMPLE_MOERO_PRO_TENNIS: case User::File::LOAD_SAMPLE_TERAO_NO_DOSUKOI_OOZUMOU: case User::File::LOAD_SAMPLE_AEROBICS_STUDIO: // Nothing here for now break; case User::File::LOAD_BATTERY: // load in battery data from a file case User::File::LOAD_EEPROM: // used by some Bandai games, can be treated the same as battery files case User::File::LOAD_TAPE: // for loading Famicom cassette tapes case User::File::LOAD_TURBOFILE: // for loading turbofile data { std::ifstream batteryFile(nstpaths.savename, std::ifstream::in|std::ifstream::binary); if (batteryFile.is_open()) { file.SetContent(batteryFile); } break; } case User::File::SAVE_BATTERY: // save battery data to a file case User::File::SAVE_EEPROM: // can be treated the same as battery files case User::File::SAVE_TAPE: // for saving Famicom cassette tapes case User::File::SAVE_TURBOFILE: // for saving turbofile data { std::ofstream batteryFile(nstpaths.savename, std::ifstream::out|std::ifstream::binary); const void* savedata; unsigned long savedatasize; file.GetContent(savedata, savedatasize); if (batteryFile.is_open()) { batteryFile.write((const char*) savedata, savedatasize); } break; } case User::File::LOAD_FDS: // for loading modified Famicom Disk System files { char fdsname[512]; snprintf(fdsname, sizeof(fdsname), "%s.ups", nstpaths.fdssave); std::ifstream batteryFile( fdsname, std::ifstream::in|std::ifstream::binary ); // no ups, look for ips if (!batteryFile.is_open()) { snprintf(fdsname, sizeof(fdsname), "%s.ips", nstpaths.fdssave); std::ifstream batteryFile( fdsname, std::ifstream::in|std::ifstream::binary ); if (!batteryFile.is_open()) { return; } file.SetPatchContent(batteryFile); return; } file.SetPatchContent(batteryFile); break; } case User::File::SAVE_FDS: // for saving modified Famicom Disk System files { char fdsname[512]; snprintf(fdsname, sizeof(fdsname), "%s.ups", nstpaths.fdssave); std::ofstream fdsFile( fdsname, std::ifstream::out|std::ifstream::binary ); if (fdsFile.is_open()) file.GetPatchContent( User::File::PATCH_UPS, fdsFile ); break; } } } static void nst_unload() { // Remove the cartridge and shut down the NES Machine machine(emulator); if (!loaded) { return; } // Power down the NES fprintf(stderr, "\rEmulation stopped\n"); machine.Power(false); // Remove the cartridge machine.Unload(); } void nst_pause() { // Pauses the game if (playing) { audio_pause(); audio_deinit(); } playing = false; video_set_cursor(); } void nst_fds_info() { Fds fds(emulator); char* disk; char* side; fds.GetCurrentDisk() == 0 ? disk = "1" : disk = "2"; fds.GetCurrentDiskSide() == 0 ? side = "A" : side = "B"; fprintf(stderr, "Fds: Disk %s Side %s\n", disk, side); } void nst_flip_disk() { // Flips the FDS disk Fds fds(emulator); if (fds.CanChangeDiskSide()) { fds.ChangeSide(); nst_fds_info(); } } void nst_switch_disk() { // Switches the FDS disk in multi-disk games Fds fds(emulator); int currentdisk = fds.GetCurrentDisk(); // If it's a multi-disk game, eject and insert the other disk if (fds.GetNumDisks() > 1) { fds.EjectDisk(); fds.InsertDisk(!currentdisk, 0); nst_fds_info(); } } static Machine::FavoredSystem nst_default_system() { switch (conf.misc_default_system) { case 0: return Machine::FAVORED_NES_NTSC; break; case 1: return Machine::FAVORED_NES_PAL; break; case 2: return Machine::FAVORED_FAMICOM; break; case 3: return Machine::FAVORED_DENDY; break; } return Machine::FAVORED_NES_NTSC; } void nst_dipswitch() { // Print DIP switch information and call handler DipSwitches dipswitches(emulator); int numdips = dipswitches.NumDips(); if (numdips > 0) { for (int i = 0; i < numdips; i++) { fprintf(stderr, "%d: %s\n", i, dipswitches.GetDipName(i)); int numvalues = dipswitches.NumValues(i); for (int j = 0; j < numvalues; j++) { fprintf(stderr, " %d: %s\n", j, dipswitches.GetValueName(i, j)); } } dip_handle(); } } void nst_state_save(char *filename) { // Save a state by filename Machine machine(emulator); std::ofstream statefile(filename, std::ifstream::out|std::ifstream::binary); if (statefile.is_open()) { machine.SaveState(statefile, Nes::Api::Machine::NO_COMPRESSION); } fprintf(stderr, "State Saved: %s\n", filename); } void nst_state_load(char *filename) { // Load a state by filename Machine machine(emulator); std::ifstream statefile(filename, std::ifstream::in|std::ifstream::binary); if (statefile.is_open()) { machine.LoadState(statefile); } fprintf(stderr, "State Loaded: %s\n", filename); } void nst_state_quicksave(int slot) { // Quick Save State char slotpath[520]; snprintf(slotpath, sizeof(slotpath), "%s_%d.nst", nstpaths.statepath, slot); nst_state_save(slotpath); } void nst_state_quickload(int slot) { // Quick Load State char slotpath[520]; snprintf(slotpath, sizeof(slotpath), "%s_%d.nst", nstpaths.statepath, slot); struct stat qloadstat; if (stat(slotpath, &qloadstat) == -1) { fprintf(stderr, "No State to Load\n"); return; } nst_state_load(slotpath); } void nst_movie_save(char *filename) { // Save/Record a movie Movie movie(emulator); movierecfile = new std::fstream(filename, std::ifstream::out|std::ifstream::binary); if (movierecfile->is_open()) { movie.Record((std::iostream&)*movierecfile, Nes::Api::Movie::CLEAN); } else { delete movierecfile; movierecfile = NULL; } } void nst_movie_load(char *filename) { // Load and play a movie Movie movie(emulator); moviefile = new std::ifstream(filename, std::ifstream::in|std::ifstream::binary); if (moviefile->is_open()) { movie.Play(*moviefile); } else { delete moviefile; moviefile = NULL; } } void nst_movie_stop() { // Stop any movie that is playing or recording Movie movie(emulator); if (movie.IsPlaying() || movie.IsRecording()) { movie.Stop(); movierecfile = NULL; delete movierecfile; moviefile = NULL; delete moviefile; } } void nst_play() { // Play the game if (playing || !loaded) { return; } video_init(); audio_init(); input_init(); cheats_init(); cNstVideo = new Video::Output; cNstSound = new Sound::Output; cNstPads = new Input::Controllers; audio_set_params(cNstSound); audio_unpause(); updateok = false; playing = true; } void nst_reset(bool hardreset) { // Reset the machine (soft or hard) Machine machine(emulator); Fds fds(emulator); machine.Reset(hardreset); // Set the FDS disk to defaults fds.EjectDisk(); fds.InsertDisk(0, 0); } void nst_schedule_quit() { nst_quit = 1; } void nst_set_dirs() { // Set up system directories #ifdef _MINGW snprintf(nstpaths.nstdir, sizeof(nstpaths.nstdir), ""); #else // create system directory if it doesn't exist snprintf(nstpaths.nstdir, sizeof(nstpaths.nstdir), "%s/.nestopia/", getenv("HOME")); if (mkdir(nstpaths.nstdir, 0755) && errno != EEXIST) { fprintf(stderr, "Failed to create %s: %d\n", nstpaths.nstdir, errno); } #endif // create save and state directories if they don't exist char dirstr[256]; snprintf(dirstr, sizeof(dirstr), "%ssave", nstpaths.nstdir); #ifdef _MINGW if (mkdir(dirstr) && errno != EEXIST) { #else if (mkdir(dirstr, 0755) && errno != EEXIST) { #endif fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno); } snprintf(dirstr, sizeof(dirstr), "%sstate", nstpaths.nstdir); #ifdef _MINGW if (mkdir(dirstr) && errno != EEXIST) { #else if (mkdir(dirstr, 0755) && errno != EEXIST) { #endif fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno); } // create cheats directory if it doesn't exist snprintf(dirstr, sizeof(dirstr), "%scheats", nstpaths.nstdir); #ifdef _MINGW if (mkdir(dirstr) && errno != EEXIST) { #else if (mkdir(dirstr, 0755) && errno != EEXIST) { #endif fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno); } } void nst_set_region() { // Set the region Machine machine(emulator); Cartridge::Database database(emulator); //Cartridge::Profile profile; if (database.IsLoaded()) { //std::ifstream dbfile(filename, std::ios::in|std::ios::binary); //Cartridge::ReadInes(dbfile, nst_default_system(), profile); //dbentry = database.FindEntry(profile.hash, nst_default_system()); machine.SetMode(machine.GetDesiredMode()); if (machine.GetMode() == Machine::PAL) { fprintf(stderr, "Region: PAL\n"); nst_pal = true; } else { fprintf(stderr, "Region: NTSC\n"); nst_pal = false; } //printf("Mapper: %d\n", dbentry.GetMapper()); } } void nst_set_rewind(int direction) { // Set the rewinder backward or forward switch (direction) { case 0: Rewinder(emulator).SetDirection(Rewinder::BACKWARD); break; case 1: Rewinder(emulator).SetDirection(Rewinder::FORWARD); break; default: break; } } void nst_set_paths(const char *filename) { // Set up the save directory snprintf(nstpaths.savedir, sizeof(nstpaths.savedir), "%ssave/", nstpaths.nstdir); // Copy the full file path to the savename variable snprintf(nstpaths.savename, sizeof(nstpaths.savename), "%s", filename); // strip the . and extention off the filename for saving for (int i = strlen(nstpaths.savename)-1; i > 0; i--) { if (nstpaths.savename[i] == '.') { nstpaths.savename[i] = '\0'; break; } } // Get the name of the game minus file path and extension snprintf(nstpaths.gamename, sizeof(nstpaths.gamename), "%s", basename(nstpaths.savename)); // Construct save path snprintf(nstpaths.savename, sizeof(nstpaths.savename), "%s%s%s", nstpaths.savedir, nstpaths.gamename, ".sav"); // Construct path for FDS save patches snprintf(nstpaths.fdssave, sizeof(nstpaths.fdssave), "%s%s", nstpaths.savedir, nstpaths.gamename); // Construct the save state path snprintf(nstpaths.statepath, sizeof(nstpaths.statepath), "%sstate/%s", nstpaths.nstdir, nstpaths.gamename); // Construct the cheat path snprintf(nstpaths.cheatpath, sizeof(nstpaths.cheatpath), "%scheats/%s.xml", nstpaths.nstdir, nstpaths.gamename); } bool nst_archive_checkext(const char *filename) { // Check if the file extension is valid int len = strlen(filename); if ((!strcasecmp(&filename[len-4], ".nes")) || (!strcasecmp(&filename[len-4], ".fds")) || (!strcasecmp(&filename[len-4], ".nsf")) || (!strcasecmp(&filename[len-4], ".unf")) || (!strcasecmp(&filename[len-5], ".unif"))|| (!strcasecmp(&filename[len-4], ".xml"))) { return true; } return false; } bool nst_archive_handle(const char *filename, char **rom, int *romsize, const char *reqfile) { // Handle archives #ifndef _MINGW struct archive *a; struct archive_entry *entry; int r; int64_t entrysize; a = archive_read_new(); archive_read_support_filter_all(a); archive_read_support_format_all(a); r = archive_read_open_filename(a, filename, 10240); // Test if it's actually an archive if (r != ARCHIVE_OK) { r = archive_read_free(a); return false; } // Scan through the archive for files while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { char *rombuf; const char *currentfile = archive_entry_pathname(entry); if (nst_archive_checkext(currentfile)) { nst_set_paths(currentfile); // If there's a specific file we want, load it if (reqfile != NULL) { if (!strcmp(currentfile, reqfile)) { entrysize = archive_entry_size(entry); rombuf = (char*)malloc(entrysize); archive_read_data(a, rombuf, entrysize); archive_read_data_skip(a); r = archive_read_free(a); *romsize = entrysize; *rom = rombuf; return true; } } // Otherwise just take the first file in the archive else { entrysize = archive_entry_size(entry); rombuf = (char*)malloc(entrysize); archive_read_data(a, rombuf, entrysize); archive_read_data_skip(a); r = archive_read_free(a); *romsize = entrysize; *rom = rombuf; return true; } } } #endif return false; } bool nst_find_patch(char *filename) { // Check for a patch in the same directory as the game FILE *file; if (!conf.misc_soft_patching) { return 0; } snprintf(filename, sizeof(nstpaths.savename), "%s.ips", nstpaths.gamename); if ((file = fopen(filename, "rb")) != NULL) { fclose(file); return 1; } else { snprintf(filename, sizeof(nstpaths.savename), "%s.ups", nstpaths.gamename); if ((file = fopen(filename, "rb")) != NULL) { fclose(file); return 1; } } return 0; } void nst_load_db() { Nes::Api::Cartridge::Database database(emulator); char dbpath[512]; if (nstdb) { return; } // Try to open the database file snprintf(dbpath, sizeof(dbpath), "%sNstDatabase.xml", nstpaths.nstdir); nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary); if (nstdb->is_open()) { database.Load(*nstdb); database.Enable(true); return; } #ifndef _MINGW // If it fails, try looking in the data directory snprintf(dbpath, sizeof(dbpath), "%s/NstDatabase.xml", DATADIR); nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary); if (nstdb->is_open()) { database.Load(*nstdb); database.Enable(true); return; } // If that fails, try looking in the working directory char *pwd = getenv("PWD"); snprintf(dbpath, sizeof(dbpath), "%s/NstDatabase.xml", pwd); nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary); if (nstdb->is_open()) { database.Load(*nstdb); database.Enable(true); return; } #endif else { fprintf(stderr, "NstDatabase.xml not found!\n"); delete nstdb; nstdb = NULL; } } void nst_load_fds_bios() { // Load the Famicom Disk System BIOS Nes::Api::Fds fds(emulator); char biospath[512]; if (fdsbios) { return; } snprintf(biospath, sizeof(biospath), "%sdisksys.rom", nstpaths.nstdir); fdsbios = new std::ifstream(biospath, std::ifstream::in|std::ifstream::binary); if (fdsbios->is_open()) { fds.SetBIOS(fdsbios); } else { fprintf(stderr, "%s not found, Disk System games will not work.\n", biospath); delete fdsbios; fdsbios = NULL; } } void nst_load(const char *filename) { // Load a Game ROM Machine machine(emulator); Sound sound(emulator); Nes::Result result; char *rom; int romsize; char patchname[512]; // Pause play before pulling out a cartridge if (playing) { nst_pause(); } // Pull out any inserted cartridges nst_unload(); // Handle the file as an archive if it is one #ifdef _GTK char reqname[256]; bool isarchive = gtkui_archive_handle(filename, reqname, sizeof(reqname)); if (isarchive) { nst_archive_handle(filename, &rom, &romsize, reqname); #else if (nst_archive_handle(filename, &rom, &romsize, NULL)) { #endif // Convert the malloc'd char* to an istream std::string rombuf(rom, romsize); std::istringstream file(rombuf); result = machine.Load(file, nst_default_system()); } // Otherwise just load the file else { std::ifstream file(filename, std::ios::in|std::ios::binary); // Set the file paths nst_set_paths(filename); if (nst_find_patch(patchname)) { // Load with a patch if there is one std::ifstream pfile(patchname, std::ios::in|std::ios::binary); Machine::Patch patch(pfile, false); result = machine.Load(file, nst_default_system(), patch); } else { result = machine.Load(file, nst_default_system()); } } if (NES_FAILED(result)) { char errorstring[32]; #ifdef _GTK if (conf.video_fullscreen) { video_toggle_fullscreen(); } #endif switch (result) { case Nes::RESULT_ERR_INVALID_FILE: snprintf(errorstring, sizeof(errorstring), "Error: Invalid file"); break; case Nes::RESULT_ERR_OUT_OF_MEMORY: snprintf(errorstring, sizeof(errorstring), "Error: Out of Memory"); break; case Nes::RESULT_ERR_CORRUPT_FILE: snprintf(errorstring, sizeof(errorstring), "Error: Corrupt or Missing File"); break; case Nes::RESULT_ERR_UNSUPPORTED_MAPPER: snprintf(errorstring, sizeof(errorstring), "Error: Unsupported Mapper"); break; case Nes::RESULT_ERR_MISSING_BIOS: snprintf(errorstring, sizeof(errorstring), "Error: Missing Fds BIOS"); break; default: snprintf(errorstring, sizeof(errorstring), "Error: %d", result); break; } fprintf(stderr, "%s\n", errorstring); #ifdef _GTK conf.misc_disable_gui ? cli_error(errorstring) : gtkui_message(errorstring); #endif return; } // Deal with any DIP Switches nst_dipswitch(); // Set the region nst_set_region(); if (machine.Is(Machine::DISK)) { Fds fds(emulator); fds.InsertDisk(0, 0); nst_fds_info(); } // Check if sound distortion should be enabled sound.SetGenie(conf.misc_genie_distortion); // note that something is loaded loaded = 1; // Set the title video_set_title(nstpaths.gamename); #ifdef _GTK if (!conf.misc_disable_gui) { gtkui_set_title(nstpaths.gamename); } #endif // power on machine.Power(true); // false = power off nst_play(); } int main(int argc, char *argv[]) { // This is the main function static SDL_Event event; void *userData = (void*)0xDEADC0DE; // Set up directories nst_set_dirs(); // Set default config options config_set_default(); // Read the config file and override defaults config_file_read(); // Exit if there is no CLI argument #ifdef _GTK if (argc == 1 && conf.misc_disable_gui) { #else if (argc == 1) { #endif cli_show_usage(); return 0; } // Handle command line arguments cli_handle_command(argc, argv); // Initialize SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); return 1; } // Detect Joysticks input_joysticks_detect(); // Set default input keys input_set_default(); // Read the input config file and override defaults input_config_read(); // Set the video dimensions video_set_dimensions(); // Create the window #ifdef _GTK if (!conf.misc_disable_gui) { gtkui_init(argc, argv); } #endif video_create(); // Set up the callbacks Video::Output::lockCallback.Set(VideoLock, userData); Video::Output::unlockCallback.Set(VideoUnlock, userData); Sound::Output::lockCallback.Set(SoundLock, userData); Sound::Output::unlockCallback.Set(SoundUnlock, userData); User::fileIoCallback.Set(nst_cb_file, userData); User::logCallback.Set(nst_cb_log, userData); User::eventCallback.Set(nst_cb_event, userData); // Initialize and load FDS BIOS and NstDatabase.xml nstdb = NULL; fdsbios = NULL; nst_load_db(); nst_load_fds_bios(); // Load a rom from the command line if (argc > 1) { #ifdef _GTK // This is a dirty hack if (conf.misc_disable_gui) { nst_load(argv[argc - 1]); if (!loaded) { fprintf(stderr, "Fatal: Could not load ROM\n"); exit(1); } } else { if (strcmp(argv[argc - 1], "-e")) { nst_load(argv[argc - 1]); } } #else conf.misc_disable_gui = true; nst_load(argv[argc - 1]); if (!loaded) { fprintf(stderr, "Fatal: Could not load ROM\n"); exit(1); } #endif } // Start the main loop nst_quit = 0; while (!nst_quit) { #ifdef _GTK while (gtk_events_pending()) { gtk_main_iteration_do(FALSE); } #endif if (playing) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: nst_quit = 1; break; case SDL_KEYDOWN: case SDL_KEYUP: case SDL_JOYHATMOTION: case SDL_JOYAXISMOTION: case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: input_process(cNstPads, event); break; default: break; } } if (NES_SUCCEEDED(Rewinder(emulator).Enable(true))) { Rewinder(emulator).EnableSound(true); } audio_play(); if (updateok) { // Pulse the turbo buttons input_pulse_turbo(cNstPads); // Execute a frame if (timing_frameskip()) { emulator.Execute(NULL, cNstSound, cNstPads); } else { emulator.Execute(cNstVideo, cNstSound, cNstPads); } } } } // Remove the cartridge and shut down the NES nst_unload(); // Unload the FDS BIOS and NstDatabase.xml if (nstdb) { delete nstdb; nstdb = NULL; } if (fdsbios) { delete fdsbios; fdsbios = NULL; } // Deinitialize audio audio_deinit(); // Deinitialize joysticks input_joysticks_close(); // Write the input config file input_config_write(); // Write the config file config_file_write(); return 0; }