nestopia/source/unix/main.cpp
2014-09-01 22:03:31 -04:00

1004 lines
24 KiB
C++

/*
* 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 <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <libgen.h>
#ifdef _MINGW
#include <io.h>
#endif
#ifndef _MINGW
#include <archive.h>
#include <archive_entry.h>
#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;
}