/*********************************************************************************** Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. (c) Copyright 1996 - 2002 Gary Henderson (gary.henderson@ntlworld.com), Jerremy Koot (jkoot@snes9x.com) (c) Copyright 2002 - 2004 Matthew Kendora (c) Copyright 2002 - 2005 Peter Bortas (peter@bortas.org) (c) Copyright 2004 - 2005 Joel Yliluoma (http://iki.fi/bisqwit/) (c) Copyright 2001 - 2006 John Weidman (jweidman@slip.net) (c) Copyright 2002 - 2006 funkyass (funkyass@spam.shaw.ca), Kris Bleakley (codeviolation@hotmail.com) (c) Copyright 2002 - 2010 Brad Jorsch (anomie@users.sourceforge.net), Nach (n-a-c-h@users.sourceforge.net), (c) Copyright 2002 - 2011 zones (kasumitokoduck@yahoo.com) (c) Copyright 2006 - 2007 nitsuja (c) Copyright 2009 - 2018 BearOso, OV2 (c) Copyright 2017 qwertymodo (c) Copyright 2011 - 2017 Hans-Kristian Arntzen, Daniel De Matteis (Under no circumstances will commercial rights be given) BS-X C emulator code (c) Copyright 2005 - 2006 Dreamer Nom, zones C4 x86 assembler and some C emulation code (c) Copyright 2000 - 2003 _Demo_ (_demo_@zsnes.com), Nach, zsKnight (zsknight@zsnes.com) C4 C++ code (c) Copyright 2003 - 2006 Brad Jorsch, Nach DSP-1 emulator code (c) Copyright 1998 - 2006 _Demo_, Andreas Naive (andreasnaive@gmail.com), Gary Henderson, Ivar (ivar@snes9x.com), John Weidman, Kris Bleakley, Matthew Kendora, Nach, neviksti (neviksti@hotmail.com) DSP-2 emulator code (c) Copyright 2003 John Weidman, Kris Bleakley, Lord Nightmare (lord_nightmare@users.sourceforge.net), Matthew Kendora, neviksti DSP-3 emulator code (c) Copyright 2003 - 2006 John Weidman, Kris Bleakley, Lancer, z80 gaiden DSP-4 emulator code (c) Copyright 2004 - 2006 Dreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden OBC1 emulator code (c) Copyright 2001 - 2004 zsKnight, pagefault (pagefault@zsnes.com), Kris Bleakley Ported from x86 assembler to C by sanmaiwashi SPC7110 and RTC C++ emulator code used in 1.39-1.51 (c) Copyright 2002 Matthew Kendora with research by zsKnight, John Weidman, Dark Force SPC7110 and RTC C++ emulator code used in 1.52+ (c) Copyright 2009 byuu, neviksti S-DD1 C emulator code (c) Copyright 2003 Brad Jorsch with research by Andreas Naive, John Weidman S-RTC C emulator code (c) Copyright 2001 - 2006 byuu, John Weidman ST010 C++ emulator code (c) Copyright 2003 Feather, John Weidman, Kris Bleakley, Matthew Kendora Super FX x86 assembler emulator code (c) Copyright 1998 - 2003 _Demo_, pagefault, zsKnight Super FX C emulator code (c) Copyright 1997 - 1999 Ivar, Gary Henderson, John Weidman Sound emulator code used in 1.5-1.51 (c) Copyright 1998 - 2003 Brad Martin (c) Copyright 1998 - 2006 Charles Bilyue' Sound emulator code used in 1.52+ (c) Copyright 2004 - 2007 Shay Green (gblargg@gmail.com) S-SMP emulator code used in 1.54+ (c) Copyright 2016 byuu SH assembler code partly based on x86 assembler code (c) Copyright 2002 - 2004 Marcus Comstedt (marcus@mc.pp.se) 2xSaI filter (c) Copyright 1999 - 2001 Derek Liauw Kie Fa HQ2x, HQ3x, HQ4x filters (c) Copyright 2003 Maxim Stepin (maxim@hiend3d.com) NTSC filter (c) Copyright 2006 - 2007 Shay Green GTK+ GUI code (c) Copyright 2004 - 2018 BearOso Win32 GUI code (c) Copyright 2003 - 2006 blip, funkyass, Matthew Kendora, Nach, nitsuja (c) Copyright 2009 - 2018 OV2 Mac OS GUI code (c) Copyright 1998 - 2001 John Stiles (c) Copyright 2001 - 2011 zones Libretro port (c) Copyright 2011 - 2017 Hans-Kristian Arntzen, Daniel De Matteis (Under no circumstances will commercial rights be given) Specific ports contains the works of other authors. See headers in individual files. Snes9x homepage: http://www.snes9x.com/ Permission to use, copy, modify and/or distribute Snes9x in both binary and source form, for non-commercial purposes, is hereby granted without fee, providing that this license information and copyright notice appear with all copies and any derived work. This software is provided 'as-is', without any express or implied warranty. In no event shall the authors be held liable for any damages arising from the use of this software or it's derivatives. Snes9x is freeware for PERSONAL USE only. Commercial users should seek permission of the copyright holders first. Commercial use includes, but is not limited to, charging money for Snes9x or software derived from Snes9x, including Snes9x or derivatives in commercial game bundles, and/or using Snes9x as a promotion for your commercial product. The copyright holders request that bug fixes and improvements to the code should be forwarded to them so everyone can benefit from the modifications in future versions. Super NES and Super Nintendo Entertainment System are trademarks of Nintendo Co., Limited and its subsidiary companies. ***********************************************************************************/ #include #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #ifdef USE_THREADS #include #include #endif #include #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #ifndef NOSOUND #include #include #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 "logger.h" #include "display.h" #include "conffile.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 { int sound_fd; uint32 fragment_size; uint32 err_counter; uint32 err_rate; int32 samples_mixed_so_far; int32 play_position; }; static int frame_advance = 0; static SUnixSettings unixSettings; static SoundStatus so; static bool8 rewinding; #ifndef NOSOUND static uint8 Buf[SOUND_BUFFER_SIZE]; #endif #ifdef USE_THREADS static pthread_t thread; static pthread_mutex_t mutex; #endif #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 long log2 (long); static void SoundTrigger (void); static void InitTimer (void); static void NSRTControllerSetup (void); static int make_snes9x_dirs (void); #ifndef NOSOUND static void * S9xProcessSound (void *); #endif #ifdef JOYSTICK_SUPPORT static void InitJoysticks (void); static void ReadJoysticks (void); #endif void _splitpath (const char *path, char *drive, char *dir, char *fname, char *ext) { *drive = 0; const char *slash = strrchr(path, SLASH_CHAR), *dot = strrchr(path, '.'); if (dot && slash && dot < slash) dot = NULL; if (!slash) { *dir = 0; strcpy(fname, path); if (dot) { fname[dot - path] = 0; strcpy(ext, dot + 1); } else *ext = 0; } else { strcpy(dir, path); dir[slash - path] = 0; strcpy(fname, slash + 1); if (dot) { fname[dot - slash - 1] = 0; strcpy(ext, dot + 1); } else *ext = 0; } } void _makepath (char *path, const char *, const char *dir, const char *fname, const char *ext) { if (dir && *dir) { strcpy(path, dir); strcat(path, SLASH_STR); } else *path = 0; strcat(path, fname); if (ext && *ext) { strcat(path, "."); strcat(path, ext); } } static long log2 (long num) { long n = 0; while (num >>= 1) n++; return (n); } 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, "-paddev1 Specify gamepad device 2"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 Specify gamepad device 3"); S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 Specify gamepad device 4"); S9xMessage(S9X_INFO, S9X_USAGE, ""); #endif #ifdef USE_THREADS 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"); S9xMessage(S9X_INFO, S9X_USAGE, "-fragmentsize Sound playback buffer fragment size in bytes"); S9xMessage(S9X_INFO, S9X_USAGE, "-sounddev Specify sound device"); S9xMessage(S9X_INFO, S9X_USAGE, ""); 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], _MAX_PATH); else S9xUsage(); } else if (!strcasecmp(argv[i], "-cartb")) { if (i + 1 < argc) strncpy(Settings.CartBName, argv[++i], _MAX_PATH); 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); sound_device = conf.GetStringDup("Unix::SoundDevice", "/dev/dsp"); 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); } const char * S9xGetDirectory (enum s9x_getdirtype dirtype) { static char s[PATH_MAX + 1]; if (dirNames[dirtype][0]) snprintf(s, PATH_MAX + 1, "%s%s%s", s9x_base_dir, SLASH_STR, dirNames[dirtype]); else { switch (dirtype) { case DEFAULT_DIR: strncpy(s, s9x_base_dir, PATH_MAX + 1); s[PATH_MAX] = 0; break; case HOME_DIR: strncpy(s, getenv("HOME"), PATH_MAX + 1); s[PATH_MAX] = 0; break; case ROMFILENAME_DIR: strncpy(s, Memory.ROMFilename, PATH_MAX + 1); s[PATH_MAX] = 0; for (int i = strlen(s); i >= 0; i--) { if (s[i] == SLASH_CHAR) { s[i] = 0; break; } } break; default: s[0] = 0; break; } } return (s); } const char * S9xGetFilename (const char *ex, enum s9x_getdirtype dirtype) { static char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(Memory.ROMFilename, drive, dir, fname, ext); snprintf(s, PATH_MAX + 1, "%s%s%s%s", S9xGetDirectory(dirtype), SLASH_STR, fname, ex); return (s); } const char * S9xGetFilenameInc (const char *ex, enum s9x_getdirtype dirtype) { static char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; unsigned int i = 0; const char *d; struct stat buf; _splitpath(Memory.ROMFilename, drive, dir, fname, ext); d = S9xGetDirectory(dirtype); do snprintf(s, PATH_MAX + 1, "%s%s%s.%03d%s", d, SLASH_STR, fname, i++, ex); while (stat(s, &buf) == 0 && i < 1000); return (s); } const char * S9xBasename (const char *f) { const char *p; if ((p = strrchr(f, '/')) != NULL || (p = strrchr(f, '\\')) != NULL) return (p + 1); return (f); } const char * S9xChooseFilename (bool8 read_only) { char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; const char *filename; char title[64]; _splitpath(Memory.ROMFilename, drive, dir, fname, ext); snprintf(s, PATH_MAX + 1, "%s.frz", fname); sprintf(title, "%s snapshot filename", read_only ? "Select load" : "Choose save"); S9xSetSoundMute(TRUE); filename = S9xSelectFilename(s, S9xGetDirectory(SNAPSHOT_DIR), "frz", title); S9xSetSoundMute(FALSE); return (filename); } const char * S9xChooseMovieFilename (bool8 read_only) { char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; const char *filename; char title[64]; _splitpath(Memory.ROMFilename, drive, dir, fname, ext); snprintf(s, PATH_MAX + 1, "%s.smv", fname); sprintf(title, "Choose movie %s filename", read_only ? "playback" : "record"); S9xSetSoundMute(TRUE); filename = S9xSelectFilename(s, S9xGetDirectory(HOME_DIR), "smv", title); S9xSetSoundMute(FALSE); return (filename); } bool8 S9xOpenSnapshotFile (const char *filename, bool8 read_only, STREAM *file) { char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(filename, drive, dir, fname, ext); if (*drive || *dir == SLASH_CHAR || (strlen(dir) > 1 && *dir == '.' && *(dir + 1) == SLASH_CHAR)) { strncpy(s, filename, PATH_MAX + 1); s[PATH_MAX] = 0; } else snprintf(s, PATH_MAX + 1, "%s%s%s", S9xGetDirectory(SNAPSHOT_DIR), SLASH_STR, fname); if (!*ext && strlen(s) <= PATH_MAX - 4) strcat(s, ".frz"); if ((*file = OPEN_STREAM(s, read_only ? "rb" : "wb"))) return (TRUE); 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)); } void S9xSyncSpeed (void) { #ifndef NOSOUND if (Settings.SoundSync) { while (!S9xSyncSound()) usleep(0); } #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 void ReadJoysticks (void) { #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; } } // 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); 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); 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); } } } #endif } #endif static void SoundTrigger (void) { #ifndef NOSOUND S9xProcessSound(NULL); #endif } static void InitTimer (void) { #ifndef NOSOUND struct itimerval timeout; #endif struct sigaction sa; #ifdef USE_THREADS if (unixSettings.ThreadSound) { pthread_mutex_init(&mutex, NULL); pthread_create(&thread, NULL, S9xProcessSound, NULL); return; } #endif sa.sa_handler = (SIG_PF) SoundTrigger; #ifdef SA_RESTART sa.sa_flags = SA_RESTART; #else sa.sa_flags = 0; #endif #ifndef NOSOUND // FIXME: Kludge to get calltree running. Remove later. sigemptyset(&sa.sa_mask); sigaction(SIGALRM, &sa, NULL); timeout.it_interval.tv_sec = 0; timeout.it_interval.tv_usec = 10000; timeout.it_value.tv_sec = 0; timeout.it_value.tv_usec = 10000; if (setitimer(ITIMER_REAL, &timeout, NULL) < 0) perror("setitimer"); #endif } bool8 S9xOpenSoundDevice (void) { #ifndef NOSOUND 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); } J = log2(unixSettings.SoundFragmentSize) | (3 << 16); if (ioctl(so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) == -1) return (FALSE); J = K = Settings.SixteenBitSound ? AFMT_S16_NE : AFMT_U8; if (ioctl(so.sound_fd, SNDCTL_DSP_SETFMT, &J) == -1 || J != K) return (FALSE); J = K = Settings.Stereo ? 1 : 0; 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); #endif return (TRUE); } #ifndef NOSOUND static void * S9xProcessSound (void *) { // If threads in use, this is to loop indefinitely. // If not, this will be called by timer. audio_buf_info info; if (!unixSettings.ThreadSound && (ioctl(so.sound_fd, SNDCTL_DSP_GETOSPACE, &info) == -1 || info.bytes < (int) so.fragment_size)) return (NULL); #ifdef USE_THREADS do { #endif int sample_count = so.fragment_size; if (Settings.SixteenBitSound) sample_count >>= 1; #ifdef USE_THREADS if (unixSettings.ThreadSound) pthread_mutex_lock(&mutex); else #endif if (block_signal) return (NULL); block_generate_sound = TRUE; if (so.samples_mixed_so_far < sample_count) { unsigned ofs = so.play_position + (Settings.SixteenBitSound ? (so.samples_mixed_so_far << 1) : so.samples_mixed_so_far); S9xMixSamples(Buf + (ofs & SOUND_BUFFER_SIZE_MASK), sample_count - so.samples_mixed_so_far); so.samples_mixed_so_far = sample_count; } unsigned bytes_to_write = sample_count; if (Settings.SixteenBitSound) bytes_to_write <<= 1; unsigned byte_offset = so.play_position; so.play_position += bytes_to_write; so.play_position &= SOUND_BUFFER_SIZE_MASK; #ifdef USE_THREADS if (unixSettings.ThreadSound) pthread_mutex_unlock(&mutex); #endif block_generate_sound = FALSE; for (;;) { int I = bytes_to_write; if (byte_offset + I > SOUND_BUFFER_SIZE) I = SOUND_BUFFER_SIZE - byte_offset; if (I == 0) break; I = write(so.sound_fd, (char *) Buf + byte_offset, I); if (I > 0) { bytes_to_write -= I; byte_offset += I; byte_offset &= SOUND_BUFFER_SIZE_MASK; } else if (I < 0 && errno != EINTR) break; } so.samples_mixed_so_far -= sample_count; #ifdef USE_THREADS } while (unixSettings.ThreadSound); #endif return (NULL); } #endif void S9xExit (void) { S9xMovieShutdown(); S9xSetSoundMute(TRUE); Settings.StopEmulation = TRUE; #ifdef NETPLAY_SUPPORT if (Settings.NetPlay) S9xNPDisconnect(); #endif Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR)); 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 = 32000; Settings.SoundInputRate = 31950; Settings.SupportHiRes = TRUE; 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(unixSettings.SoundBufferSize, 0); S9xSetSoundMute(TRUE); S9xReportControllers(); #ifdef GFX_MULTI_FORMAT S9xSetRenderPixelFormat(RGB565); #endif uint32 saved_flags = CPU.Flags; bool8 loaded = FALSE; if (Settings.Multi) { loaded = Memory.LoadMultiCart(Settings.CartAName, Settings.CartBName); if (!loaded) { char s1[PATH_MAX + 1], s2[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; s1[0] = s2[0] = 0; if (Settings.CartAName[0]) { _splitpath(Settings.CartAName, drive, dir, fname, ext); snprintf(s1, PATH_MAX + 1, "%s%s%s", S9xGetDirectory(ROM_DIR), SLASH_STR, fname); if (ext[0] && (strlen(s1) <= PATH_MAX - 1 - strlen(ext))) { strcat(s1, "."); strcat(s1, ext); } } if (Settings.CartBName[0]) { _splitpath(Settings.CartBName, drive, dir, fname, ext); snprintf(s2, PATH_MAX + 1, "%s%s%s", S9xGetDirectory(ROM_DIR), SLASH_STR, fname); if (ext[0] && (strlen(s2) <= PATH_MAX - 1 - strlen(ext))) { strcat(s2, "."); strcat(s2, ext); } } loaded = Memory.LoadMultiCart(s1, s2); } } else if (rom_filename) { loaded = Memory.LoadROM(rom_filename); if (!loaded && rom_filename[0]) { char s[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(rom_filename, drive, dir, fname, ext); snprintf(s, PATH_MAX + 1, "%s%s%s", S9xGetDirectory(ROM_DIR), SLASH_STR, fname); if (ext[0] && (strlen(s) <= PATH_MAX - 1 - strlen(ext))) { strcat(s, "."); strcat(s, ext); } loaded = Memory.LoadROM(s); } } if (!loaded) { fprintf(stderr, "Error opening the ROM file.\n"); exit(1); } S9xDeleteCheats(); S9xCheatsEnable(); NSRTControllerSetup(); Memory.LoadSRAM(S9xGetFilename(".srm", SRAM_DIR)); 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 InitTimer(); 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) ReadJoysticks(); #endif S9xProcessEvents(FALSE); #ifdef DEBUGGER if (!Settings.Paused && !(CPU.Flags & DEBUG_MODE_FLAG)) #else if (!Settings.Paused) #endif S9xSetSoundMute(FALSE); } return (0); }