/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ #include #include #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #ifdef USE_THREADS #include #include #include #endif #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #ifndef NOSOUND #ifndef ALSA #include #include #else #include #endif #endif #ifdef JOYSTICK_SUPPORT #include #endif #include "snes9x.h" #include "memmap.h" #include "apu/apu.h" #include "gfx.h" #include "snapshot.h" #include "controls.h" #include "cheats.h" #include "movie.h" #include "display.h" #include "conffile.h" #include "fscompat.h" #ifdef NETPLAY_SUPPORT #include "netplay.h" #endif #ifdef DEBUGGER #include "debug.h" #endif #include "statemanager.h" #ifdef NETPLAY_SUPPORT #ifdef _DEBUG #define NP_DEBUG 2 #endif #endif typedef std::pair strpair_t; ConfigFile::secvec_t keymaps; StateManager stateMan; #define FIXED_POINT 0x10000 #define FIXED_POINT_SHIFT 16 #define FIXED_POINT_REMAINDER 0xffff #define SOUND_BUFFER_SIZE (1024 * 16) #define SOUND_BUFFER_SIZE_MASK (SOUND_BUFFER_SIZE - 1) static volatile bool8 block_signal = FALSE; static volatile bool8 block_generate_sound = FALSE; static const char *sound_device = NULL; static const char *s9x_base_dir = NULL, *rom_filename = NULL, *snapshot_filename = NULL, *play_smv_filename = NULL, *record_smv_filename = NULL; static char default_dir[PATH_MAX + 1]; static const char dirNames[13][32] = { "", // DEFAULT_DIR "", // HOME_DIR "", // ROMFILENAME_DIR "rom", // ROM_DIR "sram", // SRAM_DIR "savestate", // SNAPSHOT_DIR "screenshot", // SCREENSHOT_DIR "spc", // SPC_DIR "cheat", // CHEAT_DIR "patch", // PATCH_DIR "bios", // BIOS_DIR "log", // LOG_DIR "" }; struct SUnixSettings { bool8 JoystickEnabled; bool8 ThreadSound; uint32 SoundBufferSize; uint32 SoundFragmentSize; uint32 rewindBufferSize; uint32 rewindGranularity; }; struct SoundStatus { #ifndef ALSA int sound_fd; uint32 fragment_size; #else snd_pcm_t *pcm_handle; int output_buffer_size; #endif }; static int frame_advance = 0; static SUnixSettings unixSettings; static SoundStatus so; static bool8 rewinding; #ifdef JOYSTICK_SUPPORT static uint8 js_mod[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; static int js_fd[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; static const char *js_device[8] = { "/dev/js0", "/dev/js1", "/dev/js2", "/dev/js3", "/dev/js4", "/dev/js5", "/dev/js6", "/dev/js7" }; static bool8 js_unplugged[8] = { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE }; #endif #ifdef NETPLAY_SUPPORT static uint32 joypads[8]; static uint32 old_joypads[8]; #endif bool8 S9xMapDisplayInput (const char *, s9xcommand_t *); s9xcommand_t S9xGetDisplayCommandT (const char *); char * S9xGetDisplayCommandName (s9xcommand_t); void S9xHandleDisplayCommand (s9xcommand_t, int16, int16); bool S9xDisplayPollButton (uint32, bool *); bool S9xDisplayPollAxis (uint32, int16 *); bool S9xDisplayPollPointer (uint32, int16 *, int16 *); static void NSRTControllerSetup (void); static int make_snes9x_dirs (void); #ifdef JOYSTICK_SUPPORT static void InitJoysticks (void); static bool8 ReadJoysticks (void); void S9xLatchJSEvent(); #endif static long snes9x_log2 (long num) { long n = 0; while (num >>= 1) n++; return (n); } namespace { #if ! defined(NOSOUND) && ! defined(ALSA) class S9xAudioOutput { public: S9xAudioOutput(int fd, uint32 sampleRateHz, bool isThreaded) { m_FD = fd; uint32 bufferSizeMS = unixSettings.SoundBufferSize; // milliseconds // 4 = sizeof(uint16) * STEREO m_BufferSize = int(uint64(sampleRateHz) * bufferSizeMS / 1000 * 4); #if defined(USE_THREADS) m_WrittenSize = 0; m_Thread = pthread_t(); m_isExit = false; if (isThreaded) { m_BufferMutex = PTHREAD_MUTEX_INITIALIZER; m_hasBuffer = PTHREAD_COND_INITIALIZER; if (pthread_create(&m_Thread, NULL, AudioOutputThreadEntry, this)) { return; } } #endif } ~S9xAudioOutput() { #if defined(USE_THREADS) if (m_Thread) { pthread_mutex_lock(&m_BufferMutex); { m_isExit = true; pthread_cond_signal(&m_hasBuffer); } pthread_mutex_unlock(&m_BufferMutex); pthread_join(m_Thread, NULL); pthread_mutex_destroy(&m_BufferMutex); pthread_cond_destroy(&m_hasBuffer); } #endif } void Write(void* data, int size) { #if defined(USE_THREADS) if (m_Thread) { pthread_mutex_lock(&m_BufferMutex); { if (int(m_Buffering.size()) < m_BufferSize) { size_t oldSize = m_Buffering.size(); size_t newSize = oldSize + size; m_WrittenSize = newSize; m_Buffering.resize(newSize); memcpy(&m_Buffering[oldSize], data, size); pthread_cond_signal(&m_hasBuffer); } } pthread_mutex_unlock(&m_BufferMutex); } else #endif { WriteImpl(data, size); } } int GetFreeBufferSize() { #if defined(USE_THREADS) if (m_Thread) { int writtenSize; pthread_mutex_lock(&m_BufferMutex); { writtenSize = m_WrittenSize; } pthread_mutex_unlock(&m_BufferMutex); return m_BufferSize - writtenSize; } else #endif { audio_buf_info info; ioctl(m_FD, SNDCTL_DSP_GETOSPACE, &info); int writtenSize = info.fragsize * info.fragstotal - info.bytes; return std::max(0, m_BufferSize - writtenSize); } } private: void WriteImpl(const void* data, int size) { const char* p = reinterpret_cast(data); while (size > 0) { int result = write(m_FD, p, size); if (result < 0) { return; } p += result; size -= result; } } int m_FD; int m_BufferSize; #if defined(USE_THREADS) pthread_t m_Thread; volatile bool m_isExit; pthread_mutex_t m_BufferMutex; pthread_cond_t m_hasBuffer; std::vector m_PlayingBuffer; std::vector m_Buffering; int m_WrittenSize; // for dynamic rate control static void* AudioOutputThreadEntry(void* arg) { S9xAudioOutput* obj = reinterpret_cast(arg); obj->AudioOutputThread(); return NULL; } void AudioOutputThread() { while (true) { pthread_mutex_lock(&m_BufferMutex); { pthread_cond_wait(&m_hasBuffer, &m_BufferMutex); if (m_isExit) { return; } m_PlayingBuffer.swap(m_Buffering); m_WrittenSize = 0; } pthread_mutex_unlock(&m_BufferMutex); if (! m_PlayingBuffer.empty()) { WriteImpl(&m_PlayingBuffer[0], m_PlayingBuffer.size()); m_PlayingBuffer.resize(0); } } } #endif // USE_THREADS }; S9xAudioOutput* s_AudioOutput = NULL; #endif // NOSOUND } void S9xExtraUsage (void) { /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ S9xMessage(S9X_INFO, S9X_USAGE, "-multi Enable multi cartridge system"); S9xMessage(S9X_INFO, S9X_USAGE, "-carta ROM in slot A (use with -multi)"); S9xMessage(S9X_INFO, S9X_USAGE, "-cartb ROM in slot B (use with -multi)"); S9xMessage(S9X_INFO, S9X_USAGE, ""); #ifdef JOYSTICK_SUPPORT S9xMessage(S9X_INFO, S9X_USAGE, "-nogamepad Disable gamepad reading"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 Specify gamepad device 1"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev2 Specify gamepad device 2"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev3 Specify gamepad device 3"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev4 Specify gamepad device 4"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev5 Specify gamepad device 5"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev6 Specify gamepad device 6"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev7 Specify gamepad device 7"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev8 Specify gamepad device 8"); S9xMessage(S9X_INFO, S9X_USAGE, ""); #endif #ifndef NOSOUND #if defined(USE_THREADS) && !defined(ALSA) S9xMessage(S9X_INFO, S9X_USAGE, "-threadsound Use a separate thread to output sound"); #endif S9xMessage(S9X_INFO, S9X_USAGE, "-buffersize Sound generating buffer size in millisecond"); #ifndef ALSA S9xMessage(S9X_INFO, S9X_USAGE, "-fragmentsize Sound playback buffer fragment size in bytes"); #endif S9xMessage(S9X_INFO, S9X_USAGE, "-sounddev Specify sound device"); S9xMessage(S9X_INFO, S9X_USAGE, ""); #endif S9xMessage(S9X_INFO, S9X_USAGE, "-loadsnapshot Load snapshot file at start"); S9xMessage(S9X_INFO, S9X_USAGE, "-playmovie Start emulator playing the .smv file"); S9xMessage(S9X_INFO, S9X_USAGE, "-recordmovie Start emulator recording the .smv file"); S9xMessage(S9X_INFO, S9X_USAGE, "-dumpstreams Save audio/video data to disk"); S9xMessage(S9X_INFO, S9X_USAGE, "-dumpmaxframes Stop emulator after saving specified number of"); S9xMessage(S9X_INFO, S9X_USAGE, " frames (use with -dumpstreams)"); S9xMessage(S9X_INFO, S9X_USAGE, ""); S9xMessage(S9X_INFO, S9X_USAGE, "-rwbuffersize Rewind buffer size in MB"); S9xMessage(S9X_INFO, S9X_USAGE, "-rwgranularity Rewind granularity in frames"); S9xMessage(S9X_INFO, S9X_USAGE, ""); S9xExtraDisplayUsage(); } void S9xParseArg (char **argv, int &i, int argc) { if (!strcasecmp(argv[i], "-multi")) Settings.Multi = TRUE; else if (!strcasecmp(argv[i], "-carta")) { if (i + 1 < argc) strncpy(Settings.CartAName, argv[++i], PATH_MAX); else S9xUsage(); } else if (!strcasecmp(argv[i], "-cartb")) { if (i + 1 < argc) strncpy(Settings.CartBName, argv[++i], PATH_MAX); else S9xUsage(); } else #ifdef JOYSTICK_SUPPORT if (!strcasecmp(argv[i], "-nogamepad")) unixSettings.JoystickEnabled = FALSE; else if (!strncasecmp(argv[i], "-paddev", 7) && argv[i][7] >= '1' && argv[i][7] <= '8' && argv[i][8] == '\0') { int j = argv[i][7] - '1'; if (i + 1 < argc) js_device[j] = argv[++i]; else S9xUsage(); } else #endif #ifdef USE_THREADS if (!strcasecmp(argv[i], "-threadsound")) unixSettings.ThreadSound = TRUE; else #endif if (!strcasecmp(argv[i], "-buffersize")) { if (i + 1 < argc) unixSettings.SoundBufferSize = atoi(argv[++i]); else S9xUsage(); } else if (!strcasecmp(argv[i], "-fragmentsize")) { if (i + 1 < argc) unixSettings.SoundFragmentSize = atoi(argv[++i]); else S9xUsage(); } else if (!strcasecmp(argv[i], "-sounddev")) { if (i + 1 < argc) sound_device = argv[++i]; else S9xUsage(); } else if (!strcasecmp(argv[i], "-loadsnapshot")) { if (i + 1 < argc) snapshot_filename = argv[++i]; else S9xUsage(); } else if (!strcasecmp(argv[i], "-playmovie")) { if (i + 1 < argc) play_smv_filename = argv[++i]; else S9xUsage(); } else if (!strcasecmp(argv[i], "-recordmovie")) { if (i + 1 < argc) record_smv_filename = argv[++i]; else S9xUsage(); } else if (!strcasecmp(argv[i], "-dumpstreams")) Settings.DumpStreams = TRUE; else if (!strcasecmp(argv[i], "-dumpmaxframes")) Settings.DumpStreamsMaxFrames = atoi(argv[++i]); else if (!strcasecmp(argv[i], "-rwbuffersize")) { if (i + 1 < argc) unixSettings.rewindBufferSize = atoi(argv[++i]); else S9xUsage(); } else if (!strcasecmp(argv[i], "-rwgranularity")) { if (i + 1 < argc) unixSettings.rewindGranularity = atoi(argv[++i]); else S9xUsage(); } else S9xParseDisplayArg(argv, i, argc); } static void NSRTControllerSetup (void) { if (!strncmp((const char *) Memory.NSRTHeader + 24, "NSRT", 4)) { // First plug in both, they'll change later as needed S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0); S9xSetController(1, CTL_JOYPAD, 1, 0, 0, 0); switch (Memory.NSRTHeader[29]) { case 0x00: // Everything goes break; case 0x10: // Mouse in Port 0 S9xSetController(0, CTL_MOUSE, 0, 0, 0, 0); break; case 0x01: // Mouse in Port 1 S9xSetController(1, CTL_MOUSE, 1, 0, 0, 0); break; case 0x03: // Super Scope in Port 1 S9xSetController(1, CTL_SUPERSCOPE, 0, 0, 0, 0); break; case 0x06: // Multitap in Port 1 S9xSetController(1, CTL_MP5, 1, 2, 3, 4); break; case 0x66: // Multitap in Ports 0 and 1 S9xSetController(0, CTL_MP5, 0, 1, 2, 3); S9xSetController(1, CTL_MP5, 4, 5, 6, 7); break; case 0x08: // Multitap in Port 1, Mouse in new Port 1 S9xSetController(1, CTL_MOUSE, 1, 0, 0, 0); // There should be a toggle here for putting in Multitap instead break; case 0x04: // Pad or Super Scope in Port 1 S9xSetController(1, CTL_SUPERSCOPE, 0, 0, 0, 0); // There should be a toggle here for putting in a pad instead break; case 0x05: // Justifier - Must ask user... S9xSetController(1, CTL_JUSTIFIER, 1, 0, 0, 0); // There should be a toggle here for how many justifiers break; case 0x20: // Pad or Mouse in Port 0 S9xSetController(0, CTL_MOUSE, 0, 0, 0, 0); // There should be a toggle here for putting in a pad instead break; case 0x22: // Pad or Mouse in Port 0 & 1 S9xSetController(0, CTL_MOUSE, 0, 0, 0, 0); S9xSetController(1, CTL_MOUSE, 1, 0, 0, 0); // There should be a toggles here for putting in pads instead break; case 0x24: // Pad or Mouse in Port 0, Pad or Super Scope in Port 1 // There should be a toggles here for what to put in, I'm leaving it at gamepad for now break; case 0x27: // Pad or Mouse in Port 0, Pad or Mouse or Super Scope in Port 1 // There should be a toggles here for what to put in, I'm leaving it at gamepad for now break; // Not Supported yet case 0x99: // Lasabirdie break; case 0x0A: // Barcode Battler break; } } } void S9xParsePortConfig (ConfigFile &conf, int pass) { s9x_base_dir = conf.GetStringDup("Unix::BaseDir", default_dir); snapshot_filename = conf.GetStringDup("Unix::SnapshotFilename", NULL); play_smv_filename = conf.GetStringDup("Unix::PlayMovieFilename", NULL); record_smv_filename = conf.GetStringDup("Unix::RecordMovieFilename", NULL); #ifdef JOYSTICK_SUPPORT unixSettings.JoystickEnabled = conf.GetBool ("Unix::EnableGamePad", true); js_device[0] = conf.GetStringDup("Unix::PadDevice1", NULL); js_device[1] = conf.GetStringDup("Unix::PadDevice2", NULL); js_device[2] = conf.GetStringDup("Unix::PadDevice3", NULL); js_device[3] = conf.GetStringDup("Unix::PadDevice4", NULL); js_device[4] = conf.GetStringDup("Unix::PadDevice5", NULL); js_device[5] = conf.GetStringDup("Unix::PadDevice6", NULL); js_device[6] = conf.GetStringDup("Unix::PadDevice7", NULL); js_device[7] = conf.GetStringDup("Unix::PadDevice8", NULL); #endif #ifdef USE_THREADS unixSettings.ThreadSound = conf.GetBool ("Unix::ThreadSound", false); #endif unixSettings.SoundBufferSize = conf.GetUInt ("Unix::SoundBufferSize", 100); unixSettings.SoundFragmentSize = conf.GetUInt ("Unix::SoundFragmentSize", 2048); #ifndef ALSA sound_device = conf.GetStringDup("Unix::SoundDevice", "/dev/dsp"); #else sound_device = conf.GetStringDup("Unix::SoundDevice", "default"); #endif keymaps.clear(); if (!conf.GetBool("Unix::ClearAllControls", false)) { #if 0 // Using an axis to control Pseudo-pointer #1 keymaps.push_back(strpair_t("J00:Axis0", "AxisToPointer 1h Var")); keymaps.push_back(strpair_t("J00:Axis1", "AxisToPointer 1v Var")); keymaps.push_back(strpair_t("PseudoPointer1", "Pointer C=2 White/Black Superscope")); #elif 0 // Using an Axis for Pseudo-buttons keymaps.push_back(strpair_t("J00:Axis0", "AxisToButtons 1/0 T=50%")); keymaps.push_back(strpair_t("J00:Axis1", "AxisToButtons 3/2 T=50%")); keymaps.push_back(strpair_t("PseudoButton0", "Joypad1 Right")); keymaps.push_back(strpair_t("PseudoButton1", "Joypad1 Left")); keymaps.push_back(strpair_t("PseudoButton2", "Joypad1 Down")); keymaps.push_back(strpair_t("PseudoButton3", "Joypad1 Up")); #else // Using 'Joypad# Axis' keymaps.push_back(strpair_t("J00:Axis0", "Joypad1 Axis Left/Right T=50%")); keymaps.push_back(strpair_t("J00:Axis1", "Joypad1 Axis Up/Down T=50%")); #endif keymaps.push_back(strpair_t("J00:B0", "Joypad1 X")); keymaps.push_back(strpair_t("J00:B1", "Joypad1 A")); keymaps.push_back(strpair_t("J00:B2", "Joypad1 B")); keymaps.push_back(strpair_t("J00:B3", "Joypad1 Y")); #if 1 keymaps.push_back(strpair_t("J00:B6", "Joypad1 L")); #else // Show off joypad-meta keymaps.push_back(strpair_t("J00:X+B6", "JS1 Meta1")); keymaps.push_back(strpair_t("J00:M1+B1", "Joypad1 Turbo A")); #endif keymaps.push_back(strpair_t("J00:B7", "Joypad1 R")); keymaps.push_back(strpair_t("J00:B8", "Joypad1 Select")); keymaps.push_back(strpair_t("J00:B11", "Joypad1 Start")); } std::string section = S9xParseDisplayConfig(conf, 1); ConfigFile::secvec_t sec = conf.GetSection((section + " Controls").c_str()); for (ConfigFile::secvec_t::iterator c = sec.begin(); c != sec.end(); c++) keymaps.push_back(*c); } static int make_snes9x_dirs (void) { if (strlen(s9x_base_dir) + 1 + sizeof(dirNames[0]) > PATH_MAX + 1) return (-1); mkdir(s9x_base_dir, 0755); for (int i = 0; i < LAST_DIR; i++) { if (dirNames[i][0]) { char s[PATH_MAX + 1]; snprintf(s, PATH_MAX + 1, "%s%s%s", s9x_base_dir, SLASH_STR, dirNames[i]); mkdir(s, 0755); } } return (0); } std::string S9xGetDirectory (enum s9x_getdirtype dirtype) { std::string retval = Memory.ROMFilename; size_t pos; if (dirNames[dirtype][0]) return std::string(s9x_base_dir) + SLASH_STR + dirNames[dirtype]; else { switch (dirtype) { case DEFAULT_DIR: retval = s9x_base_dir; break; case HOME_DIR: retval = std::string(getenv("HOME")); break; case ROMFILENAME_DIR: retval = Memory.ROMFilename; pos = retval.rfind("/"); if (pos != std::string::npos) retval = retval.substr(pos); break; default: break; } } return retval; } std::string S9xGetFilenameInc (std::string ex, enum s9x_getdirtype dirtype) { struct stat buf; SplitPath path = splitpath(Memory.ROMFilename); std::string directory = S9xGetDirectory(dirtype); if (ex[0] != '.') { ex = "." + ex; } std::string new_filename; unsigned int i = 0; do { std::string new_extension = std::to_string(i); while (new_extension.length() < 3) new_extension = "0" + new_extension; new_extension += ex; new_filename = path.stem + new_extension; i++; } while (stat(new_filename.c_str(), &buf) == 0 && i < 1000); return new_filename; } const char * S9xBasename (const char *f) { const char *p; if ((p = strrchr(f, '/')) != NULL || (p = strrchr(f, '\\')) != NULL) return (p + 1); return (f); } bool8 S9xOpenSnapshotFile (const char *filename, bool8 read_only, STREAM *file) { if (read_only) { if ((*file = OPEN_STREAM(filename, "rb"))) return (true); else fprintf(stderr, "Failed to open file stream for reading.\n"); } else { if ((*file = OPEN_STREAM(filename, "wb"))) { return (true); } else { fprintf(stderr, "Couldn't open stream with zlib.\n"); } } fprintf(stderr, "Couldn't open snapshot file:\n%s\n", filename); return false; } void S9xCloseSnapshotFile (STREAM file) { CLOSE_STREAM(file); } bool8 S9xInitUpdate (void) { return (TRUE); } bool8 S9xDeinitUpdate (int width, int height) { S9xPutImage(width, height); return (TRUE); } bool8 S9xContinueUpdate (int width, int height) { return (TRUE); } void S9xToggleSoundChannel (int c) { static uint8 sound_switch = 255; if (c == 8) sound_switch = 255; else sound_switch ^= 1 << c; S9xSetSoundControl(sound_switch); } void S9xAutoSaveSRAM (void) { Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str()); } void S9xSyncSpeed (void) { #ifndef NOSOUND if (Settings.SoundSync) { return; } #endif if (Settings.DumpStreams) return; #ifdef NETPLAY_SUPPORT if (Settings.NetPlay && NetPlay.Connected) { #if defined(NP_DEBUG) && NP_DEBUG == 2 printf("CLIENT: SyncSpeed @%d\n", S9xGetMilliTime()); #endif S9xNPSendJoypadUpdate(old_joypads[0]); for (int J = 0; J < 8; J++) joypads[J] = S9xNPGetJoypad(J); if (!S9xNPCheckForHeartBeat()) { NetPlay.PendingWait4Sync = !S9xNPWaitForHeartBeatDelay(100); #if defined(NP_DEBUG) && NP_DEBUG == 2 if (NetPlay.PendingWait4Sync) printf("CLIENT: PendingWait4Sync1 @%d\n", S9xGetMilliTime()); #endif IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } else { NetPlay.PendingWait4Sync = !S9xNPWaitForHeartBeatDelay(200); #if defined(NP_DEBUG) && NP_DEBUG == 2 if (NetPlay.PendingWait4Sync) printf("CLIENT: PendingWait4Sync2 @%d\n", S9xGetMilliTime()); #endif if (IPPU.SkippedFrames < NetPlay.MaxFrameSkip) { IPPU.RenderThisFrame = FALSE; IPPU.SkippedFrames++; } else { IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } } if (!NetPlay.PendingWait4Sync) { NetPlay.FrameCount++; S9xNPStepJoypadHistory(); } return; } #endif if (Settings.HighSpeedSeek > 0) Settings.HighSpeedSeek--; if (Settings.TurboMode) { if ((++IPPU.FrameSkip >= Settings.TurboSkipFrames) && !Settings.HighSpeedSeek) { IPPU.FrameSkip = 0; IPPU.SkippedFrames = 0; IPPU.RenderThisFrame = TRUE; } else { IPPU.SkippedFrames++; IPPU.RenderThisFrame = FALSE; } return; } static struct timeval next1 = { 0, 0 }; struct timeval now; while (gettimeofday(&now, NULL) == -1) ; // If there is no known "next" frame, initialize it now. if (next1.tv_sec == 0) { next1 = now; next1.tv_usec++; } // If we're on AUTO_FRAMERATE, we'll display frames always only if there's excess time. // Otherwise we'll display the defined amount of frames. unsigned limit = (Settings.SkipFrames == AUTO_FRAMERATE) ? (timercmp(&next1, &now, <) ? 10 : 1) : Settings.SkipFrames; IPPU.RenderThisFrame = (++IPPU.SkippedFrames >= limit) ? TRUE : FALSE; if (IPPU.RenderThisFrame) IPPU.SkippedFrames = 0; else { // If we were behind the schedule, check how much it is. if (timercmp(&next1, &now, <)) { unsigned lag = (now.tv_sec - next1.tv_sec) * 1000000 + now.tv_usec - next1.tv_usec; if (lag >= 500000) { // More than a half-second behind means probably pause. // The next line prevents the magic fast-forward effect. next1 = now; } } } // Delay until we're completed this frame. // Can't use setitimer because the sound code already could be using it. We don't actually need it either. while (timercmp(&next1, &now, >)) { // If we're ahead of time, sleep a while. unsigned timeleft = (next1.tv_sec - now.tv_sec) * 1000000 + next1.tv_usec - now.tv_usec; usleep(timeleft); while (gettimeofday(&now, NULL) == -1) ; // Continue with a while-loop because usleep() could be interrupted by a signal. } // Calculate the timestamp of the next frame. next1.tv_usec += Settings.FrameTime; if (next1.tv_usec >= 1000000) { next1.tv_sec += next1.tv_usec / 1000000; next1.tv_usec %= 1000000; } } bool8 S9xMapInput (const char *n, s9xcommand_t *cmd) { int i, j, d; char *c; char buf[4] = "M1+"; if (!strncmp(n, "PseudoPointer", 13) && n[13] >= '1' && n[13] <= '8' && n[14] == '\0') return (S9xMapPointer(PseudoPointerBase + (n[13] - '1'), *cmd, false)); if (!strncmp(n, "PseudoButton", 12) && isdigit(n[12]) && (j = strtol(n + 12, &c, 10)) < 256 && (c == NULL || *c == '\0')) return (S9xMapButton(PseudoButtonBase + j, *cmd, false)); if (n[0] != 'J' || !isdigit(n[1]) || !isdigit(n[2]) || n[3] != ':') goto unrecog; d = ((n[1] - '0') * 10 + (n[2] - '0')) << 24; d |= 0x80000000; i = 4; if (!strncmp(n + i, "X+", 2)) { d |= 0x4000; i += 2; } else { for (buf[1] = '1'; buf[1] <= '8'; buf[1]++) { if (!strncmp(n + i, buf, 3)) { d |= 1 << (buf[1] - '1' + 16); i += 3; } } } if (!strncmp(n + i, "Axis", 4)) { d |= 0x8000; i += 4; } else if (n[i] == 'B') i++; else goto unrecog; d |= j = strtol(n + i, &c, 10); if ((c != NULL && *c != '\0') || j > 0x3fff) goto unrecog; if (d & 0x8000) return (S9xMapAxis(d, *cmd, false)); return (S9xMapButton(d, *cmd, false)); unrecog: return (S9xMapDisplayInput(n, cmd)); } bool S9xPollButton (uint32 id, bool *pressed) { return (S9xDisplayPollButton(id, pressed)); } bool S9xPollAxis (uint32 id, int16 *value) { return (S9xDisplayPollAxis(id, value)); } bool S9xPollPointer (uint32 id, int16 *x, int16 *y) { return (S9xDisplayPollPointer(id, x, y)); } s9xcommand_t S9xGetPortCommandT (const char *n) { s9xcommand_t cmd; cmd.type = S9xBadMapping; cmd.multi_press = 0; cmd.button_norpt = 0; cmd.port[0] = 0; cmd.port[1] = 0; cmd.port[2] = 0; cmd.port[3] = 0; if (!strncmp(n, "JS", 2) && n[2] >= '1' && n[2] <= '8') { if (!strncmp(n + 3, " Meta", 5) && n[8] >= '1' && n[8] <= '8' && n[9] == '\0') { cmd.type = S9xButtonPort; cmd.port[1] = 0; cmd.port[2] = n[2] - '1'; cmd.port[3] = 1 << (n[8] - '1'); return (cmd); } else if (!strncmp(n + 3, " ToggleMeta", 11) && n[14] >= '1' && n[14] <= '8' && n[15] == '\0') { cmd.type = S9xButtonPort; cmd.port[1] = 1; cmd.port[2] = n[2] - '1'; cmd.port[3] = 1 << (n[13] - '1'); return (cmd); } } else if (!strcmp(n,"Rewind")) { cmd.type = S9xButtonPort; cmd.port[1] = 2; return (cmd); } else if (!strcmp(n, "Advance")) { cmd.type = S9xButtonPort; cmd.port[1] = 3; return (cmd); } return (S9xGetDisplayCommandT(n)); } char * S9xGetPortCommandName (s9xcommand_t cmd) { std::string x; switch (cmd.type) { case S9xButtonPort: if (cmd.port[0] != 0) break; switch (cmd.port[1]) { case 0: x = "JS"; x += (int) cmd.port[2]; x += " Meta"; x += (int) cmd.port[3]; return (strdup(x.c_str())); case 1: x = "JS"; x += (int) cmd.port[2]; x += " ToggleMeta"; x += (int) cmd.port[3]; return (strdup(x.c_str())); case 2: return (strdup("Rewind")); case 3: return (strdup("Advance")); } break; case S9xAxisPort: break; case S9xPointerPort: break; } return (S9xGetDisplayCommandName(cmd)); } void S9xHandlePortCommand (s9xcommand_t cmd, int16 data1, int16 data2) { #ifdef JOYSTICK_SUPPORT switch (cmd.type) { case S9xButtonPort: if (cmd.port[0] != 0) break; switch (cmd.port[1]) { case 0: if (data1) js_mod[cmd.port[2]] |= cmd.port[3]; else js_mod[cmd.port[2]] &= ~cmd.port[3]; break; case 1: if (data1) js_mod[cmd.port[2]] ^= cmd.port[3]; break; case 2: rewinding = (bool8) data1; break; case 3: frame_advance = (bool8) data1; } break; case S9xAxisPort: break; case S9xPointerPort: break; } S9xHandleDisplayCommand(cmd, data1, data2); #endif } void S9xSetupDefaultKeymap (void) { s9xcommand_t cmd; S9xUnmapAllControls(); for (ConfigFile::secvec_t::iterator i = keymaps.begin(); i != keymaps.end(); i++) { cmd = S9xGetPortCommandT(i->second.c_str()); if (cmd.type == S9xBadMapping) { cmd = S9xGetCommandT(i->second.c_str()); if (cmd.type == S9xBadMapping) { std::string s("Unrecognized command '"); s += i->second + "'"; perror(s.c_str()); continue; } } if (!S9xMapInput(i->first.c_str(), &cmd)) { std::string s("Could not map '"); s += i->second + "' to '" + i->first + "'"; perror(s.c_str()); continue; } } keymaps.clear(); } void S9xInitInputDevices (void) { #ifdef JOYSTICK_SUPPORT InitJoysticks(); #endif } #ifdef JOYSTICK_SUPPORT static void InitJoysticks (void) { #ifdef JSIOCGVERSION int version; unsigned char axes, buttons; if ((js_fd[0] = open(js_device[0], O_RDONLY | O_NONBLOCK)) == -1) { fprintf(stderr, "joystick: No joystick found.\n"); return; } if (ioctl(js_fd[0], JSIOCGVERSION, &version) == -1) { fprintf(stderr, "joystick: You need at least driver version 1.0 for joystick support.\n"); close(js_fd[0]); return; } for (int i = 1; i < 8; i++) js_fd[i] = open(js_device[i], O_RDONLY | O_NONBLOCK); #ifdef JSIOCGNAME char name[130]; bzero(name, 128); if (ioctl(js_fd[0], JSIOCGNAME(128), name) > 0) { printf("Using %s (%s) as joystick1\n", name, js_device[0]); for (int i = 1; i < 8; i++) { if (js_fd[i] > 0) { ioctl(js_fd[i], JSIOCGNAME(128), name); printf (" and %s (%s) as joystick%d\n", name, js_device[i], i + 1); } } } else #endif { ioctl(js_fd[0], JSIOCGAXES, &axes); ioctl(js_fd[0], JSIOCGBUTTONS, &buttons); printf("Using %d-axis %d-button joystick (%s) as joystick1\n", axes, buttons, js_device[0]); for (int i = 1; i < 8; i++) { if (js_fd[i] > 0) { ioctl(js_fd[i], JSIOCGAXES, &axes); ioctl(js_fd[i], JSIOCGBUTTONS, &buttons); printf(" and %d-axis %d-button joystick (%s) as joystick%d\n", axes, buttons, js_device[i], i + 1); } } } #endif } static bool8 ReadJoysticks (void) { // track if ANY joystick event happened this frame int js_latch = FALSE; #ifdef JSIOCGVERSION struct js_event js_ev; for (int i = 0; i < 8; i++) { // Try to reopen unplugged sticks if (js_unplugged[i]) { js_fd[i] = open(js_device[i], O_RDONLY | O_NONBLOCK); if (js_fd[i] >= 0) { fprintf(stderr,"Joystick %d reconnected.\n",i); js_unplugged[i] = FALSE; js_latch = TRUE; } } // skip sticks without valid file desc if (js_fd[i] < 0) continue; while (read(js_fd[i], &js_ev, sizeof(struct js_event)) == sizeof(struct js_event)) { switch (js_ev.type) { case JS_EVENT_AXIS: S9xReportAxis(0x8000c000 | (i << 24) | js_ev.number, js_ev.value); S9xReportAxis(0x80008000 | (i << 24) | (js_mod[i] << 16) | js_ev.number, js_ev.value); js_latch = TRUE; break; case JS_EVENT_BUTTON: case JS_EVENT_BUTTON | JS_EVENT_INIT: S9xReportButton(0x80004000 | (i << 24) | js_ev.number, js_ev.value); S9xReportButton(0x80000000 | (i << 24) | (js_mod[i] << 16) | js_ev.number, js_ev.value); js_latch = TRUE; break; } } /* EAGAIN is returned when the queue is empty */ if (errno != EAGAIN) { // Error reading joystick. fprintf(stderr,"Error reading joystick %d!\n",i); // Mark for reconnect attempt. js_unplugged[i] = TRUE; for (unsigned int j = 0; j < 16; j++) { // Center all axis S9xReportAxis(0x8000c000 | (i << 24) | j, 0); S9xReportAxis(0x80008000 | (i << 24) | (js_mod[i] << 16) | j, 0); // Unpress all buttons. S9xReportButton(0x80004000 | (i << 24) | j, 0); S9xReportButton(0x80000000 | (i << 24) | (js_mod[i] << 16) | j, 0); } js_latch = TRUE; } } #endif return js_latch; } #endif void S9xSamplesAvailable(void *data) { #ifndef NOSOUND int samples_to_write; static uint8 *sound_buffer = NULL; static int sound_buffer_size = 0; #ifdef ALSA snd_pcm_sframes_t frames_written, frames; frames = snd_pcm_avail(so.pcm_handle); if (frames < 0) { frames = snd_pcm_recover(so.pcm_handle, frames, 1); return; } #endif if (Settings.DynamicRateControl) { #ifndef ALSA S9xUpdateDynamicRate(s_AudioOutput->GetFreeBufferSize(), so.fragment_size * 4); #else S9xUpdateDynamicRate(snd_pcm_frames_to_bytes(so.pcm_handle, frames), so.output_buffer_size); #endif } samples_to_write = S9xGetSampleCount(); if (samples_to_write < 0) return; #ifdef ALSA if (Settings.DynamicRateControl && !Settings.SoundSync) { // Using rate control, we should always keep the emulator's sound buffers empty to // maintain an accurate measurement. if (frames < samples_to_write/2) { S9xClearSamples(); return; } } if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute) { snd_pcm_nonblock(so.pcm_handle, 0); frames = samples_to_write/2; } else { snd_pcm_nonblock(so.pcm_handle, 1); frames = MIN(frames, samples_to_write/2); } int bytes = snd_pcm_frames_to_bytes(so.pcm_handle, frames); if (bytes <= 0) return; if (sound_buffer_size < bytes || sound_buffer == NULL) { sound_buffer = (uint8 *)realloc(sound_buffer, bytes); sound_buffer_size = bytes; } #else //OSS if (sound_buffer_size < samples_to_write * 2) { sound_buffer = (uint8 *)realloc(sound_buffer, samples_to_write * 2); sound_buffer_size = samples_to_write * 2; } #endif #ifndef ALSA S9xMixSamples(sound_buffer, samples_to_write); s_AudioOutput->Write(sound_buffer, samples_to_write * 2); #else S9xMixSamples(sound_buffer, frames*2); frames_written = 0; while (frames_written < frames) { int result; result = snd_pcm_writei(so.pcm_handle, sound_buffer + snd_pcm_frames_to_bytes(so.pcm_handle, frames_written), frames - frames_written); if (result < 0) { result = snd_pcm_recover(so.pcm_handle, result, 1); if (result < 0) { break; } } else { frames_written += result; } } #endif //ALSA #endif //NOSOUND } bool8 S9xOpenSoundDevice (void) { #ifndef NOSOUND #ifndef ALSA int J, K; so.sound_fd = open(sound_device, O_WRONLY | O_NONBLOCK); if (so.sound_fd == -1) { fprintf(stderr, "ERROR: Failed to open sound device %s for writing.\n\t(Try loading snd-pcm-oss module?)\n", sound_device); return (FALSE); } s_AudioOutput = new S9xAudioOutput( so.sound_fd, Settings.SoundPlaybackRate, bool(unixSettings.ThreadSound) ); J = snes9x_log2(unixSettings.SoundFragmentSize) | (4 << 16); if (ioctl(so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) == -1) return (FALSE); J = K = AFMT_S16_NE; if (ioctl(so.sound_fd, SNDCTL_DSP_SETFMT, &J) == -1 || J != K) return (FALSE); J = K = 1; if (ioctl(so.sound_fd, SNDCTL_DSP_STEREO, &J) == -1 || J != K) return (FALSE); J = K = Settings.SoundPlaybackRate; if (ioctl(so.sound_fd, SNDCTL_DSP_SPEED, &J) == -1 || J != K) return (FALSE); J = 0; if (ioctl(so.sound_fd, SNDCTL_DSP_GETBLKSIZE, &J) == -1) return (FALSE); so.fragment_size = J; printf("fragment size: %d\n", J); #else int err; unsigned int periods = 8; unsigned int buffer_size = unixSettings.SoundBufferSize * 1000; snd_pcm_sw_params_t *sw_params; snd_pcm_hw_params_t *hw_params; snd_pcm_uframes_t alsa_buffer_size, alsa_period_size; unsigned int min = 0; unsigned int max = 0; unsigned int rate = Settings.SoundPlaybackRate; err = snd_pcm_open(&so.pcm_handle, sound_device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { printf("Failed: %s\n", snd_strerror(err)); return (FALSE); } snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_any(so.pcm_handle, hw_params); snd_pcm_hw_params_set_format(so.pcm_handle, hw_params, SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_access(so.pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_rate_resample(so.pcm_handle, hw_params, 0); snd_pcm_hw_params_set_channels(so.pcm_handle, hw_params, 2); snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL); snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL); fprintf(stderr, "Alsa available rates: %d to %d\n", min, max); if (rate > max && rate < min) { fprintf(stderr, "Rate %d not available. Using %d instead.\n", rate, max); rate = max; } snd_pcm_hw_params_set_rate_near(so.pcm_handle, hw_params, &rate, NULL); snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, NULL); snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, NULL); fprintf(stderr, "Alsa available buffer sizes: %dms to %dms\n", min / 1000, max / 1000); if (buffer_size < min && buffer_size > max) { fprintf(stderr, "Buffer size %dms not available. Using %d instead.\n", buffer_size / 1000, (min + max) / 2000); buffer_size = (min + max) / 2; } snd_pcm_hw_params_set_buffer_time_near(so.pcm_handle, hw_params, &buffer_size, NULL); snd_pcm_hw_params_get_periods_min(hw_params, &min, NULL); snd_pcm_hw_params_get_periods_max(hw_params, &max, NULL); fprintf(stderr, "Alsa period ranges: %d to %d blocks\n", min, max); if (periods > max) periods = max; snd_pcm_hw_params_set_periods_near(so.pcm_handle, hw_params, &periods, NULL); err = snd_pcm_hw_params(so.pcm_handle, hw_params); if (err < 0) { fprintf(stderr, "Alsa error: unable to install hw params\n"); snd_pcm_drain(so.pcm_handle); snd_pcm_close(so.pcm_handle); so.pcm_handle = NULL; return (FALSE); } snd_pcm_sw_params_alloca(&sw_params); snd_pcm_sw_params_current(so.pcm_handle, sw_params); snd_pcm_get_params(so.pcm_handle, &alsa_buffer_size, &alsa_period_size); /* Don't start until we're [nearly] full */ snd_pcm_sw_params_set_start_threshold(so.pcm_handle, sw_params, (alsa_buffer_size / 2)); err = snd_pcm_sw_params(so.pcm_handle, sw_params); if (err < 0) { fprintf(stderr, "Alsa error: unable to install sw params\n"); snd_pcm_drain(so.pcm_handle); snd_pcm_close(so.pcm_handle); so.pcm_handle = NULL; return (FALSE); } err = snd_pcm_prepare(so.pcm_handle); if (err < 0) { fprintf(stderr, "Alsa error: unable to prepare audio: %s\n", snd_strerror(err)); snd_pcm_drain(so.pcm_handle); snd_pcm_close(so.pcm_handle); so.pcm_handle = NULL; return (FALSE); } so.output_buffer_size = snd_pcm_frames_to_bytes(so.pcm_handle, alsa_buffer_size); #endif //ALSA #endif //NOSOUND S9xSetSamplesAvailableCallback(S9xSamplesAvailable, NULL); return (TRUE); } void S9xExit (void) { S9xMovieShutdown(); S9xSetSoundMute(TRUE); Settings.StopEmulation = TRUE; #ifdef NETPLAY_SUPPORT if (Settings.NetPlay) S9xNPDisconnect(); #endif #if !defined(NOSOUND) && !defined(ALSA) delete s_AudioOutput; #endif Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str()); S9xResetSaveTimer(FALSE); S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR)); S9xUnmapAllControls(); S9xDeinitDisplay(); Memory.Deinit(); S9xDeinitAPU(); exit(0); } #ifdef DEBUGGER static void sigbrkhandler (int) { CPU.Flags |= DEBUG_MODE_FLAG; signal(SIGINT, (SIG_PF) sigbrkhandler); } #endif int main (int argc, char **argv) { if (argc < 2) S9xUsage(); printf("\n\nSnes9x " VERSION " for unix\n"); snprintf(default_dir, PATH_MAX + 1, "%s%s%s", getenv("HOME"), SLASH_STR, ".snes9x"); s9x_base_dir = default_dir; memset(&Settings, 0, sizeof(Settings)); Settings.MouseMaster = TRUE; Settings.SuperScopeMaster = TRUE; Settings.JustifierMaster = TRUE; Settings.MultiPlayer5Master = TRUE; Settings.FrameTimePAL = 20000; Settings.FrameTimeNTSC = 16667; Settings.SixteenBitSound = TRUE; Settings.Stereo = TRUE; Settings.SoundPlaybackRate = 48000; Settings.SoundInputRate = 31950; Settings.Transparency = TRUE; Settings.AutoDisplayMessages = TRUE; Settings.InitialInfoStringTimeout = 120; Settings.HDMATimingHack = 100; Settings.BlockInvalidVRAMAccessMaster = TRUE; Settings.StopEmulation = TRUE; Settings.WrongMovieStateProtection = TRUE; Settings.DumpStreamsMaxFrames = -1; Settings.StretchScreenshots = 1; Settings.SnapshotScreenshots = TRUE; Settings.SkipFrames = AUTO_FRAMERATE; Settings.TurboSkipFrames = 15; Settings.CartAName[0] = 0; Settings.CartBName[0] = 0; #ifdef NETPLAY_SUPPORT Settings.ServerName[0] = 0; #endif #ifdef JOYSTICK_SUPPORT unixSettings.JoystickEnabled = TRUE; #else unixSettings.JoystickEnabled = FALSE; #endif unixSettings.ThreadSound = TRUE; unixSettings.SoundBufferSize = 100; unixSettings.SoundFragmentSize = 2048; unixSettings.rewindBufferSize = 0; unixSettings.rewindGranularity = 1; memset(&so, 0, sizeof(so)); rewinding = false; CPU.Flags = 0; S9xLoadConfigFiles(argv, argc); rom_filename = S9xParseArgs(argv, argc); S9xDeleteCheats(); make_snes9x_dirs(); if (!Memory.Init() || !S9xInitAPU()) { fprintf(stderr, "Snes9x: Memory allocation failure - not enough RAM/virtual memory available.\nExiting...\n"); Memory.Deinit(); S9xDeinitAPU(); exit(1); } S9xInitSound(0); S9xSetSoundMute(TRUE); S9xReportControllers(); uint32 saved_flags = CPU.Flags; bool8 loaded = FALSE; if (Settings.Multi) { loaded = Memory.LoadMultiCart(Settings.CartAName, Settings.CartBName); if (!loaded) { std::string s1, s2; if (Settings.CartAName[0]) { SplitPath path = splitpath(Settings.CartAName); s1 = makepath("", S9xGetDirectory(ROM_DIR), path.stem, path.ext); } if (Settings.CartBName[0]) { SplitPath path = splitpath(Settings.CartBName); s2 = makepath("", S9xGetDirectory(ROM_DIR), path.stem, path.ext); } loaded = Memory.LoadMultiCart(s1.c_str(), s2.c_str()); } } else if (rom_filename) { loaded = Memory.LoadROM(rom_filename); if (!loaded && rom_filename[0]) { SplitPath path = splitpath(rom_filename); std::string s = makepath("", S9xGetDirectory(ROM_DIR), path.stem, path.ext); loaded = Memory.LoadROM(s.c_str()); } } if (!loaded) { fprintf(stderr, "Error opening the ROM file.\n"); exit(1); } S9xDeleteCheats(); S9xCheatsEnable(); NSRTControllerSetup(); Memory.LoadSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str()); if (Settings.ApplyCheats) { S9xLoadCheatFile(S9xGetFilename(".cht", CHEAT_DIR)); } S9xParseArgsForCheats(argv, argc); CPU.Flags = saved_flags; Settings.StopEmulation = FALSE; #ifdef DEBUGGER struct sigaction sa; sa.sa_handler = sigbrkhandler; #ifdef SA_RESTART sa.sa_flags = SA_RESTART; #else sa.sa_flags = 0; #endif sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); #endif S9xInitInputDevices(); S9xInitDisplay(argc, argv); S9xSetupDefaultKeymap(); S9xTextMode(); #ifdef NETPLAY_SUPPORT if (strlen(Settings.ServerName) == 0) { char *server = getenv("S9XSERVER"); if (server) { strncpy(Settings.ServerName, server, 127); Settings.ServerName[127] = 0; } } char *port = getenv("S9XPORT"); if (Settings.Port >= 0 && port) Settings.Port = atoi(port); else if (Settings.Port < 0) Settings.Port = -Settings.Port; if (Settings.NetPlay) { NetPlay.MaxFrameSkip = 10; unixSettings.rewindBufferSize = 0; if (!S9xNPConnectToServer(Settings.ServerName, Settings.Port, Memory.ROMName)) { fprintf(stderr, "Failed to connect to server %s on port %d.\n", Settings.ServerName, Settings.Port); S9xExit(); } fprintf(stderr, "Connected to server %s on port %d as player #%d playing %s.\n", Settings.ServerName, Settings.Port, NetPlay.Player, Memory.ROMName); } #endif if (play_smv_filename) { uint32 flags = CPU.Flags & (DEBUG_MODE_FLAG | TRACE_FLAG); if (S9xMovieOpen(play_smv_filename, TRUE) != SUCCESS) exit(1); CPU.Flags |= flags; } else if (record_smv_filename) { uint32 flags = CPU.Flags & (DEBUG_MODE_FLAG | TRACE_FLAG); if (S9xMovieCreate(record_smv_filename, 0xFF, MOVIE_OPT_FROM_RESET, NULL, 0) != SUCCESS) exit(1); CPU.Flags |= flags; } else { if (snapshot_filename) { uint32 flags = CPU.Flags & (DEBUG_MODE_FLAG | TRACE_FLAG); if (!S9xUnfreezeGame(snapshot_filename)) exit(1); CPU.Flags |= flags; } if (unixSettings.rewindBufferSize) { stateMan.init(unixSettings.rewindBufferSize * 1024 * 1024); } } S9xGraphicsMode(); sprintf(String, "\"%s\" %s: %s", Memory.ROMName, TITLE, VERSION); S9xSetTitle(String); #ifdef JOYSTICK_SUPPORT uint32 JoypadSkip = 0; #endif S9xSetSoundMute(FALSE); #ifdef NETPLAY_SUPPORT bool8 NP_Activated = Settings.NetPlay; #endif while (1) { #ifdef NETPLAY_SUPPORT if (NP_Activated) { if (NetPlay.PendingWait4Sync && !S9xNPWaitForHeartBeatDelay(100)) { S9xProcessEvents(FALSE); continue; } for (int J = 0; J < 8; J++) old_joypads[J] = MovieGetJoypad(J); for (int J = 0; J < 8; J++) MovieSetJoypad(J, joypads[J]); if (NetPlay.Connected) { if (NetPlay.PendingWait4Sync) { NetPlay.PendingWait4Sync = FALSE; NetPlay.FrameCount++; S9xNPStepJoypadHistory(); } } else { fprintf(stderr, "Lost connection to server.\n"); S9xExit(); } } #endif #ifdef DEBUGGER if (!Settings.Paused || (CPU.Flags & (DEBUG_MODE_FLAG | SINGLE_STEP_FLAG))) #else if (!Settings.Paused) #endif { if(rewinding) { uint16 joypads[8]; for (int i = 0; i < 8; i++) joypads[i] = MovieGetJoypad(i); rewinding = stateMan.pop(); for (int i = 0; i < 8; i++) MovieSetJoypad (i, joypads[i]); } else if(IPPU.TotalEmulatedFrames % unixSettings.rewindGranularity == 0) stateMan.push(); S9xMainLoop(); } if (Settings.Paused && frame_advance) { S9xMainLoop(); frame_advance = 0; } #ifdef NETPLAY_SUPPORT if (NP_Activated) { for (int J = 0; J < 8; J++) MovieSetJoypad(J, old_joypads[J]); } #endif #ifdef DEBUGGER if (Settings.Paused || (CPU.Flags & DEBUG_MODE_FLAG)) #else if (Settings.Paused) #endif S9xSetSoundMute(TRUE); #ifdef DEBUGGER if (CPU.Flags & DEBUG_MODE_FLAG) S9xDoDebug(); else #endif if (Settings.Paused) { S9xProcessEvents(FALSE); usleep(100000); } #ifdef JOYSTICK_SUPPORT if (unixSettings.JoystickEnabled && (JoypadSkip++ & 1) == 0) { if (ReadJoysticks() == TRUE) { S9xLatchJSEvent(); } } #endif S9xProcessEvents(FALSE); #ifdef DEBUGGER if (!Settings.Paused && !(CPU.Flags & DEBUG_MODE_FLAG)) #else if (!Settings.Paused) #endif S9xSetSoundMute(FALSE); } return (0); }