QTV file and stuff

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1271 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2005-09-06 01:09:36 +00:00
parent 4ed3f5b009
commit dc613a0a01
5 changed files with 3584 additions and 0 deletions

393
fteqtv/netchan.c Normal file
View File

@ -0,0 +1,393 @@
#include "qtv.h"
#define curtime Sys_Milliseconds()
void NET_SendPacket(SOCKET sock, int length, char *data, netadr_t adr)
{
int ret;
ret = sendto(sock, data, length, 0, (struct sockaddr *)adr, sizeof(struct sockaddr_in));
if (ret < 0)
{
printf("udp send error\n");
}
}
#define PACKET_HEADER 8
/*
packet header
-------------
31 sequence
1 does this message contain a reliable payload
31 acknowledge sequence
1 acknowledge receipt of even/odd message
16 qport
The remote connection never knows if it missed a reliable message, the
local side detects that it has been dropped by seeing a sequence acknowledge
higher than the last reliable sequence, but without the correct even/odd
bit for the reliable set.
If the sender notices that a reliable message has been dropped, it will be
retransmitted. It will not be retransmitted again until a message after
the retransmit has been acknowledged and the reliable still failed to get there.
If the sequence number is -1, the packet should be handled without a netcon.
The reliable message can be added to at any time by doing
MSG_Write* (&netchan->message, <data>).
If the message buffer is overflowed, either by a single message, or by
multiple frames worth piling up while the last reliable transmit goes
unacknowledged, the netchan signals a fatal error.
Reliable messages are always placed first in a packet, then the unreliable
message is included if there is sufficient room.
To the receiver, there is no distinction between the reliable and unreliable
parts of the message, they are just processed out as a single larger message.
Illogical packet sequence numbers cause the packet to be dropped, but do
not kill the connection. This, combined with the tight window of valid
reliable acknowledgement numbers provides protection against malicious
address spoofing.
The qport field is a workaround for bad address translating routers that
sometimes remap the client's source port on a packet during gameplay.
If the base part of the net address matches and the qport matches, then the
channel matches even if the IP port differs. The IP port should be updated
to the new value before sending out any replies.
*/
/*
===============
Netchan_OutOfBand
Sends an out-of-band datagram
================
*/
void Netchan_OutOfBand (SOCKET sock, netadr_t adr, int length, unsigned char *data)
{
netmsg_t send;
unsigned char send_buf[MAX_MSGLEN + PACKET_HEADER];
// write the packet header
InitNetMsg (&send, send_buf, sizeof(send_buf));
WriteLong (&send, -1); // -1 sequence means out of band
WriteData (&send, data, length);
// send the datagram
NET_SendPacket (sock, send.cursize, send.data, adr);
}
/*
===============
Netchan_OutOfBandPrint
Sends a text message in an out-of-band datagram
================
*/
void Netchan_OutOfBandPrint (SOCKET sock, netadr_t adr, char *format, ...)
{
va_list argptr;
static char string[8192]; // ??? why static?
va_start (argptr, format);
#ifdef _WIN32
_vsnprintf (string, sizeof(string) - 1, format, argptr);
string[sizeof(string) - 1] = '\0';
#else
vsnprintf (string, sizeof(string), format, argptr);
#endif // _WIN32
va_end (argptr);
Netchan_OutOfBand (sock, adr, strlen(string), (unsigned char *)string);
}
/*
===============
Netchan_Init
===============
*/
void Netchan_Init (netadr_t adr)
{
}
/*
==============
Netchan_Setup
called to open a channel to a remote system
==============
*/
void Netchan_Setup (SOCKET sock, netchan_t *chan, netadr_t adr, int qport)
{
memset (chan, 0, sizeof(*chan));
chan->sock = sock;
memcpy(&chan->remote_address, adr, sizeof(netadr_t));
chan->qport = qport;
chan->last_received = curtime;
InitNetMsg(&chan->message, chan->message_buf, sizeof(chan->message_buf));
chan->message.allowoverflow = true;
chan->rate = 1.0/2500;
}
/*
===============
Netchan_CanPacket
Returns true if the bandwidth choke isn't active
================
*/
#define MAX_BACKUP 200
qboolean Netchan_CanPacket (netchan_t *chan)
{
// unlimited bandwidth for local client
// if (chan->remote_address.type == NA_LOOPBACK)
// return true;
if (chan->cleartime < curtime + MAX_BACKUP*chan->rate)
return true;
return false;
}
/*
===============
Netchan_CanReliable
Returns true if the bandwidth choke isn't
================
*/
qboolean Netchan_CanReliable (netchan_t *chan)
{
if (chan->reliable_length)
return false; // waiting for ack
return Netchan_CanPacket (chan);
}
/*
===============
Netchan_Transmit
tries to send an unreliable message to a connection, and handles the
transmition / retransmition of the reliable messages.
A 0 length will still generate a packet and deal with the reliable messages.
================
*/
void Netchan_Transmit (netchan_t *chan, int length, unsigned char *data)
{
netmsg_t send;
unsigned char send_buf[MAX_MSGLEN + PACKET_HEADER];
qboolean send_reliable;
unsigned w1, w2;
int i;
// check for message overflow
if (chan->message.overflowed)
{
chan->drop = true;
// printf ("%s:Outgoing message overflow\n"
// , NET_AdrToString (chan->remote_address));
return;
}
// if the remote side dropped the last reliable message, resend it
send_reliable = false;
if (chan->incoming_acknowledged > chan->last_reliable_sequence
&& chan->incoming_reliable_acknowledged != chan->reliable_sequence)
send_reliable = true;
// if the reliable transmit buffer is empty, copy the current message out
if (!chan->reliable_length && chan->message.cursize)
{
memcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize);
chan->reliable_length = chan->message.cursize;
chan->message.cursize = 0;
chan->reliable_sequence ^= 1;
send_reliable = true;
}
// write the packet header
send.data = send_buf;
send.maxsize = sizeof(send_buf);
send.readpos = send.cursize = 0;
w1 = chan->outgoing_sequence | (send_reliable<<31);
w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence<<31);
chan->outgoing_sequence++;
WriteLong (&send, w1);
WriteLong (&send, w2);
// send the qport if we are a client
// if (chan->sock == NS_CLIENT)
// MSG_WriteShort (&send, chan->qport);
// copy the reliable message to the packet first
if (send_reliable)
{
WriteData (&send, chan->reliable_buf, chan->reliable_length);
chan->last_reliable_sequence = chan->outgoing_sequence;
}
// add the unreliable part if space is available
if (send.maxsize - send.cursize >= length)
WriteData (&send, data, length);
// send the datagram
// i = chan->outgoing_sequence & (MAX_LATENT-1);
// chan->outgoing_size[i] = send.cursize;
// chan->outgoing_time[i] = curtime;
NET_SendPacket (chan->sock, send.cursize, send.data, chan->remote_address);
if (chan->cleartime < curtime)
chan->cleartime = curtime + send.cursize*chan->rate;
else
chan->cleartime += send.cursize*chan->rate;
#ifndef CLIENTONLY
// if (chan->sock == NS_SERVER && sv_paused.value)
// chan->cleartime = curtime;
#endif
/* if (showpackets.value)
Com_Printf ("--> s=%i(%i) a=%i(%i) %i\n"
, chan->outgoing_sequence
, send_reliable
, chan->incoming_sequence
, chan->incoming_reliable_sequence
, send.cursize);
*/
}
/*
=================
Netchan_Process
called when the current net_message is from remote_address
modifies net_message so that it points to the packet payload
=================
*/
qboolean Netchan_Process (netchan_t *chan, netmsg_t *msg)
{
unsigned sequence, sequence_ack;
unsigned reliable_ack, reliable_message;
// get sequence numbers
msg->readpos = 0;
sequence = ReadLong (msg);
sequence_ack = ReadLong (msg);
// read the qport if we are a server
// if (chan->sock == NS_SERVER)
ReadShort (msg);
reliable_message = sequence >> 31;
reliable_ack = sequence_ack >> 31;
sequence &= ~(1<<31);
sequence_ack &= ~(1<<31);
/* if (showpackets.value)
Com_Printf ("<-- s=%i(%i) a=%i(%i) %i\n"
, sequence
, reliable_message
, sequence_ack
, reliable_ack
, net_message.cursize);
*/
//
// discard stale or duplicated packets
//
if (sequence <= (unsigned)chan->incoming_sequence)
{
/* if (showdrop.value)
Com_Printf ("%s:Out of order packet %i at %i\n"
, NET_AdrToString (chan->remote_address)
, sequence
, chan->incoming_sequence);
*/
return false;
}
//
// dropped packets don't keep the message from being used
//
/* chan->dropped = sequence - (chan->incoming_sequence+1);
if (chan->dropped > 0)
{
chan->drop_count += 1;
if (showdrop.value)
Com_Printf ("%s:Dropped %i packets at %i\n"
, NET_AdrToString (chan->remote_address)
, chan->dropped
, sequence);
}
*/
//
// if the current outgoing reliable message has been acknowledged
// clear the buffer to make way for the next
//
if (reliable_ack == (unsigned)chan->reliable_sequence)
chan->reliable_length = 0; // it has been received
//
// if this message contains a reliable message, bump incoming_reliable_sequence
//
chan->incoming_sequence = sequence;
chan->incoming_acknowledged = sequence_ack;
chan->incoming_reliable_acknowledged = reliable_ack;
if (reliable_message)
chan->incoming_reliable_sequence ^= 1;
//
// the message can now be read from the current message pointer
// update statistics counters
//
// chan->frame_latency = chan->frame_latency*OLD_AVG
// + (chan->outgoing_sequence-sequence_ack)*(1.0-OLD_AVG);
// chan->frame_rate = chan->frame_rate*OLD_AVG
// + (curtime - chan->last_received)*(1.0-OLD_AVG);
// chan->good_count += 1;
chan->last_received = curtime;
return true;
}

905
fteqtv/parse.c Normal file
View File

@ -0,0 +1,905 @@
#include "qtv.h"
#define ParseError(m) (m)->cursize = (m)->cursize+1 //
void InitNetMsg(netmsg_t *b, char *buffer, int bufferlength)
{
b->data = buffer;
b->maxsize = bufferlength;
b->readpos = 0;
b->cursize = 0;
}
//probably not the place for these any more..
unsigned char ReadByte(netmsg_t *b)
{
if (b->readpos >= b->cursize)
{
b->readpos = b->cursize+1;
return 0;
}
return b->data[b->readpos++];
}
unsigned short ReadShort(netmsg_t *b)
{
int b1, b2;
b1 = ReadByte(b);
b2 = ReadByte(b);
return b1 | (b2<<8);
}
unsigned int ReadLong(netmsg_t *b)
{
int s1, s2;
s1 = ReadShort(b);
s2 = ReadShort(b);
return s1 | (s2<<16);
}
float ReadFloat(netmsg_t *b)
{
union {
unsigned int i;
float f;
} u;
u.i = ReadLong(b);
return u.f;
}
void ReadString(netmsg_t *b, char *string, int maxlen)
{
maxlen--; //for null terminator
while(maxlen)
{
*string = ReadByte(b);
if (!*string)
return;
string++;
maxlen--;
}
*string++ = '\0'; //add the null
}
void WriteByte(netmsg_t *b, unsigned char c)
{
if (b->cursize>=b->maxsize)
return;
b->data[b->cursize++] = c;
}
void WriteShort(netmsg_t *b, unsigned short l)
{
WriteByte(b, (l&0x00ff)>>0);
WriteByte(b, (l&0xff00)>>8);
}
void WriteLong(netmsg_t *b, unsigned int l)
{
WriteByte(b, (l&0x000000ff)>>0);
WriteByte(b, (l&0x0000ff00)>>8);
WriteByte(b, (l&0x00ff0000)>>16);
WriteByte(b, (l&0xff000000)>>24);
}
void WriteFloat(netmsg_t *b, float f)
{
union {
unsigned int i;
float f;
} u;
u.f = f;
WriteLong(b, u.i);
}
void WriteString2(netmsg_t *b, const char *str)
{ //no null terminator, convienience function.
while(*str)
WriteByte(b, *str++);
}
void WriteString(netmsg_t *b, const char *str)
{
while(*str)
WriteByte(b, *str++);
WriteByte(b, 0);
}
void WriteData(netmsg_t *b, const char *data, int length)
{
int i;
unsigned char *buf;
if (b->cursize + length > b->maxsize) //urm, that's just too big. :(
return;
buf = b->data+b->cursize;
for (i = 0; i < length; i++)
*buf++ = *data++;
b->cursize+=length;
}
#define DF_ORIGIN 1
#define DF_ANGLES (1<<3)
#define DF_EFFECTS (1<<6)
#define DF_SKINNUM (1<<7)
#define DF_DEAD (1<<8)
#define DF_GIB (1<<9)
#define DF_WEAPONFRAME (1<<10)
#define DF_MODEL (1<<11)
void SendBufferToViewer(viewer_t *v, const char *buffer, int length, qboolean reliable)
{
if (reliable)
{
//try and put it in the normal reliable
if (!v->backbuffered && v->netchan.message.cursize+length < v->netchan.message.maxsize)
WriteData(&v->netchan.message, buffer, length);
else if (v->backbuffered>0 && v->backbuf[v->backbuffered-1].cursize+length < v->backbuf[v->backbuffered-1].maxsize) //try and put it in the current backbuffer
WriteData(&v->backbuf[v->backbuffered-1], buffer, length);
else if (v->backbuffered == MAX_BACK_BUFFERS)
v->drop = true; //we would need too many backbuffers.
else
{
//create a new backbuffer
if (!v->backbuf[v->backbuffered].data)
{
InitNetMsg(&v->backbuf[v->backbuffered], (unsigned char *)malloc(MAX_BACKBUF_SIZE), MAX_BACKBUF_SIZE);
}
v->backbuf[v->backbuffered].cursize = 0; //make sure it's empty
WriteData(&v->backbuf[v->backbuffered], buffer, length);
v->backbuffered++;
}
}
}
void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playermask)
{
/*
#define dem_cmd 0 //shouldn't be present
#define dem_read 1 //intended for the proxy, equivelent to dem_all :\
#define dem_set 2 //keeps the playerinfo packets in sync, present once, at start, with specific parameters. Ignored.
#define dem_multiple 3 //send to multiple specific players if tracking - basically team_prints.
#define dem_single 4 //send to a single player, sprint, centerprint, etc
#define dem_stats 5 //overkill... same as single
#define dem_all 6 //broadcast to all
*/
viewer_t *v;
switch(to)
{
case dem_multiple:
case dem_single:
case dem_stats:
//check and send to them only if they're tracking this player(s).
for (v = tv->viewers; v; v = v->next)
{
if (v->trackplayer>=0)
if ((1<<v->trackplayer)&playermask)
SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type
}
break;
default:
//send to all
for (v = tv->viewers; v; v = v->next)
{
SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type
}
break;
}
}
static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playermask)
{
int protocol;
viewer_t *v;
protocol = ReadLong(m);
if (protocol != PROTOCOL_VERSION)
{
ParseError(m);
return;
}
tv->parsingconnectiondata = true;
ReadLong(m); //we don't care about server's servercount, it's all reliable data anyway.
ReadString(m, tv->gamedir, sizeof(tv->gamedir));
/*tv->servertime =*/ ReadFloat(m);
ReadString(m, tv->mapname, sizeof(tv->mapname));
printf("Gamedir: %s\n", tv->gamedir);
printf("---------------------\n");
printf("%s\n", tv->mapname);
printf("---------------------\n");
// get the movevars
tv->movevars.gravity = ReadFloat(m);
tv->movevars.stopspeed = ReadFloat(m);
tv->movevars.maxspeed = ReadFloat(m);
tv->movevars.spectatormaxspeed = ReadFloat(m);
tv->movevars.accelerate = ReadFloat(m);
tv->movevars.airaccelerate = ReadFloat(m);
tv->movevars.wateraccelerate = ReadFloat(m);
tv->movevars.friction = ReadFloat(m);
tv->movevars.waterfriction = ReadFloat(m);
tv->movevars.entgrav = ReadFloat(m);
for (v = tv->viewers; v; v = v->next)
v->thinksitsconnected = false;
tv->maxents = 0; //clear these
memset(tv->modellist, 0, sizeof(tv->modellist));
memset(tv->soundlist, 0, sizeof(tv->soundlist));
memset(tv->lightstyle, 0, sizeof(tv->lightstyle));
tv->staticsound_count = 0;
}
static void ParseCDTrack(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
tv->cdtrack = ReadByte(m);
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseStufftext(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
viewer_t *v;
char text[1024];
ReadString(m, text, sizeof(text));
if (!strcmp(text, "skins\n"))
{
const char newcmd[10] = {svc_stufftext, 'c', 'm', 'd', ' ', 'n','e','w','\n','\0'};
tv->servercount++;
tv->parsingconnectiondata = false;
for (v = tv->viewers; v; v = v->next)
{
SendBufferToViewer(v, newcmd, sizeof(newcmd), true);
}
return;
}
else if (!strncmp(text, "fullserverinfo ", 15))
{
strncpy(tv->serverinfo, text+15, sizeof(tv->serverinfo)-1);
return;
}
else if (!strncmp(text, "cmd ", 4))
return; //commands the game server asked for are pointless.
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseServerinfo(sv_t *tv, netmsg_t *m)
{
char key[64];
char value[64];
ReadString(m, key, sizeof(key));
ReadString(m, value, sizeof(value));
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1);
}
static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
char text[1024];
int level;
level = ReadByte(m);
ReadString(m, text, sizeof(text));
if (to == dem_all || to == dem_read)
{
if (level > 1)
printf("%s", text);
}
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseCenterprint(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
char text[1024];
ReadString(m, text, sizeof(text));
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseList(sv_t *tv, netmsg_t *m, filename_t *list, int to, unsigned int mask)
{
int first;
first = ReadByte(m)+1;
for (; first < MAX_LIST; first++)
{
ReadString(m, list[first].name, sizeof(list[first].name));
printf("read %i: %s\n", first, list[first].name);
if (!*list[first].name)
break;
// printf("%i: %s\n", first, list[first].name);
}
ReadByte(m); //wasted, we don't echo
}
static void ParseBaseline(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int i;
unsigned int entnum;
entnum = ReadShort(m);
if (entnum >= MAX_ENTITIES)
{
ParseError(m);
return;
}
tv->baseline[entnum].modelindex = ReadByte(m);
tv->baseline[entnum].frame = ReadByte(m);
tv->baseline[entnum].colormap = ReadByte(m);
tv->baseline[entnum].skinnum = ReadByte(m);
for (i = 0; i < 3; i++)
{
tv->baseline[entnum].origin[i] = ReadShort(m);
tv->baseline[entnum].angles[i] = ReadByte(m);
}
}
static void ParseStaticSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
if (tv->staticsound_count == MAX_STATICSOUNDS)
{
tv->staticsound_count = 0; // don't be fatal.
printf("Too many static sounds, wrapping\n");
}
tv->staticsound[tv->staticsound_count].origin[0] = ReadShort(m);
tv->staticsound[tv->staticsound_count].origin[1] = ReadShort(m);
tv->staticsound[tv->staticsound_count].origin[2] = ReadShort(m);
tv->staticsound[tv->staticsound_count].soundindex = ReadByte(m);
tv->staticsound[tv->staticsound_count].volume = ReadByte(m);
tv->staticsound[tv->staticsound_count].attenuation = ReadByte(m);
tv->staticsound_count++;
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParsePlayerInfo(sv_t *tv, netmsg_t *m)
{
int flags;
int num;
int i;
num = ReadByte(m);
if (num >= MAX_CLIENTS)
{
num = 0; // don't be fatal.
printf("Too many players, wrapping\n");
}
tv->players[num].old = tv->players[num].current;
flags = ReadShort(m);
tv->players[num].gibbed = !!(flags & DF_GIB);
tv->players[num].dead = !!(flags & DF_DEAD);
tv->players[num].current.frame = ReadByte(m);
for (i = 0; i < 3; i++)
{
if (flags & (DF_ORIGIN << i))
tv->players[num].current.origin[i] = ReadShort (m);
}
for (i = 0; i < 3; i++)
{
if (flags & (DF_ANGLES << i))
{
tv->players[num].current.angles[i] = ReadShort(m);
}
}
if (flags & DF_MODEL)
tv->players[num].current.modelindex = ReadByte (m);
if (flags & DF_SKINNUM)
tv->players[num].current.skinnum = ReadByte (m);
if (flags & DF_EFFECTS)
tv->players[num].current.effects = ReadByte (m);
if (flags & DF_WEAPONFRAME)
tv->players[num].current.weaponframe = ReadByte (m);
tv->players[num].active = true;
}
static void FlushPacketEntities(sv_t *tv)
{
int i;
for (i = 0; i < MAX_ENTITIES; i++)
tv->curents[i].modelindex = 0;
}
static void ParsePacketEntities(sv_t *tv, netmsg_t *m)
{
int entnum;
int flags;
viewer_t *v;
if (tv->chokeonnotupdated)
for (v = tv->viewers; v; v = v->next)
{
v->chokeme = false;
}
for (entnum = 0; entnum < MAX_CLIENTS; entnum++)
{ //hide players
//they'll be sent after this packet.
tv->players[entnum].active = false;
}
//luckilly, only updated entities are here, so that keeps cpu time down a bit.
for (;;)
{
flags = ReadShort(m);
if (!flags)
break;
entnum = flags & 511;
if (tv->maxents < entnum)
tv->maxents = entnum;
flags &= ~511;
memcpy(&tv->oldents[entnum], &tv->curents[entnum], sizeof(entity_state_t)); //ow.
if (flags & U_REMOVE)
{
tv->curents[entnum].modelindex = 0;
continue;
}
if (!tv->curents[entnum].modelindex) //lerp from baseline
memcpy(&tv->curents[entnum], &tv->baseline[entnum], sizeof(entity_state_t));
if (flags & U_MOREBITS)
flags |= ReadByte(m);
if (flags & U_MODEL)
tv->curents[entnum].modelindex = ReadByte(m);
if (flags & U_FRAME)
tv->curents[entnum].frame = ReadByte(m);
if (flags & U_COLORMAP)
tv->curents[entnum].colormap = ReadByte(m);
if (flags & U_SKIN)
tv->curents[entnum].skinnum = ReadByte(m);
if (flags & U_EFFECTS)
tv->curents[entnum].effects = ReadByte(m);
if (flags & U_ORIGIN1)
tv->curents[entnum].origin[0] = ReadShort(m);
if (flags & U_ANGLE1)
tv->curents[entnum].angles[0] = ReadByte(m);
if (flags & U_ORIGIN2)
tv->curents[entnum].origin[1] = ReadShort(m);
if (flags & U_ANGLE2)
tv->curents[entnum].angles[1] = ReadByte(m);
if (flags & U_ORIGIN3)
tv->curents[entnum].origin[2] = ReadShort(m);
if (flags & U_ANGLE3)
tv->curents[entnum].angles[2] = ReadByte(m);
tv->entupdatetime[entnum] = tv->curtime;
if (!tv->oldents[entnum].modelindex) //no old state
memcpy(&tv->oldents[entnum], &tv->curents[entnum], sizeof(entity_state_t)); //copy the new to the old, so we don't end up with interpolation glitches
}
}
static void ParseUpdatePing(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int pnum;
int ping;
pnum = ReadByte(m);
ping = ReadShort(m);
tv->players[pnum].ping = ping;
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseUpdateFrags(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int pnum;
int frags;
pnum = ReadByte(m);
frags = ReadShort(m);
tv->players[pnum].frags = frags;
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseUpdateStat(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
int statnum;
statnum = ReadByte(m);
value = ReadByte(m);
for (pnum = 0; pnum < 32; pnum++)
{
if (mask & (1<<pnum))
tv->players[pnum].stats[statnum] = value;
}
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseUpdateStatLong(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
int statnum;
statnum = ReadByte(m);
value = ReadLong(m);
for (pnum = 0; pnum < 32; pnum++)
{
if (mask & (1<<pnum))
tv->players[pnum].stats[statnum] = value;
}
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseUpdateUserinfo(sv_t *tv, netmsg_t *m)
{
int pnum;
pnum = ReadByte(m);
pnum%=MAX_CLIENTS;
ReadLong(m);
ReadString(m, tv->players[pnum].userinfo, sizeof(tv->players[pnum].userinfo));
}
static void ParsePacketloss(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
int value;
pnum = ReadByte(m)%MAX_CLIENTS;
value = ReadByte(m);
tv->players[pnum].ping = value;
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseUpdateEnterTime(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
unsigned int pnum;
float value;
pnum = ReadByte(m)%MAX_CLIENTS;
value = ReadFloat(m);
tv->players[pnum].entertime = value;
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
#define SND_VOLUME (1<<15) // a qbyte
#define SND_ATTENUATION (1<<14) // a qbyte
#define DEFAULT_SOUND_PACKET_VOLUME 255
#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0
int i;
unsigned short chan;
unsigned char vol;
unsigned char atten;
unsigned char sound_num;
short org[3];
chan = ReadShort(m);
if (chan & SND_VOLUME)
vol = ReadByte (m);
else
vol = DEFAULT_SOUND_PACKET_VOLUME;
if (chan & SND_ATTENUATION)
atten = ReadByte (m) / 64.0;
else
atten = DEFAULT_SOUND_PACKET_ATTENUATION;
sound_num = ReadByte (m);
for (i=0 ; i<3 ; i++)
org[i] = ReadShort (m);
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
static void ParseDamage(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
ReadByte (m);
ReadByte (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
enum {
TE_SPIKE = 0,
TE_SUPERSPIKE = 1,
TE_GUNSHOT = 2,
TE_EXPLOSION = 3,
TE_TAREXPLOSION = 4,
TE_LIGHTNING1 = 5,
TE_LIGHTNING2 = 6,
TE_WIZSPIKE = 7,
TE_KNIGHTSPIKE = 8,
TE_LIGHTNING3 = 9,
TE_LAVASPLASH = 10,
TE_TELEPORT = 11,
TE_BLOOD = 12,
TE_LIGHTNINGBLOOD = 13,
};
static void ParseTempEntity(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
{
int i;
i = ReadByte (m);
switch(i)
{
case TE_SPIKE:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_SUPERSPIKE:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_GUNSHOT:
ReadByte (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_EXPLOSION:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_TAREXPLOSION:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_LIGHTNING1:
case TE_LIGHTNING2:
case TE_LIGHTNING3:
ReadShort (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_WIZSPIKE:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_KNIGHTSPIKE:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_LAVASPLASH:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_TELEPORT:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_BLOOD:
ReadByte (m);
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
case TE_LIGHTNINGBLOOD:
ReadShort (m);
ReadShort (m);
ReadShort (m);
break;
default:
printf("temp entity %i not recognised\n", i);
return;
}
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask);
}
void ParseLightstyle(sv_t *tv, netmsg_t *m)
{
int style;
style = ReadByte(m);
if (style > MAX_LIGHTSTYLES)
style=0;
ReadString(m, tv->lightstyle[style].name, sizeof(tv->lightstyle[style].name));
Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_read, (unsigned)-1);
}
void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask)
{
netmsg_t buf;
buf.cursize = length;
buf.maxsize = length;
buf.readpos = 0;
buf.data = buffer;
buf.startpos = 0;
while(buf.readpos < buf.cursize)
{
if (buf.readpos > buf.cursize)
{
printf("Read past end of parse buffer\n");
return;
}
// printf("%i\n", buf.buffer[0]);
buf.startpos = buf.readpos;
switch (ReadByte(&buf))
{
case svc_bad:
ParseError(&buf);
printf("ParseMessage: svc_bad\n");
return;
case svc_nop: //quakeworld isn't meant to send these.
printf("nop\n");
break;
//#define svc_disconnect 2
case svc_updatestat:
ParseUpdateStat(tv, &buf, to, mask);
break;
//#define svc_version 4 // [long] server version
//#define svc_setview 5 // [short] entity number
case svc_sound:
ParseSound(tv, &buf, to, mask);
break;
//#define svc_time 7 // [float] server time
case svc_print:
ParsePrint(tv, &buf, to, mask);
break;
case svc_stufftext:
ParseStufftext(tv, &buf, to, mask);
break;
case svc_setangle:
ReadByte(&buf);
ReadByte(&buf);
ReadByte(&buf);
ReadByte(&buf);
break;
case svc_serverdata:
ParseServerData(tv, &buf, to, mask);
break;
case svc_lightstyle:
ParseLightstyle(tv, &buf);
break;
//#define svc_updatename 13 // [qbyte] [string]
case svc_updatefrags:
ParseUpdateFrags(tv, &buf, to, mask);
break;
//#define svc_clientdata 15 // <shortbits + data>
//#define svc_stopsound 16 // <see code>
//#define svc_updatecolors 17 // [qbyte] [qbyte] [qbyte]
//#define svc_particle 18 // [vec3] <variable>
case svc_damage:
ParseDamage(tv, &buf, to, mask);
break;
//#define svc_spawnstatic 20
//#define svc_spawnstatic2 21
case svc_spawnbaseline:
ParseBaseline(tv, &buf, to, mask);
break;
case svc_temp_entity:
ParseTempEntity(tv, &buf, to, mask);
break;
//#define svc_setpause 24 // [qbyte] on / off
//#define svc_signonnum 25 // [qbyte] used for the signon sequence
case svc_centerprint:
ParseCenterprint(tv, &buf, to, mask);
break;
//#define svc_killedmonster 27
//#define svc_foundsecret 28
case svc_spawnstaticsound:
ParseStaticSound(tv, &buf, to, mask);
break;
//#define svc_intermission 30 // [vec3_t] origin [vec3_t] angle
//#define svc_finale 31 // [string] text
case svc_cdtrack:
ParseCDTrack(tv, &buf, to, mask);
break;
//#define svc_sellscreen 33
//#define svc_cutscene 34 //hmm... nq only... added after qw tree splitt?
case svc_smallkick:
case svc_bigkick:
Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask);
break;
case svc_updateping:
ParseUpdatePing(tv, &buf, to, mask);
break;
case svc_updateentertime:
ParseUpdateEnterTime(tv, &buf, to, mask);
break;
case svc_updatestatlong:
ParseUpdateStatLong(tv, &buf, to, mask);
break;
case svc_muzzleflash:
ReadShort(&buf);
Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask);
break;
case svc_updateuserinfo:
ParseUpdateUserinfo(tv, &buf);
break;
//#define svc_download 41 // [short] size [size bytes]
case svc_playerinfo:
ParsePlayerInfo(tv, &buf);
break;
//#define svc_nails 43 // [qbyte] num [48 bits] xyzpy 12 12 12 4 8
case svc_chokecount:
ReadByte(&buf);
break;
case svc_modellist:
ParseList(tv, &buf, tv->modellist, to, mask);
break;
case svc_soundlist:
ParseList(tv, &buf, tv->soundlist, to, mask);
break;
case svc_packetentities:
FlushPacketEntities(tv);
ParsePacketEntities(tv, &buf);
break;
case svc_deltapacketentities:
ReadByte(&buf);
ParsePacketEntities(tv, &buf);
break;
//#define svc_maxspeed 49 // maxspeed change, for prediction
//#define svc_entgravity 50 // gravity change, for prediction
//#define svc_setinfo 51 // setinfo on a client
case svc_serverinfo:
ParseServerinfo(tv, &buf);
break;
case svc_updatepl:
ParsePacketloss(tv, &buf, to, mask);
break;
//#define svc_nails2 54 //qwe - [qbyte] num [52 bits] nxyzpy 8 12 12 12 4 8
default:
buf.readpos = buf.startpos;
printf("Can't handle svc %i\n", (unsigned int)ReadByte(&buf));
return;
}
}
}

448
fteqtv/qtv.h Normal file
View File

@ -0,0 +1,448 @@
//each server that we are connected to has it's own state.
//it should be easy enough to use one thread per server.
//mvd info is forwarded to other proxies instantly
//qwd stuff is buffered and delayed. :(
//this means that when a new proxy connects, we have to send initial state as well as a chunk of pending state, expect to need to send new data before the proxy even has all the init stuff. We may need to raise MAX_PROXY_BUFFER to be larger than on the server
#ifdef _WIN32
#include <winsock.h>
#pragma comment (lib, "wsock32.lib")
#define qerrno WSAGetLastError()
#define EWOULDBLOCK WSAEWOULDBLOCK
#ifndef _DEBUG
#define static //it breaks my symbol lookups. :(
#endif
#elif defined(__CYGWIN__)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <netdb.h>
#include <stdlib.h>
#ifndef SOCKET
#define SOCKET int
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
#define qerrno errno
#define ioctlsocket ioctl
#define closesocket close
#else
#error "Please insert required headers here"
//try the cygwin ones
#endif
#include <stdio.h>
#define VERSION "0.01" //this will be added to the serverinfo
#define PROX_DEFAULTLISTENPORT 27501
#define PROX_DEFAULTSERVER "localhost:27500"
#define MAX_SERVERINFO_STRING 1024 //standard quake has 512 here.
#define MAX_USERINFO 192
#define MAX_CLIENTS 32
#define MAX_STATS 32
#define MAX_LIST 256
#define MAX_MODELS MAX_LIST
#define MAX_SOUNDS MAX_LIST
#define MAX_ENTITIES 512
#define MAX_STATICSOUNDS 64
#define MAX_LIGHTSTYLES 64
#define MAX_PROXY_BUFFER (1<<14) //must be power-of-two
#define PREFERED_PROXY_BUFFER 1500 //the ammount of data we try to leave in our input buffer (must be large enough to contain any single mvd frame)
#define ENTS_PER_FRAME 512 //max number of entities per frame.
#define ENTITY_FRAMES 64 //number of frames to remember for deltaing
typedef enum {false, true} qboolean;
typedef unsigned char netadr_t[64];
#define MAX_MSGLEN 1400
#define MAX_BACKBUF_SIZE 1000 //this is smaller so we don't loose entities when lagging
typedef struct {
unsigned int readpos;
unsigned int cursize;
unsigned int maxsize;
char *data;
unsigned int startpos;
qboolean overflowed;
qboolean allowoverflow;
} netmsg_t;
typedef struct {
SOCKET sock;
netadr_t remote_address;
unsigned short qport;
unsigned int last_received;
unsigned int cleartime;
int reliable_length;
qboolean drop;
netmsg_t message;
char message_buf[MAX_MSGLEN]; //reliable message being built
char reliable_buf[MAX_MSGLEN]; //reliable message that we're making sure arrives.
float rate;
unsigned int incoming_acknowledged;
unsigned int last_reliable_sequence;
unsigned int incoming_reliable_acknowledged;
unsigned int incoming_reliable_sequence;
unsigned int reliable_sequence;
unsigned int incoming_sequence;
unsigned int outgoing_sequence;
} netchan_t;
typedef struct {
#define MAX_QPATH 64
char name[MAX_QPATH];
} filename_t;
typedef struct {
unsigned char frame;
unsigned char modelindex;
unsigned char colormap;
unsigned char skinnum;
short origin[3];
unsigned char angles[3];
unsigned char effects;
} entity_state_t;
typedef struct {
unsigned char frame;
unsigned char modelindex;
unsigned char skinnum;
short origin[3];
unsigned short angles[3];
unsigned char effects;
unsigned char weaponframe;
} player_state_t;
typedef struct {
unsigned int stats[MAX_STATS];
char userinfo[MAX_USERINFO];
int ping;
int frags;
float entertime;
qboolean active:1;
qboolean gibbed:1;
qboolean dead:1;
player_state_t current;
player_state_t old;
} playerinfo_t;
typedef struct {
entity_state_t ents[ENTS_PER_FRAME]; //ouchie ouchie!
unsigned short entnum[ENTS_PER_FRAME];
int numents;
} packet_entities_t;
#define MAX_BACK_BUFFERS 16
typedef struct viewer_s {
qboolean drop;
netchan_t netchan;
qboolean maysend;
qboolean chokeme;
qboolean thinksitsconnected;
int delta_frame;
netmsg_t backbuf[MAX_BACK_BUFFERS]; //note data is malloced!
int backbuffered;
unsigned int currentstats[MAX_STATS];
unsigned int trackplayer;
packet_entities_t frame[ENTITY_FRAMES];
struct viewer_s *next;
float origin[3];
} viewer_t;
typedef struct oproxy_s {
qboolean flushing;
qboolean drop;
SOCKET sock;
unsigned char buffer[MAX_PROXY_BUFFER];
unsigned int buffersize; //use cyclic buffering.
unsigned int bufferpos;
struct oproxy_s *next;
} oproxy_t;
typedef struct {
short origin[3];
unsigned char soundindex;
unsigned char volume;
unsigned char attenuation;
} staticsound_t;
typedef struct sv_s {
netadr_t serveraddress;
unsigned char buffer[MAX_PROXY_BUFFER]; //this doesn't cycle.
int buffersize; //it memmoves down
unsigned int parsetime;
int servercount;
char gamedir[MAX_QPATH];
char mapname[256];
struct {
float gravity;
float maxspeed;
float spectatormaxspeed;
float accelerate;
float airaccelerate;
float waterfriction;
float entgrav;
float stopspeed;
float wateraccelerate;
float friction;
} movevars;
int cdtrack;
entity_state_t baseline[MAX_ENTITIES];
entity_state_t curents[MAX_ENTITIES];
entity_state_t oldents[MAX_ENTITIES];
unsigned int entupdatetime[MAX_ENTITIES]; //to stop lerping when it's an old entity (bodies, stationary grenades, ...)
int maxents;
staticsound_t staticsound[MAX_STATICSOUNDS];
int staticsound_count;
filename_t lightstyle[MAX_LIGHTSTYLES];
char serverinfo[MAX_SERVERINFO_STRING];
playerinfo_t players[MAX_CLIENTS];
filename_t modellist[MAX_MODELS];
filename_t soundlist[MAX_SOUNDS];
int modelindex_spike; // qw is wierd.
FILE *file;
unsigned int filelength;
SOCKET sourcesock;
SOCKET listenmvd; //tcp + mvd protocol
SOCKET qwdsocket; //udp + quakeworld protocols
viewer_t *viewers;
oproxy_t *proxies;
qboolean parsingconnectiondata; //so reject any new connects for now
unsigned int curtime;
unsigned int oldpackettime;
unsigned int nextpackettime;
int tcplistenportnum;
int qwlistenportnum;
char server[MAX_QPATH];
//options:
qboolean chokeonnotupdated;
qboolean lateforward;
} sv_t;
typedef struct {
sv_t server;
} qtv_t;
unsigned char ReadByte(netmsg_t *b);
unsigned short ReadShort(netmsg_t *b);
unsigned int ReadLong(netmsg_t *b);
float ReadFloat(netmsg_t *b);
void ReadString(netmsg_t *b, char *string, int maxlen);
#define clc_bad 0
#define clc_nop 1
//define clc_doublemove 2
#define clc_move 3 // [[usercmd_t]
#define clc_stringcmd 4 // [string] message
#define clc_delta 5 // [byte] sequence number, requests delta compression of message
#define clc_tmove 6 // teleport request, spectator only
#define clc_upload 7 // teleport request, spectator only
#define svc_bad 0
#define svc_nop 1
//#define svc_disconnect 2
#define svc_updatestat 3 // [qbyte] [qbyte]
//#define svc_version 4 // [long] server version
//#define svc_setview 5 // [short] entity number
#define svc_sound 6 // <see code>
//#define svc_time 7 // [float] server time
#define svc_print 8 // [qbyte] id [string] null terminated string
#define svc_stufftext 9 // [string] stuffed into client's console buffer
// the string should be \n terminated
#define svc_setangle 10 // [angle3] set the view angle to this absolute value
#define svc_serverdata 11 // [long] protocol ...
#define svc_lightstyle 12 // [qbyte] [string]
//#define svc_updatename 13 // [qbyte] [string]
#define svc_updatefrags 14 // [qbyte] [short]
//#define svc_clientdata 15 // <shortbits + data>
//#define svc_stopsound 16 // <see code>
//#define svc_updatecolors 17 // [qbyte] [qbyte] [qbyte]
//#define svc_particle 18 // [vec3] <variable>
#define svc_damage 19
//#define svc_spawnstatic 20
//#define svc_spawnstatic2 21
#define svc_spawnbaseline 22
#define svc_temp_entity 23 // variable
//#define svc_setpause 24 // [qbyte] on / off
//#define svc_signonnum 25 // [qbyte] used for the signon sequence
#define svc_centerprint 26 // [string] to put in center of the screen
//#define svc_killedmonster 27
//#define svc_foundsecret 28
#define svc_spawnstaticsound 29 // [coord3] [qbyte] samp [qbyte] vol [qbyte] aten
//#define svc_intermission 30 // [vec3_t] origin [vec3_t] angle
//#define svc_finale 31 // [string] text
#define svc_cdtrack 32 // [qbyte] track
//#define svc_sellscreen 33
//#define svc_cutscene 34 //hmm... nq only... added after qw tree splitt?
#define svc_smallkick 34 // set client punchangle to 2
#define svc_bigkick 35 // set client punchangle to 4
#define svc_updateping 36 // [qbyte] [short]
#define svc_updateentertime 37 // [qbyte] [float]
#define svc_updatestatlong 38 // [qbyte] [long]
#define svc_muzzleflash 39 // [short] entity
#define svc_updateuserinfo 40 // [qbyte] slot [long] uid
// [string] userinfo
#define svc_download 41 // [short] size [size bytes]
#define svc_playerinfo 42 // variable
//#define svc_nails 43 // [qbyte] num [48 bits] xyzpy 12 12 12 4 8
#define svc_chokecount 44 // [qbyte] packets choked
#define svc_modellist 45 // [strings]
#define svc_soundlist 46 // [strings]
#define svc_packetentities 47 // [...]
#define svc_deltapacketentities 48 // [...]
//#define svc_maxspeed 49 // maxspeed change, for prediction
//#define svc_entgravity 50 // gravity change, for prediction
//#define svc_setinfo 51 // setinfo on a client
#define svc_serverinfo 52 // serverinfo
#define svc_updatepl 53 // [qbyte] [qbyte]
//#define svc_nails2 54 //qwe - [qbyte] num [52 bits] nxyzpy 8 12 12 12 4 8
#define dem_cmd 0
#define dem_read 1
#define dem_set 2
#define dem_multiple 3
#define dem_single 4
#define dem_stats 5
#define dem_all 6
#define dem_mask 7
#define PROTOCOL_VERSION 28
//flags on entities
#define U_ORIGIN1 (1<<9)
#define U_ORIGIN2 (1<<10)
#define U_ORIGIN3 (1<<11)
#define U_ANGLE2 (1<<12)
#define U_FRAME (1<<13)
#define U_REMOVE (1<<14) // REMOVE this entity, don't add it
#define U_MOREBITS (1<<15)
// if MOREBITS is set, these additional flags are read in next
#define U_ANGLE1 (1<<0)
#define U_ANGLE3 (1<<1)
#define U_MODEL (1<<2)
#define U_COLORMAP (1<<3)
#define U_SKIN (1<<4)
#define U_EFFECTS (1<<5)
#define U_SOLID (1<<6) // the entity should be solid for prediction
//flags on players
#define PF_MSEC (1<<0)
#define PF_COMMAND (1<<1)
#define PF_VELOCITY1 (1<<2)
#define PF_VELOCITY2 (1<<3)
#define PF_VELOCITY3 (1<<4)
#define PF_MODEL (1<<5)
#define PF_SKINNUM (1<<6)
#define PF_EFFECTS (1<<7)
#define PF_WEAPONFRAME (1<<8) // only sent for view player
#define PF_DEAD (1<<9) // don't block movement any more
#define PF_GIB (1<<10) // offset the view height differently
void InitNetMsg(netmsg_t *b, char *buffer, int bufferlength);
unsigned char ReadByte(netmsg_t *b);
unsigned short ReadShort(netmsg_t *b);
unsigned int ReadLong(netmsg_t *b);
float ReadFloat(netmsg_t *b);
void ReadString(netmsg_t *b, char *string, int maxlen);
void WriteByte(netmsg_t *b, unsigned char c);
void WriteShort(netmsg_t *b, unsigned short l);
void WriteLong(netmsg_t *b, unsigned int l);
void WriteFloat(netmsg_t *b, float f);
void WriteString2(netmsg_t *b, const char *str);
void WriteString(netmsg_t *b, const char *str);
void WriteData(netmsg_t *b, const char *data, int length);
void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask);
void BuildServerData(sv_t *tv, netmsg_t *msg, qboolean mvd);
SOCKET QW_InitUDPSocket(int port);
void QW_UpdateUDPStuff(sv_t *qtv);

1001
fteqtv/qw.c Normal file

File diff suppressed because it is too large Load Diff

837
fteqtv/source.c Normal file
View File

@ -0,0 +1,837 @@
#include "qtv.h"
qboolean NET_StringToAddr (char *s, netadr_t *sadr)
{
struct hostent *h;
char *colon;
char copy[128];
memset (sadr, 0, sizeof(netadr_t));
#ifdef USEIPX
if ((strlen(s) >= 23) && (s[8] == ':') && (s[21] == ':')) // check for an IPX address
{
unsigned int val;
((struct sockaddr_ipx *)sadr)->sa_family = AF_IPX;
copy[2] = 0;
DO(0, sa_netnum[0]);
DO(2, sa_netnum[1]);
DO(4, sa_netnum[2]);
DO(6, sa_netnum[3]);
DO(9, sa_nodenum[0]);
DO(11, sa_nodenum[1]);
DO(13, sa_nodenum[2]);
DO(15, sa_nodenum[3]);
DO(17, sa_nodenum[4]);
DO(19, sa_nodenum[5]);
sscanf (&s[22], "%u", &val);
((struct sockaddr_ipx *)sadr)->sa_socket = htons((unsigned short)val);
}
else
#endif
#ifdef IPPROTO_IPV6
if (pgetaddrinfo)
{
struct addrinfo *addrinfo, *pos;
struct addrinfo udp6hint;
int error;
char *port;
char dupbase[256];
int len;
memset(&udp6hint, 0, sizeof(udp6hint));
udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4
udp6hint.ai_socktype = SOCK_DGRAM;
udp6hint.ai_protocol = IPPROTO_UDP;
port = s + strlen(s);
while(port >= s)
{
if (*port == ':')
break;
port--;
}
if (port == s)
port = NULL;
if (port)
{
len = port - s;
if (len >= sizeof(dupbase))
len = sizeof(dupbase)-1;
strncpy(dupbase, s, len);
dupbase[len] = '\0';
error = pgetaddrinfo(dupbase, port+1, &udp6hint, &addrinfo);
}
else
error = EAI_NONAME;
if (error) //failed, try string with no port.
error = pgetaddrinfo(s, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6)
if (error)
{
return false;
}
((struct sockaddr*)sadr)->sa_family = 0;
for (pos = addrinfo; pos; pos = pos->ai_next)
{
switch(pos->ai_family)
{
case AF_INET6:
if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6)
break; //first one should be best...
//fallthrough
case AF_INET:
memcpy(sadr, addrinfo->ai_addr, addrinfo->ai_addrlen);
if (pos->ai_family == AF_INET)
goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4...
break;
}
}
dblbreak:
pfreeaddrinfo (addrinfo);
if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound
return false;
}
else
#endif
{
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
((struct sockaddr_in *)sadr)->sin_port = 0;
strcpy (copy, s);
// strip off a trailing :port if present
for (colon = copy ; *colon ; colon++)
if (*colon == ':')
{
*colon = 0;
((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1));
}
if (copy[0] >= '0' && copy[0] <= '9') //this is the wrong way to test. a server name may start with a number.
{
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
}
else
{
if (! (h = gethostbyname(copy)) )
return 0;
if (h->h_addrtype != AF_INET)
return 0;
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
}
}
return true;
}
qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2)
{
struct sockaddr_in *i1=(void*)s1, *i2=(void*)s2;
if (i1->sin_family != i2->sin_family)
return false;
if (i1->sin_family == AF_INET)
{
if (*(unsigned int*)&i1->sin_addr != *(unsigned int*)&i2->sin_addr)
return false;
if (i1->sin_port != i2->sin_port && qp1 != qp2) //allow qports to match instead of ports, if required.
return false;
return true;
}
return false;
}
SOCKET Net_MVDListen(int port)
{
int sock;
struct sockaddr_in address;
// int fromlen;
unsigned long nonblocking = true;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons((u_short)port);
if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
return INVALID_SOCKET;
}
if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1)
{
closesocket(sock);
return INVALID_SOCKET;
}
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
closesocket(sock);
return INVALID_SOCKET;
}
listen(sock, 2); //don't listen for too many clients.
return sock;
}
qboolean Net_ConnectToServer(sv_t *qtv, char *ip)
{
netadr_t from;
unsigned long nonblocking = true;
if (!strncmp(ip, "file:", 5))
{
qtv->sourcesock = INVALID_SOCKET;
qtv->file = fopen(ip+5, "rb");
if (qtv->file)
{
fseek(qtv->file, 0, SEEK_END);
qtv->filelength = ftell(qtv->file);
fseek(qtv->file, 0, SEEK_SET);
return true;
}
printf("Unable to open file %s\n", ip+5);
return false;
}
if (!NET_StringToAddr(ip, &qtv->serveraddress))
{
printf("Unable to resolve %s\n", ip);
return false;
}
qtv->sourcesock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (qtv->sourcesock == INVALID_SOCKET)
return false;
memset(&from, 0, sizeof(from));
((struct sockaddr*)&from)->sa_family = ((struct sockaddr*)&qtv->serveraddress)->sa_family;
if (bind(qtv->sourcesock, (struct sockaddr *)&from, sizeof(from)) == -1)
{
closesocket(qtv->sourcesock);
return false;
}
if (connect(qtv->sourcesock, (struct sockaddr *)&qtv->serveraddress, sizeof(qtv->serveraddress)) == INVALID_SOCKET)
{
closesocket(qtv->sourcesock);
return false;
}
if (ioctlsocket (qtv->sourcesock, FIONBIO, &nonblocking) == -1)
{
closesocket(qtv->sourcesock);
return false;
}
return true;
}
void Net_FindProxies(sv_t *qtv)
{
oproxy_t *prox;
int sock;
sock = accept(qtv->listenmvd, NULL, NULL);
if (sock == INVALID_SOCKET)
return;
prox = malloc(sizeof(*prox));
memset(prox, 0, sizeof(*prox));
prox->flushing = true; //allow the buffer overflow resumption code to send the connection info.
prox->sock = sock;
prox->next = qtv->proxies;
qtv->proxies = prox;
}
#undef IN
#define IN(x) buffer[(x)&(MAX_PROXY_BUFFER-1)]
void CheckMVDConsistancy(unsigned char *buffer, int pos, int size)
{
/*
int length;
int msec, type;
while(pos < size)
{
msec = IN(pos++);
type = IN(pos++);
if (type == dem_set)
{
pos+=8;
continue;
}
if (type == dem_multiple)
pos+=4;
length = (IN(pos+0)<<0) + (IN(pos+1)<<8) + (IN(pos+2)<<16) + (IN(pos+3)<<24);
pos+=4;
if (length > 1450)
printf("too big (%i)\n", length);
pos += length;
}
if (pos != size)
printf("pos != size\n");
*/
}
void Net_TryFlushProxyBuffer(oproxy_t *prox)
{
char *buffer;
int length;
int bufpos;
while (prox->bufferpos >= MAX_PROXY_BUFFER)
{ //so we never get any issues with wrapping..
prox->bufferpos -= MAX_PROXY_BUFFER;
prox->buffersize -= MAX_PROXY_BUFFER;
}
bufpos = prox->bufferpos&(MAX_PROXY_BUFFER-1);
length = prox->buffersize - prox->bufferpos;
if (length > MAX_PROXY_BUFFER-bufpos) //cap the length correctly.
length = MAX_PROXY_BUFFER-bufpos;
if (!length)
return; //already flushed.
buffer = prox->buffer + bufpos;
// CheckMVDConsistancy(prox->buffer, prox->bufferpos, prox->buffersize);
if (bufpos+length > MAX_PROXY_BUFFER)
printf("oversize flush\n");
length = send(prox->sock, buffer, length, 0);
switch (length)
{
case 0: //eof / they disconnected
prox->drop = true;
break;
case -1:
if (qerrno != EWOULDBLOCK) //not a problem, so long as we can flush it later.
prox->drop = true; //drop them if we get any errors
break;
default:
prox->bufferpos += length;
}
}
void Net_ProxySend(oproxy_t *prox, char *buffer, int length)
{
int wrap;
if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER)
{
Net_TryFlushProxyBuffer(prox); //try flushing
if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER) //damn, still too big.
{ //they're too slow. hopefully it was just momentary lag
prox->flushing = true;
return;
}
}
#if 1
//just simple
prox->buffersize+=length;
for (wrap = prox->buffersize-length; wrap < prox->buffersize; wrap++)
prox->buffer[wrap&(MAX_PROXY_BUFFER-1)] = *buffer++;
#else
//we don't do multiple wrappings, the above check cannot succeed if it were required.
//find the wrap point
wrap = prox->buffersize-(prox->buffersize&(MAX_PROXY_BUFFER-1)) + MAX_PROXY_BUFFER;
wrap = wrap - (prox->buffersize&(MAX_PROXY_BUFFER-1)); //the ammount of data we can fit before wrapping.
if (wrap > length)
{ //we don't wrap afterall
memcpy(prox->buffer+(prox->buffersize)&(MAX_PROXY_BUFFER-1), buffer, length);
prox->buffersize+=length;
return;
}
memcpy(prox->buffer+prox->buffersize&(MAX_PROXY_BUFFER-1), buffer, wrap);
buffer += wrap;
length -= wrap;
memcpy(prox->buffer, buffer, length);
prox->buffersize+=length;
#endif
}
void Prox_SendMessage(oproxy_t *prox, char *buf, int length, int dem_type, unsigned int playermask)
{
netmsg_t msg;
char tbuf[16];
InitNetMsg(&msg, tbuf, sizeof(tbuf));
WriteByte(&msg, 0);
WriteByte(&msg, dem_type);
WriteLong(&msg, length);
Net_ProxySend(prox, msg.data, msg.cursize);
Net_ProxySend(prox, buf, length);
}
void Net_SendConnectionMVD(sv_t *qtv, oproxy_t *prox)
{
char buffer[MAX_MSGLEN*8];
netmsg_t msg;
int prespawn;
InitNetMsg(&msg, buffer, sizeof(buffer));
prox->flushing = false;
BuildServerData(qtv, &msg, true);
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
for (prespawn = 0;prespawn >= 0;)
{
prespawn = SendList(qtv, prespawn, qtv->soundlist, svc_soundlist, &msg);
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
}
for (prespawn = 0;prespawn >= 0;)
{
prespawn = SendList(qtv, prespawn, qtv->modellist, svc_modellist, &msg);
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
}
Net_TryFlushProxyBuffer(prox); //that should be enough data to fill a packet.
for(prespawn = 0;prespawn>=0;)
{
prespawn = Prespawn(qtv, 0, &msg, prespawn);
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
}
//playerstates arn't actually delta-compressed, so the first send (simply forwarded from server) entirly replaces the old.
//we do need to send entity states.
Prox_SendInitialEnts(qtv, prox, &msg);
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
WriteByte(&msg, svc_stufftext);
WriteString(&msg, "skins\n");
Prox_SendMessage(prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
msg.cursize = 0;
Net_TryFlushProxyBuffer(prox);
if (!qtv->lateforward)
Net_ProxySend(prox, qtv->buffer, qtv->buffersize); //send all the info we've not yet processed.
if (prox->flushing)
{
printf("Connection data is too big, dropping proxy client\n");
prox->drop = true; //this is unfortunate...
}
else
Net_TryFlushProxyBuffer(prox);
}
void Net_ForwardStream(sv_t *qtv, char *buffer, int length)
{ //forward the stream on to connected clients
oproxy_t *prox, *next, *fre;
CheckMVDConsistancy(buffer, 0, length);
while (qtv->proxies && qtv->proxies->drop)
{
next = qtv->proxies->next;
fre = qtv->proxies;
closesocket(fre->sock);
free(fre);
qtv->proxies = next;
}
for (prox = qtv->proxies; prox; prox = prox->next)
{
while (prox->next && prox->next->drop)
{
next = prox->next->next;
fre = prox->next;
closesocket(fre->sock);
free(fre);
prox->next = next;
}
if (prox->flushing) //don't sent it if we're trying to empty thier buffer.
{
if (prox->buffersize == prox->bufferpos)
{
if (!qtv->parsingconnectiondata)
Net_SendConnectionMVD(qtv, prox); //they're up to date, resend the connection info.
}
else
{
Net_TryFlushProxyBuffer(prox); //try and flush it.
continue;
}
}
if (prox->drop)
continue;
//add the new data
Net_ProxySend(prox, buffer, length);
//and try to send it.
Net_TryFlushProxyBuffer(prox);
Net_TryFlushProxyBuffer(prox);
Net_TryFlushProxyBuffer(prox);
}
}
qboolean Net_ReadStream(sv_t *qtv)
{
int maxreadable;
int read;
char *buffer;
maxreadable = MAX_PROXY_BUFFER - qtv->buffersize;
if (!maxreadable)
return true; //this is bad!
buffer = qtv->buffer + qtv->buffersize;
if (qtv->file)
{
if (maxreadable > PREFERED_PROXY_BUFFER-qtv->buffersize)
maxreadable = PREFERED_PROXY_BUFFER-qtv->buffersize;
if (!maxreadable)
return true;
read = fread(buffer, 1, maxreadable, qtv->file);
}
else
{
read = recv(qtv->sourcesock, buffer, maxreadable, 0);
}
if (read > 0)
{
qtv->buffersize += read;
if (!qtv->lateforward)
Net_ForwardStream(qtv, buffer, read);
}
else
{
if (read == 0 || qerrno != EWOULDBLOCK)
{
if (qtv->sourcesock != INVALID_SOCKET)
{
closesocket(qtv->sourcesock);
qtv->sourcesock = INVALID_SOCKET;
}
if (qtv->file)
{
fclose(qtv->file);
qtv->file = NULL;
}
printf("Read error or eof\n");
return false;
}
}
return true;
}
#define BUFFERTIME 10 //secords for arificial delay, so we can buffer things properly.
unsigned int Sys_Milliseconds(void)
{
#ifdef _WIN32
#pragma comment(lib, "winmm.lib")
return timeGetTime();
#endif
}
void NetSleep(sv_t *tv)
{
struct timeval timeout;
fd_set socketset;
Sleep(0);
FD_ZERO(&socketset);
if (tv->sourcesock != INVALID_SOCKET)
FD_SET(tv->sourcesock, &socketset);
if (tv->qwdsocket != INVALID_SOCKET)
FD_SET(tv->qwdsocket, &socketset);
timeout.tv_sec = 100/1000;
timeout.tv_usec = (100%1000)*1000;
select(2, &socketset, NULL, NULL, &timeout);
}
void Trim(char *s)
{
char *f;
f = s;
while(*f <= ' ' && *f > '\0')
f++;
while(*f > ' ')
*s++ = *f++;
*s = '\0';
}
void QTV_Run(sv_t *qtv)
{
int lengthofs;
unsigned int length;
unsigned char *buffer;
int oldcurtime;
while(1)
{
if (qtv->sourcesock == INVALID_SOCKET && !qtv->file)
{
if (!Net_ConnectToServer(qtv, qtv->server))
{
printf("Couldn't connect\n");
continue;
}
printf("Connected\n");
if (qtv->sourcesock == INVALID_SOCKET)
{
qtv->parsetime = Sys_Milliseconds();
printf("Playing from file\n");
}
else
{
qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000;
printf("Buffering for %i seconds\n", BUFFERTIME);
}
}
NetSleep(qtv);
Net_FindProxies(qtv);
if (!Net_ReadStream(qtv))
continue;
//we will read out as many packets as we can until we're up to date
//note: this can cause real issues when we're overloaded for any length of time
//each new packet comes with a leading msec byte (msecs from last packet)
//then a type, an optional destination mask, and a 4byte size.
//the 4 byte size is probably excessive, a short would do.
//some of the types have thier destination mask encoded inside the type byte, yielding 8 types, and 32 max players.
//if we've no got enough data to read a new packet, we print a message and wait an extra two seconds. this will add a pause, connected clients will get the same pause, and we'll just try to buffer more of the game before playing.
//we'll stay 2 secs behind when the tcp stream catches up, however. This could be bad especially with long up-time.
//All timings are in msecs, which is in keeping with the mvd times, but means we might have issues after 72 or so days.
//the following if statement will reset the parse timer. It might cause the game to play too soon, the buffersize checks in the rest of the function will hopefully put it back to something sensible.
oldcurtime = qtv->curtime;
qtv->curtime = Sys_Milliseconds();
if (oldcurtime > qtv->curtime)
{
printf("Time wrapped\n");
qtv->parsetime = qtv->curtime;
}
while (qtv->curtime >= qtv->parsetime)
{
if (qtv->buffersize < 2)
{ //not enough stuff to play.
if (qtv->parsetime < qtv->curtime)
{
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
printf("Not enough buffered\n");
}
break;
}
buffer = qtv->buffer;
switch (qtv->buffer[1]&dem_mask)
{
case dem_set:
if (qtv->buffersize < 10)
{ //not enough stuff to play.
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
printf("Not enough buffered\n");
continue;
}
qtv->parsetime += buffer[0]; //well this was pointless
memmove(qtv->buffer, qtv->buffer+10, qtv->buffersize-(10));
qtv->buffersize -= 10;
continue;
case dem_multiple:
lengthofs = 6;
break;
default:
lengthofs = 2;
break;
}
if (qtv->buffersize < lengthofs+4)
{ //the size parameter doesn't fit.
printf("Not enough buffered\n");
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
break;
}
length = (buffer[lengthofs]<<0) + (buffer[lengthofs+1]<<8) + (buffer[lengthofs+2]<<16) + (buffer[lengthofs+3]<<24);
if (length > 1450)
{ //FIXME: THIS SHOULDN'T HAPPEN!
//Blame the upstream proxy!
printf("EGad! input packet (%i) too big! Flushing and reconnecting!\n", length);
if (qtv->file)
{
fclose(qtv->file);
qtv->file = NULL;
}
else
{
closesocket(qtv->sourcesock);
qtv->sourcesock = INVALID_SOCKET;
}
qtv->buffersize = 0;
break;
}
if (length+lengthofs+4 > qtv->buffersize)
{
printf("Not enough buffered\n");
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
break; //can't parse it yet.
}
qtv->nextpackettime = qtv->parsetime+buffer[0];
if (qtv->nextpackettime < qtv->curtime)
{
switch(qtv->buffer[1]&dem_mask)
{
case dem_multiple:
ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, (buffer[lengthofs-4]<<0) + (buffer[lengthofs+3]<<8) + (buffer[lengthofs-2]<<16) + (buffer[lengthofs-1]<<24));
break;
case dem_single:
case dem_stats:
ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, 1<<(qtv->buffer[1]>>3));
break;
case dem_read:
case dem_all:
ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, 0xffffffff);
break;
default:
printf("Message type %i\n", qtv->buffer[1]&dem_mask);
break;
}
qtv->oldpackettime = qtv->curtime;
qtv->parsetime += buffer[0];
if (qtv->lateforward)
Net_ForwardStream(qtv, qtv->buffer, lengthofs+4+length);
memmove(qtv->buffer, qtv->buffer+lengthofs+4+length, qtv->buffersize-(lengthofs+length+4));
qtv->buffersize -= lengthofs+4+length;
if (qtv->file)
Net_ReadStream(qtv);
}
else
break;
}
QW_UpdateUDPStuff(qtv);
}
}
int main(int argc, char **argv)
{
FILE *f;
sv_t qtv;
char *configfilename;
char line[1024];
char *eq,*semi;
memset(&qtv, 0, sizeof(qtv));
//set up a default config
qtv.tcplistenportnum = PROX_DEFAULTLISTENPORT;
qtv.qwlistenportnum = PROX_DEFAULTLISTENPORT;
strcpy(qtv.server, PROX_DEFAULTSERVER);
line[sizeof(line)-1] = '\0';
if (argc < 2)
configfilename = "ftv.cfg";
else
configfilename = argv[1];
f = fopen(configfilename, "rt");
if (!f)
printf("Couldn't open config file \"%s\", using defaults\n", configfilename);
else
{
while(fgets(line, sizeof(line)-1, f))
{
eq = strchr(line, '=');
if (!eq)
continue;
semi = strchr(eq, ';');
if (!semi)
{
printf("Missing ; in config file\n");
continue;
}
*eq = '\0';
*semi = '\0';
eq++;
Trim(line);
Trim(eq);
if (!strcmp(line, "server"))
strncpy(qtv.server, eq, sizeof(qtv.server)-1);
else if (!strcmp(line, "file"))
{
strcpy(qtv.server, "file:");
strncpy(qtv.server+5, eq, sizeof(qtv.server)-1);
}
else if (!strcmp(line, "tcpport"))
qtv.tcplistenportnum = atoi(eq);
else if (!strcmp(line, "udpport"))
qtv.qwlistenportnum = atoi(eq);
else if (!strcmp(line, "choke"))
qtv.chokeonnotupdated = !!atoi(eq);
else if (!strcmp(line, "lateforward"))
qtv.lateforward = !!atoi(eq);
else
{
printf("config: can't recognise %s\n", line);
}
}
fclose(f);
}
qtv.qwdsocket = INVALID_SOCKET;
qtv.listenmvd = INVALID_SOCKET;
qtv.sourcesock = INVALID_SOCKET;
#ifdef _WIN32
{
WSADATA discard;
WSAStartup(MAKEWORD(2,0), &discard);
}
#endif
qtv.qwdsocket = QW_InitUDPSocket(qtv.qwlistenportnum);
qtv.listenmvd = Net_MVDListen(qtv.tcplistenportnum);
qtv.parsingconnectiondata = true;
QTV_Run(&qtv);
}