mirror of https://github.com/snes9xgit/snes9x.git
1334 lines
30 KiB
Plaintext
1334 lines
30 KiB
Plaintext
/*****************************************************************************\
|
|
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 <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
|
|
#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 <sys/un.h>
|
|
#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++;
|
|
}
|