/*****************************************************************************\ 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. \*****************************************************************************/ /*********************************************************************************** SNES9X for Mac OS (c) Copyright John Stiles Snes9x for Mac OS X (c) Copyright 2001 - 2011 zones (c) Copyright 2002 - 2005 107 (c) Copyright 2002 PB1400c (c) Copyright 2004 Alexander and Sander (c) Copyright 2004 - 2005 Steven Seeger (c) Copyright 2005 Ryan Vogt ***********************************************************************************/ #include "snes9x.h" #include "memmap.h" #include #include #include #include #include #include "mac-prefix.h" #include "mac-controls.h" #include "mac-dialog.h" #include "mac-joypad.h" #include "mac-keyboard.h" #include "mac-os.h" #include "mac-stringtools.h" #include "mac-netplay.h" #include "mac-server.h" #ifdef SELF_TEST #include #endif #define KeyIsPressed(km, k) (1 & (((unsigned char *) km) [(k) >> 3] >> ((k) & 7))) enum { kNPSDialogNone, kNPSDialogInit, kNPSDialogProcess, kNPSDialogDone, kNPSDialogCancel }; typedef struct { volatile bool8 listenloop; volatile uint32 phasecount; volatile uint32 phasespan; volatile uint8 header; int socket; int numplayers; bool8 dialogcancel; int dialogprocess; } serverState; typedef struct { volatile bool8 padloop; volatile bool8 exitsgn; bool8 online; bool8 ready; int socket; int client; int player; char ip[256]; char name[256]; } clientsState; static char n_sememu[NP_MAX_PLAYERS][30] = { "/tmp/s9x_s_emu_semaphore1", "/tmp/s9x_s_emu_semaphore2", "/tmp/s9x_s_emu_semaphore3", "/tmp/s9x_s_emu_semaphore4", "/tmp/s9x_s_emu_semaphore5" }; static char n_sempad[NP_MAX_PLAYERS][30] = { "/tmp/s9x_s_pad_semaphore1", "/tmp/s9x_s_pad_semaphore2", "/tmp/s9x_s_pad_semaphore3", "/tmp/s9x_s_pad_semaphore4", "/tmp/s9x_s_pad_semaphore5" }; static serverState npserver; static clientsState npplayer[NP_MAX_PLAYERS]; static uint32 npactvpad[NP_MAX_PLAYERS][64], // [player number][] nprecvpad[NP_MAX_PLAYERS][64], // [player number][] npsendpad[NP_MAX_PLAYERS][64], // [player number][] npcachpad[64]; static sem_t *sememu[NP_MAX_PLAYERS], *sempad[NP_MAX_PLAYERS]; static pthread_t listenthread, processthread, padthread[NP_MAX_PLAYERS]; static int NPServerAcceptClient (int); static int NPServerGetMesFromClient (int); static void NPServerBeginListenLoop (void); static void NPServerEndListenLoop (void); static void NPServerDetachProcessThread (void); static void NPServerShutdownClient (int); static void NPServerAllotPlayers (void); static void NPServerSendPlayerList (void); static void NPServerWaitStartReply (void); static void NPServerSetPhaseSpan (void); static bool8 NPServerSendMesToClient (int, int); static bool8 NPServerGetNameFromClient (int); static bool8 NPServerSendROMInfoToClient (int); static bool8 NPServerSendSRAMToClient (int); static bool8 NPServerSendPlayerListToClient (int); static void * NPServerListenLoop (void *); static void * NPServerProcessThread (void *); static void * NPServerNetPlayThread (void *); static void NPServerDialogTimerHandler (EventLoopTimerRef, void *); static OSStatus NPServerDialogEventHandler (EventHandlerCallRef, EventRef, void *); bool8 NPServerDialog (void) { OSStatus err; IBNibRef nibRef; npserver.dialogcancel = true; err = CreateNibReference(kMacS9XCFString, &nibRef); if (err == noErr) { WindowRef tWindowRef; err = CreateWindowFromNib(nibRef, CFSTR("ClientList"), &tWindowRef); if (err == noErr) { EventHandlerRef eref; EventLoopTimerRef tref; EventHandlerUPP eventUPP; EventLoopTimerUPP timerUPP; EventTypeSpec windowEvents[] = { { kEventClassCommand, kEventCommandProcess }, { kEventClassCommand, kEventCommandUpdateStatus } }; HIViewRef ctl; HIViewID cid = { 'Chse', 0 }; npserver.dialogprocess = kNPSDialogInit; eventUPP = NewEventHandlerUPP(NPServerDialogEventHandler); err = InstallWindowEventHandler(tWindowRef, eventUPP, GetEventTypeCount(windowEvents), windowEvents, (void *) tWindowRef, &eref); timerUPP = NewEventLoopTimerUPP(NPServerDialogTimerHandler); err = InstallEventLoopTimer(GetCurrentEventLoop(), 0.0f, 0.1f, timerUPP, (void *) tWindowRef, &tref); HIViewFindByID(HIViewGetRoot(tWindowRef), cid, &ctl); HIViewSetVisible(ctl, false); MoveWindowPosition(tWindowRef, kWindowServer, false); ShowWindow(tWindowRef); err = RunAppModalLoopForWindow(tWindowRef); HideWindow(tWindowRef); SaveWindowPosition(tWindowRef, kWindowServer); err = RemoveEventLoopTimer(tref); DisposeEventLoopTimerUPP(timerUPP); err = RemoveEventHandler(eref); DisposeEventHandlerUPP(eventUPP); CFRelease(tWindowRef); } DisposeNibReference(nibRef); } return (!npserver.dialogcancel); } static void NPServerDialogTimerHandler (EventLoopTimerRef inTimer, void *userData) { WindowRef window = (WindowRef) userData; CFStringRef ref; HIViewRef ctl, root; HIViewID cid; int n = 0; root = HIViewGetRoot(window); for (int c = 0; c < NP_MAX_PLAYERS; c++) { cid.id = c; cid.signature = 'Pnum'; HIViewFindByID(root, cid, &ctl); if (npplayer[c].ready) { char num[4]; num[0] = '1' + n; num[1] = 'P'; num[2] = 0; SetStaticTextCStr(ctl, num, true); n++; } cid.signature = 'IP__'; HIViewFindByID(root, cid, &ctl); if (npplayer[c].online) { ref = CFStringCreateWithCString(kCFAllocatorDefault, npplayer[c].ip, kCFStringEncodingUTF8); if (ref) { SetStaticTextCFString(ctl, ref, true); CFRelease(ref); } else SetStaticTextCFString(ctl, CFSTR("unknown"), true); } else SetStaticTextCFString(ctl, CFSTR(""), true); cid.signature = 'Name'; HIViewFindByID(root, cid, &ctl); if (npplayer[c].online) { ref = CFStringCreateWithCString(kCFAllocatorDefault, npplayer[c].name, kCFStringEncodingUTF8); if (ref) { SetStaticTextCFString(ctl, ref, true); CFRelease(ref); } else SetStaticTextCFString(ctl, CFSTR("unknown"), true); } else SetStaticTextCFString(ctl, CFSTR(""), true); cid.signature = 'Stat'; HIViewFindByID(root, cid, &ctl); if (npplayer[c].online) { if (npplayer[c].ready) ref = CFCopyLocalizedString(CFSTR("NPReady"), "NPReady"); else ref = CFCopyLocalizedString(CFSTR("NPConnecting"), "NPConnecting"); if (ref) { SetStaticTextCFString(ctl, ref, true); CFRelease(ref); } else SetStaticTextCFString(ctl, CFSTR("error"), true); } else SetStaticTextCFString(ctl, CFSTR(""), true); } switch (npserver.dialogprocess) { case kNPSDialogNone: break; case kNPSDialogInit: NPNotification(" kNPSDialogInit", -1); npserver.dialogprocess = kNPSDialogNone; NPServerBeginListenLoop(); break; case kNPSDialogProcess: NPNotification(" kNPSDialogProcess", -1); npserver.dialogprocess = kNPSDialogNone; NPServerEndListenLoop(); cid.id = 0; cid.signature = 'Chse'; HIViewFindByID(root, cid, &ctl); HIViewSetVisible(ctl, true); NPServerDetachProcessThread(); break; case kNPSDialogDone: NPNotification(" kNPSDialogDone", -1); npserver.dialogprocess = kNPSDialogNone; npserver.dialogcancel = false; QuitAppModalLoopForWindow(window); break; case kNPSDialogCancel: NPNotification(" kNPSDialogCancel", -1); npserver.dialogprocess = kNPSDialogNone; NPServerEndListenLoop(); npserver.dialogcancel = true; QuitAppModalLoopForWindow(window); break; } } static OSStatus NPServerDialogEventHandler (EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData) { OSStatus err, result = eventNotHandledErr; WindowRef tWindowRef = (WindowRef) inUserData; switch (GetEventClass(inEvent)) { case kEventClassCommand: switch (GetEventKind(inEvent)) { HICommand tHICommand; case kEventCommandUpdateStatus: err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand); if (err == noErr && tHICommand.commandID == 'clos') { UpdateMenuCommandStatus(false); result = noErr; } break; case kEventCommandProcess: err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand); if (err == noErr) { switch (tHICommand.commandID) { case 'OKAY': HIViewRef ctl, root; HIViewID cid; root = HIViewGetRoot(tWindowRef); cid.id = 0; cid.signature = 'OKAY'; HIViewFindByID(root, cid, &ctl); DeactivateControl(ctl); cid.signature = 'CNSL'; HIViewFindByID(root, cid, &ctl); DeactivateControl(ctl); npserver.dialogprocess = kNPSDialogProcess; result = noErr; break; case 'CNSL': npserver.dialogprocess = kNPSDialogCancel; result = noErr; break; } } break; } break; } return (result); } void NPServerInit (void) { npserver.listenloop = false; npserver.phasecount = 0; npserver.phasespan = 0; npserver.header = 0; npserver.socket = -1; npserver.numplayers = 0; for (int i = 0; i < NP_MAX_PLAYERS; i++) { for (int j = 0; j < 64; j++) { npactvpad[i][j] = 0; nprecvpad[i][j] = 0; npsendpad[i][j] = 0; } } for (int j = 0; j < 64; j++) npcachpad[j] = 0; for (int c = 0; c < NP_MAX_PLAYERS; c++) { npplayer[c].padloop = false; npplayer[c].exitsgn = false; npplayer[c].online = false; npplayer[c].ready = false; npplayer[c].socket = -1; npplayer[c].client = 0; npplayer[c].player = 0; npplayer[c].ip[0] = 0; npplayer[c].name[0] = 0; } npplayer[0].online = true; npplayer[0].ready = true; char name[256]; if (gethostname(name, 256) == 0) { struct hostent *hn; if ((hn = gethostbyname(name)) != NULL) { struct in_addr addr; memcpy(&addr, hn->h_addr_list[0], sizeof(struct in_addr)); strcpy(npplayer[0].ip, inet_ntoa(addr)); } else strcpy(npplayer[0].ip, "unknown"); } else strcpy(npplayer[0].ip, "unknown"); CFStringRef ref; ref = CFCopyLocalizedString(CFSTR("NPServerName"), "NPServer"); if (ref) { Boolean r; r = CFStringGetCString(ref, npplayer[0].name, 256, kCFStringEncodingUTF8); if (!r) strcpy(npplayer[0].name, "unknown"); CFRelease(ref); } else strcpy(npplayer[0].name, "unknown"); } bool8 NPServerStartServer (int port) { #ifndef SELF_TEST struct sockaddr_in address; #else struct sockaddr_un address; #endif int v = 1; NPNotification("Server: Starting server...", -1); #ifndef SELF_TEST if ((npserver.socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) #else if ((npserver.socket = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) #endif { NPError("Server: Failed to create listening socket.", 1001); return (false); } if ((setsockopt(npserver.socket, SOL_SOCKET, SO_REUSEADDR, (char *) &v, sizeof(v))) < 0) { NPError("Server: Failed to set socket option.", 1002); return (false); } memset(&address, 0, sizeof(address)); #ifndef SELF_TEST address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_ANY); address.sin_port = htons(port); #else address.sun_family = AF_UNIX; strcpy(address.sun_path, SOCK_NAME); #endif #ifndef SELF_TEST if ((bind(npserver.socket, (struct sockaddr *) &address, sizeof(address))) < 0) #else unlink(SOCK_NAME); if ((bind(npserver.socket, (struct sockaddr *) &address, sizeof(address))) < 0) #endif { NPError("Server: Failed to bind socket to port number.", 1003); return (false); } if ((listen(npserver.socket, NP_MAX_CLIENTS)) < 0) { NPError("Server: Failed to get new socket to listen.", 1004); return (false); } npplayer[0].socket = npserver.socket; NPNotification("Server: Started server.", -1); return (true); } void NPServerStopServer (void) { NPNotification("Server: Stopping server...", -1); for (int c = 1; c <= NP_MAX_CLIENTS; c++) NPServerShutdownClient(c); npplayer[0].online = false; npplayer[0].ready = false; npplayer[0].socket = -1; npplayer[0].client = 0; npplayer[0].player = 0; npplayer[0].ip[0] = 0; npplayer[0].name[0] = 0; if (npserver.socket != -1) { close(npserver.socket); npserver.socket = -1; } NPNotification("Server: Stopped server.", -1); } static void NPServerShutdownClient (int c) { if (npplayer[c].online) { NPNotification("Server: Closing client %d connection...", c); if (npplayer[c].socket != -1) { close(npplayer[c].socket); npplayer[c].socket = -1; } npplayer[c].online = false; npplayer[c].ready = false; npplayer[c].client = 0; npplayer[c].player = 0; npplayer[c].ip[0] = 0; npplayer[c].name[0] = 0; NPNotification("Server: Client %d has disconnected.", c); } } static int NPServerAcceptClient (int port) { #ifndef SELF_TEST struct sockaddr_in address; #else struct sockaddr_un address; #endif int newfd; int c; socklen_t l; NPNotification("Server: Accepting new client connection...", -1); for (c = 1; c <= NP_MAX_CLIENTS; c++) if (!npplayer[c].online) break; if (c > NP_MAX_CLIENTS) { NPError("Server: Maximum number of clients have already connected.", 1101); return (-1); } l = sizeof(address); memset(&address, 0, l); if ((newfd = accept(port, (struct sockaddr *) &address, &l)) < 0) { NPError("Server: Can't accept client connection.", 1102); return (-1); } npplayer[c].online = true; npplayer[c].socket = newfd; #ifndef SELF_TEST if (address.sin_family == AF_INET) strcpy(npplayer[c].ip, inet_ntoa(address.sin_addr)); else strcpy(npplayer[c].ip, "unknown"); #else strcpy(npplayer[c].ip, "Unix"); #endif NPNotification("Server: new client %d has connected.", c); return (c); } static void NPServerBeginListenLoop (void) { npserver.listenloop = true; pthread_create(&listenthread, NULL, NPServerListenLoop, NULL); } static void NPServerEndListenLoop (void) { npserver.listenloop = false; pthread_join(listenthread, NULL); } static void * NPServerListenLoop (void *) { struct timeval timeout; fd_set readfds; int maxfd; NPNotification("Server: Entered listening loop.", -1); while (npserver.listenloop) { FD_ZERO(&readfds); maxfd = 0; if (npserver.socket != -1) { FD_SET(npserver.socket, &readfds); maxfd = npserver.socket; } for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].online) { FD_SET(npplayer[c].socket, &readfds); if (maxfd < npplayer[c].socket) maxfd = npplayer[c].socket; } } timeout.tv_sec = 0; timeout.tv_usec = 50000; if (select(maxfd + 1, &readfds, NULL, NULL, &timeout) > 0) { for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].online) { if (FD_ISSET(npplayer[c].socket, &readfds)) { switch (NPServerGetMesFromClient(c)) { case kNPClientNameSent: if (!NPServerSendROMInfoToClient(c)) NPServerShutdownClient(c); break; case kNPClientROMOpened: if (!NPServerSendSRAMToClient(c)) NPServerShutdownClient(c); break; case kNPClientSRAMLoaded: npplayer[c].ready = true; break; default: NPServerShutdownClient(c); break; } } } } if (FD_ISSET(npserver.socket, &readfds)) { int client; if ((client = NPServerAcceptClient(npserver.socket)) != -1) { if (!NPServerGetNameFromClient(client)) NPServerShutdownClient(client); } } } } NPNotification("Server: Exited listening loop.", -1); return (NULL); } static bool8 NPServerSendMesToClient (int c, int num) { uint8 mes[2]; mes[0] = NP_SERVER_MAGIC; mes[1] = num; if (socket_write(npplayer[c].socket, mes, 2) != 2) return (false); return (true); } static int NPServerGetMesFromClient (int c) { uint8 mes[2]; if (socket_read(npplayer[c].socket, mes, 2) != 2) return (-1); if (mes[0] != NP_CLIENT_MAGIC) return (-1); return ((int) mes[1]); } static bool8 NPServerGetNameFromClient (int c) { if (!npplayer[c].online) return (false); NPNotification("Server: Receiving player name from client %d...", c); if (NPServerSendMesToClient(c, kNPServerNameRequest) == false) { NPError("Server: Failed to send messsage to client.", 1201); return (false); } uint8 mes[4]; uint32 l; if (socket_read(npplayer[c].socket, mes, 4) != 4) { NPError("Server: Failed to receive name size from client.", 1202); return (false); } l = READ_LONG(mes + 0); if (socket_read(npplayer[c].socket, (uint8 *) npplayer[c].name, l) != (int) l) { NPError("Server: Failed to receive name from client.", 1203); return (false); } npplayer[c].name[l] = 0; if (NPServerSendMesToClient(c, kNPServerNameReceived) == false) { NPError("Server: Failed to send messsage to client.", 1204); return (false); } NPNotification("Server: Received player name from client %d.", c); return (true); // next: kNPClientNameSent } static bool8 NPServerSendROMInfoToClient (int c) { if (!npplayer[c].online) return (false); NPNotification("Server: Sending ROM information to client %d...", c); if (NPServerSendMesToClient(c, kNPServerROMInfoWillSend) == false) { NPError("Server: Failed to send messsage to client.", 1301); return (false); } if (NPServerGetMesFromClient(c) != kNPClientROMInfoWaiting) { NPError("Server: Failed to receive messsage from client.", 1302); return (false); } uint8 mes[16]; uint32 l; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(Memory.ROMFilename, drive, dir, fname, ext); l = strlen(fname); WRITE_LONG(mes + 0, Memory.ROMCRC32); WRITE_LONG(mes + 4, deviceSetting); WRITE_BYTE(mes + 8, 0); // reserved WRITE_BYTE(mes + 9, 0); // reserved WRITE_BYTE(mes + 10, 0); // reserved WRITE_BYTE(mes + 11, 0); // reserved WRITE_LONG(mes + 12, l); if (socket_write(npplayer[c].socket, mes, 16) != 16) { NPError("Server: Failed to send ROM information to client.", 1303); return (false); } if (socket_write(npplayer[c].socket, (uint8 *) fname, l) != (int) l) { NPError("Server: Failed to send ROM name to client.", 1304); return (false); } NPNotification("Server: Sent ROM information to client %d.", c); return (true); // next: kNPClientROMOpened } static bool8 NPServerSendSRAMToClient (int c) { if (!npplayer[c].online) return (false); NPNotification("Server: Sending SRAM to client %d...", c); if (NPServerSendMesToClient(c, kNPServerSRAMWillSend) == false) { NPError("Server: Failed to send messsage to client.", 1401); return (false); } if (NPServerGetMesFromClient(c) != kNPClientSRAMWaiting) { NPError("Server: Failed to receive messsage from client.", 1402); return (false); } uint8 mes[4]; uint32 sramsize; sramsize = Memory.SRAMSize ? (1 << (Memory.SRAMSize + 3)) * 128 : 0; WRITE_LONG(mes + 0, sramsize); if (socket_write(npplayer[c].socket, mes, 4) != 4) { NPError("Server: Failed to send SRAM size to client.", 1403); return (false); } if (sramsize && (socket_write(npplayer[c].socket, Memory.SRAM, sramsize) != (int) sramsize)) { NPError("Server: Failed to send SRAM to client.", 1404); return (false); } NPNotification("Server: Sent SRAM to client %d.", c); return (true); // next: kNPClientSRAMLoaded } static void NPServerDetachProcessThread (void) { pthread_create(&processthread, NULL, NPServerProcessThread, NULL); pthread_detach(processthread); } static void * NPServerProcessThread (void *) { NPNotification("Server: Entered process thread.", -1); NPServerAllotPlayers(); NPServerSendPlayerList(); NPServerSetPhaseSpan(); NPServerWaitStartReply(); npserver.dialogprocess = kNPSDialogDone; NPNotification("Server: Exited process thread.", -1); return (NULL); } static void NPServerAllotPlayers (void) { int n = 1; for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { npplayer[c].client = c; npplayer[c].player = n++; } else NPServerShutdownClient(c); } npplayer[0].client = 0; npplayer[0].player = 0; npserver.numplayers = n; NPNotification("Server: Number of players: %d", n); } static bool8 NPServerSendPlayerListToClient (int c) { if (!npplayer[c].online || !npplayer[c].ready) return (false); NPNotification("Server: Sending player list to client %d...", c); if (NPServerSendMesToClient(c, kNPServerPlayerWillSend) == false) { NPError("Server: Failed to send messsage to client.", 1601); return (false); } if (NPServerGetMesFromClient(c) != kNPClientPlayerWaiting) { NPError("Server: Failed to receive messsage from client.", 1602); return (false); } for (int i = 0; i < NP_MAX_PLAYERS; i++) { uint8 mes[10]; uint32 l; l = npplayer[i].ready ? strlen(npplayer[i].name) : 0; WRITE_BYTE(mes + 0, (i == c)); WRITE_BYTE(mes + 1, npplayer[i].ready); WRITE_LONG(mes + 2, npplayer[i].player); WRITE_LONG(mes + 6, l); if (socket_write(npplayer[c].socket, mes, 10) != 10) { NPError("Server: Failed to send name size to client.", 1603); return (false); } if (l && (socket_write(npplayer[c].socket, (uint8 *) npplayer[i].name, l) != (int) l)) { NPError("Server: Failed to send name to client.", 1604); return (false); } } NPNotification("Server: Sent player list to client %d.", c); return (true); } static void NPServerSendPlayerList (void) { for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (NPServerSendPlayerListToClient(c) == false) NPServerShutdownClient(c); } } } static void NPServerSetPhaseSpan (void) { struct timeval tv1, tv2; uint8 mes[21]; uint32 dus, dusmax; int l = npserver.numplayers * 4 + 1; NPNotification("Server: Testing sending / receiving pad states...", -1); dusmax = 0; for (int n = 0; n < 5; n++) { gettimeofday(&tv1, NULL); for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (socket_write(npplayer[c].socket, mes, l) != l) NPServerShutdownClient(c); } } for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (socket_read(npplayer[c].socket, mes, 5) != 5) NPServerShutdownClient(c); } } gettimeofday(&tv2, NULL); dus = (tv2.tv_sec * 1000000 + tv2.tv_usec) - (tv1.tv_sec * 1000000 + tv1.tv_usec); NPNotification(" %d [usec]", dus); if (dusmax < dus) dusmax = dus; usleep(50000); } NPNotification("Server: Tested sending / receiving pad states.", -1); npserver.phasespan = (uint32) Memory.ROMFramesPerSecond * dusmax / 1000000 + 1; if (npserver.phasespan > (uint32) Memory.ROMFramesPerSecond) npserver.phasespan = (uint32) Memory.ROMFramesPerSecond; NPNotification(" phase span: %d (frames)", npserver.phasespan); NPNotification("Server: Sending phase span value to clients...", -1); WRITE_LONG(mes + 0, npserver.phasespan); for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (socket_write(npplayer[c].socket, mes, 4) != 4) NPServerShutdownClient(c); } } NPNotification("Server: Sent phase span value to clients.", -1); } static void NPServerWaitStartReply (void) { struct timeval timeout; fd_set readfds; int maxfd; bool8 allok, flag[NP_MAX_PLAYERS]; NPNotification("Server: Waiting clients reply to start...", -1); allok = false; for (int c = 1; c <= NP_MAX_CLIENTS; c++) flag[c] = false; while (!allok) { FD_ZERO(&readfds); maxfd = 0; for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { FD_SET(npplayer[c].socket, &readfds); if (maxfd < npplayer[c].socket) maxfd = npplayer[c].socket; } } timeout.tv_sec = 0; timeout.tv_usec = 50000; if (select(maxfd + 1, &readfds, NULL, NULL, &timeout) > 0) { for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (FD_ISSET(npplayer[c].socket, &readfds)) { if (NPServerGetMesFromClient(c) == kNPClientStartWait) flag[c] = true; else NPServerShutdownClient(c); } } } } allok = true; for (int c = 1; c <= NP_MAX_CLIENTS; c++) if (npplayer[c].ready && !flag[c]) allok = false; } NPNotification("Server: All clients are ready to start netplay.", -1); } void NPServerDetachNetPlayThread (void) { NPNotification("Server: Detaching pad threads...", -1); for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { npplayer[c].padloop = true; npplayer[c].exitsgn = false; sememu[c] = sem_open(n_sememu[c], O_CREAT, 0600, 0); sempad[c] = sem_open(n_sempad[c], O_CREAT, 0600, 0); pthread_create(&padthread[c], NULL, NPServerNetPlayThread, &(npplayer[c].client)); pthread_detach(padthread[c]); } } NPNotification("Server: Detached pad threads.", -1); } void NPServerStopNetPlayThread (void) { NPNotification("Server: Stopping pad threads...", -1); for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { npplayer[c].padloop = false; sem_post(sempad[c]); while (!npplayer[c].exitsgn) sleep(0); sem_unlink(n_sememu[c]); sem_unlink(n_sempad[c]); sem_close(sememu[c]); sem_close(sempad[c]); } } NPNotification("Server: Stopped pad threads.", -1); } void NPServerStartClients (void) { NPNotification("Server: Sending start flag to clients...", -1); for (int c = 1; c <= NP_MAX_CLIENTS; c++) { if (npplayer[c].ready) { if (NPServerSendMesToClient(c, kNPServerStart) == false) NPServerShutdownClient(c); } } NPNotification("Server: Sent start flag to clients.", -1); npserver.phasecount = 0; npserver.header = 0; for (int c = 1; c <= NP_MAX_CLIENTS; c++) if (npplayer[c].ready) sem_post(sempad[c]); NPNotification("Server: Netplay started.", -1); } static void * NPServerNetPlayThread (void *p) { uint8 mes[NP_MAX_PLAYERS * 64 * 4 + 1]; uint8 count = 0; int c = *((int *) p), l; NPNotification("Server: Entered pad thread for client %d.", c); while (npplayer[c].padloop) { sem_wait(sempad[c]); WRITE_BYTE(mes + 0, count | npserver.header); for (int i = 0; i < npserver.numplayers; i++) for (uint32 j = 0; j < npserver.phasespan; j++) WRITE_LONG(mes + (i * npserver.phasespan + j) * 4 + 1, npsendpad[i][j]); l = npserver.numplayers * npserver.phasespan * 4 + 1; if (socket_write(npplayer[c].socket, mes, l) != l) { NPServerShutdownClient(c); npplayer[c].exitsgn = true; sem_post(sememu[c]); pthread_exit(NULL); } l = npserver.phasespan * 4 + 1; if (socket_read(npplayer[c].socket, mes, l) != l) { NPServerShutdownClient(c); npplayer[c].exitsgn = true; sem_post(sememu[c]); pthread_exit(NULL); } for (uint32 j = 0; j < npserver.phasespan; j++) nprecvpad[npplayer[c].player][j] = READ_LONG(mes + j * 4 + 1); if ((mes[0] & 0xF) != count) NPNotification("Server: Warning: Failed to synchronize client %d.", c); count = (count + 1) & 0xF; sem_post(sememu[c]); } npplayer[c].exitsgn = true; NPNotification("Server: Exited pad thread for client %d.", c); return (NULL); } void NPServerProcessInput (void) { static uint8 header = 0; static uint32 pos = 0; KeyMap myKeys; if (npserver.phasecount == 0) { for (int c = 1; c <= NP_MAX_CLIENTS; c++) if (npplayer[c].ready) sem_wait(sememu[c]); for (uint32 j = 0; j < npserver.phasespan; j++) nprecvpad[0][j] = npcachpad[j]; for (int i = 0; i < npserver.numplayers; i++) { for (uint32 j = 0; j < npserver.phasespan; j++) { npactvpad[i][j] = npsendpad[i][j]; npsendpad[i][j] = nprecvpad[i][j]; } } if (npserver.header & 0x80) { for (int i = 0; i < npserver.numplayers; i++) { npactvpad[i][npserver.phasespan] = 0; npsendpad[i][npserver.phasespan] = 0; } npserver.phasespan++; if (npserver.phasespan > (uint32) Memory.ROMFramesPerSecond) npserver.phasespan = (uint32) Memory.ROMFramesPerSecond; char str[256]; sprintf(str, "delay: %d", npserver.phasespan); S9xMessage(0, 0, str); } else if (npserver.header & 0x40) { npserver.phasespan--; if (npserver.phasespan == 0) npserver.phasespan = 1; char str[256]; sprintf(str, "delay: %d", npserver.phasespan); S9xMessage(0, 0, str); } npserver.header = header; header = 0; pos = 0; } for (int i = 0; i < npserver.numplayers; i++) { controlPad[i] = npactvpad[i][pos]; ControlPadFlagsToS9xReportButtons(i, controlPad[i]); } GetKeys(myKeys); if (ISpKeyIsPressed(kISpEsc) || KeyIsPressed(myKeys, keyCode[kKeyEsc])) { if (s9xthreadrunning) { if (!eventQueued) { PostQueueToSubEventLoop(); eventQueued = true; } } else running = false; return; } uint32 pad = 0; JoypadScanDirection(0, &pad); if (ISpKeyIsPressed(kISp1PR )) pad |= 0x0010; if (ISpKeyIsPressed(kISp1PL )) pad |= 0x0020; if (ISpKeyIsPressed(kISp1PX )) pad |= 0x0040; if (ISpKeyIsPressed(kISp1PA )) pad |= 0x0080; if (ISpKeyIsPressed(kISp1PStart )) pad |= 0x1000; if (ISpKeyIsPressed(kISp1PSelect)) pad |= 0x2000; if (ISpKeyIsPressed(kISp1PY )) pad |= 0x4000; if (ISpKeyIsPressed(kISp1PB )) pad |= 0x8000; if (KeyIsPressed(myKeys, keyCode[k1PR] )) pad |= 0x0010; if (KeyIsPressed(myKeys, keyCode[k1PL] )) pad |= 0x0020; if (KeyIsPressed(myKeys, keyCode[k1PX] )) pad |= 0x0040; if (KeyIsPressed(myKeys, keyCode[k1PA] )) pad |= 0x0080; if (KeyIsPressed(myKeys, keyCode[k1PRight] )) pad |= 0x0100; if (KeyIsPressed(myKeys, keyCode[k1PLeft] )) pad |= 0x0200; if (KeyIsPressed(myKeys, keyCode[k1PDown] )) pad |= 0x0400; if (KeyIsPressed(myKeys, keyCode[k1PUp] )) pad |= 0x0800; if (KeyIsPressed(myKeys, keyCode[k1PStart] )) pad |= 0x1000; if (KeyIsPressed(myKeys, keyCode[k1PSelect])) pad |= 0x2000; if (KeyIsPressed(myKeys, keyCode[k1PY] )) pad |= 0x4000; if (KeyIsPressed(myKeys, keyCode[k1PB] )) pad |= 0x8000; npcachpad[pos] = pad; if (KeyIsPressed(myKeys, keyCode[k2PR])) header |= 0x80; if (KeyIsPressed(myKeys, keyCode[k2PL])) header |= 0x40; if (npserver.phasecount == 0) { npserver.phasecount = npserver.phasespan; for (int c = 1; c <= NP_MAX_CLIENTS; c++) if (npplayer[c].ready) sem_post(sempad[c]); } npserver.phasecount--; pos++; }