1 /*
2 * Nestopia UE
3 *
4 * Copyright (C) 2007-2008 R. Belmont
5 * Copyright (C) 2012-2021 R. Danbrook
6 * Copyright (C) 2018-2018 Phil Smith
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301, USA.
22 *
23 */
24
25 #include <iostream>
26 #include <fstream>
27 #include <sstream>
28
29 #include <cstdio>
30 #include <cstring>
31 #include <libgen.h>
32
33 #include <errno.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <archive.h>
38 #include <archive_entry.h>
39
40 #include <SDL.h>
41
42 // Nst Common
43 #include "nstcommon.h"
44 #include "config.h"
45 #include "cheats.h"
46 #include "homebrew.h"
47 #include "input.h"
48 #include "audio.h"
49 #include "video.h"
50 #include "samples.h"
51
52 Emulator emulator;
53 Video::Output *cNstVideo;
54 Sound::Output *cNstSound;
55 Input::Controllers *cNstPads;
56
57 nstpaths_t nstpaths;
58
59 static bool ffspeed = false;
60 static bool playing = false;
61
62 static std::ifstream *nstdb;
63
64 static std::ifstream *fdsbios;
65
66 static std::ifstream *moviefile;
67 static std::fstream *movierecfile;
68
69 void *custompalette = NULL;
70 static size_t custpalsize;
71
72 int loaded = 0;
73
74 bool (*nst_archive_select)(const char*, char*, size_t);
75
nst_cb_videolock(void * userData,Video::Output & video)76 static bool NST_CALLBACK nst_cb_videolock(void* userData, Video::Output& video) {
77 video.pitch = video_lock_screen(video.pixels);
78 return true;
79 }
80
nst_cb_videounlock(void * userData,Video::Output & video)81 static void NST_CALLBACK nst_cb_videounlock(void* userData, Video::Output& video) {
82 video_unlock_screen(video.pixels);
83 }
84
nst_cb_soundlock(void * userData,Sound::Output & sound)85 static bool NST_CALLBACK nst_cb_soundlock(void* userData, Sound::Output& sound) {
86 audio_queue();
87 return true;
88 }
89
nst_cb_soundunlock(void * userData,Sound::Output & sound)90 static void NST_CALLBACK nst_cb_soundunlock(void* userData, Sound::Output& sound) {
91 // Do Nothing
92 }
93
nst_cb_event(void * userData,User::Event event,const void * data)94 static void NST_CALLBACK nst_cb_event(void *userData, User::Event event, const void* data) {
95 // Handle special events
96 switch (event) {
97 case User::EVENT_CPU_JAM:
98 fprintf(stderr, "Cpu: Jammed\n");
99 break;
100 case User::EVENT_CPU_UNOFFICIAL_OPCODE:
101 fprintf(stderr, "Cpu: Unofficial Opcode %s\n", (const char*)data);
102 break;
103 case User::EVENT_DISPLAY_TIMER:
104 nst_video_print_time((const char*)data + strlen((char*)data) - 5, true);
105 break;
106 default: break;
107 }
108 }
109
nst_cb_log(void * userData,const char * string,unsigned long int length)110 static void NST_CALLBACK nst_cb_log(void *userData, const char *string, unsigned long int length) {
111 // Print logging information to stderr
112 fprintf(stderr, "%s", string);
113 }
114
nst_cb_file(void * userData,User::File & file)115 static void NST_CALLBACK nst_cb_file(void *userData, User::File& file) {
116 unsigned char *compbuffer;
117 int compsize, compoffset;
118 char *filename;
119
120 switch (file.GetAction()) {
121 case User::File::LOAD_ROM:
122 // Nothing here for now
123 break;
124
125 case User::File::LOAD_SAMPLE: break;
126 case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU: nst_sample_load_samples(file, "moepro"); break;
127 case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU_88: nst_sample_load_samples(file, "moepro88"); break;
128 case User::File::LOAD_SAMPLE_MOERO_PRO_TENNIS: nst_sample_load_samples(file, "mptennis"); break;
129 case User::File::LOAD_SAMPLE_TERAO_NO_DOSUKOI_OOZUMOU: nst_sample_load_samples(file, "terao"); break;
130 case User::File::LOAD_SAMPLE_AEROBICS_STUDIO: nst_sample_load_samples(file, "ftaerobi"); break;
131
132 case User::File::LOAD_BATTERY: // load in battery data from a file
133 case User::File::LOAD_EEPROM: // used by some Bandai games, can be treated the same as battery files
134 case User::File::LOAD_TAPE: // for loading Famicom cassette tapes
135 case User::File::LOAD_TURBOFILE: // for loading turbofile data
136 {
137 std::ifstream batteryFile(nstpaths.savename, std::ifstream::in|std::ifstream::binary);
138
139 if (batteryFile.is_open()) { file.SetContent(batteryFile); }
140 break;
141 }
142
143 case User::File::SAVE_BATTERY: // save battery data to a file
144 case User::File::SAVE_EEPROM: // can be treated the same as battery files
145 case User::File::SAVE_TAPE: // for saving Famicom cassette tapes
146 case User::File::SAVE_TURBOFILE: // for saving turbofile data
147 {
148 std::ofstream batteryFile(nstpaths.savename, std::ifstream::out|std::ifstream::binary);
149 const void* savedata;
150 unsigned long savedatasize;
151
152 file.GetContent(savedata, savedatasize);
153
154 if (batteryFile.is_open()) { batteryFile.write((const char*) savedata, savedatasize); }
155
156 break;
157 }
158
159 case User::File::LOAD_FDS: // for loading modified Famicom Disk System files
160 {
161 char fdsname[512];
162
163 snprintf(fdsname, sizeof(fdsname), "%s.ups", nstpaths.fdssave);
164
165 std::ifstream batteryFile( fdsname, std::ifstream::in|std::ifstream::binary );
166
167 // no ups, look for ips
168 if (!batteryFile.is_open())
169 {
170 snprintf(fdsname, sizeof(fdsname), "%s.ips", nstpaths.fdssave);
171
172 std::ifstream batteryFile( fdsname, std::ifstream::in|std::ifstream::binary );
173
174 if (!batteryFile.is_open())
175 {
176 return;
177 }
178
179 file.SetPatchContent(batteryFile);
180 return;
181 }
182
183 file.SetPatchContent(batteryFile);
184 break;
185 }
186
187 case User::File::SAVE_FDS: // for saving modified Famicom Disk System files
188 {
189 char fdsname[512];
190
191 snprintf(fdsname, sizeof(fdsname), "%s.ups", nstpaths.fdssave);
192
193 std::ofstream fdsFile( fdsname, std::ifstream::out|std::ifstream::binary );
194
195 if (fdsFile.is_open())
196 file.GetPatchContent( User::File::PATCH_UPS, fdsFile );
197
198 break;
199 }
200 }
201 }
202
nst_default_system()203 static Machine::FavoredSystem nst_default_system() {
204 switch (conf.misc_default_system) {
205 case 2: return Machine::FAVORED_NES_PAL; break;
206 case 3: return Machine::FAVORED_FAMICOM; break;
207 case 4: return Machine::FAVORED_DENDY; break;
208 default: return Machine::FAVORED_NES_NTSC; break;
209 }
210 }
211
nst_ptr_video()212 void* nst_ptr_video() { return &cNstVideo; }
nst_ptr_sound()213 void* nst_ptr_sound() { return &cNstSound; }
nst_ptr_input()214 void* nst_ptr_input() { return &cNstPads; }
215
nst_archive_checkext(const char * filename)216 bool nst_archive_checkext(const char *filename) {
217 // Check if the file extension is valid
218 int len = strlen(filename);
219
220 if ((!strcasecmp(&filename[len-4], ".nes")) ||
221 (!strcasecmp(&filename[len-4], ".fds")) ||
222 (!strcasecmp(&filename[len-4], ".nsf")) ||
223 (!strcasecmp(&filename[len-4], ".unf")) ||
224 (!strcasecmp(&filename[len-5], ".unif"))||
225 (!strcasecmp(&filename[len-4], ".xml"))) {
226 return true;
227 }
228 return false;
229 }
230
nst_archive_select_file(const char * filename,char * reqfile,size_t reqsize)231 bool nst_archive_select_file(const char *filename, char *reqfile, size_t reqsize) {
232 // Select a filename to pull out of the archive
233 struct archive *a;
234 struct archive_entry *entry;
235 int r, numarchives = 0;
236
237 a = archive_read_new();
238 archive_read_support_filter_all(a);
239 archive_read_support_format_all(a);
240 r = archive_read_open_filename(a, filename, 10240);
241
242 // Test if it's actually an archive
243 if (r != ARCHIVE_OK) {
244 r = archive_read_free(a);
245 return false;
246 }
247 // If it is an archive, handle it
248 else {
249 // Find files with valid extensions within the archive
250 while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
251 const char *currentfile = archive_entry_pathname(entry);
252 if (nst_archive_checkext(currentfile)) {
253 numarchives++;
254 snprintf(reqfile, reqsize, "%s", currentfile);
255 }
256 archive_read_data_skip(a);
257 break; // Load the first one found
258 }
259 // Free the archive
260 r = archive_read_free(a);
261
262 // If there are no valid files in the archive, return
263 if (numarchives == 0) { return false; }
264 else { return true; }
265 }
266 return false;
267 }
268
nst_archive_open(const char * filename,char ** rom,int * romsize,const char * reqfile)269 bool nst_archive_open(const char *filename, char **rom, int *romsize, const char *reqfile) {
270 // Opens archives
271 struct archive *a;
272 struct archive_entry *entry;
273 int r;
274 int64_t entrysize;
275
276 a = archive_read_new();
277 archive_read_support_filter_all(a);
278 archive_read_support_format_all(a);
279 r = archive_read_open_filename(a, filename, 10240);
280
281 // Test if it's actually an archive
282 if (r != ARCHIVE_OK) {
283 r = archive_read_free(a);
284 return false;
285 }
286
287 // Scan through the archive for files
288 while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
289 char *rombuf;
290 const char *currentfile = archive_entry_pathname(entry);
291 if (nst_archive_checkext(currentfile)) {
292 nst_set_paths(currentfile);
293 // If there's a specific file we want, load it
294 if (reqfile != NULL) {
295 if (!strcmp(currentfile, reqfile)) {
296 entrysize = archive_entry_size(entry);
297 rombuf = (char*)malloc(entrysize);
298 archive_read_data(a, rombuf, entrysize);
299 archive_read_data_skip(a);
300 r = archive_read_free(a);
301 *romsize = entrysize;
302 *rom = rombuf;
303 return true;
304 }
305 }
306 // Otherwise just take the first file in the archive
307 else {
308 entrysize = archive_entry_size(entry);
309 rombuf = (char*)malloc(entrysize);
310 archive_read_data(a, rombuf, entrysize);
311 archive_read_data_skip(a);
312 r = archive_read_free(a);
313 *romsize = entrysize;
314 *rom = rombuf;
315 return true;
316 }
317 }
318 }
319 return false;
320 }
321
nst_db_load()322 void nst_db_load() {
323 Nes::Api::Cartridge::Database database(emulator);
324 char dbpath[512];
325
326 if (nstdb) { return; }
327
328 // Try to open the database file
329 snprintf(dbpath, sizeof(dbpath), "%sNstDatabase.xml", nstpaths.nstdir);
330 nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary);
331
332 if (nstdb->is_open()) {
333 database.Load(*nstdb);
334 database.Enable(true);
335 return;
336 }
337
338 // If it fails, try looking in the data directory
339 snprintf(dbpath, sizeof(dbpath), "%s/NstDatabase.xml", DATADIR);
340 nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary);
341
342 if (nstdb->is_open()) {
343 database.Load(*nstdb);
344 database.Enable(true);
345 return;
346 }
347
348 // If that fails, try looking in the working directory
349 char *pwd = getenv("PWD");
350 snprintf(dbpath, sizeof(dbpath), "%s/NstDatabase.xml", pwd);
351 nstdb = new std::ifstream(dbpath, std::ifstream::in|std::ifstream::binary);
352
353 if (nstdb->is_open()) {
354 database.Load(*nstdb);
355 database.Enable(true);
356 return;
357 }
358 else {
359 fprintf(stderr, "NstDatabase.xml not found!\n");
360 delete nstdb;
361 nstdb = NULL;
362 }
363 }
364
nst_db_unload()365 void nst_db_unload() {
366 if (nstdb) { delete nstdb; nstdb = NULL; }
367 }
368
nst_dipswitch()369 void nst_dipswitch() {
370 // Print DIP switch information and call handler
371 DipSwitches dipswitches(emulator);
372
373 int numdips = dipswitches.NumDips();
374
375 if (numdips > 0) {
376 for (int i = 0; i < numdips; i++) {
377 fprintf(stderr, "%d: %s\n", i, dipswitches.GetDipName(i));
378 int numvalues = dipswitches.NumValues(i);
379
380 for (int j = 0; j < numvalues; j++) {
381 fprintf(stderr, " %d: %s\n", j, dipswitches.GetValueName(i, j));
382 }
383 }
384
385 char dippath[512];
386 snprintf(dippath, sizeof(dippath), "%s%s.dip", nstpaths.savedir, nstpaths.gamename);
387 nst_dip_handle(dippath);
388 }
389 }
390
nst_fds_bios_load()391 void nst_fds_bios_load() {
392 // Load the Famicom Disk System BIOS
393 Nes::Api::Fds fds(emulator);
394 char biospath[512];
395
396 if (fdsbios) { return; }
397
398 snprintf(biospath, sizeof(biospath), "%sdisksys.rom", nstpaths.nstdir);
399
400 fdsbios = new std::ifstream(biospath, std::ifstream::in|std::ifstream::binary);
401
402 if (fdsbios->is_open()) {
403 fds.SetBIOS(fdsbios);
404 }
405 else {
406 fprintf(stderr, "Fds: BIOS not found: %s\n", biospath);
407 delete fdsbios;
408 fdsbios = NULL;
409 }
410 }
411
nst_fds_bios_unload()412 void nst_fds_bios_unload() {
413 if (fdsbios) { delete fdsbios; fdsbios = NULL; }
414 }
415
nst_fds_info()416 void nst_fds_info() {
417 Fds fds(emulator);
418
419 const char* disk;
420 const char* side;
421 char textbuf[24];
422
423 fds.GetCurrentDisk() == 0 ? disk = "1" : disk = "2";
424 fds.GetCurrentDiskSide() == 0 ? side = "A" : side = "B";
425
426 fprintf(stderr, "Fds: Disk %s Side %s\n", disk, side);
427 snprintf(textbuf, sizeof(textbuf), "Disk %s Side %s", disk, side);
428 nst_video_print((const char*)textbuf, 8, 16, 2, true);
429 }
430
nst_fds_flip()431 void nst_fds_flip() {
432 // Flips the FDS disk
433 Fds fds(emulator);
434
435 if (fds.CanChangeDiskSide()) {
436 fds.ChangeSide();
437 nst_fds_info();
438 }
439 }
440
nst_fds_switch()441 void nst_fds_switch() {
442 // Switches the FDS disk in multi-disk games
443 Fds fds(emulator);
444
445 int currentdisk = fds.GetCurrentDisk();
446
447 // If it's a multi-disk game, eject and insert the other disk
448 if (fds.GetNumDisks() > 1) {
449 fds.EjectDisk();
450 fds.InsertDisk(!currentdisk, 0);
451 nst_fds_info();
452 }
453 }
454
nst_movie_save(const char * filename)455 void nst_movie_save(const char *filename) {
456 // Save/Record a movie
457 Movie movie(emulator);
458
459 movierecfile = new std::fstream(filename, std::ifstream::out|std::ifstream::binary);
460
461 if (movierecfile->is_open()) {
462 movie.Record((std::iostream&)*movierecfile, Nes::Api::Movie::CLEAN);
463 }
464 else {
465 delete movierecfile;
466 movierecfile = NULL;
467 }
468 }
469
nst_movie_load(const char * filename)470 void nst_movie_load(const char *filename) {
471 // Load and play a movie
472 Movie movie(emulator);
473
474 moviefile = new std::ifstream(filename, std::ifstream::in|std::ifstream::binary);
475
476 if (moviefile->is_open()) {
477 movie.Play(*moviefile);
478 }
479 else {
480 delete moviefile;
481 moviefile = NULL;
482 }
483 }
484
nst_movie_stop()485 void nst_movie_stop() {
486 // Stop any movie that is playing or recording
487 Movie movie(emulator);
488
489 if (movie.IsPlaying() || movie.IsRecording()) {
490 movie.Stop();
491 movierecfile = NULL;
492 delete movierecfile;
493 moviefile = NULL;
494 delete moviefile;
495 }
496 }
497
nst_nsf()498 bool nst_nsf() {
499 Machine machine(emulator);
500 return machine.Is(Machine::SOUND);
501 }
502
nst_nsf_play()503 void nst_nsf_play() {
504 Nsf nsf(emulator);
505 nsf.PlaySong();
506 video_clear_buffer();
507 video_disp_nsf();
508 }
509
nst_nsf_stop()510 void nst_nsf_stop() {
511 Nsf nsf(emulator);
512 nsf.StopSong();
513 }
514
nst_nsf_prev()515 void nst_nsf_prev() {
516 Nsf nsf(emulator);
517 nsf.SelectPrevSong();
518 video_clear_buffer();
519 video_disp_nsf();
520 }
521
nst_nsf_next()522 void nst_nsf_next() {
523 Nsf nsf(emulator);
524 nsf.SelectNextSong();
525 video_clear_buffer();
526 video_disp_nsf();
527 }
528
nst_pal()529 bool nst_pal() {
530 Machine machine(emulator);
531 return machine.GetMode() == Machine::PAL;
532 }
533
nst_playing()534 bool nst_playing() { return playing; }
535
nst_palette_load(const char * filename)536 void nst_palette_load(const char *filename) {
537 // Load a custom palette
538
539 FILE *file;
540 long filesize; // File size in bytes
541 size_t result;
542
543 char custgamepalpath[512];
544 snprintf(custgamepalpath, sizeof(custgamepalpath), "%s%s%s", nstpaths.nstdir, nstpaths.gamename, ".pal");
545
546 // Try the game-specific palette first
547 file = fopen(custgamepalpath, "rb");
548 if (!file) { file = fopen(filename, "rb"); }
549
550 // Then try the global custom palette
551 if (!file) {
552 if (conf.video_palette_mode == 2) {
553 fprintf(stderr, "Custom palette: not found: %s\n", filename);
554 conf.video_palette_mode = 0;
555 }
556 return;
557 }
558
559 fseek(file, 0, SEEK_END);
560 filesize = ftell(file);
561 fseek(file, 0, SEEK_SET);
562
563 if (custompalette) { free(custompalette); }
564 custompalette = malloc(filesize * sizeof(uint8_t));
565 custpalsize = filesize * sizeof(uint8_t);
566
567 result = fread(custompalette, sizeof(uint8_t), filesize, file);
568
569 fclose(file);
570 }
571
nst_palette_save()572 void nst_palette_save() {
573 // Save a custom palette
574 FILE *file;
575 void *custpalout;
576
577 file = fopen(nstpaths.palettepath, "wb");
578 if (!file) { return; }
579
580 custpalout = malloc(custpalsize);
581
582 memcpy(custpalout, custompalette, custpalsize);
583
584 fwrite(custpalout, custpalsize, sizeof(uint8_t), file);
585 fclose(file);
586 free(custpalout);
587 }
588
nst_palette_unload()589 void nst_palette_unload() {
590 if (custompalette) { free(custompalette); }
591 }
592
nst_find_patch(char * patchname,unsigned int patchname_length,const char * filename)593 bool nst_find_patch(char *patchname, unsigned int patchname_length, const char *filename) {
594 // Check for a patch in the same directory as the game
595 FILE *file;
596 char filedir[512];
597
598 // Copy filename (will be used by dirname)
599 // dirname needs a copy because it can modify its argument
600 strncpy(filedir, filename, sizeof(filedir));
601 filedir[sizeof(filedir) - 1] = '\0';
602 // Use memmove because dirname can return the same pointer as its argument,
603 // since copying into same string as the argument we don't want any overlap
604 memmove(filedir, dirname(filedir), sizeof(filedir));
605 filedir[sizeof(filedir) - 1] = '\0';
606
607 if (!conf.misc_soft_patching) { return 0; }
608
609 snprintf(patchname, patchname_length, "%s/%s.ips", filedir, nstpaths.gamename);
610
611 if ((file = fopen(patchname, "rb")) != NULL) { fclose(file); return 1; }
612 else {
613 snprintf(patchname, patchname_length, "%s/%s.ups", filedir, nstpaths.gamename);
614 if ((file = fopen(patchname, "rb")) != NULL) { fclose(file); return 1; }
615 }
616
617 return 0;
618 }
619
nst_set_callbacks()620 void nst_set_callbacks() {
621 // Set up the callbacks
622 void *userData = (void*)0xDEADC0DE;
623
624 Video::Output::lockCallback.Set(nst_cb_videolock, userData);
625 Video::Output::unlockCallback.Set(nst_cb_videounlock, userData);
626
627 Sound::Output::lockCallback.Set(nst_cb_soundlock, userData);
628 Sound::Output::unlockCallback.Set(nst_cb_soundunlock, userData);
629
630 User::fileIoCallback.Set(nst_cb_file, userData);
631 User::logCallback.Set(nst_cb_log, userData);
632 User::eventCallback.Set(nst_cb_event, userData);
633 }
634
nst_set_dirs()635 void nst_set_dirs() {
636 // Set up system directories
637 // create config directory if it doesn't exist
638 if (getenv("XDG_CONFIG_HOME")) {
639 snprintf(nstpaths.nstconfdir, sizeof(nstpaths.nstconfdir), "%s/nestopia/", getenv("XDG_CONFIG_HOME"));
640 }
641 else {
642 snprintf(nstpaths.nstconfdir, sizeof(nstpaths.nstconfdir), "%s/.config/nestopia/", getenv("HOME"));
643 }
644
645 if (mkdir(nstpaths.nstconfdir, 0755) && errno != EEXIST) {
646 fprintf(stderr, "Failed to create %s: %d\n", nstpaths.nstconfdir, errno);
647 }
648
649 // create data directory if it doesn't exist
650 if (getenv("XDG_DATA_HOME")) {
651 snprintf(nstpaths.nstdir, sizeof(nstpaths.nstdir), "%s/nestopia/", getenv("XDG_DATA_HOME"));
652 }
653 else {
654 snprintf(nstpaths.nstdir, sizeof(nstpaths.nstdir), "%s/.local/share/nestopia/", getenv("HOME"));
655 }
656
657 if (mkdir(nstpaths.nstdir, 0755) && errno != EEXIST) {
658 fprintf(stderr, "Failed to create %s: %d\n", nstpaths.nstdir, errno);
659 }
660
661 // create save and state directories if they don't exist
662 char dirstr[256];
663 snprintf(dirstr, sizeof(dirstr), "%ssave", nstpaths.nstdir);
664
665 if (mkdir(dirstr, 0755) && errno != EEXIST) {
666 fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno);
667 }
668
669 snprintf(dirstr, sizeof(dirstr), "%sstate", nstpaths.nstdir);
670 if (mkdir(dirstr, 0755) && errno != EEXIST) {
671 fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno);
672 }
673
674 // create cheats directory if it doesn't exist
675 snprintf(dirstr, sizeof(dirstr), "%scheats", nstpaths.nstdir);
676 if (mkdir(dirstr, 0755) && errno != EEXIST) {
677 fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno);
678 }
679
680 // create screenshots directory if it doesn't exist
681 snprintf(dirstr, sizeof(dirstr), "%sscreenshots", nstpaths.nstdir);
682 if (mkdir(dirstr, 0755) && errno != EEXIST) {
683 fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno);
684 }
685
686 // Construct the custom palette path
687 snprintf(nstpaths.palettepath, sizeof(nstpaths.palettepath), "%s%s", nstpaths.nstdir, "custom.pal");
688
689 // Construct samples directory if it doesn't exist
690 snprintf(dirstr, sizeof(dirstr), "%ssamples", nstpaths.nstdir);
691 if (mkdir(dirstr, 0755) && errno != EEXIST) {
692 fprintf(stderr, "Failed to create %s: %d\n", dirstr, errno);
693 }
694 }
695
nst_set_paths(const char * filename)696 void nst_set_paths(const char *filename) {
697
698 // Set up the save directory
699 snprintf(nstpaths.savedir, sizeof(nstpaths.savedir), "%ssave/", nstpaths.nstdir);
700
701 // Copy the full file path to the savename variable
702 snprintf(nstpaths.savename, sizeof(nstpaths.savename), "%s", filename);
703
704 // strip the . and extention off the filename for saving
705 for (int i = strlen(nstpaths.savename)-1; i > 0; i--) {
706 if (nstpaths.savename[i] == '.') {
707 nstpaths.savename[i] = '\0';
708 break;
709 }
710 }
711
712 // Set up the sample directory
713 snprintf(nstpaths.sampdir, sizeof(nstpaths.sampdir), "%ssamples/", nstpaths.nstdir);
714
715 // Get the name of the game minus file path and extension
716 snprintf(nstpaths.gamename, sizeof(nstpaths.gamename), "%s", basename(nstpaths.savename));
717
718 // Construct save path
719 snprintf(nstpaths.savename, sizeof(nstpaths.savename), "%s%s%s", nstpaths.savedir, nstpaths.gamename, ".sav");
720
721 // Construct path for FDS save patches
722 snprintf(nstpaths.fdssave, sizeof(nstpaths.fdssave), "%s%s", nstpaths.savedir, nstpaths.gamename);
723
724 // Construct the save state path
725 snprintf(nstpaths.statepath, sizeof(nstpaths.statepath), "%sstate/%s", nstpaths.nstdir, nstpaths.gamename);
726
727 // Construct the cheat path
728 snprintf(nstpaths.cheatpath, sizeof(nstpaths.cheatpath), "%scheats/%s.xml", nstpaths.nstdir, nstpaths.gamename);
729 }
730
nst_set_region()731 void nst_set_region() {
732 // Set the region
733 Machine machine(emulator);
734 Cartridge::Database database(emulator);
735
736 /*if (database.IsLoaded()) {
737 std::ifstream dbfile(filename, std::ios::in|std::ios::binary);
738 Cartridge::Profile profile;
739 Cartridge::ReadInes(dbfile, nst_default_system(), profile);
740 dbentry = database.FindEntry(profile.hash, nst_default_system());
741 printf("Mapper: %d\n", dbentry.GetMapper());
742 }*/
743
744 switch (conf.misc_default_system) {
745 case 0: machine.SetMode(machine.GetDesiredMode()); break; // Auto
746 case 1: machine.SetMode(Machine::NTSC); break; // NTSC
747 case 2: machine.SetMode(Machine::PAL); break; // PAL
748 case 3: machine.SetMode(Machine::NTSC); break; // Famicom
749 case 4: machine.SetMode(Machine::PAL); break; // Dendy
750 }
751 }
752
nst_set_rewind(int direction)753 void nst_set_rewind(int direction) {
754 // Set the rewinder backward or forward
755 switch (direction) {
756 case 0: Rewinder(emulator).SetDirection(Rewinder::BACKWARD); break;
757 case 1: Rewinder(emulator).SetDirection(Rewinder::FORWARD); break;
758 default: break;
759 }
760 }
761
nst_state_save(const char * filename)762 void nst_state_save(const char *filename) {
763 // Save a state by filename
764 Machine machine(emulator);
765
766 std::ofstream statefile(filename, std::ifstream::out|std::ifstream::binary);
767
768 if (statefile.is_open()) { machine.SaveState(statefile, Nes::Api::Machine::NO_COMPRESSION); }
769 fprintf(stderr, "State Saved: %s\n", filename);
770 nst_video_print("State Saved", 8, 212, 2, true);
771 }
772
nst_state_load(const char * filename)773 void nst_state_load(const char *filename) {
774 // Load a state by filename
775 Machine machine(emulator);
776
777 std::ifstream statefile(filename, std::ifstream::in|std::ifstream::binary);
778
779 if (statefile.is_open()) { machine.LoadState(statefile); }
780 fprintf(stderr, "State Loaded: %s\n", filename);
781 nst_video_print("State Loaded", 8, 212, 2, true);
782 }
783
nst_state_quicksave(int slot)784 void nst_state_quicksave(int slot) {
785 // Quick Save State
786 if (!loaded) { return; }
787 char slotpath[520];
788 snprintf(slotpath, sizeof(slotpath), "%s_%d.nst", nstpaths.statepath, slot);
789 nst_state_save(slotpath);
790 }
791
792
nst_state_quickload(int slot)793 void nst_state_quickload(int slot) {
794 // Quick Load State
795 if (!loaded) { return; }
796 char slotpath[520];
797 snprintf(slotpath, sizeof(slotpath), "%s_%d.nst", nstpaths.statepath, slot);
798
799 struct stat qloadstat;
800 if (stat(slotpath, &qloadstat) == -1) {
801 fprintf(stderr, "No State to Load\n");
802 nst_video_print("No State to Load", 8, 212, 2, true);
803 return;
804 }
805
806 nst_state_load(slotpath);
807 }
808
nst_timing_runframes()809 int nst_timing_runframes() {
810 // Calculate how many emulation frames to run
811 if (ffspeed) { return conf.timing_ffspeed; }
812 return 1;
813 }
814
nst_timing_set_ffspeed()815 void nst_timing_set_ffspeed() {
816 // Set the framerate to the fast-forward speed
817 ffspeed = true;
818 audio_set_speed(conf.timing_ffspeed);
819 }
820
nst_timing_set_default()821 void nst_timing_set_default() {
822 // Set the framerate to the default
823 ffspeed = false;
824 audio_set_speed(1);
825 }
826
nst_reset(bool hardreset)827 void nst_reset(bool hardreset) {
828 // Reset the machine (soft or hard)
829 Machine machine(emulator);
830 Fds fds(emulator);
831 machine.SetRamPowerState(conf.misc_power_state);
832 machine.Reset(hardreset);
833
834 // Set the FDS disk to defaults
835 fds.EjectDisk();
836 fds.InsertDisk(0, 0);
837 }
838
nst_emuloop()839 void nst_emuloop() {
840 // Main Emulation Loop
841 if (NES_SUCCEEDED(Rewinder(emulator).Enable(true))) {
842 Rewinder(emulator).EnableSound(true);
843 }
844
845 if (playing) {
846 // Pulse the turbo buttons
847 nst_input_turbo_pulse(cNstPads);
848
849 // Execute frames
850 for (int i = 0; i < nst_timing_runframes(); i++) {
851 emulator.Execute(cNstVideo, cNstSound, cNstPads);
852 }
853 }
854 }
855
nst_unload()856 void nst_unload() {
857 // Remove the cartridge and shut down the NES
858 Machine machine(emulator);
859
860 // Power down the NES
861 machine.Power(false);
862
863 // Remove the cartridge
864 machine.Unload();
865 }
866
nst_pause()867 void nst_pause() {
868 // Pauses the game
869 if (playing) {
870 audio_pause();
871 audio_deinit();
872 }
873
874 playing = false;
875 }
876
nst_play()877 void nst_play() {
878 // Play the game
879 if (playing) { return; }
880
881 video_init();
882 audio_init();
883 nst_input_init();
884 nst_cheats_init(nstpaths.cheatpath);
885 nst_homebrew_init();
886
887 cNstVideo = new Video::Output;
888 cNstSound = new Sound::Output;
889 cNstPads = new Input::Controllers;
890
891 audio_set_params(cNstSound);
892 audio_unpause();
893
894 if (nst_nsf()) {
895 Nsf nsf(emulator);
896 nsf.PlaySong();
897 video_disp_nsf();
898 }
899
900 playing = true;
901 }
902
nst_load(const char * filename)903 int nst_load(const char *filename) {
904 // Load a Game ROM
905 Machine machine(emulator);
906 Nsf nsf(emulator);
907 Sound sound(emulator);
908 Nes::Result result;
909 char *rom;
910 int romsize;
911 char patchname[512];
912
913 // Pause play before pulling out a cartridge
914 if (playing) { nst_pause(); }
915
916 // Pull out any inserted cartridges
917 if (loaded) { nst_unload(); }
918 nst_video_print_time("", false);
919
920 // Check if the file is an archive and select the file within
921 char reqfile[256]; // Requested file inside the archive
922 if (nst_archive_select(filename, reqfile, sizeof(reqfile))) {
923 // Extract the contents
924 nst_archive_open(filename, &rom, &romsize, reqfile);
925
926 // Convert the malloc'd char* to an istream
927 std::string rombuf(rom, romsize);
928 std::istringstream file(rombuf);
929 free(rom);
930
931 result = machine.Load(file, nst_default_system());
932 }
933 else { // Otherwise just load the file
934 std::ifstream file(filename, std::ios::in|std::ios::binary);
935
936 // Set the file paths
937 nst_set_paths(filename);
938
939 if (nst_find_patch(patchname, sizeof(patchname), filename)) { // Load with a patch if there is one
940 std::ifstream pfile(patchname, std::ios::in|std::ios::binary);
941 Machine::Patch patch(pfile, false);
942 result = machine.Load(file, nst_default_system(), patch);
943 }
944 else { result = machine.Load(file, nst_default_system()); }
945 }
946
947 if (NES_FAILED(result)) {
948 char errorstring[32];
949 switch (result) {
950 case Nes::RESULT_ERR_INVALID_FILE:
951 snprintf(errorstring, sizeof(errorstring), "Error: Invalid file");
952 break;
953
954 case Nes::RESULT_ERR_OUT_OF_MEMORY:
955 snprintf(errorstring, sizeof(errorstring), "Error: Out of Memory");
956 break;
957
958 case Nes::RESULT_ERR_CORRUPT_FILE:
959 snprintf(errorstring, sizeof(errorstring), "Error: Corrupt or Missing File");
960 break;
961
962 case Nes::RESULT_ERR_UNSUPPORTED_MAPPER:
963 snprintf(errorstring, sizeof(errorstring), "Error: Unsupported Mapper");
964 break;
965
966 case Nes::RESULT_ERR_MISSING_BIOS:
967 snprintf(errorstring, sizeof(errorstring), "Error: Missing Fds BIOS");
968 break;
969
970 default:
971 snprintf(errorstring, sizeof(errorstring), "Error: %d", result);
972 break;
973 }
974
975 fprintf(stderr, "%s\n", errorstring);
976
977 return 0;
978 }
979
980 // Deal with any DIP Switches
981 nst_dipswitch();
982
983 // Set the region
984 nst_set_region();
985
986 if (machine.Is(Machine::DISK)) {
987 Fds fds(emulator);
988 fds.InsertDisk(0, 0);
989 nst_fds_info();
990 }
991
992 // Check if this is an NSF
993 if (nst_nsf()) { nsf.StopSong(); }
994
995 // Check if sound distortion should be enabled
996 sound.SetGenie(conf.misc_genie_distortion);
997
998 // Load the custom palette
999 nst_palette_load(nstpaths.palettepath);
1000
1001 // Set the RAM's power state
1002 machine.SetRamPowerState(conf.misc_power_state);
1003
1004 // Power on
1005 machine.Power(true);
1006
1007 loaded = 1;
1008 return loaded;
1009 }
1010