mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-14 00:10:46 +00:00
9033f7b237
reimplemented qtvreverse command. fixed some stuffcmds being handled by the wrong splitscreen seats (was noticable in TF). rework smartjump to try to be more predictable... rework relighting to try to be more robust (and more self-contained). allow the csqc to actually use VF_PROJECTIONOFFSET. jump now moves upwards instead of trying to lock on to a nearby player when spectating. assume 32 fullbright pixels when running with a palette.lmp yet no colormap.lmp (happens with some total conversions). tweaked scoreboard for fainter backgrounds. rearranged autoid, to be smaller etc. hacked around dodgy conchars.lmp - don't treat 128*128 qpics as qpics to work around workarounds for buggy wad tools (with a warning). fixed missing fullbrights on h2holey models. avoided warning about mod_h2holey_bugged on dedicated servers. added net_ice_exchangeprivateips, for people worried about exposing lan IPs when using ICE. sv_public 2: implemented client support for our webrtc broker in order to use our own ICE implementation without needing to faff around with irc accounts or plugins etc. TODO: ensure at least one ephemerial udp port when using ice or come up with some better sv_port handling fixed multiple tls bugs (one could cause server problems). change net_enable_tls to disabled by default anyway (reenable for the server to be able to respond to https/wss/tls schemes again). don't colourmap when there appears to be a highres diffusemap on q1 models. imgtool now understands exporting from qpics in wads, as well as just mips. implemented speed-o-meter in ezhud. added removeinstant builtin to avoid the half-second rule. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5614 fc73d0e0-1445-4013-8a0c-d673dee63da5
1304 lines
34 KiB
C
1304 lines
34 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the included (GNU.txt) GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
This is the file responsible for handling incoming tcp connections.
|
|
This includes mvd recording.
|
|
Password checks and stuff are implemented here. This i server side stuff.
|
|
|
|
*/
|
|
|
|
#include "qtv.h"
|
|
#include "time.h"
|
|
|
|
|
|
#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 > MAX_MSGLEN)
|
|
printf("too big (%i)\n", length);
|
|
pos += length;
|
|
}
|
|
if (pos != size)
|
|
printf("pos != size\n");
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SV_FindProxies(SOCKET sock, cluster_t *cluster, sv_t *defaultqtv)
|
|
{
|
|
unsigned long nonblocking = true;
|
|
oproxy_t *prox;
|
|
|
|
if (sock == INVALID_SOCKET)
|
|
return;
|
|
sock = accept(sock, NULL, NULL);
|
|
if (sock == INVALID_SOCKET)
|
|
return;
|
|
|
|
if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1)
|
|
{
|
|
Sys_Printf(cluster, "failed to set client socket to nonblocking. dropping.\n");
|
|
closesocket(sock); //failed...
|
|
return;
|
|
}
|
|
|
|
if (cluster->maxproxies >= 0 && cluster->numproxies >= cluster->maxproxies)
|
|
{
|
|
const char buffer[] = {dem_all, 1, 'P','r','o','x','y',' ','i','s',' ','f','u','l','l','.'};
|
|
send(sock, buffer, strlen(buffer), 0);
|
|
closesocket(sock);
|
|
return;
|
|
}
|
|
|
|
prox = malloc(sizeof(*prox));
|
|
if (!prox)
|
|
{//out of mem?
|
|
closesocket(sock);
|
|
return;
|
|
}
|
|
memset(prox, 0, sizeof(*prox));
|
|
prox->sock = sock;
|
|
prox->file = NULL;
|
|
|
|
cluster->numproxies++;
|
|
|
|
prox->droptime = cluster->curtime + 5*1000;
|
|
#if 1
|
|
prox->defaultstream = defaultqtv;
|
|
|
|
prox->next = cluster->pendingproxies;
|
|
cluster->pendingproxies = prox;
|
|
#else
|
|
prox->next = qtv->pendingproxies;
|
|
qtv->pendingproxies = prox;
|
|
Net_SendConnectionMVD(qtv, prox);
|
|
#endif
|
|
}
|
|
|
|
|
|
void Fwd_ParseCommands(cluster_t *cluster, oproxy_t *prox)
|
|
{
|
|
netmsg_t buf;
|
|
int packetlength;
|
|
int bytes;
|
|
bytes = NET_WebSocketRecv(prox->sock, &prox->websocket, prox->inbuffer+prox->inbuffersize, sizeof(prox->inbuffer)-prox->inbuffersize, NULL);
|
|
if (bytes < 0)
|
|
{
|
|
if (qerrno != NET_EWOULDBLOCK && qerrno != NET_EAGAIN) //not a problem, so long as we can flush it later.
|
|
{
|
|
Sys_Printf(cluster, "network error from client proxy\n");
|
|
prox->drop = true; //drop them if we get any errors
|
|
return;
|
|
}
|
|
bytes = 0;
|
|
}
|
|
else if (bytes == 0)
|
|
{
|
|
prox->drop = true;
|
|
return;
|
|
}
|
|
|
|
prox->inbuffersize += bytes;
|
|
|
|
for(;;)
|
|
{
|
|
if (prox->inbuffersize < 2) //we do need at least 3 bytes for anything useful
|
|
break;
|
|
|
|
packetlength = prox->inbuffer[0] + (prox->inbuffer[1]<<8);
|
|
packetlength -= 2; //qqshka's inconsistent-upstream-sizes stupidity.
|
|
if (packetlength+2 > prox->inbuffersize)
|
|
break;
|
|
|
|
InitNetMsg(&buf, prox->inbuffer+2, packetlength);
|
|
buf.cursize = packetlength;
|
|
|
|
while(buf.readpos < buf.cursize)
|
|
{
|
|
switch (ReadByte(&buf))
|
|
{
|
|
case qtv_clc_stringcmd:
|
|
{
|
|
char stringbuf[1024];
|
|
ReadString(&buf, stringbuf, sizeof(stringbuf));
|
|
QTV_Printf(prox->stream, "ds: %s\n", stringbuf);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
Sys_Printf(cluster, "Received unrecognized packet type from downstream proxy.\n");
|
|
buf.readpos = buf.cursize;
|
|
break;
|
|
}
|
|
}
|
|
packetlength+=2;
|
|
memmove(prox->inbuffer, prox->inbuffer+packetlength, prox->inbuffersize - packetlength);
|
|
prox->inbuffersize -= packetlength;
|
|
}
|
|
}
|
|
|
|
void Net_TryFlushProxyBuffer(cluster_t *cluster, oproxy_t *prox)
|
|
{
|
|
unsigned char *buffer;
|
|
int length;
|
|
int bufpos;
|
|
|
|
// if (prox->drop)
|
|
// return;
|
|
|
|
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)
|
|
Sys_Printf(cluster, "oversize flush\n");
|
|
|
|
if (prox->file)
|
|
length = fwrite(buffer, 1, length, prox->file);
|
|
else
|
|
length = send(prox->sock, buffer, length, 0);
|
|
|
|
|
|
switch (length)
|
|
{
|
|
case 0: //eof / they disconnected
|
|
prox->drop = true;
|
|
prox->flushing = false;
|
|
break;
|
|
case -1:
|
|
if (qerrno != NET_EWOULDBLOCK && qerrno != NET_EAGAIN) //not a problem, so long as we can flush it later.
|
|
{
|
|
Sys_Printf(cluster, "network error from client proxy\n");
|
|
prox->drop = true; //drop them if we get any errors
|
|
prox->flushing = false;
|
|
}
|
|
break;
|
|
default:
|
|
prox->bufferpos += length;
|
|
}
|
|
}
|
|
|
|
void Net_ProxySendString(cluster_t *cluster, oproxy_t *prox, void *buffer)
|
|
{
|
|
Net_ProxySend(cluster, prox, buffer, strlen(buffer));
|
|
}
|
|
|
|
void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, void *buffer, int length)
|
|
{
|
|
int wrap;
|
|
|
|
if (!length)
|
|
return;
|
|
|
|
if (prox->websocket.websocket)
|
|
{
|
|
unsigned int c;
|
|
int enclen = 0;
|
|
int datatype = 2; //1=utf-8, 2=binary
|
|
|
|
/*work out how much buffer space we'll need*/
|
|
if (datatype == 2)
|
|
enclen += length;
|
|
else
|
|
{
|
|
for (c = 0; c < length; c++)
|
|
{
|
|
if (((unsigned char*)buffer)[c] == 0 || ((unsigned char*)buffer)[c] >= 0x80)
|
|
enclen += 2;
|
|
else
|
|
enclen += 1;
|
|
}
|
|
}
|
|
|
|
if (prox->buffersize-prox->bufferpos + (enclen+4) > MAX_PROXY_BUFFER)
|
|
{
|
|
Net_TryFlushProxyBuffer(cluster, prox); //try flushing
|
|
if (prox->buffersize-prox->bufferpos + (enclen+4) > MAX_PROXY_BUFFER) //damn, still too big.
|
|
{ //they're too slow. hopefully it was just momentary lag
|
|
if (!prox->flushing)
|
|
{
|
|
printf("QTV client is too lagged\n");
|
|
prox->flushing = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (enclen >= 126)
|
|
{
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x80|datatype;
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 126;
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen>>8;
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen;
|
|
}
|
|
else
|
|
{
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x80|datatype;
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = enclen;
|
|
}
|
|
if (datatype == 2)
|
|
{
|
|
for(; length-->0; buffer = (char*)buffer+1)
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = *(unsigned char*)buffer;
|
|
}
|
|
else
|
|
{ //utf-8 (or really just bytes with upper bits truncated. sue me.
|
|
while(length-->0)
|
|
{
|
|
c = *(unsigned char*)buffer;
|
|
buffer = (char*)buffer+1;
|
|
if (!c)
|
|
c |= 0x100; /*will get truncated at the other end*/
|
|
if (c >= 0x80)
|
|
{
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0xc0 | (c>>6);
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = 0x80 | (c & 0x3f);
|
|
}
|
|
else
|
|
prox->buffer[prox->buffersize++&(MAX_PROXY_BUFFER-1)] = c;
|
|
}
|
|
}
|
|
Net_TryFlushProxyBuffer(cluster, prox); //try flushing in a desperate attempt to reduce bugs in google chrome.
|
|
return;
|
|
}
|
|
|
|
if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER)
|
|
{
|
|
Net_TryFlushProxyBuffer(cluster, 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
|
|
if (!prox->flushing)
|
|
{
|
|
printf("QTV client is too lagged\n");
|
|
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)] = *(unsigned char*)buffer;
|
|
buffer = (char*)buffer+1;
|
|
}
|
|
#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(cluster_t *cluster, 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);
|
|
if (dem_type == dem_multiple)
|
|
WriteLong(&msg, playermask);
|
|
|
|
if (prox->buffersize-prox->bufferpos + length + msg.cursize > MAX_PROXY_BUFFER)
|
|
{
|
|
Net_TryFlushProxyBuffer(cluster, prox); //try flushing
|
|
if (prox->buffersize-prox->bufferpos + length + msg.cursize > MAX_PROXY_BUFFER) //damn, still too big.
|
|
{ //they're too slow. hopefully it was just momentary lag
|
|
prox->flushing = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
Net_ProxySend(cluster, prox, msg.data, msg.cursize);
|
|
|
|
Net_ProxySend(cluster, prox, buf, length);
|
|
}
|
|
|
|
void Fwd_SendDownstream(sv_t *qtv, void *buffer, int length)
|
|
{ //broadcasts data to all client proxies, with dont-buffer
|
|
oproxy_t *prox;
|
|
for (prox = qtv->proxies; prox; prox = prox->next)
|
|
{
|
|
Prox_SendMessage(qtv->cluster, prox, buffer, length, dem_qtvdata, (unsigned int)-1);
|
|
}
|
|
}
|
|
|
|
void Fwd_SayToDownstream(sv_t *qtv, char *message)
|
|
{
|
|
netmsg_t msg;
|
|
char buffer[1024];
|
|
|
|
InitNetMsg(&msg, buffer, sizeof(buffer));
|
|
WriteByte(&msg, svc_print);
|
|
WriteByte(&msg, PRINT_CHAT);
|
|
WriteString2(&msg, "[QTV]");
|
|
WriteString(&msg, message);
|
|
|
|
Fwd_SendDownstream(qtv, msg.data, msg.cursize);
|
|
}
|
|
|
|
void Prox_SendPlayerStats(sv_t *qtv, oproxy_t *prox)
|
|
{
|
|
char buffer[MAX_MSGLEN];
|
|
netmsg_t msg;
|
|
int player, snum;
|
|
|
|
InitNetMsg(&msg, buffer, sizeof(buffer));
|
|
|
|
for (player = 0; player < MAX_CLIENTS; player++)
|
|
{
|
|
for (snum = 0; snum < MAX_STATS; snum++)
|
|
{
|
|
if (qtv->map.players[player].stats[snum])
|
|
{
|
|
if ((unsigned)qtv->map.players[player].stats[snum] > 255)
|
|
{
|
|
WriteByte(&msg, svc_updatestatlong);
|
|
WriteByte(&msg, snum);
|
|
WriteLong(&msg, qtv->map.players[player].stats[snum]);
|
|
}
|
|
else
|
|
{
|
|
WriteByte(&msg, svc_updatestat);
|
|
WriteByte(&msg, snum);
|
|
WriteByte(&msg, qtv->map.players[player].stats[snum]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (msg.cursize)
|
|
{
|
|
// Prox_SendMessage(prox, msg.data, msg.cursize, dem_stats|(player<<3), (1<<player));
|
|
msg.cursize = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Prox_SendInitialPlayers(sv_t *qtv, oproxy_t *prox, netmsg_t *msg)
|
|
{
|
|
int i, j, flags;
|
|
char buffer[64];
|
|
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (!qtv->map.players[i].active) // interesting, is this set to false if player disconnect from server?
|
|
continue;
|
|
|
|
flags = (DF_ORIGIN << 0) | (DF_ORIGIN << 1) | (DF_ORIGIN << 2)
|
|
| (DF_ANGLES << 0) | (DF_ANGLES << 1) | (DF_ANGLES << 2) // angles is something what changed frequently, so may be not send it?
|
|
| DF_EFFECTS
|
|
| DF_SKINNUM // though it rare thingie, so better send it?
|
|
| (qtv->map.players[i].dead ? DF_DEAD : 0)
|
|
| (qtv->map.players[i].gibbed ? DF_GIB : 0)
|
|
| DF_WEAPONFRAME // do we so really need it?
|
|
| DF_MODEL; // generally, that why we wrote this function, so YES send this
|
|
|
|
if (*qtv->map.players[i].userinfo && atoi(Info_ValueForKey(qtv->map.players[i].userinfo, "*spectator", buffer, sizeof(buffer))))
|
|
flags = DF_MODEL; // oh, that spec, just sent his model, may be even better ignore him?
|
|
|
|
WriteByte (msg, svc_playerinfo);
|
|
WriteByte (msg, i);
|
|
WriteShort (msg, flags);
|
|
|
|
WriteByte (msg, qtv->map.players[i].current.frame); // always sent
|
|
|
|
for (j = 0 ; j < 3 ; j++)
|
|
if (flags & (DF_ORIGIN << j))
|
|
WriteCoord (msg, qtv->map.players[i].current.origin[j], qtv->pext1);
|
|
|
|
for (j = 0 ; j < 3 ; j++)
|
|
if (flags & (DF_ANGLES << j))
|
|
WriteShort (msg, qtv->map.players[i].current.angles[j]);
|
|
|
|
if (flags & DF_MODEL) // generally, that why we wrote this function, so YES send this
|
|
WriteByte (msg, qtv->map.players[i].current.modelindex);
|
|
|
|
if (flags & DF_SKINNUM)
|
|
WriteByte (msg, qtv->map.players[i].current.skinnum);
|
|
|
|
if (flags & DF_EFFECTS)
|
|
WriteByte (msg, qtv->map.players[i].current.effects);
|
|
|
|
if (flags & DF_WEAPONFRAME)
|
|
WriteByte (msg, qtv->map.players[i].current.weaponframe);
|
|
}
|
|
}
|
|
|
|
void Net_GreetingMessage(oproxy_t *prox)
|
|
{
|
|
char buffer[1024];
|
|
netmsg_t msg;
|
|
|
|
InitNetMsg(&msg, buffer, sizeof(buffer));
|
|
WriteByte(&msg, svc_print);
|
|
WriteByte(&msg, PRINT_HIGH);
|
|
WriteString2(&msg, "Welcome to ");
|
|
WriteString2(&msg, prox->stream->cluster->hostname);
|
|
WriteString(&msg, "\n");
|
|
|
|
Prox_SendMessage(prox->stream->cluster, prox, msg.data, msg.cursize, dem_qtvdata, (unsigned)-1);
|
|
}
|
|
|
|
void Net_SendConnectionMVD(sv_t *qtv, oproxy_t *prox)
|
|
{
|
|
char buffer[MAX_MSGLEN*8];
|
|
netmsg_t msg;
|
|
int prespawn;
|
|
|
|
//only send connection data if there's actual data to be sent
|
|
//if not, the other end will get the data when we receive it anyway.
|
|
if (!*qtv->map.mapname)
|
|
return;
|
|
|
|
InitNetMsg(&msg, buffer, sizeof(buffer));
|
|
|
|
prox->flushing = false;
|
|
|
|
BuildServerData(qtv, &msg, 0, NULL);
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
|
|
for (prespawn = 0;prespawn >= 0;)
|
|
{
|
|
prespawn = SendList(qtv, prespawn, qtv->map.soundlist, svc_soundlist, &msg);
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
}
|
|
|
|
for (prespawn = 0;prespawn >= 0;)
|
|
{
|
|
prespawn = SendList(qtv, prespawn, qtv->map.modellist, svc_modellist, &msg);
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
}
|
|
|
|
Net_TryFlushProxyBuffer(qtv->cluster, prox); //that should be enough data to fill a packet.
|
|
|
|
for(prespawn = 0;prespawn>=0;)
|
|
{
|
|
prespawn = Prespawn(qtv, 0, &msg, prespawn, MAX_CLIENTS-1);
|
|
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
}
|
|
|
|
//playerstates are delta-compressed, unfortunatly this isn't qwd (thanks to qqshka for showing my folly)
|
|
Prox_SendInitialPlayers(qtv, prox, &msg);
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
|
|
//we do need to send entity states.
|
|
Prox_SendInitialEnts(qtv, prox, &msg);
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
|
|
WriteByte(&msg, svc_stufftext);
|
|
WriteString(&msg, "skins\n");
|
|
Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1);
|
|
msg.cursize = 0;
|
|
|
|
Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
|
|
Prox_SendPlayerStats(qtv, prox);
|
|
Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
|
|
if (!qtv->cluster->lateforward)
|
|
Net_ProxySend(qtv->cluster, prox, qtv->buffer, qtv->forwardpoint); //send all the info we've not yet processed (but have already forwarded).
|
|
|
|
|
|
if (prox->flushing)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Connection data is too big, dropping proxy client\n");
|
|
prox->drop = true; //this is unfortunate...
|
|
}
|
|
else
|
|
Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
}
|
|
|
|
|
|
oproxy_t *Net_FileProxy(sv_t *qtv, char *filename)
|
|
{
|
|
oproxy_t *prox;
|
|
FILE *f;
|
|
|
|
f = fopen(filename, "wb");
|
|
if (!f)
|
|
return NULL;
|
|
|
|
//no full proxy check, this is going to be used by proxy admins, who won't want to have to raise the limit to start recording.
|
|
|
|
prox = malloc(sizeof(*prox));
|
|
if (!prox)
|
|
return NULL;
|
|
memset(prox, 0, sizeof(*prox));
|
|
|
|
prox->sock = INVALID_SOCKET;
|
|
prox->file = f;
|
|
|
|
prox->next = qtv->proxies;
|
|
qtv->proxies = prox;
|
|
|
|
qtv->cluster->numproxies++;
|
|
|
|
Net_SendConnectionMVD(qtv, prox);
|
|
|
|
return prox;
|
|
}
|
|
|
|
qboolean Net_StopFileProxy(sv_t *qtv)
|
|
{
|
|
oproxy_t *prox;
|
|
for (prox = qtv->proxies; prox; prox = prox->next)
|
|
{
|
|
if (prox->file)
|
|
{
|
|
prox->drop = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
void SV_ForwardStream(sv_t *qtv, void *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;
|
|
if (fre->file)
|
|
fclose(fre->file);
|
|
else
|
|
closesocket(fre->sock);
|
|
free(fre);
|
|
qtv->cluster->numproxies--;
|
|
qtv->proxies = next;
|
|
}
|
|
|
|
for (prox = qtv->proxies; prox; prox = prox->next)
|
|
{
|
|
while (prox->next && prox->next->drop)
|
|
{
|
|
next = prox->next->next;
|
|
fre = prox->next;
|
|
if (fre->file)
|
|
fclose(fre->file);
|
|
else
|
|
closesocket(fre->sock);
|
|
if (fre->srcfile)
|
|
fclose(fre->srcfile);
|
|
free(fre);
|
|
qtv->cluster->numproxies--;
|
|
prox->next = next;
|
|
}
|
|
|
|
if (prox->flushing) //don't send 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(qtv->cluster, prox); //try and flush it.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (prox->drop)
|
|
continue;
|
|
|
|
//add the new data
|
|
Net_ProxySend(qtv->cluster, prox, buffer, length);
|
|
|
|
Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
// Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
// Net_TryFlushProxyBuffer(qtv->cluster, prox);
|
|
|
|
#ifndef _MSC_VER
|
|
#warning This is not the place for this
|
|
#endif
|
|
if (prox->sock != INVALID_SOCKET)
|
|
{
|
|
Fwd_ParseCommands(qtv->cluster, prox);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*wrapper around recv to handle websocket connections*/
|
|
int NET_WebSocketRecv(SOCKET sock, wsrbuf_t *ws, unsigned char *out, unsigned int outlen, int *clen)
|
|
{
|
|
unsigned int mask = 0;
|
|
unsigned int paylen;
|
|
int len, i;
|
|
|
|
if (clen)
|
|
*clen = -1;
|
|
if (!ws->websocket)
|
|
return recv(sock, out, outlen, 0);
|
|
|
|
if (!ws->wsbuflen)
|
|
{
|
|
len = recv(sock, ws->wsbuf, 2, 0);
|
|
if (len > 0)
|
|
ws->wsbuflen+=2;
|
|
else
|
|
return len;
|
|
}
|
|
if (ws->wsbuflen >= 2)
|
|
{
|
|
unsigned short ctrl = (ws->wsbuf[0]<<8) | ws->wsbuf[1];
|
|
len = 2;
|
|
if ((ctrl & 0x7f) == 127)
|
|
len += 8;
|
|
else if ((ctrl & 0x7f) == 126)
|
|
len += 2;
|
|
if (ctrl & 0x80)
|
|
len += 4;
|
|
if (ws->wsbuflen < len)
|
|
{
|
|
paylen = recv(sock, ws->wsbuf+ws->wsbuflen, len - ws->wsbuflen, 0);
|
|
if (paylen > 0)
|
|
ws->wsbuflen += paylen;
|
|
else
|
|
return paylen;
|
|
}
|
|
|
|
/*headers are complete*/
|
|
if (ws->wsbuflen >= len)
|
|
{
|
|
if ((ctrl & 0x7f) == 127)
|
|
{
|
|
// paylen = paylen = ((ws->wsbuf[2]<<56) | (ws->wsbuf[3]<<48) | (ws->wsbuf[4]<<40) | (ws->wsbuf[5]<<32) | (ws->wsbuf[6]<<24) | (ws->wsbuf[7]<<16) | (ws->wsbuf[8]<<8) | (ws->wsbuf[9]<<0);
|
|
// if (paylen < 65536)
|
|
return 0; //error
|
|
}
|
|
else if ((ctrl & 0x7f) == 126)
|
|
{
|
|
paylen = (ws->wsbuf[2]<<8) | ws->wsbuf[3];
|
|
if (paylen < 126)
|
|
return 0; //error
|
|
}
|
|
else
|
|
paylen = ctrl & 0x7f;
|
|
|
|
if (ctrl & 0x80)
|
|
{
|
|
((unsigned char*)&mask)[0] = ws->wsbuf[ws->wsbuflen-4];
|
|
((unsigned char*)&mask)[1] = ws->wsbuf[ws->wsbuflen-3];
|
|
((unsigned char*)&mask)[2] = ws->wsbuf[ws->wsbuflen-2];
|
|
((unsigned char*)&mask)[3] = ws->wsbuf[ws->wsbuflen-1];
|
|
}
|
|
if (!(ctrl & 0x8000))
|
|
return 0; //can't handle fragmented frames
|
|
|
|
switch((ctrl>>8) & 0xf)
|
|
{
|
|
case 1: /*text frame*/
|
|
len = 0;
|
|
while (outlen>len && ws->wspushed < paylen)
|
|
{
|
|
unsigned char n;
|
|
ctrl = recv(sock, &n, 1, 0); //FIXME: not my fastest code...
|
|
if (ctrl <= 0)
|
|
return len>0?len:ctrl;
|
|
|
|
n ^= ((unsigned char*)&mask)[(ws->wspushed++)&3];
|
|
|
|
if (ws->wsbits)
|
|
{
|
|
*out++ = ((ws->wsbits&0x1f)<<6) | (n & 0x3f);
|
|
len++;
|
|
ws->wsbits = 0;
|
|
}
|
|
else if ((n & 0xe0) == 0xc0)
|
|
{
|
|
ws->wsbits = n;
|
|
}
|
|
else if (n & 0x80)
|
|
return 0; //error
|
|
else
|
|
{
|
|
*out++ = n;
|
|
len++;
|
|
}
|
|
}
|
|
if (ws->wspushed == paylen)
|
|
{
|
|
if (ws->wsbits)
|
|
return 0; //error
|
|
if (clen)
|
|
*clen = ws->wspushed;
|
|
ws->wspushed = 0;
|
|
ws->wsbuflen = 0;
|
|
}
|
|
return len;
|
|
case 2: /*binary frame*/
|
|
if (outlen > paylen - ws->wspushed)
|
|
outlen = paylen - ws->wspushed;
|
|
len = recv(sock, out, outlen, 0);
|
|
if (len > 0)
|
|
{
|
|
for(i = 0; i < len; i++)
|
|
out[i] ^= ((unsigned char*)&mask)[(ws->wspushed+i)&3];
|
|
ws->wspushed += len;
|
|
}
|
|
if (paylen == ws->wspushed)
|
|
{
|
|
/*success! move on to the next*/
|
|
if (clen)
|
|
*clen = ws->wspushed;
|
|
ws->wspushed = 0;
|
|
ws->wsbuflen = 0;
|
|
}
|
|
return len;
|
|
case 8: /*close*/
|
|
case 9: /*ping*/
|
|
case 10: /*pong*/
|
|
default:
|
|
return 0; //unsupported
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//returns true if the pending proxy should be unlinked
|
|
//truth does not imply that it should be freed/released, just unlinked.
|
|
qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend)
|
|
{
|
|
//drop: ignored if flushing is still set. clear flushing and set drop on errors.
|
|
char tempbuf[512];
|
|
unsigned char *s;
|
|
unsigned char *e;
|
|
char *colon;
|
|
float clientversion = 0;
|
|
int len;
|
|
qboolean eoh;
|
|
int headersize;
|
|
qboolean raw;
|
|
sv_t *qtv;
|
|
|
|
if (pend->drop && !pend->flushing)
|
|
{
|
|
printf("pending drop\n");
|
|
if (pend->srcfile)
|
|
fclose(pend->srcfile);
|
|
if (pend->sock != INVALID_SOCKET)
|
|
closesocket(pend->sock);
|
|
free(pend);
|
|
cluster->numproxies--;
|
|
return true;
|
|
}
|
|
#define QTVSVHEADER "QTVSV 1.1\n"
|
|
|
|
Net_TryFlushProxyBuffer(cluster, pend);
|
|
|
|
if (pend->flushing)
|
|
{
|
|
if (pend->srcfile)
|
|
{
|
|
#if 0
|
|
//bufferend = transmit point
|
|
//buffersize = write point
|
|
if (bufferend < buffersize)
|
|
space = (MAX_PROXY_BUFFER - pend->buffersize) + pend->bufferend;
|
|
else
|
|
space = pend->bufferend - pend->buffersize;
|
|
|
|
if (space < 256) /*don't bother reading if we're dribbling*/
|
|
return false;
|
|
if (space > 0) /*never fully saturate so as to not confuse the ring*/
|
|
space--;
|
|
|
|
if (space > MAX_PROXY_BUFFER -
|
|
fread(prox->buffer + pend->buffersize, 1, space, pend->srcfile);
|
|
#else
|
|
if (pend->bufferpos == pend->buffersize)
|
|
{
|
|
char buffer[MAX_PROXY_BUFFER/2];
|
|
pend->droptime = cluster->curtime + 5*1000;
|
|
len = fread(buffer, 1, sizeof(buffer), pend->srcfile);
|
|
if (!len)
|
|
{
|
|
fclose(pend->srcfile);
|
|
pend->srcfile = NULL;
|
|
}
|
|
Net_ProxySend(cluster, pend, buffer, len);
|
|
}
|
|
#endif
|
|
return false; //don't try reading anything yet
|
|
}
|
|
|
|
if (pend->bufferpos != pend->buffersize)
|
|
return false;
|
|
pend->flushing = false;
|
|
if (pend->drop)
|
|
return false;
|
|
}
|
|
|
|
if (pend->droptime < cluster->curtime)
|
|
{
|
|
printf("pending timeout\n");
|
|
pend->drop = true;
|
|
pend->flushing = false;
|
|
return false;
|
|
}
|
|
|
|
len = sizeof(pend->inbuffer) - pend->inbuffersize - 1;
|
|
len = NET_WebSocketRecv(pend->sock, &pend->websocket, pend->inbuffer+pend->inbuffersize, len, NULL);
|
|
if (len == 0)
|
|
{
|
|
pend->drop = true;
|
|
return false;
|
|
}
|
|
else if (len > 0)
|
|
pend->droptime = cluster->curtime + 5*1000;
|
|
if (len < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pend->inbuffersize += len;
|
|
pend->inbuffer[pend->inbuffersize] = '\0';
|
|
|
|
if (pend->inbuffersize >= 4)
|
|
{
|
|
if (!strncmp(pend->inbuffer, "qizmo\n", 6))
|
|
{
|
|
wsrbuf_t ws = pend->websocket;
|
|
SOCKET sock;
|
|
//carries unreliable packets
|
|
printf("tcpconnect\n");
|
|
if (pend->srcfile)
|
|
fclose(pend->srcfile);
|
|
sock = pend->sock;
|
|
free(pend);
|
|
cluster->numproxies--;
|
|
|
|
send(sock, "qizmo\n", 6, 0);
|
|
QW_TCPConnection(cluster, sock, ws);
|
|
|
|
return true;
|
|
}
|
|
if (pend->websocket.websocket && !strncmp(pend->inbuffer, "\xff\xff\xff\xff", 4))
|
|
{
|
|
wsrbuf_t ws = pend->websocket;
|
|
SOCKET sock;
|
|
//carries unreliable packets
|
|
printf("wsconnect\n");
|
|
if (pend->srcfile)
|
|
fclose(pend->srcfile);
|
|
sock = pend->sock;
|
|
free(pend);
|
|
cluster->numproxies--;
|
|
|
|
QW_TCPConnection(cluster, sock, ws);
|
|
|
|
return true;
|
|
}
|
|
if (ustrncmp(pend->inbuffer, "QTV\r", 4) && ustrncmp(pend->inbuffer, "QTV\n", 4) && ustrncmp(pend->inbuffer, "GET ", 4) && ustrncmp(pend->inbuffer, "POST ", 5))
|
|
{ //I have no idea what the smeg you are.
|
|
pend->drop = true;
|
|
|
|
pend->inbuffer[16] = 0;
|
|
Sys_Printf(cluster, "pending proxy: Connect for unrecognized protocol %s\n", pend->inbuffer);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//make sure there's a double \n somewhere
|
|
|
|
eoh = false;
|
|
for (s = pend->inbuffer; s<=pend->inbuffer+pend->inbuffersize; s++)
|
|
{
|
|
if (s[0] == '\n' && s[1] == '\n')
|
|
{
|
|
s += 2;
|
|
eoh = true;
|
|
break;
|
|
}
|
|
if (s[0] == '\n' && s[1] == '\r' && s[2] == '\n')
|
|
{
|
|
s += 3;
|
|
eoh = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!eoh)
|
|
return false; //don't have enough yet
|
|
headersize = s - pend->inbuffer;
|
|
|
|
if (!ustrncmp(pend->inbuffer, "POST ", 5))
|
|
{
|
|
HTTPSV_PostMethod(cluster, pend, (char*)s);
|
|
|
|
return false; //not keen on this..
|
|
}
|
|
else if (!ustrncmp(pend->inbuffer, "GET ", 4))
|
|
{
|
|
pend->drop = true;
|
|
HTTPSV_GetMethod(cluster, pend);
|
|
pend->flushing = true;
|
|
memmove(pend->inbuffer, pend->inbuffer+headersize, pend->inbuffersize-headersize);
|
|
pend->inbuffersize -= headersize;
|
|
return SV_ReadPendingProxy(cluster, pend);
|
|
}
|
|
|
|
raw = false;
|
|
|
|
qtv = pend->defaultstream;
|
|
|
|
e = pend->inbuffer;
|
|
s = e;
|
|
while(*e)
|
|
{
|
|
if (*e == '\n' || *e == '\r')
|
|
{
|
|
*e = '\0';
|
|
colon = strchr((char*)s, ':');
|
|
if (*s)
|
|
{
|
|
if (!colon)
|
|
{
|
|
if (!ustrcmp(s, "QTV"))
|
|
{
|
|
//just a qtv request (as in, not http or some other protocol)
|
|
}
|
|
else if (!ustrcmp(s, "SOURCELIST"))
|
|
{ //lists sources that are currently playing
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER);
|
|
if (!cluster->servers)
|
|
{
|
|
Net_ProxySendString(cluster, pend, "PERROR: No sources currently available\n");
|
|
}
|
|
else
|
|
{
|
|
for (qtv = cluster->servers; qtv; qtv = qtv->next)
|
|
{
|
|
if (clientversion > 1)
|
|
{
|
|
int plyrs = 0;
|
|
int i;
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (*qtv->map.players[i].userinfo)
|
|
plyrs++;
|
|
}
|
|
sprintf(tempbuf, "SRCSRV: %s\n", qtv->server);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
sprintf(tempbuf, "SRCHOST: %s\n", qtv->map.hostname);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
sprintf(tempbuf, "SRCPLYRS: %i\n", plyrs);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
sprintf(tempbuf, "SRCVIEWS: %i\n", qtv->numviewers);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
sprintf(tempbuf, "SRCID: %i\n", qtv->streamid); //final part of each source
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
}
|
|
else
|
|
{
|
|
sprintf(tempbuf, "ASOURCE: %i: %15s: %15s\n", qtv->streamid, qtv->server, qtv->map.hostname);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
}
|
|
}
|
|
qtv = NULL;
|
|
}
|
|
Net_ProxySendString(cluster, pend, "\n");
|
|
pend->flushing = true;
|
|
}
|
|
else if (!ustrcmp(s, "REVERSE"))
|
|
{ //this is actually a server trying to connect to us
|
|
//start up a new stream
|
|
|
|
if (cluster->reverseallowed)
|
|
{
|
|
qtv = QTV_NewServerConnection(cluster, 0, "reverse"/*server*/, "", true, AD_REVERSECONNECT, false, 0);
|
|
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER);
|
|
Net_ProxySendString(cluster, pend, "VERSION: 1\n");
|
|
Net_ProxySendString(cluster, pend, "REVERSED\n");
|
|
Net_ProxySendString(cluster, pend, "\n");
|
|
|
|
//switch over the socket to the actual source connection rather than the pending
|
|
Net_TryFlushProxyBuffer(cluster, pend); //flush anything... this isn't ideal, but should be small enough
|
|
qtv->sourcesock = pend->sock;
|
|
pend->sock = INVALID_SOCKET;
|
|
|
|
memcpy(qtv->buffer, pend->inbuffer + headersize, pend->inbuffersize - headersize);
|
|
qtv->parsingqtvheader = true;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"PERROR: Reverse connections are disabled on this proxy\n");
|
|
pend->flushing = true;
|
|
}
|
|
}
|
|
else if (!ustrcmp(s, "RECEIVE"))
|
|
{ //a client connection request without a source
|
|
if (cluster->numservers == 1)
|
|
{ //only one stream anyway
|
|
qtv = cluster->servers;
|
|
}
|
|
else
|
|
{ //try and hunt down an explicit stream (rather than a user-recorded one)
|
|
int numfound = 0;
|
|
sv_t *suitable = NULL; //shush noisy compilers
|
|
for (qtv = cluster->servers; qtv; qtv = qtv->next)
|
|
{
|
|
if (qtv->autodisconnect == AD_NO)
|
|
{
|
|
suitable = qtv;
|
|
numfound++;
|
|
}
|
|
}
|
|
if (numfound == 1)
|
|
qtv = suitable;
|
|
}
|
|
if (!qtv)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER);
|
|
Net_ProxySendString(cluster, pend, "PERROR: Multiple streams are currently playing\n");
|
|
Net_ProxySendString(cluster, pend, "\n");
|
|
pend->flushing = true;
|
|
}
|
|
}
|
|
else if (!ustrcmp(s, "DEMOLIST"))
|
|
{ //lists sources that are currently playing
|
|
int i;
|
|
|
|
Cluster_BuildAvailableDemoList(cluster);
|
|
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER);
|
|
if (!cluster->availdemoscount)
|
|
{
|
|
Net_ProxySendString(cluster, pend, "PERROR: No demos currently available\n");
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < cluster->availdemoscount; i++)
|
|
{
|
|
sprintf(tempbuf, "ADEMO: %i: %15s\n", cluster->availdemos[i].size, cluster->availdemos[i].name);
|
|
Net_ProxySendString(cluster, pend, tempbuf);
|
|
}
|
|
qtv = NULL;
|
|
}
|
|
Net_ProxySendString(cluster, pend, "\n");
|
|
pend->flushing = true;
|
|
}
|
|
else if (!ustrcmp(s, "AUTH"))
|
|
{ //lists the demos available on this proxy
|
|
//part of the connection process, can be ignored if there's no password
|
|
}
|
|
else
|
|
printf("Unrecognized token in QTV connection request (%s)\n", s);
|
|
}
|
|
else
|
|
{
|
|
*colon++ = '\0';
|
|
if (!ustrcmp(s, "VERSION"))
|
|
{
|
|
clientversion = atof(colon);
|
|
}
|
|
else if (!ustrcmp(s, "RAW"))
|
|
raw = atoi(colon);
|
|
/*else if (!ustrcmp(s, "ROUTE"))
|
|
{ //pure rewroute...
|
|
//is this safe? probably not.
|
|
s = QTVSVHEADER
|
|
"PERROR: ROUTE command not yet implemented\n"
|
|
"\n";
|
|
Net_ProxySend(cluster, pend, s, ustrlen(s));
|
|
pend->flushing = true;
|
|
}
|
|
*/
|
|
else if (!ustrcmp(s, "SOURCE"))
|
|
{ //connects, creating a new source
|
|
char *t;
|
|
while (*colon == ' ')
|
|
colon++;
|
|
for (t = colon; *t; t++)
|
|
if (*t < '0' || *t > '9')
|
|
break;
|
|
if (*t)
|
|
qtv = QTV_NewServerConnection(cluster, 0, colon, "", false, AD_WHENEMPTY, true, false);
|
|
else
|
|
{
|
|
//numerical source, use a stream id.
|
|
for (qtv = cluster->servers; qtv; qtv = qtv->next)
|
|
if (qtv->streamid == atoi(colon))
|
|
break;
|
|
}
|
|
}
|
|
else if (!ustrcmp(s, "DEMO"))
|
|
{ //starts a demo off the server... source does the same thing though...
|
|
char buf[256];
|
|
|
|
snprintf(buf, sizeof(buf), "demo:%s", colon);
|
|
qtv = QTV_NewServerConnection(cluster, 0, buf, "", false, AD_WHENEMPTY, true, false);
|
|
if (!qtv)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"PERROR: couldn't open demo\n"
|
|
"\n");
|
|
pend->flushing = true;
|
|
}
|
|
}
|
|
else if (!ustrcmp(s, "AUTH"))
|
|
{ //lists the demos available on this proxy
|
|
//part of the connection process, can be ignored if there's no password
|
|
}
|
|
else
|
|
printf("Unrecognized token in QTV connection request (%s)\n", s);
|
|
}
|
|
}
|
|
s = e+1;
|
|
}
|
|
|
|
e++;
|
|
}
|
|
|
|
if (!pend->flushing)
|
|
{
|
|
if (clientversion < 1)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"PERROR: Requested protocol version not supported\n"
|
|
"\n");
|
|
pend->flushing = true;
|
|
}
|
|
else if (!qtv)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"PERROR: No stream selected\n"
|
|
"\n");
|
|
pend->flushing = true;
|
|
}
|
|
}
|
|
if (pend->flushing)
|
|
return false;
|
|
|
|
|
|
if (qtv->usequakeworldprotocols)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"PERROR: This version of QTV is unable to convert QuakeWorld to QTV protocols\n"
|
|
"\n");
|
|
pend->flushing = true;
|
|
return false;
|
|
}
|
|
if (cluster->maxproxies>=0 && cluster->numproxies >= cluster->maxproxies)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER
|
|
"TERROR: This QTV has reached it's connection limit\n"
|
|
"\n");
|
|
pend->flushing = true;
|
|
return false;
|
|
}
|
|
|
|
pend->next = qtv->proxies;
|
|
qtv->proxies = pend;
|
|
|
|
if (!raw)
|
|
{
|
|
Net_ProxySendString(cluster, pend, QTVSVHEADER);
|
|
Net_ProxySendString(cluster, pend, "BEGIN: ");
|
|
Net_ProxySendString(cluster, pend, qtv->server);
|
|
Net_ProxySendString(cluster, pend, "\n\n");
|
|
}
|
|
// else if (passwordprotected) //raw mode doesn't support passwords, so reject them
|
|
// {
|
|
// pend->flushing = true;
|
|
// return;
|
|
// }
|
|
|
|
pend->stream = qtv;
|
|
|
|
memmove(pend->inbuffer, pend->inbuffer+headersize, pend->inbuffersize-headersize);
|
|
pend->inbuffersize -= headersize;
|
|
|
|
Net_GreetingMessage(pend);
|
|
Net_SendConnectionMVD(qtv, pend);
|
|
|
|
return true;
|
|
}
|