sdlbomber/network.c

551 lines
11 KiB
C

#include "bomber.h"
#include "announce.h"
#include "game.h"
#include "menu.h"
#include "network.h"
#include "utils.h"
#define MAXMSG 4096
int udpsocket;
const unsigned char gameversion[4]={0xda,0x01,0x00,0x04};
struct netnode netnodes[64];
static int informsize;
static unsigned char regpacket[64];
static struct sockaddr_in myname={0},mastername={0};
static socklen_t senderlength;
static struct sockaddr_in sender={0};
static unsigned char mesg[MAXMSG]="";
uchar needwhole=0;
int mydatacount;
int myslot;
network_type network = NETWORK_NONE;
static unsigned char hist[ACTIONHIST][MAXNETNODES];
int actionput,actioncount;
unsigned char actionblock[ACTIONHIST*MAXNETNODES];
int myaction;
unsigned char actions[MAXNETNODES];
unsigned char latestactions[MAXNETNODES];
long latestcounts[MAXNETNODES];
/* Network I/O, building/checking packets */
#if defined (TEST_LATENCY)
#define NUMQ 512
struct message {
int time;
struct sockaddr_in *to;
int tosize;
unsigned char msg[512];
int len;
} message[NUMQ]={0};
outmsgs() {
int i;
for(i=0;i<NUMQ;++i) {
if(message[i].time) {
--message[i].time;
if(message[i].time) continue;
sendto(udpsocket,message[i].msg,message[i].len,0,
message[i].to,sizeof(struct sockaddr_in));
}
}
}
#endif
static int putmsg(struct sockaddr_in *toname,unsigned char *msg,int len) {
int status;
#if defined (TEST_LATENCY)
int i;
for(i=0;i<NUMQ;++i) {
if(!message[i].time) {
message[i].time=10;
message[i].to=toname;
memcpy(message[i].msg,msg,len);
message[i].len=len;
break;
}
}
return 0;
#else
status=sendto(udpsocket,msg,len,0,
(struct sockaddr *)toname,sizeof(struct sockaddr_in));
return status;
#endif
}
static int getmsg(int msec) {
int size;
memset(&sender,0,sizeof(sender));
senderlength=sizeof(sender);
if(msec) {
struct timeval timeout;
fd_set readfds;
int res;
memset(&timeout,0,sizeof(timeout));
timeout.tv_sec=msec/1000;
timeout.tv_usec=(msec%1000)*1000;
FD_ZERO(&readfds);
FD_SET(udpsocket,&readfds);
res=select(udpsocket+1,&readfds,0,0,&timeout);
if(res<=0) return -1;
}
size=recvfrom(udpsocket,mesg,MAXMSG,0,
(struct sockaddr *)&sender,&senderlength);
return size;
}
static int isvalidunique(unsigned char *p) {
return 0 == memcmp(p, &network_unique, 4);
}
static int isvalidversion(unsigned char *p) {
return 0 == memcmp(p, gameversion, 4);
}
static unsigned char* writeuint32(unsigned char *p, Uint32 i) {
p[0]=i>>24L;
p[1]=i>>16L;
p[2]=i>>8L;
p[3]=i;
return p+4;
}
static Uint32 readuint32(unsigned char *p) {
return (p[0]<<24L) | (p[1]<<16L) | (p[2]<<8) | p[3];
}
static unsigned char* writeunique(unsigned char *p) {
memcpy(p, &network_unique, 4);
return p + 4;
}
static unsigned char* writeversion(unsigned char *p) {
memcpy(p, &gameversion, 4);
return p + 4;
}
static int isvalidmsg() {
int i;
void *host;
void *port;
if (!isvalidunique(mesg+1)) return -1;
host=&sender.sin_addr.s_addr;
port=&sender.sin_port;
for(i=1;i<MAXNETNODES;++i)
if(netnodes[i].used &&
!memcmp(&netnodes[i].netname.sin_addr.s_addr,host,4) &&
!memcmp(&netnodes[i].netname.sin_port,port,2))
return i;
return -1;
}
/* Handling game actions */
static void addactions(void) {
memmove(hist[actionput],actions,MAXNETNODES);
++actionput;
if(actionput==ACTIONHIST)
actionput=0;
++actioncount;
}
static void buildactions(void) {
unsigned char *p;
int i,j;
p=actionblock;
i=0;
while(i<20) {
if(actioncount-i>0) {
j=actionput-i-1;
if(j<0) j+=ACTIONHIST;
memmove(p,hist[j],MAXNETNODES);
} else {
memset(p,0,MAXNETNODES);
}
p+=MAXNETNODES;
++i;
}
}
static void sendactions(int which) {
unsigned char msg[512];
msg[0] = PKT_STEP;
writeunique(msg+1);
writeuint32(msg+5, actioncount);
memcpy(msg+9,actionblock,MAXNETNODES*ACTIONHIST);
putmsg(&netnodes[which].netname,msg,MAXNETNODES*ACTIONHIST+9);
}
static void sendmine(int frame) {
unsigned char msg[64];
msg[0] = PKT_MYDATA;
writeunique(msg+1);
writeuint32(msg+5, frame);
msg[9]=myaction;
putmsg(&mastername,msg,10);
}
int networktraffic(void) {
int i;
int length;
int whosent;
unsigned char newactions[MAXNETNODES];
long now;
long count;
switch (network) {
case NETWORK_NONE:
return -1;
case NETWORK_MASTER:
memcpy(newactions,latestactions,MAXNETNODES);
newactions[0]=myaction;
now=gtime();
for(;;) {
if(gtime()-now>15) break;
length=getmsg(5);
if(length>0 && *mesg!=PKT_MYDATA) fprintf(stderr, "Strange packet %d\n", (int) *mesg);
// check for unexpected old packets...
// for example JOIN on frame 0, respond with BEGIN if player already in game
// respond with uninvite INVITE on JOIN from others
if(length<10) continue;
whosent=isvalidmsg();
if(whosent<=0) continue;
count=readuint32(mesg+5);
if(count>latestcounts[whosent]) {
latestcounts[whosent]=count;
newactions[whosent]=mesg[9];
}
}
if(myaction==ACT_QUIT) {
for(i=1;i<MAXNETNODES;++i)
if(netnodes[i].used)
newactions[i]=ACT_QUIT;
}
memmove(actions,newactions,sizeof(actions));
addactions();
buildactions();
for(i=1;i<MAXNETNODES;++i)
if(netnodes[i].used)
sendactions(i);
for(i=1;i<MAXNETNODES;++i)
if(netnodes[i].used && actions[i]==ACT_QUIT)
netnodes[i].used=0;
return actioncount;
case NETWORK_SLAVE:
{
long latest=-1;
long lastsent;
lastsent=now=gtime();
++mydatacount;
sendmine(mydatacount);
for(;;) {
/*
if(gtime()-lastsent>=1)
{
lastsent=gtime();
sendmine(mydatacount);
}
*/
if(latest>=0 && gtime()-now>3) break;
length=getmsg(3);
if(length==MAXNETNODES*ACTIONHIST+9 &&
sender.sin_addr.s_addr==mastername.sin_addr.s_addr &&
sender.sin_port==mastername.sin_port) {
i=readuint32(mesg+5);
if(i<latest) continue;
latest=i;
memmove(actionblock,mesg+9,MAXNETNODES*ACTIONHIST);
}
}
return latest;
}
}
return -1;
}
/* Handling socket init */
int winsock=0;
void getsocket(void) {
int status;
socklen_t slen = sizeof(myname);
#if defined(__WIN32__) || defined(WIN32)
char dummydata[128];
if(WSAStartup(0x0101,(void *)dummydata)) {
printf("Windows dumped\n");
exit(1);
}
winsock=1;
#endif
udpsocket=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(udpsocket==-1) {
perror("socket()");
exit(1);
}
memset(&myname,0,sizeof(myname));
myname.sin_family=AF_INET;
myname.sin_addr.s_addr=htonl(INADDR_ANY);
myname.sin_port=htons(0);
status=bind(udpsocket,(struct sockaddr *) &myname,sizeof(myname));
if(-1 == status) {
perror("bind()");
exit(1);
}
status = getsockname(udpsocket, (struct sockaddr *) &myname, &slen);
if(-1 == status) {
perror("getsockname()");
exit(1);
}
}
void freesocket(void) {
#if defined(__WIN32__) || defined(WIN32)
if(!winsock) return;
WSACleanup();
#endif
}
/* Join / Host Games */
static void buildinform(unsigned char type) {
unsigned char *put;
int i;
put=mesg;
*put++=type;
put = writeunique(put);
++put; // slot specific for each slave
for (i = 0; i < MAXNETNODES; ++i) {
if(!netnodes[i].used) continue;
*put++=i;
memmove(put,netnodes[i].name,16);
put+=16;
}
*put++=0xff;
memcpy(put, configopts, sizeof(configopts));
put += sizeof(configopts);
informsize=put-mesg;
}
static void inform1(int which) {
mesg[5]=which;
putmsg(&netnodes[which].netname,mesg,informsize);
}
static void inform(unsigned char type) {
int i;
buildinform(type);
for(i=1;i<MAXNETNODES;++i)
if(netnodes[i].used)
inform1(i);
}
//returns 0=ignore packet,1=we're rejected,2=INVITE,3=BEGIN
int scaninvite(int msec) {
int i, size;
unsigned char *take;
Uint32 unique = 0;
size = getmsg(msec);
if (size < 6) return 0;
if (*mesg!=PKT_INVITE && *mesg!=PKT_BEGIN) return 0;
if (sender.sin_family != mastername.sin_family
|| sender.sin_addr.s_addr != mastername.sin_addr.s_addr
|| sender.sin_port != mastername.sin_port) return 0;
myslot=mesg[5];
if (6 == size || 0xff == mesg[5] || 0xff == mesg[6]) return 1;
/* update unique */
memcpy(&unique, mesg+1, 4);
if (unique != network_unique)
set_unique(ntohl(unique));
memset(netnodes,0,sizeof(netnodes));
size-=6;
take=mesg+6;
while(*take!=0xff && size>=17) {
if((i=*take)<MAXNETNODES) {
netnodes[i].used=1;
memmove(netnodes[i].name,take+1,16);
}
take+=17;
size-=17;
}
take++; size--; /* read 0xff */
if(*mesg==PKT_INVITE) {
return 2;
} else { /* BEGIN */
if (size != sizeof(configopts)) {
fprintf(stderr, "PKT_BEGIN has wrong size: %i != %i\n", size, (int) sizeof(configopts));
return 1; /* broken packet */
}
set_game_options(take);
return 3;
}
}
int send_join(struct sockaddr_in *netname, char playername[16]) {
int res = 0;
long now;
set_unique(-1); /* reset unique and random */
mastername = *netname;
*regpacket=PKT_JOIN;
writeversion(regpacket + 1);
memmove(regpacket+5, playername, 16);
now=longtime();
while(longtime()-now<10) {
putmsg(&mastername,regpacket,1+4+16);
if (0 == (res=scaninvite(1000))) continue;
return res;
}
return 0;
}
void send_quit() {
long now;
int size;
*regpacket = PKT_QUIT;
writeunique(regpacket+1);
now=longtime();
while(longtime()-now<10) {
putmsg(&mastername,regpacket,5);
size=getmsg(1000);
if(size<6) continue;
if(mesg[0] != PKT_ACK || mesg[1] != PKT_QUIT) continue;
if (isvalidunique(mesg+2))
break;
}
}
int start_network_game() {
if(!registergame(playername, myname.sin_port, gameversion)) return 0;
memset(netnodes,0,sizeof(netnodes));
netnodes[0].used=1;
memmove(netnodes[0].name,playername,16);
myslot=0;
return 1;
}
int begin_network_game() {
inform(PKT_BEGIN);
network = NETWORK_MASTER;
/* TODO: wait for ack */
return 1;
}
void send_invites() {
inform(PKT_INVITE);
}
void cancel_network_game() {
int i;
mesg[0] = PKT_INVITE;
writeunique(mesg+1);
mesg[5] = 0xff;
for(i=1;i<MAXNETNODES;++i) {
if(netnodes[i].used) {
putmsg(&netnodes[i].netname,mesg,6);
}
}
}
int handle_joins() {
int size;
int i, j;
unsigned char temp[64];
size=getmsg(40);
switch (*mesg) {
case PKT_JOIN:
if (size < 21 || !isvalidversion(mesg+1)) return 0;
break;
case PKT_QUIT:
if (size < 5 || !isvalidunique(mesg+1)) return 0;
break;
default:
return 0;
}
/* Find host in list:
* i == MAXETNODES: host not found, otherwise the found host
* only if host not found:
* j == -1: no free slot, otherwise first free slot
*/
j = -1;
for (i = 1; i < MAXNETNODES; ++i) {
if (!netnodes[i].used) {
if (-1 == j) j = i;
continue; /* don't compare with unused host */
}
if(memcmp(&netnodes[i].netname.sin_addr.s_addr,
&sender.sin_addr.s_addr,4)) continue;
if(memcmp(&netnodes[i].netname.sin_port,
&sender.sin_port,2)) continue;
/* found host */
break;
}
if(*mesg==PKT_QUIT) {
if(i < MAXNETNODES) /* if host found, reset entry */
memset(netnodes+i,0,sizeof(struct netnode));
/* send always ACK for QUITs */
*temp=PKT_ACK;
memmove(temp+1,mesg,5);
putmsg(&sender,temp,6);
} else {
if (i==MAXNETNODES && j==-1) { /* reject */
*mesg=PKT_INVITE;
writeunique(mesg+1);
mesg[5]=0xff;
putmsg(&sender,mesg,6);
return 0;
}
/* no explicit ack, send invites to all later */
if(i==MAXNETNODES) i=j;
memmove(&netnodes[i].netname.sin_addr.s_addr,
&sender.sin_addr.s_addr,4);
memmove(&netnodes[i].netname.sin_port,
&sender.sin_port,2);
netnodes[i].netname.sin_family=AF_INET;
netnodes[i].used=1;
memmove(netnodes[i].name,mesg+5,16);
netnodes[i].name[15] = '\0';
}
return 1;
}