1312 lines
36 KiB
C++
1312 lines
36 KiB
C++
/*****************************************************************************\
|
|
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 <shlwapi.h>
|
|
#include <direct.h>
|
|
|
|
#include <io.h>
|
|
|
|
#include <math.h>
|
|
|
|
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 DoAVIOpen(const char* filename);
|
|
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
|
|
|
|
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);
|
|
}
|
|
}
|