mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1004 lines
24 KiB
C++
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;
|
|
}
|