/*****************************************************************************\ 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 #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include "snes9x.h" #ifdef __WIN32__ #include #include #include "win32/wsnes9x.h" #define ioctl ioctlsocket #define close closesocket #define read(a,b,c) recv(a, b, c, 0) #define write(a,b,c) send(a, b, c, 0) #define gettimeofday(a,b) S9xGetTimeOfDay (a) #define exit(a) _endthread() void S9xGetTimeOfDay (struct timeval *n); #else #include #include #include #include #include #include #include #include #ifdef __SVR4 #include #endif #endif // !__WIN32__ #include "memmap.h" #include "snapshot.h" #include "netplay.h" #ifdef __WIN32__ #define NP_ONE_CLIENT 1 #else #define NP_ONE_CLIENT 0 #endif struct SNPServer NPServer; extern unsigned long START; void S9xNPSendToAllClients (uint8 *data, int len); bool8 S9xNPLoadFreezeFile (const char *fname, uint8 *&data, uint32 &len); void S9xNPSendFreezeFile (int c, uint8 *data, uint32 len); void S9xNPNoClientReady (int start_index = NP_ONE_CLIENT); void S9xNPRecomputePause (); void S9xNPWaitForEmulationToComplete (); void S9xNPSendROMImageToAllClients (); bool8 S9xNPSendROMImageToClient (int client); void S9xNPSendSRAMToClient (int c); void S9xNPSendSRAMToAllClients (); void S9xNPSyncClient (int); void S9xNPSendROMLoadRequest (const char *filename); void S9xNPSendFreezeFileToAllClients (const char *filename); void S9xNPStopServer (); void S9xNPShutdownClient (int c, bool8 report_error = FALSE) { if (NPServer.Clients [c].Connected) { NPServer.Clients [c].Connected = FALSE; NPServer.Clients [c].SaidHello = FALSE; close (NPServer.Clients [c].Socket); #ifdef NP_DEBUG printf ("SERVER: Player %d disconnecting @%ld\n", c + 1, S9xGetMilliTime () - START); #endif if (report_error) { sprintf (NetPlay.ErrorMsg, "Player %d on '%s' has disconnected.", c + 1, NPServer.Clients [c].HostName); S9xNPSetWarning (NetPlay.ErrorMsg); } if (NPServer.Clients [c].HostName) { free ((char *) NPServer.Clients [c].HostName); NPServer.Clients [c].HostName = NULL; } if (NPServer.Clients [c].ROMName) { free ((char *) NPServer.Clients [c].ROMName); NPServer.Clients [c].ROMName = NULL; } if (NPServer.Clients [c].Who) { free ((char *) NPServer.Clients [c].Who); NPServer.Clients [c].Who = NULL; } NPServer.Joypads [c] = 0; NPServer.NumClients--; S9xNPRecomputePause (); } } static bool8 S9xNPSGetData (int socket, uint8 *data, int length) { int len = length; uint8 *ptr = data; do { int num_bytes = len; // Read the data in small chunks, allowing this thread to spot an // abort request from another thread. if (num_bytes > 512) num_bytes = 512; 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 ) continue; #ifdef WSAEMSGSIZE if (errno != WSAEMSGSIZE) return (FALSE); else { got = num_bytes; #ifdef NP_DEBUG printf ("SERVER: WSAEMSGSIZE, actual bytes %d while receiving data @%d\n", got, S9xGetMilliTime () - START); #endif } #else return (FALSE); #endif } else if (got == 0) return (FALSE); len -= got; ptr += got; } while (len > 0); return (TRUE); } static bool8 S9xNPSSendData (int fd, const uint8 *data, int length) { int len = length; int chunk = length / 50; if (chunk < 1024) chunk = 1024; do { int num_bytes = len; // Write the data in small chunks, allowing this thread to spot an // abort request from another thread. if (num_bytes > chunk) num_bytes = chunk; int sent; sent = write (fd, (char *) data, len); if (sent < 0) { if (errno == EINTR #ifdef EAGAIN || errno == EAGAIN #endif #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ) { #ifdef NP_DEBUG printf ("SERVER: EINTR, EAGAIN or EWOULDBLOCK while sending data @%ld\n", S9xGetMilliTime () - START); #endif continue; } return (FALSE); } else if (sent == 0) return (FALSE); len -= sent; data += sent; if (length > 1024) { #ifdef __WIN32__ int Percent = (uint8) (((length - len) * 100) / length); PostMessage (GUI.hWnd, WM_USER, Percent, Percent); Sleep (0); #endif } } while (len > 0); return (TRUE); } void S9xNPSendHeartBeat () { int len = 3; uint8 data [3 + 4 * 5]; uint8 *ptr = data; int n; for (n = NP_MAX_CLIENTS - 1; n >= 0; n--) { if (NPServer.Clients [n].SaidHello) break; } if (n >= 0) { bool8 Paused = NPServer.Paused != 0; NPServer.FrameCount++; *ptr++ = NP_SERV_MAGIC; *ptr++ = 0; // Individual client sequence number will get placed here *ptr++ = NP_SERV_JOYPAD | (n << 6) | ((Paused != 0) << 5); WRITE_LONG (ptr, NPServer.FrameCount); len += 4; ptr += 4; int i; for (i = 0; i <= n; i++) { WRITE_LONG (ptr, NPServer.Joypads [i]); len += 4; ptr += 4; } S9xNPSendToAllClients (data, len); } } void S9xNPSendToAllClients (uint8 *data, int len) { int i; for (i = 0; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].SaidHello) { data [1] = NPServer.Clients [i].SendSequenceNum++; if (!S9xNPSSendData (NPServer.Clients [i].Socket, data, len)) S9xNPShutdownClient (i, TRUE); } } } void S9xNPProcessClient (int c) { uint8 header [7]; uint8 *data; uint32 len; uint8 *ptr; if (!S9xNPSGetData (NPServer.Clients [c].Socket, header, 7)) { S9xNPSetWarning ("SERVER: Failed to get message header from client.\n"); S9xNPShutdownClient (c, TRUE); return; } if (header [0] != NP_CLNT_MAGIC) { S9xNPSetWarning ("SERVER: Bad header magic value received from client.\n"); S9xNPShutdownClient (c, TRUE); return; } if (header [1] != NPServer.Clients [c].ReceiveSequenceNum) { #ifdef NP_DEBUG printf ("SERVER: Messages lost from '%s', expected %d, got %d\n", NPServer.Clients [c].HostName ? NPServer.Clients [c].HostName : "Unknown", NPServer.Clients [c].ReceiveSequenceNum, header [1]); #endif sprintf (NetPlay.WarningMsg, "SERVER: Messages lost from '%s', expected %d, got %d\n", NPServer.Clients [c].HostName ? NPServer.Clients [c].HostName : "Unknown", NPServer.Clients [c].ReceiveSequenceNum, header [1]); NPServer.Clients [c].ReceiveSequenceNum = header [1] + 1; S9xNPSetWarning (NetPlay.WarningMsg); } else NPServer.Clients [c].ReceiveSequenceNum++; len = READ_LONG (&header [3]); switch (header [2] & 0x3f) { case NP_CLNT_HELLO: #ifdef NP_DEBUG printf ("SERVER: Got HELLO from client @%ld\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("Got HELLO from client...", TRUE); if (len > 0x10000) { S9xNPSetWarning ("SERVER: Client HELLO message length error."); S9xNPShutdownClient (c, TRUE); return; } data = new uint8 [len - 7]; if (!S9xNPSGetData (NPServer.Clients [c].Socket, data, len - 7)) { S9xNPSetWarning ("SERVER: Failed to get HELLO message content from client."); S9xNPShutdownClient (c, TRUE); return; } if (NPServer.NumClients <= NP_ONE_CLIENT) { NPServer.FrameTime = READ_LONG (data); strncpy (NPServer.ROMName, (char *) &data [4], 29); NPServer.ROMName [29] = 0; } NPServer.Clients [c].ROMName = strdup ((char *) &data [4]); #ifdef NP_DEBUG printf ("SERVER: Client is playing: %s, Frame Time: %d @%ld\n", data + 4, READ_LONG (data), S9xGetMilliTime () - START); #endif NPServer.Clients [c].SendSequenceNum = 0; len = 7 + 1 + 1 + 4 + strlen (NPServer.ROMName) + 1; delete[] data; ptr = data = new uint8 [len]; *ptr++ = NP_SERV_MAGIC; *ptr++ = NPServer.Clients [c].SendSequenceNum++; if (NPServer.SendROMImageOnConnect && NPServer.NumClients > NP_ONE_CLIENT) *ptr++ = NP_SERV_HELLO | 0x80; else *ptr++ = NP_SERV_HELLO; WRITE_LONG (ptr, len); ptr += 4; *ptr++ = NP_VERSION; *ptr++ = c + 1; WRITE_LONG (ptr, NPServer.FrameCount); ptr += 4; strcpy ((char *) ptr, NPServer.ROMName); #ifdef NP_DEBUG printf ("SERVER: Sending welcome information to client @%ld...\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("SERVER: Sending welcome information to new client...", TRUE); if (!S9xNPSSendData (NPServer.Clients [c].Socket, data, len)) { S9xNPSetWarning ("SERVER: Failed to send welcome message to client."); S9xNPShutdownClient (c, TRUE); return; } delete[] data; #ifdef NP_DEBUG printf ("SERVER: Waiting for a response from the client @%ld...\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("SERVER: Waiting for a response from the client...", TRUE); break; case NP_CLNT_LOADED_ROM: #ifdef NP_DEBUG printf ("SERVER: Client %d loaded requested ROM @%ld...\n", c, S9xGetMilliTime () - START); #endif NPServer.Clients [c].SaidHello = TRUE; NPServer.Clients [c].Ready = FALSE; NPServer.Clients [c].Paused = FALSE; S9xNPRecomputePause (); S9xNPWaitForEmulationToComplete (); if (NPServer.SyncByReset) { S9xNPServerAddTask (NP_SERVER_SEND_SRAM, (void *) (pint) c); S9xNPServerAddTask (NP_SERVER_RESET_ALL, 0); } else S9xNPServerAddTask (NP_SERVER_SYNC_CLIENT, (void *) (pint) c); break; case NP_CLNT_RECEIVED_ROM_IMAGE: #ifdef NP_DEBUG printf ("SERVER: Client %d received ROM image @%ld...\n", c, S9xGetMilliTime () - START); #endif NPServer.Clients [c].SaidHello = TRUE; NPServer.Clients [c].Ready = FALSE; NPServer.Clients [c].Paused = FALSE; S9xNPRecomputePause (); S9xNPWaitForEmulationToComplete (); if (NPServer.SyncByReset) { S9xNPServerAddTask (NP_SERVER_SEND_SRAM, (void *) (pint) c); S9xNPServerAddTask (NP_SERVER_RESET_ALL, 0); } else S9xNPServerAddTask (NP_SERVER_SYNC_CLIENT, (void *) (pint) c); break; case NP_CLNT_WAITING_FOR_ROM_IMAGE: #ifdef NP_DEBUG printf ("SERVER: Client %d waiting for ROM image @%ld...\n", c, S9xGetMilliTime () - START); #endif NPServer.Clients [c].SaidHello = TRUE; NPServer.Clients [c].Ready = FALSE; NPServer.Clients [c].Paused = FALSE; S9xNPRecomputePause (); S9xNPSendROMImageToClient (c); break; case NP_CLNT_READY: #ifdef NP_DEBUG printf ("SERVER: Client %d ready @%ld...\n", c, S9xGetMilliTime () - START); #endif if (NPServer.Clients [c].SaidHello) { NPServer.Clients [c].Paused = FALSE; NPServer.Clients [c].Ready = TRUE; S9xNPRecomputePause (); break; } NPServer.Clients [c].SaidHello = TRUE; NPServer.Clients [c].Ready = TRUE; NPServer.Clients [c].Paused = FALSE; S9xNPRecomputePause (); //printf ("SERVER: SaidHello = TRUE, SeqNum = %d @%d\n", NPServer.Clients [c].SendSequenceNum, S9xGetMilliTime () - START); if (NPServer.NumClients > NP_ONE_CLIENT) { if (!NPServer.SendROMImageOnConnect) { S9xNPWaitForEmulationToComplete (); if (NPServer.SyncByReset) { S9xNPServerAddTask (NP_SERVER_SEND_SRAM, (void *) (pint) c); S9xNPServerAddTask (NP_SERVER_RESET_ALL, 0); } else #ifdef __WIN32__ S9xNPServerAddTask (NP_SERVER_SYNC_CLIENT, (void *)(UINT_PTR) c); #else /* We need to resync all clients on new player connect as we don't have a 'reference game' */ S9xNPServerAddTask (NP_SERVER_SYNC_ALL, (void *) (pint) c); #endif } } else { NPServer.Clients [c].Ready = TRUE; S9xNPRecomputePause (); } break; case NP_CLNT_JOYPAD: NPServer.Joypads [c] = len; break; case NP_CLNT_PAUSE: #ifdef NP_DEBUG printf ("SERVER: Client %d Paused: %s @%ld\n", c, (header [2] & 0x80) ? "YES" : "NO", S9xGetMilliTime () - START); #endif NPServer.Clients [c].Paused = (header [2] & 0x80) != 0; if (NPServer.Clients [c].Paused) sprintf (NetPlay.WarningMsg, "SERVER: Client %d has paused.", c + 1); else sprintf (NetPlay.WarningMsg, "SERVER: Client %d has resumed.", c + 1); S9xNPSetWarning (NetPlay.WarningMsg); S9xNPRecomputePause (); break; } } void S9xNPAcceptClient (int Listen, bool8 block) { struct sockaddr_in remote_address; struct linger val2; struct hostent *host; int new_fd; int i; #ifdef NP_DEBUG printf ("SERVER: attempting to accept new client connection @%ld\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("SERVER: Attempting to accept client connection...", TRUE); memset (&remote_address, 0, sizeof (remote_address)); socklen_t len = sizeof (remote_address); new_fd = accept (Listen, (struct sockaddr *)&remote_address, &len); S9xNPSetAction ("Setting socket options...", TRUE); val2.l_onoff = 1; val2.l_linger = 0; if (setsockopt (new_fd, SOL_SOCKET, SO_LINGER, (char *) &val2, sizeof (val2)) < 0) { S9xNPSetError ("Setting socket options failed."); close (new_fd); return; } for (i = 0; i < NP_MAX_CLIENTS; i++) { if (!NPServer.Clients [i].Connected) { NPServer.NumClients++; NPServer.Clients [i].Socket = new_fd; NPServer.Clients [i].SendSequenceNum = 0; NPServer.Clients [i].ReceiveSequenceNum = 0; NPServer.Clients [i].Connected = TRUE; NPServer.Clients [i].SaidHello = FALSE; NPServer.Clients [i].Paused = FALSE; NPServer.Clients [i].Ready = FALSE; NPServer.Clients [i].ROMName = NULL; NPServer.Clients [i].HostName = NULL; NPServer.Clients [i].Who = NULL; break; } } if (i >= NP_MAX_CLIENTS) { S9xNPSetError ("SERVER: Maximum number of NetPlay Clients have already connected."); close (new_fd); return; } if (remote_address.sin_family == AF_INET) { #ifdef NP_DEBUG printf ("SERVER: Looking up new client's hostname @%ld\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("SERVER: Looking up new client's hostname...", TRUE); host = gethostbyaddr ((char *) &remote_address.sin_addr, sizeof (remote_address.sin_addr), AF_INET); if (host) { #ifdef NP_DEBUG printf ("SERVER: resolved new client's hostname (%s) @%ld\n", host->h_name, S9xGetMilliTime () - START); #endif sprintf (NetPlay.WarningMsg, "SERVER: Player %d on %s has connected.", i + 1, host->h_name); NPServer.Clients [i].HostName = strdup (host->h_name); } else { char *ip = inet_ntoa (remote_address.sin_addr); if (ip) NPServer.Clients [i].HostName = strdup (ip); #ifdef NP_DEBUG printf ("SERVER: couldn't resolve new client's hostname (%s) @%ld\n", ip ? ip : "Unknown", S9xGetMilliTime () - START); #endif sprintf (NetPlay.WarningMsg, "SERVER: Player %d on %s has connected.", i + 1, ip ? ip : "Unknown"); } S9xNPSetWarning (NetPlay.WarningMsg); } #ifdef NP_DEBUG printf ("SERVER: waiting for HELLO message from new client @%ld\n", S9xGetMilliTime () - START); #endif S9xNPSetAction ("SERVER: Waiting for HELLO message from new client..."); } static bool8 server_continue = TRUE; static bool8 S9xNPServerInit (int port) { struct sockaddr_in address; int i; int val; if (!S9xNPInitialise ()) return (FALSE); for (i = 0; i < NP_MAX_CLIENTS; i++) { NPServer.Clients [i].SendSequenceNum = 0; NPServer.Clients [i].ReceiveSequenceNum = 0; NPServer.Clients [i].Connected = FALSE; NPServer.Clients [i].SaidHello = FALSE; NPServer.Clients [i].Paused = FALSE; NPServer.Clients [i].Ready = FALSE; NPServer.Clients [i].Socket = 0; NPServer.Clients [i].ROMName = NULL; NPServer.Clients [i].HostName = NULL; NPServer.Clients [i].Who = NULL; NPServer.Joypads [i] = 0; } NPServer.NumClients = 0; NPServer.FrameCount = 0; #ifdef NP_DEBUG printf ("SERVER: Creating socket @%ld\n", S9xGetMilliTime () - START); #endif if ((NPServer.Socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) { S9xNPSetError ("NetPlay Server: Can't create listening socket."); return (FALSE); } val = 1; setsockopt (NPServer.Socket, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof (val)); memset (&address, 0, sizeof (address)); address.sin_family = AF_INET; address.sin_addr.s_addr = htonl (INADDR_ANY); address.sin_port = htons (port); #ifdef NP_DEBUG printf ("SERVER: Binding socket to address and port @%ld\n", S9xGetMilliTime () - START); #endif if (bind (NPServer.Socket, (struct sockaddr *) &address, sizeof (address)) < 0) { S9xNPSetError ("NetPlay Server: Can't bind socket to port number.\nPort already in use?"); return (FALSE); } #ifdef NP_DEBUG printf ("SERVER: Getting socket to listen @%ld\n", S9xGetMilliTime () - START); #endif if (listen (NPServer.Socket, NP_MAX_CLIENTS) < 0) { S9xNPSetError ("NetPlay Server: Can't get new socket to listen."); return (FALSE); } #ifdef NP_DEBUG printf ("SERVER: Init complete @%ld\n", S9xGetMilliTime () - START); #endif return (TRUE); } void S9xNPSendServerPause (bool8 paused) { #ifdef NP_DEBUG printf ("SERVER: Pause - %s @%ld\n", paused ? "YES" : "NO", S9xGetMilliTime () - START); #endif uint8 pause [7]; uint8 *ptr = pause; *ptr++ = NP_SERV_MAGIC; *ptr++ = 0; *ptr++ = NP_SERV_PAUSE | (paused ? 0x20 : 0); WRITE_LONG (ptr, NPServer.FrameCount); S9xNPSendToAllClients (pause, 7); } void S9xNPSendJoypadSwap() { #ifdef NP_DEBUG printf("SERVER: Swap Joypads - @%ld\n", S9xGetMilliTime() - START); #endif uint8 swap[7]; uint8 *ptr = swap; *ptr++ = NP_SERV_MAGIC; *ptr++ = 0; *ptr++ = NP_SERV_JOYPAD_SWAP; WRITE_LONG(ptr, 7); S9xNPSendToAllClients(swap, 7); } void S9xNPServerLoop (void *) { #ifdef __WIN32__ BOOL success = FALSE; #else bool8 success = FALSE; static struct timeval next1 = {0, 0}; struct timeval now; #endif int pausedState = -1, newPausedState = -1; while (server_continue) { fd_set read_fds; struct timeval timeout; int res; int i; int max_fd = NPServer.Socket; #ifdef __WIN32__ Sleep (0); #endif if (success && !(Settings.Paused && !Settings.FrameAdvance) && !Settings.StopEmulation && !Settings.ForcedPause && !NPServer.Paused) { S9xNPSendHeartBeat (); newPausedState = 0; } else { newPausedState = 1; } if(pausedState != newPausedState) { pausedState = newPausedState; // S9xNPSendServerPause(pausedState); // XXX: doesn't seem to work yet... } do { FD_ZERO (&read_fds); FD_SET (NPServer.Socket, &read_fds); for (i = 0; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].Connected) { FD_SET (NPServer.Clients [i].Socket, &read_fds); if (NPServer.Clients [i].Socket > max_fd) max_fd = NPServer.Clients [i].Socket; } } timeout.tv_sec = 0; timeout.tv_usec = 1000; res = select (max_fd + 1, &read_fds, NULL, NULL, &timeout); if (res > 0) { if (FD_ISSET (NPServer.Socket, &read_fds)) S9xNPAcceptClient (NPServer.Socket, FALSE); for (i = 0; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].Connected && FD_ISSET (NPServer.Clients [i].Socket, &read_fds)) { S9xNPProcessClient (i); } } } } while (res > 0); #ifdef __WIN32__ success = WaitForSingleObject (GUI.ServerTimerSemaphore, 200) == WAIT_OBJECT_0; #else while (gettimeofday (&now, NULL) < 0) ; /* If there is no known "next" frame, initialize it now */ if (next1.tv_sec == 0) { next1 = now; ++next1.tv_usec; } success=FALSE; if (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<(200*1000)?timeleft:(200*1000)); } if (!timercmp(&next1, &now, >)) { /* 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; } success=TRUE; } #endif while (NPServer.TaskHead != NPServer.TaskTail) { void *task_data = NPServer.TaskQueue [NPServer.TaskHead].Data; #if defined(NP_DEBUG) && NP_DEBUG == 2 printf ("SERVER: task %d @%ld\n", NPServer.TaskQueue [NPServer.TaskHead].Task, S9xGetMilliTime () - START); #endif switch (NPServer.TaskQueue [NPServer.TaskHead].Task) { case NP_SERVER_SEND_ROM_IMAGE: S9xNPSendROMImageToAllClients (); break; case NP_SERVER_SYNC_CLIENT: NPServer.Clients [(pint) task_data].Ready = FALSE; S9xNPRecomputePause (); S9xNPSyncClient ((pint) task_data); break; case NP_SERVER_SYNC_ALL: S9xNPSyncClients (); break; case NP_SERVER_SEND_FREEZE_FILE_ALL: S9xNPSendFreezeFileToAllClients ((char *) task_data); free ((char *) task_data); break; case NP_SERVER_SEND_ROM_LOAD_REQUEST_ALL: S9xNPSendROMLoadRequest ((char *) task_data); free ((char *) task_data); break; case NP_SERVER_RESET_ALL: S9xNPNoClientReady (0); S9xNPWaitForEmulationToComplete (); S9xNPSetAction ("SERVER: Sending RESET to all clients...", TRUE); #ifdef NP_DEBUG printf ("SERVER: Sending RESET to all clients @%ld\n", S9xGetMilliTime () - START); #endif { uint8 reset [7]; uint8 *ptr; ptr = reset; *ptr++ = NP_SERV_MAGIC; *ptr++ = 0; *ptr++ = NP_SERV_RESET; WRITE_LONG (ptr, NPServer.FrameCount); S9xNPSendToAllClients (reset, 7); } S9xNPSetAction ("", TRUE); break; case NP_SERVER_SEND_SRAM: NPServer.Clients [(pint) task_data].Ready = FALSE; S9xNPRecomputePause (); S9xNPWaitForEmulationToComplete (); S9xNPSendSRAMToClient ((pint) task_data); break; case NP_SERVER_SEND_SRAM_ALL: S9xNPNoClientReady (); S9xNPWaitForEmulationToComplete (); S9xNPSendSRAMToAllClients (); break; default: S9xNPSetError ("SERVER: *** Unknown task ***\n"); break; } NPServer.TaskHead = (NPServer.TaskHead + 1) % NP_MAX_TASKS; } } #ifdef NP_DEBUG printf ("SERVER: Server thread exiting @%ld\n", S9xGetMilliTime () - START); #endif // OV2: S9xNPStopServer has already been called if we get here // S9xNPStopServer (); } bool8 S9xNPStartServer (int port) { #ifdef __WIN32__ static int p; p = port; #endif #ifdef NP_DEBUG printf ("SERVER: Starting server on port %d @%ld\n", port, S9xGetMilliTime () - START); #endif server_continue = TRUE; if (S9xNPServerInit (port)) #ifdef __WIN32__ return (_beginthread (S9xNPServerLoop, 0, &p) != (uintptr_t)(~0)); #else S9xNPServerLoop(NULL); return (TRUE); #endif return (FALSE); } void S9xNPStopServer () { #ifdef NP_DEBUG printf ("SERVER: Stopping server @%ld\n", S9xGetMilliTime () - START); #endif server_continue = FALSE; close (NPServer.Socket); for (int i = 0; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].Connected) S9xNPShutdownClient(i, FALSE); } } #ifdef __WIN32__ void S9xGetTimeOfDay (struct timeval *n) { unsigned long t = S9xGetMilliTime (); n->tv_sec = t / 1000; n->tv_usec = (t % 1000) * 1000; } #endif void S9xNPSendROMImageToAllClients () { S9xNPNoClientReady (); S9xNPWaitForEmulationToComplete (); int c; for (c = NP_ONE_CLIENT; c < NP_MAX_CLIENTS; c++) { if (NPServer.Clients [c].SaidHello) S9xNPSendROMImageToClient (c); } if (NPServer.SyncByReset) { S9xNPServerAddTask (NP_SERVER_SEND_SRAM_ALL, 0); S9xNPServerAddTask (NP_SERVER_RESET_ALL, 0); } else S9xNPSyncClient (-1); } bool8 S9xNPSendROMImageToClient (int c) { #ifdef NP_DEBUG printf ("SERVER: Sending ROM image to player %d @%ld\n", c + 1, S9xGetMilliTime () - START); #endif sprintf (NetPlay.ActionMsg, "Sending ROM image to player %d...", c + 1); S9xNPSetAction (NetPlay.ActionMsg, TRUE); uint8 header [7 + 1 + 4]; uint8 *ptr = header; int len = sizeof (header) + Memory.CalculatedSize + strlen (Memory.ROMFilename) + 1; *ptr++ = NP_SERV_MAGIC; *ptr++ = NPServer.Clients [c].SendSequenceNum++; *ptr++ = NP_SERV_ROM_IMAGE; WRITE_LONG (ptr, len); ptr += 4; *ptr++ = Memory.HiROM; WRITE_LONG (ptr, Memory.CalculatedSize); if (!S9xNPSSendData (NPServer.Clients [c].Socket, header, sizeof (header)) || !S9xNPSSendData (NPServer.Clients [c].Socket, Memory.ROM, Memory.CalculatedSize) || !S9xNPSSendData (NPServer.Clients [c].Socket, (uint8 *) Memory.ROMFilename, strlen (Memory.ROMFilename) + 1)) { S9xNPShutdownClient (c, TRUE); return (FALSE); } return (TRUE); } void S9xNPSyncClients () { S9xNPNoClientReady (); S9xNPSyncClient (-1); } void S9xNPSyncClient (int client) { #ifdef HAVE_MKSTEMP char fname[] = "/tmp/snes9x_fztmpXXXXXX"; int fd=-1; #else char fname [L_tmpnam]; #endif S9xNPWaitForEmulationToComplete (); S9xNPSetAction ("SERVER: Freezing game...", TRUE); #ifdef HAVE_MKSTEMP if ( ((fd=mkstemp(fname)) >= 0) && S9xFreezeGame(fname) ) #else if ( tmpnam(fname) && S9xFreezeGame(fname) ) #endif { uint8 *data; uint32 len; S9xNPSetAction ("SERVER: Loading freeze file...", TRUE); if (S9xNPLoadFreezeFile (fname, data, len)) { int c; if (client < 0) { for (c = NP_ONE_CLIENT; c < NP_MAX_CLIENTS; c++) { if (NPServer.Clients [c].SaidHello) { NPServer.Clients [c].Ready = FALSE; S9xNPRecomputePause (); S9xNPSendFreezeFile (c, data, len); } } } else { NPServer.Clients [client].Ready = FALSE; S9xNPRecomputePause (); S9xNPSendFreezeFile (client, data, len); } delete data; } remove (fname); } #ifdef HAVE_MKSTEMP if (fd != -1) close(fd); #endif } bool8 S9xNPLoadFreezeFile (const char *fname, uint8 *&data, uint32 &len) { FILE *ff; if ((ff = fopen (fname, "rb"))) { fseek (ff, 0, SEEK_END); len = ftell (ff); fseek (ff, 0, SEEK_SET); data = new uint8 [len]; bool8 ok = (fread (data, 1, len, ff) == len); fclose (ff); return (ok); } return (FALSE); } void S9xNPSendFreezeFile (int c, uint8 *data, uint32 len) { #ifdef NP_DEBUG printf ("SERVER: Sending freeze file to player %d @%ld\n", c + 1, S9xGetMilliTime () - START); #endif sprintf (NetPlay.ActionMsg, "SERVER: Sending freeze-file to player %d...", c + 1); S9xNPSetAction (NetPlay.ActionMsg, TRUE); uint8 header [7 + 4]; uint8 *ptr = header; *ptr++ = NP_SERV_MAGIC; *ptr++ = NPServer.Clients [c].SendSequenceNum++; *ptr++ = NP_SERV_FREEZE_FILE; WRITE_LONG (ptr, len + 7 + 4); ptr += 4; WRITE_LONG (ptr, NPServer.FrameCount); if (!S9xNPSSendData (NPServer.Clients [c].Socket, header, 7 + 4) || !S9xNPSSendData (NPServer.Clients [c].Socket, data, len)) { S9xNPShutdownClient (c, TRUE); } S9xNPSetAction ("", TRUE); } void S9xNPRecomputePause () { int c; for (c = 0; c < NP_MAX_CLIENTS; c++) { if (NPServer.Clients [c].SaidHello && (!NPServer.Clients [c].Ready || NPServer.Clients [c].Paused)) { #if defined(NP_DEBUG) && NP_DEBUG == 2 printf ("SERVER: Paused because of client %d (%d,%d) @%ld\n", c, NPServer.Clients [c].Ready, NPServer.Clients [c].Paused, S9xGetMilliTime () - START); #endif NPServer.Paused = TRUE; return; } } #if defined(NP_DEBUG) && NP_DEBUG == 2 printf ("SERVER: not paused @%ld\n", S9xGetMilliTime () - START); #endif NPServer.Paused = FALSE; } void S9xNPNoClientReady (int start_index) { int c; for (c = start_index; c < NP_MAX_CLIENTS; c++) NPServer.Clients [c].Ready = FALSE; S9xNPRecomputePause (); } void S9xNPSendROMLoadRequest (const char *filename) { S9xNPNoClientReady (); int len = 7 + strlen (filename) + 1; uint8 *data = new uint8 [len]; uint8 *ptr = data; *ptr++ = NP_SERV_MAGIC; *ptr++ = 0; *ptr++ = NP_SERV_LOAD_ROM; WRITE_LONG (ptr, len); ptr += 4; strcpy ((char *) ptr, filename); for (int i = NP_ONE_CLIENT; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].SaidHello) { #ifdef NP_DEBUG printf ("SERVER: Sending load ROM requesting to player %d @%ld\n", i + 1, S9xGetMilliTime () - START); #endif sprintf (NetPlay.WarningMsg, "SERVER: sending ROM load request to player %d...", i + 1); S9xNPSetAction (NetPlay.WarningMsg, TRUE); data [1] = NPServer.Clients [i].SendSequenceNum++; if (!S9xNPSSendData (NPServer.Clients [i].Socket, data, len)) { S9xNPShutdownClient (i, TRUE); } } } delete[] data; } void S9xNPSendSRAMToAllClients () { int i; for (i = NP_ONE_CLIENT; i < NP_MAX_CLIENTS; i++) { if (NPServer.Clients [i].SaidHello) S9xNPSendSRAMToClient (i); } } void S9xNPSendSRAMToClient (int c) { #ifdef NP_DEBUG printf ("SERVER: Sending S-RAM data to player %d @%ld\n", c + 1, S9xGetMilliTime () - START); #endif uint8 sram [7]; int SRAMSize = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0; if (Memory.LoROM) SRAMSize = SRAMSize < 0x70000 ? SRAMSize : 0x70000; else if (Memory.HiROM) SRAMSize = SRAMSize < 0x40000 ? SRAMSize : 0x40000; int len = 7 + SRAMSize; sprintf (NetPlay.ActionMsg, "SERVER: Sending S-RAM to player %d...", c + 1); S9xNPSetAction (NetPlay.ActionMsg, TRUE); uint8 *ptr = sram; *ptr++ = NP_SERV_MAGIC; *ptr++ = NPServer.Clients [c].SendSequenceNum++; *ptr++ = NP_SERV_SRAM_DATA; WRITE_LONG (ptr, len); if (!S9xNPSSendData (NPServer.Clients [c].Socket, sram, sizeof (sram)) || (len > 7 && !S9xNPSSendData (NPServer.Clients [c].Socket, Memory.SRAM, len - 7))) { S9xNPShutdownClient (c, TRUE); } } void S9xNPSendFreezeFileToAllClients (const char *filename) { uint8 *data; uint32 len; if (NPServer.NumClients > NP_ONE_CLIENT && S9xNPLoadFreezeFile (filename, data, len)) { S9xNPNoClientReady (); for (int c = NP_ONE_CLIENT; c < NP_MAX_CLIENTS; c++) { if (NPServer.Clients [c].SaidHello) S9xNPSendFreezeFile (c, data, len); } delete data; } } void S9xNPServerAddTask (uint32 task, void *data) { NPServer.TaskQueue [NPServer.TaskTail].Task = task; NPServer.TaskQueue [NPServer.TaskTail].Data = data; NPServer.TaskTail = (NPServer.TaskTail + 1) % NP_MAX_TASKS; } void S9xNPReset () { S9xNPNoClientReady (0); S9xNPServerAddTask (NP_SERVER_RESET_ALL, 0); } void S9xNPWaitForEmulationToComplete () { #ifdef NP_DEBUG printf ("SERVER: WaitForEmulationToComplete start @%ld\n", S9xGetMilliTime () - START); #endif while (!NetPlay.PendingWait4Sync && NetPlay.Connected && !Settings.ForcedPause && !Settings.StopEmulation && !(Settings.Paused && !Settings.FrameAdvance)) { #ifdef __WIN32__ Sleep (40); #endif } #ifdef NP_DEBUG printf ("SERVER: WaitForEmulationToComplete end @%ld\n", S9xGetMilliTime () - START); #endif } void S9xNPServerQueueSyncAll () { if (Settings.NetPlay && Settings.NetPlayServer && NPServer.NumClients > NP_ONE_CLIENT) { S9xNPNoClientReady (); S9xNPDiscardHeartbeats (); S9xNPServerAddTask (NP_SERVER_SYNC_ALL, 0); } } void S9xNPServerQueueSendingROMImage () { if (Settings.NetPlay && Settings.NetPlayServer && NPServer.NumClients > NP_ONE_CLIENT) { S9xNPNoClientReady (); S9xNPDiscardHeartbeats (); S9xNPServerAddTask (NP_SERVER_SEND_ROM_IMAGE, 0); } } void S9xNPServerQueueSendingFreezeFile (const char *filename) { if (Settings.NetPlay && Settings.NetPlayServer && NPServer.NumClients > NP_ONE_CLIENT) { S9xNPNoClientReady (); S9xNPDiscardHeartbeats (); S9xNPServerAddTask (NP_SERVER_SEND_FREEZE_FILE_ALL, (void *) strdup (filename)); } } void S9xNPServerQueueSendingLoadROMRequest (const char *filename) { if (Settings.NetPlay && Settings.NetPlayServer && NPServer.NumClients > NP_ONE_CLIENT) { S9xNPNoClientReady (); S9xNPDiscardHeartbeats (); S9xNPServerAddTask (NP_SERVER_SEND_ROM_LOAD_REQUEST_ALL, (void *) strdup (filename)); } } #ifndef __WIN32__ uint32 S9xGetMilliTime () { static bool8 first = TRUE; static long start_sec; struct timeval tv; gettimeofday (&tv, NULL); if (first) { start_sec = tv.tv_sec; first = FALSE; } return ((uint32) ((tv.tv_sec - start_sec) * 1000 + tv.tv_usec / 1000)); } #endif #endif