snes9x/netplay.cpp
2023-03-07 13:58:21 -06:00

1060 lines
28 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.
\*****************************************************************************/
#ifdef NETPLAY_SUPPORT
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <memory.h>
#include <sys/types.h>
#include "snes9x.h"
#include "controls.h"
#ifdef __WIN32__
#include <winsock.h>
#include <process.h>
#include "win32/wsnes9x.h"
#define ioctl ioctlsocket
#define close(h) if(h){closesocket(h);}
#define read(a,b,c) recv(a, b, c, 0)
#define write(a,b,c) send(a, b, c, 0)
#else
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef __SVR4
#include <sys/stropts.h>
#endif
#endif
#ifdef USE_THREADS
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#endif
#include "memmap.h"
#include "netplay.h"
#include "snapshot.h"
#include "display.h"
void S9xNPClientLoop (void *);
bool8 S9xNPLoadROM (uint32 len);
bool8 S9xNPLoadROMDialog (const char *);
bool8 S9xNPGetROMImage (uint32 len);
void S9xNPGetSRAMData (uint32 len);
void S9xNPGetFreezeFile (uint32 len);
unsigned long START = 0;
bool8 S9xNPConnect ();
bool8 S9xNPConnectToServer (const char *hostname, int port,
const char *rom_name)
{
if (!S9xNPInitialise ())
return (FALSE);
S9xNPDisconnect ();
NetPlay.MySequenceNum = 0;
NetPlay.ServerSequenceNum = 0;
NetPlay.Connected = FALSE;
NetPlay.Abort = FALSE;
NetPlay.Player = 0;
NetPlay.Paused = FALSE;
NetPlay.PercentageComplete = 0;
NetPlay.Socket = 0;
if (NetPlay.ServerHostName)
free ((char *) NetPlay.ServerHostName);
NetPlay.ServerHostName = strdup (hostname);
if (NetPlay.ROMName)
free ((char *) NetPlay.ROMName);
NetPlay.ROMName = strdup (rom_name);
NetPlay.Port = port;
NetPlay.PendingWait4Sync = FALSE;
#ifdef __WIN32__
if (GUI.ClientSemaphore == NULL)
GUI.ClientSemaphore = CreateSemaphore (NULL, 0, NP_JOYPAD_HIST_SIZE, NULL);
if (NetPlay.ReplyEvent == NULL)
NetPlay.ReplyEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
_beginthread (S9xNPClientLoop, 0, NULL);
return (TRUE);
#endif
return S9xNPConnect();
}
bool8 S9xNPConnect ()
{
struct sockaddr_in address;
struct hostent *hostinfo;
unsigned int addr;
address.sin_family = AF_INET;
address.sin_port = htons (NetPlay.Port);
#ifdef NP_DEBUG
printf ("CLIENT: Looking up server's hostname (%s) @%ld\n", NetPlay.ServerHostName, S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Looking up server's hostname...");
if ((int) (addr = inet_addr (NetPlay.ServerHostName)) == -1)
{
if ((hostinfo = gethostbyname (NetPlay.ServerHostName)))
{
memcpy ((char *)&address.sin_addr, hostinfo->h_addr,
hostinfo->h_length);
}
else
{
S9xNPSetError ("\
Unable to look up server's IP address from hostname.\n\n\
Unknown hostname or may be your nameserver isn't set\n\
up correctly?");
return (FALSE);
}
}
else
{
memcpy ((char *)&address.sin_addr, &addr, sizeof (addr));
}
#ifdef NP_DEBUG
printf ("CLIENT: Creating socket @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Creating network socket...");
if ((NetPlay.Socket = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
S9xNPSetError ("Creating network socket failed.");
return (FALSE);
}
#ifdef NP_DEBUG
printf ("CLIENT: Trying to connect to server @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Trying to connect to Snes9x server...");
if (connect (NetPlay.Socket, (struct sockaddr *) &address, sizeof (address)) < 0)
{
char buf [100];
#ifdef __WIN32__
if (WSAGetLastError () == WSAECONNREFUSED)
#else
if (errno == ECONNREFUSED)
#endif
{
S9xNPSetError ("\
Connection to remote server socket refused:\n\n\
Is there actually a Snes9x NetPlay server running\n\
on the remote machine on this port?");
}
else
{
sprintf (buf, "Connection to server failed with error number %d",
#ifdef __WIN32__
WSAGetLastError ()
#else
errno
#endif
);
S9xNPSetError(buf);
S9xNPDisconnect ();
}
return (FALSE);
}
NetPlay.Connected = TRUE;
#ifdef NP_DEBUG
printf ("CLIENT: Sending 'HELLO' message @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Sending 'HELLO' message...");
/* Send the server a HELLO packet*/
int len = 7 + 4 + strlen (NetPlay.ROMName) + 1;
uint8 *tmp = new uint8 [len];
uint8 *ptr = tmp;
*ptr++ = NP_CLNT_MAGIC;
*ptr++ = NetPlay.MySequenceNum++;
*ptr++ = NP_CLNT_HELLO;
WRITE_LONG (ptr, len);
ptr += 4;
#ifdef __WIN32__
uint32 ft = Settings.FrameTime;
WRITE_LONG (ptr, ft);
#else
WRITE_LONG (ptr, Settings.FrameTime);
#endif
ptr += 4;
strcpy ((char *) ptr, NetPlay.ROMName);
if (!S9xNPSendData (NetPlay.Socket, tmp, len))
{
S9xNPSetError ("Sending 'HELLO' message failed.");
S9xNPDisconnect ();
delete[] tmp;
return (FALSE);
}
delete[] tmp;
#ifdef NP_DEBUG
printf ("CLIENT: Waiting for 'WELCOME' reply from server @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Waiting for 'HELLO' reply from server...");
uint8 header [7];
if (!S9xNPGetData (NetPlay.Socket, header, 7) ||
header [0] != NP_SERV_MAGIC || header [1] != 0 ||
(header [2] & 0x1f) != NP_SERV_HELLO)
{
S9xNPSetError ("Error in 'HELLO' reply packet received from server.");
S9xNPDisconnect ();
return (FALSE);
}
#ifdef NP_DEBUG
printf ("CLIENT: Got 'WELCOME' reply @%ld\n", S9xGetMilliTime () - START);
#endif
len = READ_LONG (&header [3]);
if (len > 256)
{
S9xNPSetError ("Error in 'HELLO' reply packet received from server.");
S9xNPDisconnect ();
return (FALSE);
}
uint8 *data = new uint8 [len];
if (!S9xNPGetData (NetPlay.Socket, data, len - 7))
{
S9xNPSetError ("Error in 'HELLO' reply packet received from server.");
delete[] data;
S9xNPDisconnect ();
return (FALSE);
}
if (data [0] != NP_VERSION)
{
S9xNPSetError ("\
The Snes9x NetPlay server implements a different\n\
version of the protocol. Disconnecting.");
delete[] data;
S9xNPDisconnect ();
return (FALSE);
}
NetPlay.FrameCount = READ_LONG (&data [2]);
if (!(header [2] & 0x80) &&
strcmp ((char *) data + 4 + 2, NetPlay.ROMName) != 0)
{
if (!S9xNPLoadROMDialog ((char *) data + 4 + 2))
{
delete[] data;
S9xNPDisconnect ();
return (FALSE);
}
}
NetPlay.Player = data [1];
delete[] data;
NetPlay.PendingWait4Sync = TRUE;
Settings.NetPlay = TRUE;
S9xNPResetJoypadReadPos ();
NetPlay.ServerSequenceNum = 1;
#ifdef NP_DEBUG
printf ("CLIENT: Sending 'READY' to server @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Sending 'READY' to the server...");
return (S9xNPSendReady ((header [2] & 0x80) ?
NP_CLNT_WAITING_FOR_ROM_IMAGE :
NP_CLNT_READY));
}
bool8 S9xNPSendReady (uint8 op)
{
uint8 ready [7];
uint8 *ptr = ready;
*ptr++ = NP_CLNT_MAGIC;
*ptr++ = NetPlay.MySequenceNum++;
*ptr++ = op;
WRITE_LONG (ptr, 7);
ptr += 4;
if (!S9xNPSendData (NetPlay.Socket, ready, 7))
{
S9xNPDisconnect ();
S9xNPSetError ("Sending 'READY' message failed.");
return (FALSE);
}
return (TRUE);
}
bool8 S9xNPSendPause (bool8 paused)
{
#ifdef NP_DEBUG
printf ("CLIENT: Pause - %s @%ld\n", paused ? "YES" : "NO", S9xGetMilliTime () - START);
#endif
uint8 pause [7];
uint8 *ptr = pause;
*ptr++ = NP_CLNT_MAGIC;
*ptr++ = NetPlay.MySequenceNum++;
*ptr++ = NP_CLNT_PAUSE | (paused ? 0x80 : 0);
WRITE_LONG (ptr, 7);
ptr += 4;
if (!S9xNPSendData (NetPlay.Socket, pause, 7))
{
S9xNPSetError ("Sending 'PAUSE' message failed.");
S9xNPDisconnect ();
return (FALSE);
}
return (TRUE);
}
#ifdef __WIN32__
void S9xNPClientLoop (void *)
{
NetPlay.Waiting4EmulationThread = FALSE;
if (S9xNPConnect ())
{
S9xClearPause (PAUSE_NETPLAY_CONNECT);
while (NetPlay.Connected)
{
if (S9xNPWaitForHeartBeat ())
{
LONG prev;
if (!ReleaseSemaphore (GUI.ClientSemaphore, 1, &prev))
{
#ifdef NP_DEBUG
printf ("CLIENT: ReleaseSemaphore failed - already hit max count (%d) %ld\n", NP_JOYPAD_HIST_SIZE, S9xGetMilliTime () - START);
#endif
S9xNPSetWarning ("NetPlay: Client may be out of sync with server.");
}
else
{
if (!NetPlay.Waiting4EmulationThread &&
prev == (int) NetPlay.MaxBehindFrameCount)
{
NetPlay.Waiting4EmulationThread = TRUE;
S9xNPSendPause (TRUE);
}
}
}
else
S9xNPDisconnect ();
}
}
else
{
S9xClearPause (PAUSE_NETPLAY_CONNECT);
}
#ifdef NP_DEBUG
printf ("CLIENT: Client thread exiting @%ld\n", S9xGetMilliTime () - START);
#endif
}
#endif
bool8 S9xNPCheckForHeartBeat (uint32 time_msec)
{
fd_set read_fds;
struct timeval timeout;
int res;
int i;
int max_fd = NetPlay.Socket;
FD_ZERO (&read_fds);
FD_SET (NetPlay.Socket, &read_fds);
timeout.tv_sec = 0;
timeout.tv_usec = time_msec * 1000;
res = select (max_fd + 1, &read_fds, NULL, NULL, &timeout);
i = (res > 0 && FD_ISSET(NetPlay.Socket, &read_fds));
#if defined(NP_DEBUG) && NP_DEBUG >= 4
printf ("CLIENT: S9xCheckForHeartBeat %s @%ld\n", (i?"successful":"still waiting"), S9xGetMilliTime () - START);
#endif
return i;
}
bool8 S9xNPWaitForHeartBeatDelay (uint32 time_msec)
{
if (!S9xNPCheckForHeartBeat(time_msec))
return FALSE;
if (!S9xNPWaitForHeartBeat())
{
S9xNPDisconnect();
return FALSE;
}
return TRUE;
}
bool8 S9xNPWaitForHeartBeat ()
{
uint8 header [3 + 4 + 4 * 5];
while (S9xNPGetData (NetPlay.Socket, header, 3 + 4))
{
if (header [0] != NP_SERV_MAGIC)
{
S9xNPSetError ("Bad magic value from server while waiting for heart-beat message\n");
S9xNPDisconnect ();
return (FALSE);
}
if (header [1] != NetPlay.ServerSequenceNum)
{
char buf [200];
sprintf (buf, "Unexpected message sequence number from server, expected %d, got %d\n", NetPlay.ServerSequenceNum, header [1]);
S9xNPSetWarning (buf);
NetPlay.ServerSequenceNum = header [1] + 1;
}
else
NetPlay.ServerSequenceNum++;
if ((header [2] & 0x1f) == NP_SERV_JOYPAD)
{
// Top 2 bits + 1 of opcode is joypad data count.
int num = (header [2] >> 6) + 1;
if (num)
{
if (!S9xNPGetData (NetPlay.Socket, header + 3 + 4, num * 4))
{
S9xNPSetError ("Error while receiving 'JOYPAD' message.");
S9xNPDisconnect ();
return (FALSE);
}
}
NetPlay.Frame [NetPlay.JoypadWriteInd] = READ_LONG (&header [3]);
int i;
for (i = 0; i < num; i++)
NetPlay.Joypads [NetPlay.JoypadWriteInd][i] = READ_LONG (&header [3 + 4 + i * sizeof (uint32)]);
for (i = 0; i < NP_MAX_CLIENTS; i++)
NetPlay.JoypadsReady [NetPlay.JoypadWriteInd][i] = TRUE;
NetPlay.Paused = (header [2] & 0x20) != 0;
NetPlay.JoypadWriteInd = (NetPlay.JoypadWriteInd + 1) % NP_JOYPAD_HIST_SIZE;
if (NetPlay.JoypadWriteInd != (NetPlay.JoypadReadInd + 1) % NP_JOYPAD_HIST_SIZE)
{
//printf ("(%d)", (NetPlay.JoypadWriteInd - NetPlay.JoypadReadInd) % NP_JOYPAD_HIST_SIZE); fflush (stdout);
}
//printf ("CLIENT: HB: @%d\n", S9xGetMilliTime () - START);
return (TRUE);
}
else
{
uint32 len = READ_LONG (&header [3]);
switch (header [2] & 0x1f)
{
case NP_SERV_RESET:
#ifdef NP_DEBUG
printf ("CLIENT: RESET received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDiscardHeartbeats ();
S9xReset ();
NetPlay.FrameCount = READ_LONG (&header [3]);
S9xNPResetJoypadReadPos ();
S9xNPSendReady ();
break;
case NP_SERV_PAUSE:
NetPlay.Paused = (header [2] & 0x20) != 0;
if (NetPlay.Paused)
S9xNPSetWarning("CLIENT: Server has paused.");
else
S9xNPSetWarning("CLIENT: Server has resumed.");
break;
case NP_SERV_JOYPAD_SWAP:
#ifdef NP_DEBUG
printf("CLIENT: Joypad Swap received @%ld\n", S9xGetMilliTime() - START);
#endif
S9xApplyCommand(S9xGetCommandT("SwapJoypads"), 1, 1);
break;
case NP_SERV_LOAD_ROM:
#ifdef NP_DEBUG
printf ("CLIENT: LOAD_ROM received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDiscardHeartbeats ();
if (S9xNPLoadROM (len - 7))
S9xNPSendReady (NP_CLNT_LOADED_ROM);
break;
case NP_SERV_ROM_IMAGE:
#ifdef NP_DEBUG
printf ("CLIENT: ROM_IMAGE received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDiscardHeartbeats ();
if (S9xNPGetROMImage (len - 7))
S9xNPSendReady (NP_CLNT_RECEIVED_ROM_IMAGE);
break;
case NP_SERV_SRAM_DATA:
#ifdef NP_DEBUG
printf ("CLIENT: SRAM_DATA received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDiscardHeartbeats ();
S9xNPGetSRAMData (len - 7);
break;
case NP_SERV_FREEZE_FILE:
#ifdef NP_DEBUG
printf ("CLIENT: FREEZE_FILE received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDiscardHeartbeats ();
S9xNPGetFreezeFile (len - 7);
S9xNPResetJoypadReadPos ();
S9xNPSendReady ();
break;
default:
#ifdef NP_DEBUG
printf ("CLIENT: UNKNOWN received @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPDisconnect ();
return (FALSE);
}
}
}
S9xNPDisconnect ();
return (FALSE);
}
bool8 S9xNPLoadROMDialog (const char *rom_name)
{
NetPlay.Answer = FALSE;
#ifdef __WIN32__
ResetEvent (NetPlay.ReplyEvent);
#ifdef NP_DEBUG
printf ("CLIENT: Asking GUI thread to open ROM load dialog...\n");
#endif
PostMessage (GUI.hWnd, WM_USER + 3, (WPARAM) rom_name, (LPARAM) rom_name);
#ifdef NP_DEBUG
printf ("CLIENT: Waiting for reply from GUI thread...\n");
#endif
WaitForSingleObject (NetPlay.ReplyEvent, INFINITE);
#ifdef NP_DEBUG
printf ("CLIENT: Got reply from GUI thread (%d)\n", NetPlay.Answer);
#endif
#else
NetPlay.Answer = TRUE;
#endif
return (NetPlay.Answer);
}
bool8 S9xNPLoadROM (uint32 len)
{
uint8 *data = new uint8 [len];
S9xNPSetAction ("Receiving ROM name...");
if (!S9xNPGetData (NetPlay.Socket, data, len))
{
S9xNPSetError ("Error while receiving ROM name.");
delete[] data;
S9xNPDisconnect ();
return (FALSE);
}
S9xNPSetAction ("Opening LoadROM dialog...");
if (!S9xNPLoadROMDialog ((char *) data))
{
S9xNPSetError ("Disconnected from NetPlay server because you are playing a different game!");
delete[] data;
S9xNPDisconnect ();
return (FALSE);
}
delete[] data;
return (TRUE);
}
bool8 S9xNPGetROMImage (uint32 len)
{
uint8 rom_info [5];
S9xNPSetAction ("Receiving ROM information...");
if (!S9xNPGetData (NetPlay.Socket, rom_info, 5))
{
S9xNPSetError ("Error while receiving ROM information.");
S9xNPDisconnect ();
return (FALSE);
}
uint32 CalculatedSize = READ_LONG (&rom_info [1]);
#ifdef NP_DEBUG
printf ("CLIENT: Hi-ROM: %s, Size: %04x\n", rom_info [0] ? "Y" : "N", CalculatedSize);
#endif
if (CalculatedSize + 5 >= len ||
CalculatedSize >= CMemory::MAX_ROM_SIZE)
{
S9xNPSetError ("Size error in ROM image data received from server.");
S9xNPDisconnect ();
return (FALSE);
}
Memory.HiROM = rom_info [0];
Memory.LoROM = !Memory.HiROM;
Memory.HeaderCount = 0;
Memory.CalculatedSize = CalculatedSize;
// Load up ROM image
#ifdef NP_DEBUG
printf ("CLIENT: Receiving ROM image @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Receiving ROM image...");
if (!S9xNPGetData (NetPlay.Socket, Memory.ROM, Memory.CalculatedSize))
{
S9xNPSetError ("Error while receiving ROM image from server.");
Settings.StopEmulation = TRUE;
S9xNPDisconnect ();
return (FALSE);
}
#ifdef NP_DEBUG
printf ("CLIENT: Receiving ROM filename @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Receiving ROM filename...");
uint32 filename_len = len - Memory.CalculatedSize - 5;
if (filename_len > PATH_MAX ||
!S9xNPGetData (NetPlay.Socket, (uint8 *) Memory.ROMFilename.c_str(), filename_len))
{
S9xNPSetError ("Error while receiving ROM filename from server.");
S9xNPDisconnect ();
Settings.StopEmulation = TRUE;
return (FALSE);
}
Memory.InitROM ();
S9xReset ();
S9xNPResetJoypadReadPos ();
Settings.StopEmulation = FALSE;
#ifdef __WIN32__
PostMessage (GUI.hWnd, WM_NULL, 0, 0);
#endif
return (TRUE);
}
void S9xNPGetSRAMData (uint32 len)
{
if (len > 0x70000)
{
S9xNPSetError ("Length error in S-RAM data received from server.");
S9xNPDisconnect ();
return;
}
S9xNPSetAction ("Receiving S-RAM data...");
if (len > 0 && !S9xNPGetData (NetPlay.Socket, Memory.SRAM, len))
{
S9xNPSetError ("Error while receiving S-RAM data from server.");
S9xNPDisconnect ();
}
S9xNPSetAction ("", TRUE);
}
void S9xNPGetFreezeFile (uint32 len)
{
uint8 frame_count [4];
#ifdef NP_DEBUG
printf ("CLIENT: Receiving freeze file information @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Receiving freeze file information...");
if (!S9xNPGetData (NetPlay.Socket, frame_count, 4))
{
S9xNPSetError ("Error while receiving freeze file information from server.");
S9xNPDisconnect ();
return;
}
NetPlay.FrameCount = READ_LONG (frame_count);
#ifdef NP_DEBUG
printf ("CLIENT: Receiving freeze file @%ld...\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Receiving freeze file...");
uint8 *data = new uint8 [len];
if (!S9xNPGetData (NetPlay.Socket, data, len - 4))
{
S9xNPSetError ("Error while receiving freeze file from server.");
S9xNPDisconnect ();
delete[] data;
return;
}
S9xNPSetAction ("", TRUE);
//FIXME: Setting umask here wouldn't hurt.
FILE *file;
#ifdef HAVE_MKSTEMP
int fd;
char fname[] = "/tmp/snes9x_fztmpXXXXXX";
if ((fd = mkstemp(fname)) >= 0)
{
if ((file = fdopen (fd, "wb")))
#else
char fname [L_tmpnam];
if (tmpnam (fname))
{
if ((file = fopen (fname, "wb")))
#endif
{
if (fwrite (data, 1, len, file) == len)
{
fclose(file);
#ifndef __WIN32__
/* We need .s96 extension, else .s96 is addded by unix code */
char buf[PATH_MAX +1 ];
strncpy(buf, fname, PATH_MAX);
strcat(buf, ".s96");
rename(fname, buf);
if (!S9xUnfreezeGame (buf))
#else
if (!S9xUnfreezeGame (fname))
#endif
S9xNPSetError ("Unable to load freeze file just received.");
} else {
S9xNPSetError ("Failed to write to temporary freeze file.");
fclose(file);
}
} else
S9xNPSetError ("Failed to create temporary freeze file.");
remove (fname);
} else
S9xNPSetError ("Unable to get name for temporary freeze file.");
delete[] data;
}
uint32 S9xNPGetJoypad (int which1)
{
if (Settings.NetPlay && which1 < 8)
{
#ifdef NP_DEBUG
if(!NetPlay.JoypadsReady [NetPlay.JoypadReadInd][which1])
{
S9xNPSetWarning ("Missing input from server!");
}
#endif
NetPlay.JoypadsReady [NetPlay.JoypadReadInd][which1] = FALSE;
return (NetPlay.Joypads [NetPlay.JoypadReadInd][which1]);
}
return (0);
}
void S9xNPStepJoypadHistory ()
{
if ((NetPlay.JoypadReadInd + 1) % NP_JOYPAD_HIST_SIZE != NetPlay.JoypadWriteInd)
{
NetPlay.JoypadReadInd = (NetPlay.JoypadReadInd + 1) % NP_JOYPAD_HIST_SIZE;
if (NetPlay.FrameCount != NetPlay.Frame [NetPlay.JoypadReadInd])
{
S9xNPSetWarning ("This Snes9x session may be out of sync with the server.");
#ifdef NP_DEBUG
printf ("*** CLIENT: client out of sync with server (%d, %d) @%ld\n", NetPlay.FrameCount, NetPlay.Frame [NetPlay.JoypadReadInd], S9xGetMilliTime () - START);
#endif
}
}
else
{
#ifdef NP_DEBUG
printf ("*** CLIENT: S9xNPStepJoypadHistory NOT OK@%ld\n", S9xGetMilliTime () - START);
#endif
}
}
void S9xNPResetJoypadReadPos ()
{
#ifdef NP_DEBUG
printf ("CLIENT: ResetJoyReadPos @%ld\n", S9xGetMilliTime () - START); fflush (stdout);
#endif
NetPlay.JoypadWriteInd = 0;
NetPlay.JoypadReadInd = NP_JOYPAD_HIST_SIZE - 1;
for (int h = 0; h < NP_JOYPAD_HIST_SIZE; h++)
memset ((void *) &NetPlay.Joypads [h], 0, sizeof (NetPlay.Joypads [0]));
for (int h = 0; h < NP_JOYPAD_HIST_SIZE; h++)
memset ((void *) &NetPlay.JoypadsReady [h], 0, sizeof (NetPlay.JoypadsReady [0]));
}
bool8 S9xNPSendJoypadUpdate (uint32 joypad)
{
uint8 data [7];
uint8 *ptr = data;
*ptr++ = NP_CLNT_MAGIC;
*ptr++ = NetPlay.MySequenceNum++;
*ptr++ = NP_CLNT_JOYPAD;
joypad |= 0x80000000;
WRITE_LONG (ptr, joypad);
if (!S9xNPSendData (NetPlay.Socket, data, 7))
{
S9xNPSetError ("Error while sending joypad data server.");
S9xNPDisconnect ();
return (FALSE);
}
return (TRUE);
}
void S9xNPDisconnect ()
{
close (NetPlay.Socket);
NetPlay.Socket = -1;
NetPlay.Connected = FALSE;
Settings.NetPlay = FALSE;
}
bool8 S9xNPSendData (int socket, const uint8 *data, int length)
{
int len = length;
const uint8 *ptr = data;
NetPlay.PercentageComplete = 0;
do
{
if (NetPlay.Abort)
return (FALSE);
int num_bytes = len;
// Write the data in small chunks, allowing this thread to spot an
// abort request from another thread.
if (num_bytes > 512)
num_bytes = 512;
int sent = write (socket, (char *) ptr, num_bytes);
if (sent < 0)
{
if (errno == EINTR
#ifdef EAGAIN
|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
|| errno == EWOULDBLOCK
#endif
)
{
#ifdef NP_DEBUG
printf ("CLIENT: EINTR, EAGAIN or EWOULDBLOCK while sending data @%ld\n", S9xGetMilliTime () - START);
#endif
continue;
}
return (FALSE);
}
else
if (sent == 0)
return (FALSE);
len -= sent;
ptr += sent;
NetPlay.PercentageComplete = (uint8) (((length - len) * 100) / length);
} while (len > 0);
return (TRUE);
}
bool8 S9xNPGetData (int socket, uint8 *data, int length)
{
int len = length;
uint8 *ptr = data;
int chunk = length / 50;
if (chunk < 1024)
chunk = 1024;
NetPlay.PercentageComplete = 0;
do
{
if (NetPlay.Abort)
return (FALSE);
int num_bytes = len;
// Read the data in small chunks, allowing this thread to spot an
// abort request from another thread.
if (num_bytes > chunk)
num_bytes = chunk;
int got = read (socket, (char *) ptr, num_bytes);
if (got < 0)
{
if (errno == EINTR
#ifdef EAGAIN
|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
|| errno == EWOULDBLOCK
#endif
#ifdef WSAEWOULDBLOCK
|| errno == WSAEWOULDBLOCK
#endif
)
{
#ifdef NP_DEBUG
printf ("CLIENT: EINTR, EAGAIN or EWOULDBLOCK while receiving data @%ld\n", S9xGetMilliTime () - START);
#endif
continue;
}
#ifdef WSAEMSGSIZE
if (errno != WSAEMSGSIZE)
return (FALSE);
else
{
got = num_bytes;
#ifdef NP_DEBUG
printf ("CLIENT: WSAEMSGSIZE, actual bytes %d while receiving data @%ld\n", got, S9xGetMilliTime () - START);
#endif
}
#else
return (FALSE);
#endif
}
else
if (got == 0)
return (FALSE);
len -= got;
ptr += got;
if (!Settings.NetPlayServer && length > 1024)
{
NetPlay.PercentageComplete = (uint8) (((length - len) * 100) / length);
#ifdef __WIN32__
PostMessage (GUI.hWnd, WM_USER, NetPlay.PercentageComplete,
NetPlay.PercentageComplete);
Sleep (0);
#endif
}
} while (len > 0);
return (TRUE);
}
bool8 S9xNPInitialise ()
{
#ifdef __WIN32__
static bool8 initialised = FALSE;
if (!initialised)
{
initialised = TRUE;
WSADATA data;
#ifdef NP_DEBUG
START = S9xGetMilliTime ();
printf ("CLIENT/SERVER: Initialising WinSock @%ld\n", S9xGetMilliTime () - START);
#endif
S9xNPSetAction ("Initialising Windows sockets interface...");
if (WSAStartup (MAKEWORD (1, 0), &data) != 0)
{
S9xNPSetError ("Call to init Windows sockets failed. Do you have WinSock2 installed?");
return (FALSE);
}
}
#endif
return (TRUE);
}
void S9xNPDiscardHeartbeats ()
{
// Discard any pending heartbeats and wait for any frame that is currently
// being emulated to complete.
#ifdef NP_DEBUG
printf ("CLIENT: DiscardHeartbeats @%ld, finished @", S9xGetMilliTime () - START);
fflush (stdout);
#endif
#ifdef __WIN32__
while (WaitForSingleObject (GUI.ClientSemaphore, 200) == WAIT_OBJECT_0)
;
#endif
#ifdef NP_DEBUG
printf ("%ld\n", S9xGetMilliTime () - START);
#endif
NetPlay.Waiting4EmulationThread = FALSE;
}
void S9xNPSetAction (const char *action, bool8 force)
{
#ifdef NP_DEBUG
printf ("NPSetAction: %s, forced = %d %ld\n", action, force, S9xGetMilliTime () - START);
#endif
if (force || !Settings.NetPlayServer)
{
strncpy (NetPlay.ActionMsg, action, NP_MAX_ACTION_LEN - 1);
NetPlay.ActionMsg [NP_MAX_ACTION_LEN - 1] = 0;
#ifdef __WIN32__
PostMessage (GUI.hWnd, WM_USER, 0, 0);
Sleep (0);
#endif
}
}
void S9xNPSetError (const char *error)
{
#if defined(NP_DEBUG) && NP_DEBUG == 2
printf("ERROR: %s\n", error);
fflush (stdout);
#endif
strncpy (NetPlay.ErrorMsg, error, NP_MAX_ACTION_LEN - 1);
NetPlay.ErrorMsg [NP_MAX_ACTION_LEN - 1] = 0;
#ifdef __WIN32__
PostMessage (GUI.hWnd, WM_USER + 1, 0, 0);
Sleep (0);
#endif
}
void S9xNPSetWarning (const char *warning)
{
#if defined(NP_DEBUG) && NP_DEBUG == 3
printf("Warning: %s\n", warning);
fflush (stdout);
#endif
strncpy (NetPlay.WarningMsg, warning, NP_MAX_ACTION_LEN - 1);
NetPlay.WarningMsg [NP_MAX_ACTION_LEN - 1] = 0;
#ifdef __WIN32__
PostMessage (GUI.hWnd, WM_USER + 2, 0, 0);
Sleep (0);
#endif
}
#endif