/* Nestopia / Linux Original Port by R. Belmont Resurrected by R. Danbrook main.cpp - main file */ #include #include #include #include #include #include #include #include #include #include #include #include #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/NstApiNsf.hpp" #include "core/api/NstApiMovie.hpp" #include "core/api/NstApiFds.hpp" #include "core/api/NstApiRewinder.hpp" #include "core/api/NstApiCartridge.hpp" #include "core/api/NstApiCheats.hpp" #include "core/NstCrc32.hpp" #include "core/NstChecksum.hpp" #include "core/NstXml.hpp" #include "oss.h" #include "settings.h" #include "auxio.h" #include "input.h" #include "controlconfig.h" #include "cheats.h" #include "seffect.h" #include "main.h" #include "GL/glu.h" #define NST_VERSION "1.41" extern "C" { #include #include "callbacks.h" } #include "uihelp.h" using namespace Nes::Api; using namespace LinuxNst; // base class, all interfaces derives from this Emulator emulator; // forward declaration void SetupVideo(); void SetupSound(); void SetupInput(); SDL_Surface *screen; static short lbuf[48000]; static long exholding[48000*2]; static unsigned short keys[65536]; static int updateok, playing = 0, cur_width, cur_Rwidth, cur_height, cur_Rheight, loaded = 0, framerate; static int nst_quit = 0, nsf_mode = 0, state_save = 0, state_load = 0, movie_save = 0, movie_load = 0, movie_stop = 0; static int schedule_stop = 0; static SDL_Joystick *joy[10]; extern int lnxdrv_apimode; extern GtkWidget *mainwindow; static char savename[512], capname[512], gamebasename[512]; static char caption[128]; char rootname[512], lastarchname[512]; static InputDefT *ctl_defs; static Video::Output *cNstVideo; static Sound::Output *cNstSound; static Input::Controllers *cNstPads; static Cartridge::Database::Entry dbentry; static Settings *sSettings; static CheatMgr *sCheatMgr; static bool using_opengl = false; static bool linear_filter = false; static GLuint screenTexID = 0; static int gl_w, gl_h; static void *intbuffer; // intermediate buffer: the NST engine draws into this, and we may blit it // either as-is or in other ways // get the favored system selected by the user static Machine::FavoredSystem get_favored_system(void) { switch (sSettings->GetPrefSystem()) { 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; } // convert a number into the next highest power of 2 static int powerOfTwo( const int value ) { int result = 1; while ( result < value ) result <<= 1; return result; } // init OpenGL and set up for blitting static void opengl_init_structures() { int scalefactor = sSettings->GetScaleAmt() + 1; glEnable( GL_TEXTURE_2D ); gl_w = powerOfTwo(cur_width); gl_h = powerOfTwo(cur_height); glGenTextures( 1, &screenTexID ) ; glBindTexture( GL_TEXTURE_2D, screenTexID ) ; glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST) ; glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ; glViewport( 0,0, screen->w, screen->h ); glDisable( GL_DEPTH_TEST ); glDisable( GL_ALPHA_TEST ); glDisable( GL_BLEND ); glDisable( GL_LIGHTING ); glDisable( GL_TEXTURE_3D_EXT ); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glOrtho(0.0, (GLdouble)screen->w, (GLdouble)screen->h, 0.0, 0.0, -1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } // tears down OpenGL when it's no longer needed static void opengl_cleanup() { if (using_opengl) { SDL_FreeSurface( screen ); glDeleteTextures( 1, &screenTexID ); if (intbuffer) { free(intbuffer); intbuffer = NULL; } } } // blit the image using OpenGL static void opengl_blit() { double gl_blit_width = (double)cur_width / (double)gl_w; double gl_blit_height = (double)cur_height / (double)gl_h; glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, gl_w, gl_h, 0, GL_BGRA, GL_UNSIGNED_BYTE, intbuffer ) ; glBegin( GL_QUADS ) ; glTexCoord2f(gl_blit_width, gl_blit_height); glVertex2i(cur_Rwidth, cur_Rheight); glTexCoord2f(gl_blit_width, 0.0f ); glVertex2i(cur_Rwidth, 0); glTexCoord2f(0.0f, 0.0f ); glVertex2i(0, 0); glTexCoord2f(0.0f, gl_blit_height); glVertex2i(0, cur_Rheight); glEnd(); SDL_GL_SwapBuffers(); } // ******************* // emulation callbacks // ******************* long Linux_LockScreen(void*& ptr) { if (using_opengl) // have the engine blit directly to our memory buffer { ptr = intbuffer; return gl_w*4; } else { if (SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); ptr = intbuffer; } return screen->pitch; } void Linux_UnlockScreen(void*) { if (using_opengl) { opengl_blit(); } else { unsigned short *src, *dst1; unsigned int *srcL, *dst1L; int x, y, vdouble; // is this a software x2 expand for NTSC mode? vdouble = 0; if (screen->h == (cur_height<<1)) { vdouble = 1; } if (screen->format->BitsPerPixel == 16) { src = (UINT16 *)intbuffer; dst1 = (UINT16 *)screen->pixels; for (y = 0; y < cur_Rheight; y++) { memcpy(dst1, src, cur_width*screen->format->BitsPerPixel/8); if (vdouble) { if (!(y & 1)) { src += cur_width; } } else { src += cur_width; } dst1 += screen->pitch/2; } } else if (screen->format->BitsPerPixel == 32) { srcL = (UINT32 *)intbuffer; dst1L = (UINT32 *)screen->pixels; for (y = 0; y < cur_Rheight; y++) { memcpy(dst1L, srcL, cur_width*screen->format->BitsPerPixel/8); if (vdouble) { if (!(y & 1)) { srcL += cur_width; } } else { srcL += cur_width; } dst1L += screen->pitch/4; } } else printf("ERROR: Unknown pixel format %d bpp\n", screen->format->BitsPerPixel); if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_Flip(screen); } } // called right before Nestopia is about to write pixels static bool NST_CALLBACK VideoLock(void* userData, Video::Output& video) { if (nsf_mode) return false; video.pitch = Linux_LockScreen( 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) { if (nsf_mode) return; Linux_UnlockScreen( video.pixels ); } // callback to feed a frame of audio to the output driver void nst_do_frame(unsigned long dwSamples, signed short *out) { int s; short *pbufL = (short *)lbuf; short *outbuf; long dtl, dtr; outbuf = out; if (nsf_mode) { Nsf nsf( emulator ); if (!nsf.IsPlaying()) { for (s = 0; s < dwSamples; s++) { *out++ = 0; *out++ = 0; } return; } } if (sSettings->GetUseExciter()) { int j = 0; if (!sSettings->GetStereo()) { // exciter can't handle "hot" samples, so // tone them down a bit for (s = 0; s < dwSamples; s++) { exholding[j++] = (*pbufL)/4; exholding[j++] = (*pbufL++)/4; } } else // stereo { for (s = 0; s < dwSamples; s++) { exholding[j++] = (*pbufL++)/4; exholding[j++] = (*pbufL++)/4; } seffect_ex_process((long *)exholding, dwSamples); j = 0; for (s = 0; s < dwSamples; s++) { dtr = exholding[j++]; dtl = exholding[j++]; if(dtl>0x7fffL) dtl=0x7fffL; else if(dtl<-0x7fffL) dtl=-0x7fffL; if(dtr>0x7fffL) dtr=0x7fffL; else if(dtr<-0x7fffL) dtr=-0x7fffL; *out++ = (dtr & 0xffff); *out++ = (dtl & 0xffff); } } } else { if (!sSettings->GetStereo()) { for (s = 0; s < dwSamples; s++) { *out++ = *pbufL; *out++ = *pbufL++; } } else // stereo { for (s = 0; s < dwSamples; s++) { *out++ = *pbufL++; *out++ = *pbufL++; } } } if (sSettings->GetUseSurround()) { seffect_surround_lite_process(outbuf, dwSamples*4); } updateok = 1; } // do a "partial" shutdown static void nst_unload2(void) { Machine machine(emulator); // if nothing's loaded, do nothing if (!loaded) { return; } // power down the emulated NES std::cout << "Powering down the emulated machine\n"; machine.Power(false); // unload the cart machine.Unload(); // erase any cheats sCheatMgr->Unload(); } static void nst_unload(void) { nst_unload2(); UIHelp_Unload(); } // if we're in full screen, kills video temporarily static void kill_video_if_fs(void) { if (sSettings->GetFullscreen()) { if (SDL_NumJoysticks() > 0) { int i; for (i = 0; i < SDL_NumJoysticks(); i++) { // we only? support 10 joysticks if (i < 10) { SDL_JoystickClose(joy[i]); } } SDL_JoystickEventState(SDL_ENABLE); // turn on regular updates } SDL_ShowCursor(1); SDL_Quit(); } } // returns if we're currently playing a game or NSF bool NstIsPlaying() { return playing; } // shuts everything down void NstStopPlaying() { if (playing) { int i; // kill any movie auxio_do_movie_stop(); // close video sanely if (!nsf_mode) { SDL_FreeSurface(screen); opengl_cleanup(); if (intbuffer) { free(intbuffer); intbuffer = NULL; } } // get machine interface... Machine machine(emulator); // shut down the sound system too m1sdr_PlayStop(); m1sdr_Exit(); // flush the sound buffer memset(lbuf, 0, sizeof(lbuf)); // kill SDL if (SDL_NumJoysticks() > 0) { for (i = 0; i < SDL_NumJoysticks(); i++) { // we only? support 10 joysticks if (i < 10) { SDL_JoystickClose(joy[i]); } } SDL_JoystickEventState(SDL_ENABLE); // turn on regular updates } SDL_ShowCursor(1); SDL_Quit(); } // show main window gtk_widget_show(mainwindow); playing = 0; } #define CRg(rg) (sizeof(rg) / sizeof(rg[0])) std::string svst[2]; // generate the filename for quicksave files std::string StrQuickSaveFile(int isvst) { const char *home = getenv("HOME"); if (!home) { std::cout << "couldn't get home directory\n"; return ""; } std::ostringstream ossFile; ossFile << home; ossFile << "/.nestopia/qsave"; if (mkdir(ossFile.str().c_str(), 0777) && errno != EEXIST) { std::cout << "couldn't make qsave directory: " << errno << "\n"; return ""; } ossFile << "/" << std::setbase(16) << std::setfill('0') << std::setw(8) << basename(gamebasename) << std::string("_") << isvst << ".nst"; return ossFile.str(); } // save state to memory slot static void QuickSave(int isvst) { std::string strFile = StrQuickSaveFile(isvst); if (strFile.empty()) return; Machine machine( emulator ); std::ofstream os(strFile.c_str()); // use "NO_COMPRESSION" to make it easier to hack save states Nes::Result res = machine.SaveState(os, Nes::Api::Machine::USE_COMPRESSION); } // restore state from memory slot static void QuickLoad(int isvst) { std::string strFile = StrQuickSaveFile(isvst); if (strFile.empty()) return; Machine machine( emulator ); std::ifstream is(strFile.c_str()); Nes::Result res = machine.LoadState(is); } // start playing void NstPlayGame(void) { // hide main window gtk_widget_hide(mainwindow); // process pending gtk events while (gtk_events_pending()) { gtk_main_iteration(); } // initialization SetupVideo(); SetupSound(); SetupInput(); // apply any cheats into the engine sCheatMgr->Enable(); cNstVideo = new Video::Output; cNstSound = new Sound::Output; cNstPads = new Input::Controllers; cNstSound->samples[0] = lbuf; cNstSound->length[0] = sSettings->GetRate()/framerate; cNstSound->samples[1] = NULL; cNstSound->length[1] = 0; SDL_WM_SetCaption(caption, caption); m1sdr_SetSamplesPerTick(cNstSound->length[0]); updateok = 0; schedule_stop = 0; playing = 1; } // start playing an NSF file void NstPlayNsf(void) { Nsf nsf( emulator ); nsf.PlaySong(); } // stop playing an NSF file void NstStopNsf(void) { Nsf nsf( emulator ); nsf.StopSong(); // clear the audio buffer memset(lbuf, 0, sizeof(lbuf)); } // schedule a NEStopia quit void NstScheduleQuit(void) { nst_quit = 1; } // launch the controller configurator void NstLaunchConfig(void) { run_configurator(ctl_defs, sSettings->GetConfigItem(), sSettings->GetUseJoypads()); } // toggle fullscreen state static void ToggleFullscreen() { if (SDL_NumJoysticks() > 0) { for (int i = 0; i < SDL_NumJoysticks(); i++) { // we only? support 10 joysticks if (i < 10) { SDL_JoystickClose(joy[i]); } } SDL_JoystickEventState(SDL_ENABLE); // turn on regular updates } SDL_ShowCursor(1); SDL_FreeSurface(screen); opengl_cleanup(); if (intbuffer) { free(intbuffer); intbuffer = NULL; } SDL_Quit(); sSettings->SetFullscreen(sSettings->GetFullscreen()^1); SetupVideo(); lnxdrv_apimode = sSettings->GetSndAPI(); if (lnxdrv_apimode == 0) // the SDL driver needs a harder restart { m1sdr_Exit(); m1sdr_Init(sSettings->GetRate()); m1sdr_SetCallback((void *)nst_do_frame); m1sdr_PlayStart(); } SDL_WM_SetCaption(caption, caption); } // handle input event static void handle_input_event(Input::Controllers *controllers, InputEvtT inevt) { #ifdef DEBUG_INPUT printf("metaevent: %d\n", (int)inevt); #endif switch (inevt) { case MSAVE: movie_save = 1; break; case MLOAD: movie_load = 1; break; case MSTOP: movie_stop = 1; break; case RESET: { Machine machine( emulator ); Fds fds( emulator ); machine.Reset(true); // put the disk system back to disk 0 side 0 fds.EjectDisk(); fds.InsertDisk(0, 0); } break; case FLIP: { Fds fds( emulator ); if (fds.CanChangeDiskSide()) fds.ChangeSide(); } break; case FSCREEN: ToggleFullscreen(); break; case RBACK: Rewinder(emulator).SetDirection(Rewinder::BACKWARD); break; case RFORE: Rewinder(emulator).SetDirection(Rewinder::FORWARD); break; case QSAVE1: QuickSave(0); break; case QLOAD1: QuickLoad(0); break; case QSAVE2: QuickSave(1); break; case QLOAD2: QuickLoad(1); break; case SAVE: state_save = 1; break; case LOAD: state_load = 1; break; case STOP: schedule_stop = 1; break; case EXIT: schedule_stop = 1; nst_quit = 1; break; case COIN1: controllers->vsSystem.insertCoin |= Input::Controllers::VsSystem::COIN_1; break; case COIN2: controllers->vsSystem.insertCoin |= Input::Controllers::VsSystem::COIN_2; break; default: assert(0); } } // match input event; if pind is not NULL, continue after it // on is set if the key/button is hit, clear if key is up/axis centered static const InputDefT *nst_match(const SDL_Event &evt, const InputDefT *pind, bool &on) { pind = (pind == NULL) ? ctl_defs : pind + 1; bool match = false; for (; pind->player != -1 && !match; ++pind) { switch (evt.type) { case SDL_KEYDOWN: case SDL_KEYUP: #ifdef DEBUG_INPUT if (evt.type == SDL_KEYDOWN) { printf("key is down: sym %x mod %x vs sym %x mod %x\n", evt.key.keysym.sym, evt.key.keysym.mod, pind->evt.key.keysym.sym, pind->evt.key.keysym.mod); } #endif match = (pind->evt.type == SDL_KEYDOWN && pind->evt.key.keysym.sym == evt.key.keysym.sym); // do better mod checking if ((pind->evt.key.keysym.mod & evt.key.keysym.mod) != pind->evt.key.keysym.mod) { match = 0; } on = (evt.key.state == SDL_PRESSED); break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: match = pind->evt.type == SDL_JOYBUTTONDOWN && pind->evt.jbutton.which == evt.jbutton.which && pind->evt.jbutton.button == evt.jbutton.button; on = (evt.jbutton.state == SDL_PRESSED); break; case SDL_JOYAXISMOTION: { const Sint16 nvalue = (abs(evt.jaxis.value) < DEADZONE) ? 0 : (evt.jaxis.value < 0) ? -1 : 1; // normalized axis direction match = pind->evt.type == evt.type && pind->evt.jaxis.which == evt.jaxis.which && pind->evt.jaxis.axis == evt.jaxis.axis && (nvalue == 0 || pind->evt.jaxis.value == nvalue); on = nvalue != 0; break; } } if (match) { return pind; } } return NULL; } // try to dispatch an input event static void nst_dispatch(Input::Controllers *controllers, const SDL_Event &evt) { bool on; const InputDefT *pind = NULL; controllers->vsSystem.insertCoin = 0; while ((pind = nst_match(evt, pind, on)) != NULL) { if (on) { if (pind->player == 0) { handle_input_event(controllers, (InputEvtT)pind->codeout); } else { #ifdef DEBUG_INPUT printf("player %d event: codeout %x\n", pind->player, pind->codeout); #endif controllers->pad[pind->player - 1].buttons |= pind->codeout; } } else { if (pind->player != 0) { controllers->pad[pind->player - 1].buttons &= ~pind->codeout; } } } } // logging callback called by the core static void NST_CALLBACK DoLog(void *userData, const char *string, ulong length) { fprintf(stderr, "%s", string); } // for various file operations, usually called during image file load, power on/off and reset static void NST_CALLBACK DoFileIO(void *userData, User::File& file) { unsigned char *compbuffer; int compsize, compoffset; char mbstr[512]; switch (file.GetAction()) { case User::File::LOAD_ROM: wcstombs(mbstr, file.GetName(), 511); if (auxio_load_archive(lastarchname, &compbuffer, &compsize, &compoffset, (const char *)mbstr)) { file.SetContent((const void*)&compbuffer[compoffset], (ulong)compsize); free(compbuffer); } 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: wcstombs(mbstr, file.GetName(), 511); if (auxio_load_archive(lastarchname, &compbuffer, &compsize, &compoffset, (const char *)mbstr)) { int chan, bits, rate; if (!strncmp((const char *)compbuffer, "RIFF", 4)) { chan = compbuffer[20] | compbuffer[21]<<8; rate = compbuffer[24] | compbuffer[25]<<8 | compbuffer[26]<<16 | compbuffer[27]<<24; bits = compbuffer[34] | compbuffer[35]<<8; // std::cout << "WAV has " << chan << " chan, " << bits << " bits per sample, rate = " << rate << "\n"; file.SetSampleContent((const void*)&compbuffer[compoffset], (ulong)compsize, (chan == 2) ? true : false, bits, rate); } free(compbuffer); } 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 { int size; FILE *f; f = fopen(savename, "rb"); if (!f) { return; } fseek(f, 0, SEEK_END); size = ftell(f); fclose(f); std::ifstream batteryFile( 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( 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]; sprintf(fdsname, "%s.ups", rootname); std::ifstream batteryFile( fdsname, std::ifstream::in|std::ifstream::binary ); // no ups, look for ips if (!batteryFile.is_open()) { sprintf(fdsname, "%s.ips", rootname); 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]; sprintf(fdsname, "%s.ups", rootname); std::ofstream fdsFile( fdsname, std::ifstream::out|std::ifstream::binary ); if (fdsFile.is_open()) file.GetPatchContent( User::File::PATCH_UPS, fdsFile ); break; } } } static void cleanup_after_io(void) { gtk_main_iteration_do(FALSE); gtk_main_iteration_do(FALSE); gtk_main_iteration_do(FALSE); if (sSettings->GetFullscreen()) { SetupVideo(); } } int main(int argc, char *argv[]) { static SDL_Event event; int i; void* userData = (void*) 0xDEADC0DE; char dirname[1024], savedirname[1024], *home; // read the key/controller mapping ctl_defs = parse_input_file(); if (!ctl_defs) { std::cout << "~/.nestopia/nstcontrols not found, creating a new one.\n"; // make sure the output directory exists home = getenv("HOME"); sprintf(dirname, "%s/.nestopia/", home); sprintf(savedirname, "%ssave/", dirname); mkdir(dirname, 0700); mkdir(savedirname, 0700); create_input_file(); ctl_defs = parse_input_file(); if (!ctl_defs) { std::cout << "Reading ~/.nestopia/nstcontrols file: FAIL\n"; return -1; } } playing = 0; intbuffer = NULL; auxio_init(); sSettings = new Settings; sCheatMgr = new CheatMgr; UIHelp_Init(argc, argv, sSettings, sCheatMgr); // setup video lock/unlock callbacks Video::Output::lockCallback.Set( VideoLock, userData ); Video::Output::unlockCallback.Set( VideoUnlock, userData ); // misc callbacks (for others, see NstApuUser.hpp) User::fileIoCallback.Set( DoFileIO, userData ); User::logCallback.Set( DoLog, userData ); // try to load the FDS BIOS auxio_set_fds_bios(); // and the NST database auxio_load_db(); // attempt to load and autostart a file specified on the commandline if (argc > 1) { NstLoadGame(argv[1]); if (loaded) { if (nsf_mode) { on_nsfplay_clicked(NULL, NULL); } else { on_playbutton_clicked(NULL, NULL); } } } nst_quit = 0; while (!nst_quit) { if (playing) { if (nsf_mode) { gtk_main_iteration_do(FALSE); } else { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: schedule_stop = 1; break; case SDL_KEYDOWN: case SDL_KEYUP: // ignore num lock, caps lock, and "mode" (whatever that is) event.key.keysym.mod = (SDLMod)((int)event.key.keysym.mod & (~(KMOD_NUM | KMOD_CAPS | KMOD_MODE))); // (intentional fallthrough) case SDL_JOYAXISMOTION: case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: nst_dispatch(cNstPads, event); break; } } if (NES_SUCCEEDED(Rewinder(emulator).Enable(true))) { Rewinder(emulator).EnableSound(true); } } m1sdr_TimeCheck(); if (updateok) { emulator.Execute(cNstVideo, cNstSound, cNstPads); updateok = 0; } if (state_save) { kill_video_if_fs(); auxio_do_state_save(); state_save = 0; cleanup_after_io(); } if (state_load) { kill_video_if_fs(); auxio_do_state_load(); state_load = 0; cleanup_after_io(); } if (movie_load) { kill_video_if_fs(); auxio_do_movie_load(); movie_load = 0; cleanup_after_io(); } if (movie_save) { kill_video_if_fs(); auxio_do_movie_save(); movie_load = 0; cleanup_after_io(); } if (movie_stop) { movie_stop = 0; auxio_do_movie_stop(); } if (schedule_stop) { NstStopPlaying(); } } else { gtk_main_iteration_do(FALSE); } } nst_unload2(); auxio_shutdown(); delete sSettings; delete sCheatMgr; write_output_file(ctl_defs); free(ctl_defs); return 0; } void SetupVideo() { // renderstate structure Video::RenderState renderState; Machine machine( emulator ); Cartridge::Database database( emulator ); Video::RenderState::Filter filter; int scalefactor = sSettings->GetScaleAmt() + 1; int i; // init SDL if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_JOYSTICK)) { std::cout << "Unable to init SDL\n"; return; } // figure out the region framerate = 60; if (sSettings->GetVideoMode() == 2) // force PAL { machine.SetMode(Machine::PAL); framerate = 50; } else if (sSettings->GetVideoMode() == 1) // force NTSC { machine.SetMode(Machine::NTSC); } else // auto { if (database.IsLoaded()) { if (dbentry.GetSystem() == Cartridge::Profile::System::NES_PAL) { machine.SetMode(Machine::PAL); framerate = 50; } else { machine.SetMode(Machine::NTSC); } } else { machine.SetMode(machine.GetDesiredMode()); } } // we don't create a window in NSF mode if (nsf_mode) { return; } if (SDL_NumJoysticks() > 0) { for (i = 0; i < SDL_NumJoysticks(); i++) { // we only? support 10 joysticks if (i < 10) { joy[i] = SDL_JoystickOpen(i); } } SDL_JoystickEventState(SDL_ENABLE); // turn on regular updates } // compute the major video parameters from the scaler type and scale factor switch (sSettings->GetScale()) { case 0: // None (no scaling unless OpenGL) if (sSettings->GetRenderType() == 0) { if (scalefactor > 1) { std::cout << "Warning: raw scale factors > 1 not allowed with pure software, use OpenGL\n"; } cur_width = cur_Rwidth = Video::Output::WIDTH; cur_height = cur_Rheight = Video::Output::HEIGHT; } else { cur_width = Video::Output::WIDTH; cur_height = Video::Output::HEIGHT; cur_Rwidth = cur_width * scalefactor; cur_Rheight = cur_height * scalefactor; } filter = Video::RenderState::FILTER_NONE; break; case 1: // NTSC if (sSettings->GetRenderType() == 0) { if (scalefactor > 1) { std::cout << "Warning: NTSC scale factors > 1 not allowed with pure software - use OpenGL\n"; } scalefactor = 1; } cur_width = Video::Output::NTSC_WIDTH; cur_Rwidth = cur_width * scalefactor; cur_height = Video::Output::HEIGHT; cur_Rheight = cur_height * 2 * scalefactor; filter = Video::RenderState::FILTER_NTSC; break; case 2: // scale x if (scalefactor == 4) { std::cout << "Warning: Scale x only allows scale factors of 3 or less\n"; scalefactor = 3; // there is no scale4x } cur_width = cur_Rwidth = Video::Output::WIDTH * scalefactor; cur_height = cur_Rheight = Video::Output::HEIGHT * scalefactor; switch (scalefactor) { case 2: filter = Video::RenderState::FILTER_SCALE2X; break; case 3: filter = Video::RenderState::FILTER_SCALE3X; break; default: filter = Video::RenderState::FILTER_NONE; break; } break; case 3: // scale HQx cur_width = cur_Rwidth = Video::Output::WIDTH * scalefactor; cur_height = cur_Rheight = Video::Output::HEIGHT * scalefactor; switch (scalefactor) { case 2: filter = Video::RenderState::FILTER_HQ2X; break; case 3: filter = Video::RenderState::FILTER_HQ3X; break; case 4: filter = Video::RenderState::FILTER_HQ4X; break; default: filter = Video::RenderState::FILTER_NONE; break; } break; } int eFlags = SDL_HWSURFACE; using_opengl = (sSettings->GetRenderType() > 0); linear_filter = (sSettings->GetRenderType() == 2); if (using_opengl) { SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); eFlags = SDL_OPENGL; } if (sSettings->GetFullscreen()) { screen = SDL_SetVideoMode(cur_Rwidth, cur_Rheight, 16, SDL_ANYFORMAT | SDL_DOUBLEBUF | SDL_FULLSCREEN | eFlags); } else { screen = SDL_SetVideoMode(cur_Rwidth, cur_Rheight, 16, SDL_ANYFORMAT | SDL_DOUBLEBUF | eFlags); } if (!screen) { std::cout << "SDL couldn't set video mode\n"; exit(-1); } renderState.filter = filter; renderState.width = cur_width; renderState.height = cur_height; // example configuration if (using_opengl) { opengl_init_structures(); renderState.bits.count = 32; renderState.bits.mask.r = 0x00ff0000; renderState.bits.mask.g = 0x0000ff00; renderState.bits.mask.b = 0x000000ff; } else { renderState.bits.count = screen->format->BitsPerPixel; renderState.bits.mask.r = screen->format->Rmask; renderState.bits.mask.g = screen->format->Gmask; renderState.bits.mask.b = screen->format->Bmask; } // allocate the intermediate render buffer intbuffer = malloc(renderState.bits.count * renderState.width * renderState.height); // acquire the video interface Video video( emulator ); // set the sprite limit video.EnableUnlimSprites(sSettings->GetSprlimit() ? false : true); // set up the NTSC type switch (sSettings->GetNtscMode()) { case 0: // composite video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP); video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP); video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP); video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP); video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP); break; case 1: // S-Video video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO); video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO); video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO); video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO); video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO); break; case 2: // RGB video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB); video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB); video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB); video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB); video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB); break; } // set the render state if (NES_FAILED(video.SetRenderState( renderState ))) { std::cout << "Nestopia core rejected render state\n"; ::exit(0); } if (sSettings->GetFullscreen()) { SDL_ShowCursor(0); } } // initialize sound going into the game void SetupSound() { // acquire interface Sound sound( emulator ); lnxdrv_apimode = sSettings->GetSndAPI(); m1sdr_Init(sSettings->GetRate()); m1sdr_SetCallback((void *)nst_do_frame); m1sdr_PlayStart(); // init DSP module seffect_init(sSettings); // example configuration (these are the default values) sound.SetSampleBits( 16 ); sound.SetSampleRate(sSettings->GetRate()); sound.SetVolume(Sound::ALL_CHANNELS, sSettings->GetVolume()); if (sSettings->GetStereo()) { sound.SetSpeaker( Sound::SPEAKER_STEREO ); } else { sound.SetSpeaker( Sound::SPEAKER_MONO ); } } // initialize input going into the game void SetupInput() { // connect a standard NES pad onto the first port Input(emulator).ConnectController( 0, Input::PAD1 ); // connect a standard NES pad onto the second port too Input(emulator).ConnectController( 1, Input::PAD2 ); } void configure_savename( const char* filename ) { int i = 0; char savedir[1024], *homedir; homedir = getenv("HOME"); sprintf(savedir, "%s/.nestopia/save/", homedir); strcpy(savename, filename); // strip the . and extention off the filename for saving for (i = strlen(savename)-1; i > 0; i--) { if (savename[i] == '.') { savename[i] = '\0'; break; } } strcpy(capname, savename); strcpy(gamebasename, savename); // strip the path off the savename to get the filename only for (i = strlen(capname)-1; i > 0; i--) { if (capname[i] == '/') { strcpy(capname, &capname[i+1]); break; } } //Save to the home directory instead of the location of the rom strcat(savedir, capname); strcpy(savename, savedir); // also generate the window caption sprintf(caption, "Nestopia Undead %s: %s", NST_VERSION, capname); strcpy(rootname, savename); strcat(savename, ".sav"); } // try and find a patch for the game being loaded static int find_patch(char *patchname) { FILE *f; // did the user turn off auto softpatching? if (sSettings->GetSoftPatch() == 0) { return 0; } snprintf(patchname, 511, "%s.ips", gamebasename); if ((f = fopen(patchname, "rb")) != NULL) { fclose(f); return 1; } else { snprintf(patchname, 511, "%s.ups", gamebasename); if ((f = fopen(patchname, "rb")) != NULL) { fclose(f); return 1; } } return 0; } // load a game or NES music file void NstLoadGame(const char* filename) { // acquire interface to machine Cartridge::Database database( emulator ); Machine machine( emulator ); Nsf nsf( emulator ); Nes::Result result; unsigned char *compbuffer; int compsize, wascomp, compoffset; char gamename[512], patchname[512]; if (nsf_mode) { Nsf nsf( emulator ); nsf.StopSong(); // clear the audio buffer memset(lbuf, 0, sizeof(lbuf)); playing = 0; } // unload if necessary nst_unload(); // (re)configure savename configure_savename(filename); // check if it's an archive wascomp = 0; if (auxio_load_archive(filename, &compbuffer, &compsize, &compoffset, NULL, gamename)) { std::istrstream file((char *)compbuffer+compoffset, compsize); wascomp = 1; strncpy(lastarchname, filename, 511); configure_savename(gamename); if (database.IsLoaded()) { dbentry = database.FindEntry((void *)&compbuffer[compoffset], compsize, get_favored_system()); } if (find_patch(patchname)) { std::ifstream pfile(patchname, std::ios::in|std::ios::binary); Machine::Patch patch(pfile, false); // load game and softpatch result = machine.Load( file, get_favored_system(), patch ); } else { // load game result = machine.Load( file, get_favored_system() ); } } else { FILE *f; int length; unsigned char *buffer; // this is a little ugly if (database.IsLoaded()) { f = fopen(filename, "rb"); if (!f) { loaded = 0; UIHelp_Unload(); return; } fseek(f, 0, SEEK_END); length = ftell(f); fseek(f, 0, SEEK_SET); buffer = (unsigned char *)malloc(length); fread(buffer, length, 1, f); fclose(f); dbentry = database.FindEntry(buffer, length, get_favored_system()); free(buffer); } configure_savename(filename); // C++ file stream std::ifstream file(filename, std::ios::in|std::ios::binary); if (find_patch(patchname)) { std::ifstream pfile(patchname, std::ios::in|std::ios::binary); Machine::Patch patch(pfile, false); // load game and softpatch result = machine.Load( file, get_favored_system(), patch ); } else { // load game result = machine.Load( file, get_favored_system() ); } } // failed? if (NES_FAILED(result)) { switch (result) { case Nes::RESULT_ERR_INVALID_FILE: std::cout << "Invalid file\n"; break; case Nes::RESULT_ERR_OUT_OF_MEMORY: std::cout << "Out of memory\n"; break; case Nes::RESULT_ERR_CORRUPT_FILE: std::cout << "Corrupt or missing file\n"; break; case Nes::RESULT_ERR_UNSUPPORTED_MAPPER: std::cout << "Unsupported mapper\n"; break; case Nes::RESULT_ERR_MISSING_BIOS: std::cout << "Can't find disksys.rom for FDS game\n"; break; default: std::cout << "Unknown error #" << result << "\n"; break; } return; } // free the buffer if necessary if (wascomp) { free(compbuffer); } // is this an NSF? nsf_mode = (machine.Is(Machine::SOUND)) ? 1 : 0; if (nsf_mode) { // update the UI UIHelp_NSFLoaded(); // initialization SetupVideo(); SetupSound(); SetupInput(); cNstVideo = new Video::Output; cNstSound = new Sound::Output; cNstPads = new Input::Controllers; cNstSound->samples[0] = lbuf; cNstSound->length[0] = sSettings->GetRate()/framerate; cNstSound->samples[1] = NULL; cNstSound->length[1] = 0; m1sdr_SetSamplesPerTick(cNstSound->length[0]); updateok = 0; playing = 1; schedule_stop = 0; } else { UIHelp_GameLoaded(); if (machine.Is(Machine::DISK)) { Fds fds( emulator ); fds.InsertDisk(0, 0); } } // note that something is loaded loaded = 1; // power on machine.Power( true ); // false = power off }