/*****************************************************************************\ 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 "../snes9x.h" #include "../memmap.h" #include "../debug.h" #include "../cpuexec.h" #include "../ppu.h" #include "../snapshot.h" #include "../apu/apu.h" #include "../display.h" #include "../gfx.h" #include "../movie.h" #include "../netplay.h" #include "wsnes9x.h" #include "win32_sound.h" #include "win32_display.h" #include "render.h" #include "AVIOutput.h" #include "wlanguage.h" #include #include #include #include struct SJoyState Joystick [16]; uint32 joypads [8]; bool8 do_frame_adjust=false; // avi variables static uint8* avi_buffer = NULL; static uint8* avi_sound_buffer = NULL; static int avi_sound_bytes_per_sample = 0; static double avi_sound_samples_per_update = 0; static double avi_sound_samples_error = 0; static int avi_width = 0; static int avi_height = 0; static int avi_pitch = 0; static int avi_image_size = 0; static uint32 avi_skip_frames = 0; static bool pre_avi_soundsync = true; static uint32 pre_avi_soundinputrate = 32000; void DoAVIClose(int reason); void S9xWinScanJoypads (); typedef struct { uint8 red; uint8 green; uint8 blue; } Colour; void ConvertDepth (SSurface *src, SSurface *dst, RECT *); static Colour FixedColours [256]; static uint8 palette [0x10000]; FILE *trace_fs = NULL; int __fastcall Normalize (int cur, int min, int max) { int Result = 0; if ((max - min) == 0) return (Result); Result = cur - min; Result = (Result * 200) / (max - min); Result -= 100; return (Result); } void S9xTextMode( void) { } void S9xGraphicsMode () { } void S9xExit( void) { SendMessage (GUI.hWnd, WM_COMMAND, ID_FILE_EXIT, 0); } #define IS_SLASH(x) ((x) == TEXT('\\') || (x) == TEXT('/')) static TCHAR startDirectory [PATH_MAX]; static bool startDirectoryValid = false; const TCHAR *S9xGetDirectoryT (enum s9x_getdirtype dirtype) { static TCHAR filename[PATH_MAX]; if(!startDirectoryValid) { // directory of the executable's location: GetModuleFileName(NULL, startDirectory, PATH_MAX); for(int i=lstrlen(startDirectory); i>=0; i--){ if(IS_SLASH(startDirectory[i])){ startDirectory[i]=TEXT('\0'); break; } } startDirectoryValid = true; } const TCHAR* rv = startDirectory; switch(dirtype){ default: case DEFAULT_DIR: case HOME_DIR: break; case SCREENSHOT_DIR: rv = GUI.ScreensDir; break; case ROM_DIR: rv = GUI.RomDir; break; case SRAM_DIR: rv = GUI.SRAMFileDir; break; case BIOS_DIR: rv = GUI.BiosDir; break; case SPC_DIR: rv = GUI.SPCDir; break; case PATCH_DIR: rv = GUI.PatchDir; break; case CHEAT_DIR: rv = GUI.CheatDir; break; case SNAPSHOT_DIR: rv = GUI.FreezeFileDir; break; case SAT_DIR: rv = GUI.SatDir; break; case ROMFILENAME_DIR: { lstrcpy(filename, _tFromChar(Memory.ROMFilename.c_str())); if(!filename[0]) rv = GUI.RomDir; for(int i=lstrlen(filename); i>=0; i--){ if(IS_SLASH(filename[i])){ filename[i]=TEXT('\0'); break; } } rv = filename; } break; } if (PathIsRelative(rv)) { TCHAR temp_container[PATH_MAX]; _sntprintf(temp_container, PATH_MAX, TEXT("%s\\%s"), startDirectory, rv); GetFullPathName(temp_container, PATH_MAX, filename, NULL); rv = filename; } _tmkdir(rv); return rv; } std::string S9xGetDirectory (enum s9x_getdirtype dirtype) { return std::string(_tToChar(S9xGetDirectoryT(dirtype))); } std::string S9xGetFilenameInc (std::string e, enum s9x_getdirtype dirtype) { std::string filename; auto split = splitpath(Memory.ROMFilename); std::string directory_string = S9xGetDirectory(dirtype); 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 += e; filename = makepath("", directory_string, split.stem, new_extension); i++; } while(_taccess(_tFromChar(filename.c_str()), 0) == 0 && i < 1000); return (filename); } bool8 S9xOpenSnapshotFile( const char *fname, bool8 read_only, STREAM *file) { char filename [_MAX_PATH + 1]; char drive [_MAX_DRIVE + 1]; char dir [_MAX_DIR + 1]; char fn [_MAX_FNAME + 1]; char ext [_MAX_EXT + 1]; _splitpath( fname, drive, dir, fn, ext); _makepath( filename, drive, dir, fn, ext[0] == '\0' ? ".000" : ext); if (read_only) { if ((*file = OPEN_STREAM (filename, "rb"))) return (TRUE); } else { if ((*file = OPEN_STREAM (filename, "wb"))) return (TRUE); FILE *fs = fopen (filename, "rb"); if (fs) { sprintf (String, "Freeze file \"%s\" exists but is read only", filename); fclose (fs); S9xMessage (S9X_ERROR, S9X_FREEZE_FILE_NOT_FOUND, String); } else { sprintf (String, "Cannot create freeze file \"%s\". Directory is read-only or does not exist.", filename); S9xMessage (S9X_ERROR, S9X_FREEZE_FILE_NOT_FOUND, String); } } return (FALSE); } void S9xCloseSnapshotFile( STREAM file) { CLOSE_STREAM (file); } void S9xMessage (int type, int, const char *str) { #ifdef DEBUGGER static FILE *out = NULL; if (out == NULL) out = fopen ("out.txt", "w"); fprintf (out, "%s\n", str); #endif S9xSetInfoString (str); // if we can't draw on the screen, messagebox it // also send to stderr/stdout depending on message type switch(type) { case S9X_INFO: if(Settings.StopEmulation) fprintf(stdout, "%s\n", str); break; case S9X_WARNING: fprintf(stdout, "%s\n", str); if(Settings.StopEmulation) MessageBoxA(GUI.hWnd, str, "Warning", MB_OK | MB_ICONWARNING); break; case S9X_ERROR: fprintf(stderr, "%s\n", str); if(Settings.StopEmulation) MessageBoxA(GUI.hWnd, str, "Error", MB_OK | MB_ICONERROR); break; case S9X_FATAL_ERROR: fprintf(stderr, "%s\n", str); if(Settings.StopEmulation) MessageBoxA(GUI.hWnd, str, "Fatal Error", MB_OK | MB_ICONERROR); break; default: fprintf(stdout, "%s\n", str); break; } } extern unsigned long START; void S9xSyncSpeed( void) { #ifdef NETPLAY_SUPPORT if (Settings.NetPlay) { #if defined (NP_DEBUG) && NP_DEBUG == 2 printf ("CLIENT: SyncSpeed @%d\n", timeGetTime () - START); #endif S9xWinScanJoypads (); LONG prev; BOOL success; // Wait for heart beat from server if ((success = ReleaseSemaphore (GUI.ClientSemaphore, 1, &prev)) && prev == 0) { // No heartbeats already arrived, have to wait for one. // Mop up the ReleaseSemaphore test above... WaitForSingleObject (GUI.ClientSemaphore, 0); // ... and then wait for the real sync-signal from the // client loop thread. NetPlay.PendingWait4Sync = WaitForSingleObject (GUI.ClientSemaphore, 100) != WAIT_OBJECT_0; #if defined (NP_DEBUG) && NP_DEBUG == 2 if (NetPlay.PendingWait4Sync) printf ("CLIENT: PendingWait4Sync1 @%d\n", timeGetTime () - START); #endif IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } else { if (success) { // Once for the ReleaseSemaphore above... WaitForSingleObject (GUI.ClientSemaphore, 0); if (prev == 4 && NetPlay.Waiting4EmulationThread) { // Reached the lower behind count threshold - tell the // server its safe to start sending sync pulses again. NetPlay.Waiting4EmulationThread = FALSE; S9xNPSendPause (FALSE); } #if defined (NP_DEBUG) && NP_DEBUG == 2 if (prev > 1) { printf ("CLIENT: SyncSpeed prev: %d @%d\n", prev, timeGetTime () - START); } #endif } else { #ifdef NP_DEBUG printf ("*** CLIENT: SyncSpeed: Release failed @ %d\n", timeGetTime () - START); #endif } // ... and again to mop up the already-waiting sync-signal NetPlay.PendingWait4Sync = WaitForSingleObject (GUI.ClientSemaphore, 200) != WAIT_OBJECT_0; #if defined (NP_DEBUG) && NP_DEBUG == 2 if (NetPlay.PendingWait4Sync) printf ("CLIENT: PendingWait4Sync2 @%d\n", timeGetTime () - START); #endif if (IPPU.SkippedFrames < NetPlay.MaxFrameSkip) { IPPU.SkippedFrames++; IPPU.RenderThisFrame = FALSE; } else { IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } } // Give up remainder of time-slice to any other waiting threads, // if they need any time, that is. Sleep (0); if (!NetPlay.PendingWait4Sync) { NetPlay.FrameCount++; S9xNPStepJoypadHistory (); } } else #endif if (!Settings.TurboMode && Settings.SkipFrames == AUTO_FRAMERATE && !GUI.AVIOut) { if (!do_frame_adjust) { IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } else { if (IPPU.SkippedFrames < Settings.AutoMaxSkipFrames) { IPPU.SkippedFrames++; IPPU.RenderThisFrame = FALSE; } else { IPPU.RenderThisFrame = TRUE; IPPU.SkippedFrames = 0; } } } else { uint32 SkipFrames; if(Settings.TurboMode && !GUI.AVIOut) SkipFrames = Settings.TurboSkipFrames; else SkipFrames = (Settings.SkipFrames == AUTO_FRAMERATE) ? 0 : Settings.SkipFrames; if (IPPU.FrameSkip++ >= SkipFrames) { IPPU.FrameSkip = 0; IPPU.SkippedFrames = 0; IPPU.RenderThisFrame = TRUE; } else { IPPU.SkippedFrames++; IPPU.RenderThisFrame = GUI.AVIOut!=0; } } } const char *S9xBasename (const char *f) { const char *p = f; const char *last = p; const char *slash; const char *backslash; // search rightmost separator while (true) { slash = strchr (p, '/'); backslash = strchr (p, '\\'); if (backslash != NULL) { if (slash == NULL || slash > backslash) { slash = backslash; } } if (slash == NULL) { break; } p = slash + 1; #ifdef UNICODE // update always; UTF-8 doesn't have a problem between ASCII character and multi-byte character. last = p; #else // update if it's not a trailer byte of a double-byte character. if (CharPrev(f, p) == slash) { last = p; } #endif } return last; } bool8 S9xReadMousePosition (int which, int &x, int &y, uint32 &buttons) { if (which == 0) { x = GUI.MouseX; y = GUI.MouseY; buttons = GUI.MouseButtons; return (TRUE); } return (FALSE); } bool S9xGetState (WORD KeyIdent) { if(KeyIdent == 0 || KeyIdent == VK_ESCAPE) // if it's the 'disabled' key, it's never pressed return true; if(!GUI.BackgroundInput && GUI.hWnd != GetForegroundWindow()) return true; if (KeyIdent & 0x8000) // if it's a joystick 'key': { int j = (KeyIdent >> 8) & 15; switch (KeyIdent & 0xff) { case 0: return !Joystick [j].Left; case 1: return !Joystick [j].Right; case 2: return !Joystick [j].Up; case 3: return !Joystick [j].Down; case 4: return !Joystick [j].PovLeft; case 5: return !Joystick [j].PovRight; case 6: return !Joystick [j].PovUp; case 7: return !Joystick [j].PovDown; case 49:return !Joystick [j].PovDnLeft; case 50:return !Joystick [j].PovDnRight; case 51:return !Joystick [j].PovUpLeft; case 52:return !Joystick [j].PovUpRight; case 41:return !Joystick [j].ZUp; case 42:return !Joystick [j].ZDown; case 43:return !Joystick [j].RUp; case 44:return !Joystick [j].RDown; case 45:return !Joystick [j].UUp; case 46:return !Joystick [j].UDown; case 47:return !Joystick [j].VUp; case 48:return !Joystick [j].VDown; default: if ((KeyIdent & 0xff) > 40) return true; // not pressed return !Joystick [j].Button [(KeyIdent & 0xff) - 8]; } } // the pause key is special, need this to catch all presses of it if(KeyIdent == VK_PAUSE) { if(GetAsyncKeyState(VK_PAUSE)) // not &'ing this with 0x8000 is intentional and necessary return false; } return ((GetKeyState (KeyIdent) & 0x80) == 0); } void CheckAxis (int val, int min, int max, bool &first, bool &second) { if (Normalize (val, min, max) < -S9X_JOY_NEUTRAL) { second = false; first = true; } else first = false; if (Normalize (val, min, max) > S9X_JOY_NEUTRAL) { first = false; second = true; } else second = false; } void S9xWinScanJoypads () { uint8 PadState[2]; JOYINFOEX jie; for (int C = 0; C != 16; C ++) { if (Joystick[C].Attached) { jie.dwSize = sizeof (jie); jie.dwFlags = JOY_RETURNALL; if (joyGetPosEx (JOYSTICKID1+C, &jie) != JOYERR_NOERROR) { Joystick[C].Attached = false; continue; } CheckAxis (jie.dwXpos, Joystick[C].Caps.wXmin, Joystick[C].Caps.wXmax, Joystick[C].Left, Joystick[C].Right); CheckAxis (jie.dwYpos, Joystick[C].Caps.wYmin, Joystick[C].Caps.wYmax, Joystick[C].Up, Joystick[C].Down); CheckAxis (jie.dwZpos, Joystick[C].Caps.wZmin, Joystick[C].Caps.wZmax, Joystick[C].ZUp, Joystick[C].ZDown); CheckAxis (jie.dwRpos, Joystick[C].Caps.wRmin, Joystick[C].Caps.wRmax, Joystick[C].RUp, Joystick[C].RDown); CheckAxis (jie.dwUpos, Joystick[C].Caps.wUmin, Joystick[C].Caps.wUmax, Joystick[C].UUp, Joystick[C].UDown); CheckAxis (jie.dwVpos, Joystick[C].Caps.wVmin, Joystick[C].Caps.wVmax, Joystick[C].VUp, Joystick[C].VDown); switch (jie.dwPOV) { case JOY_POVBACKWARD: Joystick[C].PovDown = true; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; case 4500: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = true; break; case 13500: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = true; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; case 22500: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = true; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; case 31500: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = true; Joystick[C].PovUpRight = false; break; case JOY_POVFORWARD: Joystick[C].PovDown = false; Joystick[C].PovUp = true; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; case JOY_POVLEFT: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = true; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; case JOY_POVRIGHT: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = true; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; default: Joystick[C].PovDown = false; Joystick[C].PovUp = false; Joystick[C].PovLeft = false; Joystick[C].PovRight = false; Joystick[C].PovDnLeft = false; Joystick[C].PovDnRight = false; Joystick[C].PovUpLeft = false; Joystick[C].PovUpRight = false; break; } for (int B = 0; B < 32; B ++) Joystick[C].Button[B] = (jie.dwButtons & (1 << B)) != 0; } } for (int J = 0; J < 8; J++) { if (Joypad [J].Enabled) { // toggle checks { PadState[0] = 0; PadState[0] |= ToggleJoypadStorage[J].R||TurboToggleJoypadStorage[J].R ? 16 : 0; PadState[0] |= ToggleJoypadStorage[J].L||TurboToggleJoypadStorage[J].L ? 32 : 0; PadState[0] |= ToggleJoypadStorage[J].X||TurboToggleJoypadStorage[J].X ? 64 : 0; PadState[0] |= ToggleJoypadStorage[J].A||TurboToggleJoypadStorage[J].A ? 128 : 0; PadState[1] = 0; PadState[1] |= ToggleJoypadStorage[J].Right||TurboToggleJoypadStorage[J].Right ? 1 : 0; PadState[1] |= ToggleJoypadStorage[J].Left||TurboToggleJoypadStorage[J].Left ? 2 : 0; PadState[1] |= ToggleJoypadStorage[J].Down||TurboToggleJoypadStorage[J].Down ? 4 : 0; PadState[1] |= ToggleJoypadStorage[J].Up||TurboToggleJoypadStorage[J].Up ? 8 : 0; PadState[1] |= ToggleJoypadStorage[J].Start||TurboToggleJoypadStorage[J].Start ? 16 : 0; PadState[1] |= ToggleJoypadStorage[J].Select||TurboToggleJoypadStorage[J].Select ? 32 : 0; PadState[1] |= ToggleJoypadStorage[J].Y||TurboToggleJoypadStorage[J].Y ? 64 : 0; PadState[1] |= ToggleJoypadStorage[J].B||TurboToggleJoypadStorage[J].B ? 128 : 0; } // auto-hold AND regular key/joystick presses if(S9xGetState(Joypad[J+8].Autohold)) { PadState[0] ^= (!S9xGetState(Joypad[J].R)||!S9xGetState(Joypad[J+8].R)) ? 16 : 0; PadState[0] ^= (!S9xGetState(Joypad[J].L)||!S9xGetState(Joypad[J+8].L)) ? 32 : 0; PadState[0] ^= (!S9xGetState(Joypad[J].X)||!S9xGetState(Joypad[J+8].X)) ? 64 : 0; PadState[0] ^= (!S9xGetState(Joypad[J].A)||!S9xGetState(Joypad[J+8].A)) ? 128 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Right)) ? 1 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Right_Up)) ? 1 + 8 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Right_Down)) ? 1 + 4 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Left)) ? 2 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Left_Up)) ? 2 + 8 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Left_Down)) ? 2 + 4 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Down)) ? 4 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Up)) ? 8 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Start)||!S9xGetState(Joypad[J+8].Start)) ? 16 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Select)||!S9xGetState(Joypad[J+8].Select)) ? 32 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].Y)||!S9xGetState(Joypad[J+8].Y)) ? 64 : 0; PadState[1] ^= (!S9xGetState(Joypad[J].B)||!S9xGetState(Joypad[J+8].B)) ? 128 : 0; } bool turbofy = !S9xGetState(Joypad[J+8].TempTurbo); // All Mod for turbo //handle turbo case! (autofire / auto-fire) if(turbofy || ((GUI.TurboMask&TURBO_A_MASK))&&(PadState[0]&128) || !S9xGetState(Joypad[J+8].A )) PadState[0]^=(joypads[J]&128); if(turbofy || ((GUI.TurboMask&TURBO_B_MASK))&&(PadState[1]&128) || !S9xGetState(Joypad[J+8].B )) PadState[1]^=((joypads[J]&(128<<8))>>8); if(turbofy || ((GUI.TurboMask&TURBO_Y_MASK))&&(PadState[1]&64) || !S9xGetState(Joypad[J+8].Y )) PadState[1]^=((joypads[J]&(64<<8))>>8); if(turbofy || ((GUI.TurboMask&TURBO_X_MASK))&&(PadState[0]&64) || !S9xGetState(Joypad[J+8].X )) PadState[0]^=(joypads[J]&64); if(turbofy || ((GUI.TurboMask&TURBO_L_MASK))&&(PadState[0]&32) || !S9xGetState(Joypad[J+8].L )) PadState[0]^=(joypads[J]&32); if(turbofy || ((GUI.TurboMask&TURBO_R_MASK))&&(PadState[0]&16) || !S9xGetState(Joypad[J+8].R )) PadState[0]^=(joypads[J]&16); if(turbofy || ((GUI.TurboMask&TURBO_STA_MASK))&&(PadState[1]&16) || !S9xGetState(Joypad[J+8].Start )) PadState[1]^=((joypads[J]&(16<<8))>>8); if(turbofy || ((GUI.TurboMask&TURBO_SEL_MASK))&&(PadState[1]&32) || !S9xGetState(Joypad[J+8].Select)) PadState[1]^=((joypads[J]&(32<<8))>>8); if( ((GUI.TurboMask&TURBO_LEFT_MASK))&&(PadState[1]&2) ) PadState[1]^=((joypads[J]&(2<<8))>>8); if( ((GUI.TurboMask&TURBO_UP_MASK))&&(PadState[1]&8) ) PadState[1]^=((joypads[J]&(8<<8))>>8); if( ((GUI.TurboMask&TURBO_RIGHT_MASK))&&(PadState[1]&1) ) PadState[1]^=((joypads[J]&(1<<8))>>8); if( ((GUI.TurboMask&TURBO_DOWN_MASK))&&(PadState[1]&4) ) PadState[1]^=((joypads[J]&(4<<8))>>8); if(TurboToggleJoypadStorage[J].A ) PadState[0]^=(joypads[J]&128); if(TurboToggleJoypadStorage[J].B ) PadState[1]^=((joypads[J]&(128<<8))>>8); if(TurboToggleJoypadStorage[J].Y ) PadState[1]^=((joypads[J]&(64<<8))>>8); if(TurboToggleJoypadStorage[J].X ) PadState[0]^=(joypads[J]&64); if(TurboToggleJoypadStorage[J].L ) PadState[0]^=(joypads[J]&32); if(TurboToggleJoypadStorage[J].R ) PadState[0]^=(joypads[J]&16); if(TurboToggleJoypadStorage[J].Start ) PadState[1]^=((joypads[J]&(16<<8))>>8); if(TurboToggleJoypadStorage[J].Select) PadState[1]^=((joypads[J]&(32<<8))>>8); if(TurboToggleJoypadStorage[J].Left ) PadState[1]^=((joypads[J]&(2<<8))>>8); if(TurboToggleJoypadStorage[J].Up ) PadState[1]^=((joypads[J]&(8<<8))>>8); if(TurboToggleJoypadStorage[J].Right ) PadState[1]^=((joypads[J]&(1<<8))>>8); if(TurboToggleJoypadStorage[J].Down ) PadState[1]^=((joypads[J]&(4<<8))>>8); //end turbo case... // enforce left+right/up+down disallowance here to // avoid recording unused l+r/u+d that will cause desyncs // when played back with l+r/u+d is allowed if(!Settings.UpAndDown) { if((PadState[1] & 2) != 0) PadState[1] &= ~(1); if((PadState[1] & 8) != 0) PadState[1] &= ~(4); } joypads [J] = PadState [0] | (PadState [1] << 8) | 0x80000000; } else joypads [J] = 0; } #ifdef NETPLAY_SUPPORT if (Settings.NetPlay) { // Send joypad position update to server S9xNPSendJoypadUpdate (joypads [GUI.NetplayUseJoypad1 ? 0 : NetPlay.Player-1]); // set input from network for (int J = 0; J < NP_MAX_CLIENTS; J++) joypads[J] = S9xNPGetJoypad (J); } #endif } void S9xDetectJoypads() { for (int C = 0; C != 16; C ++) Joystick[C].Attached = joyGetDevCaps (JOYSTICKID1+C, &Joystick[C].Caps, sizeof( JOYCAPS)) == JOYERR_NOERROR; } void InitSnes9x( void) { #ifdef DEBUGGER // extern FILE *trace; // trace = fopen( "SNES9X.TRC", "wt"); // freopen( "SNES9X.OUT", "wt", stdout); // freopen( "SNES9X.ERR", "wt", stderr); // CPU.Flags |= TRACE_FLAG; // APU.Flags |= TRACE_FLAG; #endif //#ifdef GENERATE_OFFSETS_H // offsets_h = fopen ("offsets.h", "wt"); // generate_offsets_h (0, NULL); // fclose (offsets_h); //#endif S9xCustomDisplayString = S9xWinDisplayString; Memory.Init(); extern void S9xPostRomInit(); Memory.PostRomInitFunc = S9xPostRomInit; InitializeCriticalSection(&GUI.SoundCritSect); GUI.SoundSyncEvent = CreateEvent(NULL,TRUE,TRUE,NULL); CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); S9xInitAPU(); WinDisplayReset(); ReInitSound(); S9xMovieInit (); S9xDetectJoypads(); } void DeinitS9x() { DeleteCriticalSection(&GUI.SoundCritSect); CloseHandle(GUI.SoundSyncEvent); CoUninitialize(); if(GUI.GunSight) DestroyCursor(GUI.GunSight);//= LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR_SCOPE)); if(GUI.Arrow) DestroyCursor(GUI.Arrow);// = LoadCursor (NULL, IDC_ARROW); if(GUI.Accelerators) DestroyAcceleratorTable(GUI.Accelerators);// = LoadAccelerators (hInstance, MAKEINTRESOURCE (IDR_SNES9X_ACCELERATORS)); } //void Convert8To24 (SSurface *src, SSurface *dst, RECT *srect) //{ // uint32 brightness = IPPU.MaxBrightness >> 1; // uint8 levels [32]; // int height = srect->bottom - srect->top; // int width = srect->right - srect->left; // int offset1 = srect->top * src->Pitch + srect->left; // int offset2 = ((dst->Height - height) >> 1) * dst->Pitch + // ((dst->Width - width) >> 1) * 3; // // for (int l = 0; l < 32; l++) // levels [l] = l * brightness; // // for (int y = 0; y < height; y++) // { // uint8 *s = ((uint8 *) src->Surface + y * src->Pitch + offset1); // uint8 *d = ((uint8 *) dst->Surface + y * dst->Pitch + offset2); // //#ifdef LSB_FIRST // if (GUI.RedShift < GUI.BlueShift) //#else // if (GUI.RedShift > GUI.BlueShift) //#endif // { // // Order is RGB // for (int x = 0; x < width; x++) // { // uint16 pixel = PPU.CGDATA [*s++]; // *(d + 0) = levels [(pixel & 0x1f)]; // *(d + 1) = levels [((pixel >> 5) & 0x1f)]; // *(d + 2) = levels [((pixel >> 10) & 0x1f)]; // d += 3; // } // } // else // { // // Order is BGR // for (int x = 0; x < width; x++) // { // uint16 pixel = PPU.CGDATA [*s++]; // *(d + 0) = levels [((pixel >> 10) & 0x1f)]; // *(d + 1) = levels [((pixel >> 5) & 0x1f)]; // *(d + 2) = levels [(pixel & 0x1f)]; // d += 3; // } // } // } //} void S9xAutoSaveSRAM () { Memory.SaveSRAM (S9xGetFilename (".srm", SRAM_DIR).c_str()); } void S9xSetPause (uint32 mask) { Settings.ForcedPause |= mask; S9xSetSoundMute(TRUE); } void S9xClearPause (uint32 mask) { Settings.ForcedPause &= ~mask; if (!Settings.ForcedPause) { // Wake up the main loop thread just if its blocked in a GetMessage call. PostMessage (GUI.hWnd, WM_NULL, 0, 0); } } bool JustifierOffscreen() { return (bool)((GUI.MouseButtons&2)!=0); } //void JustifierButtons(uint32& justifiers) //{ // if(IPPU.Controller==SNES_JUSTIFIER_2) // { // if((GUI.MouseButtons&1)||(GUI.MouseButtons&2)) // { // justifiers|=0x00200; // } // if(GUI.MouseButtons&4) // { // justifiers|=0x00800; // } // } // else // { // if((GUI.MouseButtons&1)||(GUI.MouseButtons&2)) // { // justifiers|=0x00100; // } // if(GUI.MouseButtons&4) // { // justifiers|=0x00400; // } // } //} #define Interp(c1, c2) \ (c1 == c2) ? c1 : \ (((((c1 & 0x07E0) + (c2 & 0x07E0)) >> 1) & 0x07E0) + \ ((((c1 & 0xF81F) + (c2 & 0xF81F)) >> 1) & 0xF81F)) // Src: GFX.Screen (variable size) 16bpp top-down // Dst: avi_buffer 256xH (1x1) 24bpp bottom-up void BuildAVIVideoFrame1X (void) { const int srcWidth = IPPU.RenderedScreenWidth; const int srcHeight = IPPU.RenderedScreenHeight; const bool hires = srcWidth > SNES_WIDTH; const bool interlaced = srcHeight > SNES_HEIGHT_EXTENDED; const int srcPitch = GFX.Pitch >> 1; const int srcStep = (interlaced ? srcPitch << 1 : srcPitch) - (hires ? avi_width << 1 : avi_width); const int dstStep = avi_pitch + avi_width * 3; #ifdef LSB_FIRST const bool order_is_rgb = (GUI.RedShift < GUI.BlueShift); #else const bool order_is_rgb = (GUI.RedShift > GUI.BlueShift); #endif const int image_offset = (avi_height - (interlaced?srcHeight>>1:srcHeight)) * avi_pitch; uint16 *s = GFX.Screen; uint8 *d = avi_buffer + (avi_height - 1) * avi_pitch; for(int y = 0; y < avi_height; y++) { for(int x = 0; x < avi_width; x++) { uint32 pixel; if(hires && interlaced) { uint16 *s2 = s + srcPitch; pixel = Interp(Interp(*s,*(s+1)),Interp(*s2,*(s2+1))); s+=2; } else if(interlaced) { uint16 *s2 = s + srcPitch; pixel = Interp(*s,*s2); s++; } else if(hires) { pixel = Interp(*s,*(s+1)); s+=2; } else { pixel = *s; s++; } if(order_is_rgb) { // Order is RGB *(d + 0) = (pixel >> (11 - 3)) & 0xf8; *(d + 1) = (pixel >> (6 - 3)) & 0xf8; *(d + 2) = (pixel & 0x1f) << 3; d += 3; } else { // Order is BGR *(d + 0) = (pixel & 0x1f) << 3; *(d + 1) = (pixel >> (6 - 3)) & 0xf8; *(d + 2) = (pixel >> (11 - 3)) & 0xf8; d += 3; } } s += srcStep; d -= dstStep; } // black out what we might have missed if(image_offset > 0) memset(avi_buffer, 0, image_offset); } // Src: GFX.Screen (variable size) 16bpp top-down // Dst: avi_buffer 512x2H (2x2) 24bpp bottom-up void BuildAVIVideoFrame2X (void) { const int srcWidth = IPPU.RenderedScreenWidth; const int srcHeight = IPPU.RenderedScreenHeight; const bool hires = srcWidth > SNES_WIDTH; const bool interlaced = srcHeight > SNES_HEIGHT_EXTENDED; const int srcPitch = GFX.Pitch >> 1; const int srcStep = (interlaced ? srcPitch << 1 : srcPitch) - (hires ? avi_width : avi_width >> 1); const int dstStep = (avi_pitch << 1) + avi_width * 3; #ifdef LSB_FIRST const bool order_is_rgb = (GUI.RedShift < GUI.BlueShift); #else const bool order_is_rgb = (GUI.RedShift > GUI.BlueShift); #endif const int image_offset = (avi_height - (interlaced?srcHeight:srcHeight<<1)) * avi_pitch; uint16 *s = GFX.Screen; uint8 *d = avi_buffer + (avi_height - 1) * avi_pitch; uint8 *d2 = d - avi_pitch; for(int y = 0; y < avi_height >> 1; y++) { for(int x = 0; x < avi_width >> 1; x++) { uint32 pixel, pixel2, pixel3, pixel4; if(hires && interlaced) { uint16 *s2 = s + srcPitch; pixel = *s; pixel2 = *(s+1); pixel3 = *s2; pixel4 = *(s2+1); s+=2; } else if(interlaced) { uint16 *s2 = s + srcPitch; pixel = pixel2 = *s; pixel3 = pixel4 = *s2; s++; } else if(hires) { pixel = pixel3 = *s; pixel2 = pixel4 = *(s+1); s+=2; } else { pixel = pixel2 = pixel3 = pixel4 = *s; s++; } if(order_is_rgb) { // Order is RGB *(d + 0) = (pixel >> (11 - 3)) & 0xf8; *(d + 1) = (pixel >> (6 - 3)) & 0xf8; *(d + 2) = (pixel & 0x1f) << 3; *(d + 0) = (pixel2 >> (11 - 3)) & 0xf8; *(d + 1) = (pixel2 >> (6 - 3)) & 0xf8; *(d + 2) = (pixel2 & 0x1f) << 3; d += 6; *(d2 + 0) = (pixel3 >> (11 - 3)) & 0xf8; *(d2 + 1) = (pixel3 >> (6 - 3)) & 0xf8; *(d2 + 2) = (pixel3 & 0x1f) << 3; *(d2 + 0) = (pixel4 >> (11 - 3)) & 0xf8; *(d2 + 1) = (pixel4 >> (6 - 3)) & 0xf8; *(d2 + 2) = (pixel4 & 0x1f) << 3; d2 += 6; } else { // Order is BGR *(d + 0) = (pixel & 0x1f) << 3; *(d + 1) = (pixel >> (6 - 3)) & 0xf8; *(d + 2) = (pixel >> (11 - 3)) & 0xf8; *(d + 3) = (pixel2 & 0x1f) << 3; *(d + 4) = (pixel2 >> (6 - 3)) & 0xf8; *(d + 5) = (pixel2 >> (11 - 3)) & 0xf8; d += 6; *(d2 + 0) = (pixel3 & 0x1f) << 3; *(d2 + 1) = (pixel3 >> (6 - 3)) & 0xf8; *(d2 + 2) = (pixel3 >> (11 - 3)) & 0xf8; *(d2 + 3) = (pixel4 & 0x1f) << 3; *(d2 + 4) = (pixel4 >> (6 - 3)) & 0xf8; *(d2 + 5) = (pixel4 >> (11 - 3)) & 0xf8; d2 += 6; } } s += srcStep; d -= dstStep; d2 -= dstStep; } // black out what we might have missed if(image_offset > 0) memset(avi_buffer, 0, image_offset); } void DoAVIOpen(const TCHAR* filename) { // close current instance if(GUI.AVIOut) { AVIClose(&GUI.AVIOut); GUI.AVIOut = NULL; } pre_avi_soundsync = Settings.SoundSync; pre_avi_soundinputrate = Settings.SoundInputRate; Settings.SoundSync = false; Settings.SoundInputRate = 32000; ReInitSound(); CloseSoundDevice(); // create new writer AVICreate(&GUI.AVIOut); int framerate = Memory.ROMFramesPerSecond; int frameskip = Settings.SkipFrames; if(frameskip == AUTO_FRAMERATE) frameskip = 1; else frameskip++; AVISetFramerate(framerate, frameskip, GUI.AVIOut); avi_width = SNES_WIDTH; avi_height = Settings.ShowOverscan ? SNES_HEIGHT_EXTENDED : SNES_HEIGHT; avi_skip_frames = Settings.SkipFrames; if(GUI.AVIHiRes) { avi_width *= 2; avi_height *= 2; } if(avi_height % 2 != 0) // most codecs can't handle odd-height images avi_height++; avi_pitch = ((avi_width * 3) + 3) & ~3; //((avi_width * 24 + 31) / 8) & ~3; avi_image_size = avi_pitch * avi_height; BITMAPINFOHEADER bi; memset(&bi, 0, sizeof(bi)); bi.biSize = 0x28; bi.biPlanes = 1; bi.biBitCount = 24; bi.biWidth = avi_width; bi.biHeight = avi_height; bi.biSizeImage = avi_image_size; AVISetVideoFormat(&bi, GUI.AVIOut); WAVEFORMATEX wfx; wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = Settings.SoundPlaybackRate; wfx.nBlockAlign = 2 * 2; wfx.wBitsPerSample = 16; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.cbSize = 0; if(!GUI.Mute) { AVISetSoundFormat(&wfx, GUI.AVIOut); } if(!AVIBegin(filename, GUI.AVIOut)) { DoAVIClose(2); GUI.AVIOut = NULL; return; } avi_sound_samples_per_update = (double) (wfx.nSamplesPerSec * frameskip) / framerate; avi_sound_bytes_per_sample = wfx.nBlockAlign; avi_sound_samples_error = 0; // init buffers avi_buffer = new uint8[avi_image_size]; avi_sound_buffer = new uint8[(int) ceil(avi_sound_samples_per_update) * avi_sound_bytes_per_sample]; } void DoAVIClose(int reason) { if(!GUI.AVIOut) { return; } AVIClose(&GUI.AVIOut); GUI.AVIOut = NULL; delete [] avi_buffer; delete [] avi_sound_buffer; avi_buffer = NULL; avi_sound_buffer = NULL; Settings.SoundSync = pre_avi_soundsync; Settings.SoundInputRate = pre_avi_soundinputrate; ReInitSound(); switch(reason) { case 1: // emu settings changed S9xMessage(S9X_INFO, S9X_AVI_INFO, AVI_CONFIGURATION_CHANGED); break; case 2: // create AVI failed S9xMessage(S9X_INFO, S9X_AVI_INFO, AVI_CREATION_FAILED); break; default: // print nothing break; } } void DoAVIVideoFrame() { static uint32 lastFrameCount=0; if(!GUI.AVIOut || !avi_buffer || (IPPU.FrameCount==lastFrameCount)) { return; } lastFrameCount=IPPU.FrameCount; // check configuration const WAVEFORMATEX* pwfex = NULL; WAVEFORMATEX wfx; wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = Settings.SoundPlaybackRate; wfx.nBlockAlign = 2 * 2; wfx.wBitsPerSample = 16; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.cbSize = 0; if(avi_skip_frames != Settings.SkipFrames || (AVIGetSoundFormat(GUI.AVIOut, &pwfex) && memcmp(pwfex, &wfx, sizeof(WAVEFORMATEX)))) { DoAVIClose(1); return; } if(GUI.AVIHiRes) BuildAVIVideoFrame2X(); else BuildAVIVideoFrame1X(); // write to AVI AVIAddVideoFrame(avi_buffer, GUI.AVIOut); // generate sound if(pwfex) { const int stereo_multiplier = 2; avi_sound_samples_error += avi_sound_samples_per_update; int samples = (int) avi_sound_samples_error; avi_sound_samples_error -= samples; S9xMixSamples(avi_sound_buffer, samples*stereo_multiplier); AVIAddSoundSamples(avi_sound_buffer, samples, GUI.AVIOut); } }