mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-29 23:22:01 +00:00
4943723a8b
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@3872 fc73d0e0-1445-4013-8a0c-d673dee63da5
2250 lines
59 KiB
C
2250 lines
59 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.
|
|
*/
|
|
|
|
|
|
//connection notes
|
|
//The connection is like http.
|
|
//The stream starts with a small header.
|
|
//The header is a list of 'key: value' pairs, separated by new lines.
|
|
//The header ends with a totally blank line.
|
|
//to record an mvd from telnet or somesuch, you would use:
|
|
//"QTV\nRAW: 1\n\n"
|
|
|
|
//VERSION: a list of the different qtv protocols supported. Multiple versions can be specified. The first is assumed to be the preferred version.
|
|
//RAW: if non-zero, send only a raw mvd with no additional markup anywhere (for telnet use). Doesn't work with challenge-based auth, so will only be accepted when proxy passwords are not required.
|
|
//AUTH: specifies an auth method, the exact specs varies based on the method
|
|
// PLAIN: the password is sent as a PASSWORD line
|
|
// MD4: the server responds with an "AUTH: MD4\n" line as well as a "CHALLENGE: somerandomchallengestring\n" line, the client sends a new 'initial' request with CHALLENGE: MD4\nRESPONSE: hexbasedmd4checksumhere\n"
|
|
// MD5: same as md4
|
|
// CCITT: same as md4, but using the CRC stuff common to all quake engines.
|
|
// if the supported/allowed auth methods don't match, the connection is silently dropped.
|
|
//SOURCE: which stream to play from, DEFAULT is special. Without qualifiers, it's assumed to be a tcp address.
|
|
//COMPRESSION: Suggests a compression method (multiple are allowed). You'll get a COMPRESSION response, and compression will begin with the binary data.
|
|
//SOURCELIST: Asks for a list of active sources from the proxy.
|
|
//DEMOLIST: Asks for a list of available mvd demos.
|
|
|
|
//Response:
|
|
//if using RAW, there will be no header or anything
|
|
//Otherwise you'll get a QTVSV %f response (%f being the protocol version being used)
|
|
//same structure, terminated by a \n
|
|
//AUTH: Server requires auth before proceeding. If you don't support the method the server says, then, urm, the server shouldn't have suggested it.
|
|
//CHALLENGE: used with auth
|
|
//COMPRESSION: Method of compression used. Compression begins with the raw data after the connection process.
|
|
//ASOURCE: names a source
|
|
//ADEMO: gives a demo file name
|
|
|
|
|
|
|
|
|
|
#include "qtv.h"
|
|
#include <string.h>
|
|
|
|
#include "bsd_string.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#define RECONNECT_TIME (1000*30)
|
|
#define RECONNECT_TIME_DEMO (1000*5)
|
|
#define UDPRECONNECT_TIME (1000)
|
|
#define STATUSPOLL_TIME 1000*30
|
|
#define PINGSINTERVAL_TIME (1000*5)
|
|
#define UDPTIMEOUT_LENGTH (1000*20)
|
|
#define UDPPACKETINTERVAL (1000/72)
|
|
|
|
void Net_SendConnectionMVD(sv_t *qtv, oproxy_t *prox);
|
|
void Net_QueueUpstream(sv_t *qtv, int size, char *buffer);
|
|
|
|
|
|
qboolean NET_StringToAddr (char *s, netadr_t *sadr, int defaultport)
|
|
{
|
|
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
|
|
#if 0//def IPPROTO_IPV6
|
|
if (getaddrinfo)
|
|
{//ipv6 method (can return ipv4 addresses too)
|
|
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);
|
|
strlcpy(dupbase, s, len);
|
|
error = getaddrinfo(dupbase, port+1, &udp6hint, &addrinfo);
|
|
}
|
|
else
|
|
error = EAI_NONAME;
|
|
if (error) //failed, try string with no port.
|
|
error = getaddrinfo(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
|
|
{ //old fashioned method
|
|
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
|
|
|
|
((struct sockaddr_in *)sadr)->sin_port = htons(defaultport);
|
|
|
|
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_TCPListen(int port, qboolean ipv6)
|
|
{
|
|
SOCKET sock;
|
|
|
|
struct sockaddr_in address4;
|
|
struct sockaddr_in6 address6;
|
|
struct sockaddr *address;
|
|
int prot;
|
|
int addrsize;
|
|
// int fromlen;
|
|
|
|
unsigned long nonblocking = true;
|
|
|
|
if (ipv6)
|
|
{
|
|
prot = PF_INET6;
|
|
memset(&address6, 0, sizeof(address6));
|
|
address6.sin6_family = AF_INET6;
|
|
address6.sin6_port = htons((u_short)port);
|
|
address = (struct sockaddr *)&address6;
|
|
addrsize = sizeof(struct sockaddr_in6);
|
|
}
|
|
else
|
|
{
|
|
prot = PF_INET;
|
|
address4.sin_family = AF_INET;
|
|
address4.sin_addr.s_addr = INADDR_ANY;
|
|
address4.sin_port = htons((u_short)port);
|
|
address = (struct sockaddr *)&address4;
|
|
addrsize = sizeof(struct sockaddr_in);
|
|
}
|
|
|
|
|
|
|
|
if ((sock = socket (prot, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
|
|
{
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1)
|
|
{
|
|
closesocket(sock);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if( bind (sock, address, addrsize) == -1)
|
|
{
|
|
printf("socket bind error %i (%s)\n", qerrno, strerror(qerrno));
|
|
closesocket(sock);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
listen(sock, 2); //don't listen for too many clients.
|
|
|
|
return sock;
|
|
}
|
|
|
|
char *strchrrev(char *str, char chr)
|
|
{
|
|
char *firstchar = str;
|
|
for (str = str + strlen(str)-1; str>=firstchar; str--)
|
|
if (*str == chr)
|
|
return str;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void Net_SendQTVConnectionRequest(sv_t *qtv, char *authmethod, char *challenge)
|
|
{
|
|
char *at;
|
|
char *str;
|
|
char hash[512];
|
|
|
|
//due to mvdsv sucking and stuff, we try using raw connections where possibleso that we don't end up expecting a header.
|
|
//at some point, this will be forced to 1
|
|
qtv->parsingqtvheader = true;//!!*qtv->connectpassword;
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
|
|
str = "QTV\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "VERSION: 1\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
|
|
if (qtv->serverquery)
|
|
{
|
|
if (qtv->serverquery == 2)
|
|
{
|
|
str = "DEMOLIST\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else
|
|
{
|
|
str = "SOURCELIST\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
at = strchrrev(qtv->server, '@');
|
|
if (at)
|
|
{
|
|
*at = '\0';
|
|
str = "SOURCE: "; Net_QueueUpstream(qtv, strlen(str), str);
|
|
|
|
if (strncmp(qtv->server, "tcp:", 4))
|
|
{
|
|
str = qtv->server;
|
|
Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else
|
|
{
|
|
str = strchr(qtv->server, ':');
|
|
if (str)
|
|
{
|
|
str++;
|
|
Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
}
|
|
|
|
str = "\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
*at = '@';
|
|
}
|
|
else
|
|
{
|
|
str = "RECEIVE\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
|
|
if (!qtv->parsingqtvheader)
|
|
{
|
|
str = "RAW: 1\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else
|
|
{
|
|
if (authmethod)
|
|
{
|
|
if (!strcmp(authmethod, "PLAIN"))
|
|
{
|
|
str = "AUTH: PLAIN\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "PASSWORD: \""; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = qtv->connectpassword; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "\"\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else if (challenge && strlen(challenge)>=32 && !strcmp(authmethod, "CCITT"))
|
|
{
|
|
unsigned short crcvalue;
|
|
str = "AUTH: CCITT\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "PASSWORD: \""; Net_QueueUpstream(qtv, strlen(str), str);
|
|
|
|
snprintf(hash, sizeof(hash), "%s%s", challenge, qtv->connectpassword);
|
|
crcvalue = QCRC_Block(hash, strlen(hash));
|
|
sprintf(hash, "0x%X", (unsigned int)QCRC_Value(crcvalue));
|
|
|
|
str = hash; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "\"\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else if (challenge && strlen(challenge)>=8 && !strcmp(authmethod, "MD4"))
|
|
{
|
|
unsigned int md4sum[4];
|
|
str = "AUTH: MD4\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "PASSWORD: \""; Net_QueueUpstream(qtv, strlen(str), str);
|
|
|
|
snprintf(hash, sizeof(hash), "%s%s", challenge, qtv->connectpassword);
|
|
Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum);
|
|
sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]);
|
|
|
|
str = hash; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "\"\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else if (!strcmp(authmethod, "NONE"))
|
|
{
|
|
str = "AUTH: NONE\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "PASSWORD: \n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
else
|
|
{
|
|
qtv->errored = ERR_PERMANENT;
|
|
qtv->upstreambuffersize = 0;
|
|
Sys_Printf(qtv->cluster, "Auth method %s was not usable\n", authmethod);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str = "AUTH: MD4\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "AUTH: CCITT\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "AUTH: PLAIN\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
str = "AUTH: NONE\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
}
|
|
}
|
|
str = "\n"; Net_QueueUpstream(qtv, strlen(str), str);
|
|
}
|
|
|
|
qboolean Net_ConnectToTCPServer(sv_t *qtv, char *ip)
|
|
{
|
|
int err;
|
|
netadr_t from;
|
|
unsigned long nonblocking = true;
|
|
int afam;
|
|
int pfam;
|
|
int asz;
|
|
|
|
if (!NET_StringToAddr(ip, &qtv->serveraddress, 27500))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Unable to resolve %s\n", qtv->streamid, ip);
|
|
strcpy(qtv->status, "Unable to resolve server\n");
|
|
return false;
|
|
}
|
|
afam = ((struct sockaddr*)&qtv->serveraddress)->sa_family;
|
|
pfam = ((afam==AF_INET6)?PF_INET6:PF_INET);
|
|
asz = ((afam==AF_INET6)?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in));
|
|
qtv->sourcesock = socket(pfam, SOCK_STREAM, IPPROTO_TCP);
|
|
if (qtv->sourcesock == INVALID_SOCKET)
|
|
{
|
|
strcpy(qtv->status, "Network error\n");
|
|
return false;
|
|
}
|
|
|
|
memset(&from, 0, sizeof(from));
|
|
((struct sockaddr*)&from)->sa_family = afam;
|
|
if (bind(qtv->sourcesock, (struct sockaddr *)&from, sizeof(from)) == -1)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
strcpy(qtv->status, "Network error\n");
|
|
return false;
|
|
}
|
|
|
|
if (ioctlsocket (qtv->sourcesock, FIONBIO, &nonblocking) == -1)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
strcpy(qtv->status, "Network error\n");
|
|
return false;
|
|
}
|
|
|
|
if (connect(qtv->sourcesock, (struct sockaddr *)&qtv->serveraddress, asz) == INVALID_SOCKET)
|
|
{
|
|
err = qerrno;
|
|
if (err != EINPROGRESS && err != EAGAIN && err != EWOULDBLOCK) //bsd sockets are meant to return EINPROGRESS, but some winsock drivers use EWOULDBLOCK instead. *sigh*...
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
strcpy(qtv->status, "Connection failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//make sure the buffers are empty. we could have disconnected prematurly
|
|
qtv->upstreambuffersize = 0;
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
|
|
//read the notes at the start of this file for what these text strings mean
|
|
Net_SendQTVConnectionRequest(qtv, NULL, NULL);
|
|
return true;
|
|
}
|
|
qboolean Net_ConnectToUDPServer(sv_t *qtv, char *ip)
|
|
{
|
|
netadr_t from;
|
|
unsigned long nonblocking = true;
|
|
int afam, pfam, asz;
|
|
|
|
if (!NET_StringToAddr(ip, &qtv->serveraddress, 27500))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Unable to resolve %s\n", qtv->streamid, ip);
|
|
return false;
|
|
}
|
|
afam = ((struct sockaddr*)&qtv->serveraddress)->sa_family;
|
|
pfam = ((afam==AF_INET6)?PF_INET6:PF_INET);
|
|
asz = ((afam==AF_INET6)?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in));
|
|
qtv->sourcesock = socket(pfam, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (qtv->sourcesock == INVALID_SOCKET)
|
|
return false;
|
|
|
|
memset(&from, 0, sizeof(from));
|
|
((struct sockaddr*)&from)->sa_family = afam;
|
|
if (bind(qtv->sourcesock, (struct sockaddr *)&from, asz) == -1)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
return false;
|
|
}
|
|
|
|
if (ioctlsocket (qtv->sourcesock, FIONBIO, &nonblocking) == -1)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
return false;
|
|
}
|
|
|
|
qtv->qport = Sys_Milliseconds()*1000+Sys_Milliseconds();
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean DemoFilenameIsOkay(char *fname)
|
|
{
|
|
int len;
|
|
if (strchr(fname, '/'))
|
|
return false; //unix path seperator
|
|
if (strchr(fname, '\\'))
|
|
return false; //windows path seperator
|
|
if (strchr(fname, ':'))
|
|
return false; //mac path seperator
|
|
|
|
//now make certain that the last four characters are '.mvd' and not something like '.cfg' perhaps
|
|
len = strlen(fname);
|
|
if (len < 5)
|
|
return false;
|
|
if (strcmp(fname+len-4, ".mvd"))
|
|
return false;
|
|
|
|
return true;
|
|
|
|
/*
|
|
if (strchr(fname, '\\'))
|
|
{
|
|
char *s;
|
|
Con_Printf("Warning: \\ characters in filename %s\n", fname);
|
|
while((s = strchr(fname, '\\')))
|
|
*s = '/';
|
|
}
|
|
|
|
if (strstr(fname, ".."))
|
|
{
|
|
Con_Printf("Error: '..' characters in filename %s\n", fname);
|
|
}
|
|
else if (fname[0] == '/')
|
|
{
|
|
Con_Printf("Error: absolute path in filename %s\n", fname);
|
|
}
|
|
else if (strstr(fname, ":")) //win32 drive seperator (or mac path seperator, but / works there and they're used to it)
|
|
{
|
|
Con_Printf("Error: absolute path in filename %s\n", fname);
|
|
}
|
|
else
|
|
return false;
|
|
return true;
|
|
*/
|
|
}
|
|
|
|
/*figures out the ip to connect to, and decides the protocol for it*/
|
|
char *Net_DiagnoseProtocol(sv_t *qtv)
|
|
{
|
|
char *at;
|
|
sourcetype_t type = SRC_BAD;
|
|
char *ip = qtv->server;
|
|
|
|
if (!strncmp(ip, "udp:", 4))
|
|
{
|
|
type = SRC_UDP;
|
|
ip += 4;
|
|
}
|
|
else if (!strncmp(ip, "tcp:", 4))
|
|
{
|
|
type = SRC_TCP;
|
|
ip += 4;
|
|
}
|
|
else if (!strncmp(ip, "demo:", 5))
|
|
{
|
|
type = SRC_DEMO;
|
|
ip += 5;
|
|
}
|
|
else if (!strncmp(ip, "file:", 5))
|
|
{
|
|
type = SRC_DEMO;
|
|
ip += 5;
|
|
}
|
|
|
|
at = strchrrev(ip, '@');
|
|
if (at && (type == SRC_DEMO || type == SRC_TCP))
|
|
{
|
|
if (type == SRC_DEMO)
|
|
type = SRC_TCP;
|
|
ip = at+1;
|
|
}
|
|
|
|
qtv->sourcetype = type;
|
|
return ip;
|
|
}
|
|
|
|
qboolean Net_ConnectToServer(sv_t *qtv)
|
|
{
|
|
char *ip = Net_DiagnoseProtocol(qtv);
|
|
|
|
qtv->usequakeworldprotocols = false;
|
|
|
|
if (qtv->sourcetype == SRC_DEMO)
|
|
qtv->nextconnectattempt = qtv->curtime + RECONNECT_TIME_DEMO; //wait half a minuite before trying to reconnect
|
|
else
|
|
qtv->nextconnectattempt = qtv->curtime + RECONNECT_TIME; //wait half a minuite before trying to reconnect
|
|
|
|
switch( qtv->sourcetype)
|
|
{
|
|
case SRC_DEMO:
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
if (DemoFilenameIsOkay(ip))
|
|
{
|
|
char fullname[512];
|
|
snprintf(fullname, sizeof(fullname), "%s%s", qtv->cluster->demodir, ip);
|
|
qtv->sourcefile = fopen(fullname, "rb");
|
|
}
|
|
else
|
|
qtv->sourcefile = NULL;
|
|
if (qtv->sourcefile)
|
|
{
|
|
char smallbuffer[17];
|
|
fseek(qtv->sourcefile, 0, SEEK_END);
|
|
qtv->filelength = ftell(qtv->sourcefile);
|
|
|
|
//attempt to detect the end of the file
|
|
fseek(qtv->sourcefile, 0-sizeof(smallbuffer), SEEK_CUR);
|
|
fread(smallbuffer, 1, 17, qtv->sourcefile);
|
|
//0 is the time
|
|
if (smallbuffer[1] == dem_all || smallbuffer[1] == dem_read) //mvdsv changed it to read...
|
|
{
|
|
//2,3,4,5 are the length
|
|
if (smallbuffer[6] == svc_disconnect)
|
|
{
|
|
if (!strcmp(smallbuffer+7, "EndOfDemo"))
|
|
{
|
|
qtv->filelength -= 17;
|
|
}
|
|
}
|
|
}
|
|
|
|
fseek(qtv->sourcefile, 0, SEEK_SET);
|
|
return true;
|
|
}
|
|
Sys_Printf(qtv->cluster, "Stream %i: Unable to open file %s\n", qtv->streamid, ip);
|
|
return false;
|
|
|
|
|
|
case SRC_UDP:
|
|
qtv->usequakeworldprotocols = true;
|
|
return Net_ConnectToUDPServer(qtv, ip);
|
|
|
|
case SRC_TCP:
|
|
return Net_ConnectToTCPServer(qtv, ip);
|
|
|
|
default:
|
|
Sys_Printf(qtv->cluster, "Unknown source type %s\n", ip);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Net_QueueUpstream(sv_t *qtv, int size, char *buffer)
|
|
{
|
|
if (qtv->usequakeworldprotocols)
|
|
return;
|
|
|
|
if (qtv->upstreambuffersize + size > sizeof(qtv->upstreambuffer))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Upstream queue overflowed for %s\n", qtv->streamid, qtv->server);
|
|
strcpy(qtv->status, "Upstream overflow");
|
|
qtv->errored = ERR_RECONNECT;
|
|
return;
|
|
}
|
|
memcpy(qtv->upstreambuffer + qtv->upstreambuffersize, buffer, size);
|
|
qtv->upstreambuffersize += size;
|
|
}
|
|
|
|
qboolean Net_WriteUpstream(sv_t *qtv)
|
|
{
|
|
int len;
|
|
|
|
if (qtv->upstreambuffersize && qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
len = send(qtv->sourcesock, qtv->upstreambuffer, qtv->upstreambuffersize, 0);
|
|
if (len == 0)
|
|
return false;
|
|
if (len < 0)
|
|
{
|
|
int err = qerrno;
|
|
if (err != EWOULDBLOCK && err != EAGAIN && err != ENOTCONN)
|
|
{
|
|
int err;
|
|
err = qerrno;
|
|
if (qerrno)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: source socket error %i (%s)\n", qtv->streamid, qerrno, strerror(qerrno));
|
|
strcpy(qtv->status, "Network error\n");
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: server %s disconnected\n", qtv->streamid, qtv->server);
|
|
strcpy(qtv->status, "Server disconnected");
|
|
}
|
|
qtv->errored = ERR_RECONNECT; //if the server is down, we'll detect it on reconnect
|
|
}
|
|
return false;
|
|
}
|
|
qtv->upstreambuffersize -= len;
|
|
memmove(qtv->upstreambuffer, qtv->upstreambuffer + len, qtv->upstreambuffersize);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SV_SendUpstream(sv_t *qtv, netmsg_t *nm)
|
|
{
|
|
char size[2];
|
|
|
|
size[0] = (nm->cursize&0x00ff)>>0;
|
|
size[1] = (nm->cursize&0xff00)>>8;
|
|
Net_QueueUpstream(qtv, 2, size);
|
|
Net_QueueUpstream(qtv, nm->cursize, nm->data);
|
|
Net_WriteUpstream(qtv); //try and flush it
|
|
}
|
|
|
|
int SV_SayToUpstream(sv_t *qtv, char *message)
|
|
{
|
|
char buffer[1024];
|
|
netmsg_t nm;
|
|
|
|
if (!qtv->upstreamacceptschat)
|
|
{
|
|
#ifndef _MSC_VER
|
|
#warning This is incomplete!
|
|
#endif
|
|
//Sys_Printf(qtv->cluster, "not forwarding say\n");
|
|
return 0;
|
|
}
|
|
|
|
InitNetMsg(&nm, buffer, sizeof(buffer));
|
|
|
|
WriteByte(&nm, qtv_clc_stringcmd);
|
|
WriteString2(&nm, "say ");
|
|
WriteString(&nm, message);
|
|
SV_SendUpstream(qtv, &nm);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void SV_SayToViewers(sv_t *qtv, char *message)
|
|
{
|
|
Fwd_SayToDownstream(qtv, message);
|
|
#ifndef _MSC_VER
|
|
#warning Send to viewers here too
|
|
#endif
|
|
}
|
|
|
|
//This function 1: parses the 'don't delay' packets in the stream
|
|
// 2: returns the length of continuous data (that is, whole-packet bytes that have not been truncated by the networking layer)
|
|
// this means we know that the client proxies have valid data, at least from our side.
|
|
int SV_EarlyParse(sv_t *qtv, unsigned char *buffer, int remaining)
|
|
{
|
|
int lengthofs;
|
|
int length;
|
|
int available = 0;
|
|
while(1)
|
|
{
|
|
if (remaining < 2)
|
|
return available;
|
|
|
|
//buffer[0] is time
|
|
|
|
switch (buffer[1]&dem_mask)
|
|
{
|
|
case dem_set:
|
|
lengthofs = 0; //to silence gcc, nothing more
|
|
break;
|
|
case dem_multiple:
|
|
lengthofs = 6;
|
|
break;
|
|
default:
|
|
lengthofs = 2;
|
|
break;
|
|
}
|
|
|
|
if (lengthofs > 0)
|
|
{
|
|
if (lengthofs+4 > remaining)
|
|
return available;
|
|
|
|
length = (buffer[lengthofs]<<0) + (buffer[lengthofs+1]<<8) + (buffer[lengthofs+2]<<16) + (buffer[lengthofs+3]<<24);
|
|
|
|
length += lengthofs+4;
|
|
if (length > MAX_MSGLEN)
|
|
printf("Probably corrupt mvd (length %i)\n", length);
|
|
}
|
|
else
|
|
length = 10;
|
|
|
|
if (remaining < length)
|
|
return available;
|
|
|
|
if ((buffer[1]&dem_mask) == dem_all && (buffer[1] & ~dem_mask) && qtv->sourcetype != SRC_DEMO) //dem_qtvdata
|
|
{
|
|
ParseMessage(qtv, buffer+lengthofs+4, length - (lengthofs+4), buffer[1], 0xffffffff);
|
|
}
|
|
|
|
remaining -= length;
|
|
available += length;
|
|
buffer += length;
|
|
}
|
|
}
|
|
|
|
qboolean Net_ReadStream(sv_t *qtv)
|
|
{
|
|
int maxreadable;
|
|
int read;
|
|
void *buffer;
|
|
int err;
|
|
|
|
maxreadable = MAX_PROXY_BUFFER - qtv->buffersize;
|
|
if (!maxreadable)
|
|
return true; //this is bad!
|
|
buffer = qtv->buffer + qtv->buffersize;
|
|
|
|
if (qtv->sourcefile)
|
|
{
|
|
if (maxreadable > PREFERRED_PROXY_BUFFER-qtv->buffersize)
|
|
maxreadable = PREFERRED_PROXY_BUFFER-qtv->buffersize;
|
|
if (maxreadable<=0)
|
|
return true;
|
|
|
|
//reuse read a little...
|
|
read = ftell(qtv->sourcefile);
|
|
if (read+maxreadable > qtv->filelength)
|
|
maxreadable = qtv->filelength-read; //clamp to the end of the file
|
|
//even if that 'end' is before the svc_disconnect
|
|
|
|
read = fread(buffer, 1, maxreadable, qtv->sourcefile);
|
|
}
|
|
else
|
|
{
|
|
unsigned int errsize;
|
|
errsize = sizeof(err);
|
|
err = 0;
|
|
getsockopt(qtv->sourcesock, SOL_SOCKET, SO_ERROR, (char*)&err, &errsize);
|
|
if (err == ECONNREFUSED)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: server %s refused connection\n", qtv->streamid, qtv->server);
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
qtv->upstreambuffersize = 0; //probably contains initial connection request info
|
|
return false;
|
|
}
|
|
|
|
read = recv(qtv->sourcesock, buffer, maxreadable, 0);
|
|
}
|
|
if (read > 0)
|
|
{
|
|
qtv->buffersize += read;
|
|
if (!qtv->cluster->lateforward && !qtv->parsingqtvheader) //qtv header being the auth part of the connection rather than the stream
|
|
{
|
|
int forwardable;
|
|
//this has the effect of not only parsing early packets, but also saying how much complete data there is.
|
|
forwardable = SV_EarlyParse(qtv, qtv->buffer+qtv->forwardpoint, qtv->buffersize - qtv->forwardpoint);
|
|
if (forwardable > 0)
|
|
{
|
|
SV_ForwardStream(qtv, qtv->buffer+qtv->forwardpoint, forwardable);
|
|
qtv->forwardpoint += forwardable;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (read == 0)
|
|
err = 0;
|
|
else
|
|
err = qerrno;
|
|
if (read == 0 || (err != EWOULDBLOCK && err != EAGAIN && err != ENOTCONN)) //ENOTCONN can be returned whilst waiting for a connect to finish.
|
|
{
|
|
if (qtv->sourcefile)
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: End of file\n", qtv->streamid);
|
|
else if (read)
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: source socket error %i (%s)\n", qtv->streamid, qerrno, strerror(qerrno));
|
|
else
|
|
Sys_Printf(qtv->cluster, "Stream %i: Error: server %s disconnected\n", qtv->streamid, qtv->server);
|
|
if (qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
}
|
|
if (qtv->sourcefile)
|
|
{
|
|
fclose(qtv->sourcefile);
|
|
qtv->sourcefile = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define BUFFERTIME 10 //secords for artificial delay, so we can buffer things properly.
|
|
|
|
unsigned int Sys_Milliseconds(void)
|
|
{
|
|
#ifdef _WIN32
|
|
#ifdef _MSC_VER
|
|
#pragma comment(lib, "winmm.lib")
|
|
#endif
|
|
|
|
#if 0
|
|
static firsttime = 1;
|
|
static starttime;
|
|
if (firsttime)
|
|
{
|
|
starttime = timeGetTime() + 1000*20;
|
|
firsttime = 0;
|
|
}
|
|
return timeGetTime() - starttime;
|
|
#endif
|
|
|
|
|
|
|
|
return timeGetTime();
|
|
#else
|
|
//assume every other system follows standards.
|
|
unsigned int t;
|
|
struct timeval tv;
|
|
|
|
gettimeofday(&tv, NULL);
|
|
t = ((unsigned int)tv.tv_sec)*1000 + (((unsigned int)tv.tv_usec)/1000);
|
|
return t;
|
|
#endif
|
|
}
|
|
/*
|
|
void NetSleep(sv_t *tv)
|
|
{
|
|
int m;
|
|
struct timeval timeout;
|
|
fd_set socketset;
|
|
|
|
FD_ZERO(&socketset);
|
|
m = 0;
|
|
if (tv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
FD_SET(tv->sourcesock, &socketset);
|
|
if (tv->sourcesock >= m)
|
|
m = tv->sourcesock+1;
|
|
}
|
|
if (tv->qwdsocket != INVALID_SOCKET)
|
|
{
|
|
FD_SET(tv->qwdsocket, &socketset);
|
|
if (tv->sourcesock >= m)
|
|
m = tv->sourcesock+1;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
#ifndef STDIN
|
|
#define STDIN 0
|
|
#endif
|
|
FD_SET(STDIN, &socketset);
|
|
if (STDIN >= m)
|
|
m = STDIN+1;
|
|
#endif
|
|
|
|
timeout.tv_sec = 100/1000;
|
|
timeout.tv_usec = (100%1000)*1000;
|
|
|
|
select(m, &socketset, NULL, NULL, &timeout);
|
|
|
|
#ifdef _WIN32
|
|
for (;;)
|
|
{
|
|
char buffer[8192];
|
|
char *result;
|
|
char c;
|
|
|
|
if (!kbhit())
|
|
break;
|
|
c = getch();
|
|
|
|
if (c == '\n' || c == '\r')
|
|
{
|
|
printf("\n");
|
|
if (tv->inputlength)
|
|
{
|
|
tv->commandinput[tv->inputlength] = '\0';
|
|
result = Rcon_Command(tv->cluster, tv, tv->commandinput, buffer, sizeof(buffer), true);
|
|
printf("%s", result);
|
|
tv->inputlength = 0;
|
|
tv->commandinput[0] = '\0';
|
|
}
|
|
}
|
|
else if (c == '\b')
|
|
{
|
|
if (tv->inputlength > 0)
|
|
{
|
|
tv->inputlength--;
|
|
tv->commandinput[tv->inputlength] = '\0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tv->inputlength < sizeof(tv->commandinput)-1)
|
|
{
|
|
tv->commandinput[tv->inputlength++] = c;
|
|
tv->commandinput[tv->inputlength] = '\0';
|
|
}
|
|
}if (FD_ISSET(STDIN, &socketset))
|
|
printf("\r%s \b", tv->commandinput);
|
|
}
|
|
#else
|
|
if (FD_ISSET(STDIN, &socketset))
|
|
{
|
|
char buffer[8192];
|
|
char *result;
|
|
tv->inputlength = read (0, tv->commandinput, sizeof(tv->commandinput));
|
|
if (tv->inputlength >= 1)
|
|
{
|
|
tv->commandinput[tv->inputlength-1] = 0; // rip off the /n and terminate
|
|
|
|
if (tv->inputlength)
|
|
{
|
|
tv->commandinput[tv->inputlength] = '\0';
|
|
result = Rcon_Command(tv, tv->commandinput, buffer, sizeof(buffer), true);
|
|
printf("%s", result);
|
|
tv->inputlength = 0;
|
|
tv->commandinput[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
*/
|
|
|
|
void Trim(char *s)
|
|
{
|
|
char *f;
|
|
f = s;
|
|
while(*f <= ' ' && *f > '\0')
|
|
f++;
|
|
while(*f > ' ')
|
|
*s++ = *f++;
|
|
*s = '\0';
|
|
}
|
|
|
|
qboolean QTV_Connect(sv_t *qtv, char *serverurl)
|
|
{
|
|
if (qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
}
|
|
|
|
if (qtv->sourcefile)
|
|
{
|
|
fclose(qtv->sourcefile);
|
|
qtv->sourcefile = NULL;
|
|
}
|
|
|
|
memcpy(qtv->server, serverurl, sizeof(qtv->server)-1);
|
|
if (qtv->autodisconnect == AD_STATUSPOLL && !qtv->numviewers && !qtv->proxies)
|
|
{
|
|
char *ip;
|
|
ip = Net_DiagnoseProtocol(qtv);
|
|
if (!NET_StringToAddr(ip, &qtv->serveraddress, 27500))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Unable to resolve %s\n", qtv->streamid, ip);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
*qtv->map.serverinfo = '\0';
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "*version", "FTEQTV", sizeof(qtv->map.serverinfo));
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "*qtv", VERSION, sizeof(qtv->map.serverinfo));
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "hostname", qtv->cluster->hostname, sizeof(qtv->map.serverinfo));
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "maxclients", "99", sizeof(qtv->map.serverinfo));
|
|
if (!strncmp(qtv->server, "file:", 5))
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "server", "file", sizeof(qtv->map.serverinfo));
|
|
else
|
|
Info_SetValueForStarKey(qtv->map.serverinfo, "server", qtv->server, sizeof(qtv->map.serverinfo));
|
|
|
|
if (qtv->autodisconnect == AD_REVERSECONNECT)
|
|
{ //added because of paranoia rather than need. Should never occur.
|
|
printf("bug: autoclose==2\n");
|
|
strcpy(qtv->status, "Network error\n");
|
|
return false;
|
|
}
|
|
else if (!Net_ConnectToServer(qtv))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Couldn't connect (%s)\n", qtv->streamid, qtv->server);
|
|
return false;
|
|
}
|
|
|
|
if (qtv->sourcesock == INVALID_SOCKET)
|
|
{
|
|
qtv->parsetime = Sys_Milliseconds();
|
|
// Sys_Printf(qtv->cluster, "Stream %i: Playing from file\n", qtv->streamid);
|
|
}
|
|
else
|
|
{
|
|
qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QTV_CleanupMap(sv_t *qtv)
|
|
{
|
|
int i;
|
|
|
|
//free the bsp
|
|
BSP_Free(qtv->map.bsp);
|
|
qtv->map.bsp = NULL;
|
|
|
|
//clean up entity state
|
|
for (i = 0; i < ENTITY_FRAMES; i++)
|
|
{
|
|
if (qtv->map.frame[i].ents)
|
|
{
|
|
free(qtv->map.frame[i].ents);
|
|
qtv->map.frame[i].ents = NULL;
|
|
}
|
|
if (qtv->map.frame[i].entnums)
|
|
{
|
|
free(qtv->map.frame[i].entnums);
|
|
qtv->map.frame[i].entnums = NULL;
|
|
}
|
|
}
|
|
memset(&qtv->map, 0, sizeof(qtv->map));
|
|
}
|
|
|
|
void QTV_DisconnectFromSource(sv_t *qtv)
|
|
{
|
|
// close the source handle
|
|
if (qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
if (qtv->usequakeworldprotocols)
|
|
{
|
|
char dying[] = {clc_stringcmd, 'd', 'r', 'o', 'p', '\0'};
|
|
Netchan_Transmit (qtv->cluster, &qtv->netchan, sizeof(dying), dying);
|
|
Netchan_Transmit (qtv->cluster, &qtv->netchan, sizeof(dying), dying);
|
|
Netchan_Transmit (qtv->cluster, &qtv->netchan, sizeof(dying), dying);
|
|
}
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
}
|
|
if (qtv->sourcefile)
|
|
{
|
|
fclose(qtv->sourcefile);
|
|
qtv->sourcefile = NULL;
|
|
}
|
|
|
|
//cancel downloads
|
|
if (qtv->downloadfile)
|
|
{
|
|
fclose(qtv->downloadfile);
|
|
qtv->downloadfile = NULL;
|
|
unlink(qtv->downloadname);
|
|
*qtv->downloadname = '\0';
|
|
}
|
|
}
|
|
|
|
void QTV_Cleanup(sv_t *qtv, qboolean leaveadmins)
|
|
{ //disconnects the stream
|
|
viewer_t *v;
|
|
cluster_t *cluster;
|
|
oproxy_t *prox;
|
|
oproxy_t *old;
|
|
|
|
cluster = qtv->cluster;
|
|
|
|
//set connected viewers to a different stream
|
|
if (cluster->viewserver == qtv)
|
|
cluster->viewserver = NULL;
|
|
for (v = cluster->viewers; v; v = v->next)
|
|
{
|
|
//warning fixme: honour leaveadmins
|
|
if (v->server == qtv)
|
|
{ //they were watching this one
|
|
QW_SetViewersServer(qtv->cluster, v, NULL);
|
|
QW_SetMenu(v, MENU_NONE);
|
|
QTV_SayCommand(cluster, v->server, v, "menu");
|
|
QW_PrintfToViewer(v, "Stream %s is closing\n", qtv->server);
|
|
}
|
|
}
|
|
|
|
QTV_DisconnectFromSource(qtv);
|
|
|
|
QTV_CleanupMap(qtv);
|
|
|
|
//boot connected downstream proxies
|
|
for (prox = qtv->proxies; prox; )
|
|
{
|
|
if (prox->file)
|
|
fclose(prox->file);
|
|
if (prox->sock != INVALID_SOCKET)
|
|
closesocket(prox->sock);
|
|
old = prox;
|
|
prox = prox->next;
|
|
free(old);
|
|
cluster->numproxies--;
|
|
}
|
|
}
|
|
|
|
void QTV_Shutdown(sv_t *qtv)
|
|
{
|
|
sv_t *peer;
|
|
cluster_t *cluster;
|
|
Sys_Printf(qtv->cluster, "Stream %i: Closing source %s\n", qtv->streamid, qtv->server);
|
|
|
|
QTV_Cleanup(qtv, false);
|
|
|
|
//unlink it
|
|
cluster = qtv->cluster;
|
|
if (cluster->servers == qtv)
|
|
cluster->servers = qtv->next;
|
|
else
|
|
{
|
|
for (peer = cluster->servers; peer->next; peer = peer->next)
|
|
{
|
|
if (peer->next == qtv)
|
|
{
|
|
peer->next = qtv->next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(qtv);
|
|
cluster->numservers--;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SendClientCommand(sv_t *qtv, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char buf[1024];
|
|
|
|
va_start (argptr, fmt);
|
|
vsnprintf (buf, sizeof(buf), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
WriteByte(&qtv->netchan.message, clc_stringcmd);
|
|
WriteString(&qtv->netchan.message, buf);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void ChooseFavoriteTrack(sv_t *tv)
|
|
{
|
|
int frags, best, pnum;
|
|
char buffer[64];
|
|
|
|
frags = -10000;
|
|
best = -1;
|
|
if (tv->controller || tv->proxyplayer)
|
|
best = tv->map.trackplayer;
|
|
else
|
|
{
|
|
for (pnum = 0; pnum < MAX_CLIENTS; pnum++)
|
|
{
|
|
if (*tv->map.players[pnum].userinfo && !atoi(Info_ValueForKey(tv->map.players[pnum].userinfo, "*spectator", buffer, sizeof(buffer))))
|
|
{
|
|
if (tv->map.thisplayer == pnum)
|
|
continue;
|
|
if (frags < tv->map.players[pnum].frags)
|
|
{
|
|
best = pnum;
|
|
frags = tv->map.players[pnum].frags;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (best != tv->map.trackplayer)
|
|
{
|
|
SendClientCommand (tv, "ptrack %i\n", best);
|
|
tv->map.trackplayer = best;
|
|
|
|
if (tv->usequakeworldprotocols)
|
|
QW_StreamStuffcmd(tv->cluster, tv, "track %i\n", best);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const unsigned char chktbl[1024] = {
|
|
0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01,
|
|
0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a,
|
|
0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58,
|
|
0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3,
|
|
0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b,
|
|
0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36,
|
|
0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8,
|
|
0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27,
|
|
0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd,
|
|
0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63,
|
|
0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2,
|
|
0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d,
|
|
0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65,
|
|
0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f,
|
|
0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f,
|
|
0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42,
|
|
0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59,
|
|
0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9,
|
|
0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69,
|
|
0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba,
|
|
0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85,
|
|
0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4,
|
|
0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8,
|
|
0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa,
|
|
0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05,
|
|
0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7,
|
|
0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a,
|
|
0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb,
|
|
0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b,
|
|
0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d,
|
|
0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce,
|
|
0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3
|
|
};
|
|
|
|
|
|
unsigned char COM_BlockSequenceCRCByte (void *base, int length, int sequence)
|
|
{
|
|
unsigned short crc;
|
|
const unsigned char *p;
|
|
unsigned char chkb[60 + 4];
|
|
|
|
p = chktbl + (sequence % (sizeof(chktbl) - 4));
|
|
|
|
if (length > 60)
|
|
length = 60;
|
|
memcpy (chkb, base, length);
|
|
|
|
chkb[length] = (sequence & 0xff) ^ p[0];
|
|
chkb[length+1] = p[1];
|
|
chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2];
|
|
chkb[length+3] = p[3];
|
|
|
|
length += 4;
|
|
|
|
crc = QCRC_Block(chkb, length);
|
|
|
|
crc &= 0xff;
|
|
|
|
return crc;
|
|
}
|
|
void SetMoveCRC(sv_t *qtv, netmsg_t *msg)
|
|
{
|
|
char *outbyte;
|
|
outbyte = (char*)msg->data + msg->startpos+1;
|
|
|
|
*outbyte = COM_BlockSequenceCRCByte(
|
|
outbyte+1, msg->cursize - (msg->startpos+2),
|
|
qtv->netchan.outgoing_sequence);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void QTV_ParseQWStream(sv_t *qtv)
|
|
{
|
|
char buffer[1500];
|
|
netadr_t from;
|
|
unsigned int fromlen;
|
|
int readlen;
|
|
netmsg_t msg;
|
|
fromlen = sizeof(from); //bug: this won't work on (free)bsd
|
|
|
|
for (;;)
|
|
{
|
|
readlen = recvfrom(qtv->sourcesock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&from, &fromlen);
|
|
if (readlen < 0)
|
|
{
|
|
//FIXME: Check for error
|
|
break;
|
|
}
|
|
if (readlen > sizeof(buffer)-1)
|
|
break; //oversized!
|
|
|
|
buffer[readlen] = 0;
|
|
if (*(int*)buffer == -1)
|
|
{
|
|
if (buffer[4] == 'c')
|
|
{ //got a challenge
|
|
strcpy(qtv->status, "Attemping connection\n");
|
|
qtv->challenge = atoi(buffer+5);
|
|
if (qtv->controller)
|
|
sprintf(buffer, "connect %i %i %i \"%s\\*qtv\\1\\Qizmo\\2.9 notimer\"", 28, qtv->qport, qtv->challenge, qtv->controller->userinfo);
|
|
else if (qtv->proxyplayer)
|
|
sprintf(buffer, "connect %i %i %i \"%s\\name\\%s\"", 28, qtv->qport, qtv->challenge, "\\*ver\\fteqtv\\spectator\\0\\rate\\10000", qtv->cluster->hostname);
|
|
else
|
|
sprintf(buffer, "connect %i %i %i \"%s\\name\\%s\"", 28, qtv->qport, qtv->challenge, "\\*ver\\fteqtv\\spectator\\1\\rate\\10000", qtv->cluster->hostname);
|
|
Netchan_OutOfBand(qtv->cluster, qtv->sourcesock, qtv->serveraddress, strlen(buffer), buffer);
|
|
continue;
|
|
}
|
|
if (buffer[4] == 'n')
|
|
{
|
|
strlcpy(qtv->status, buffer+5, sizeof(qtv->status));
|
|
Sys_Printf(qtv->cluster, "%s: %s", qtv->server, buffer+5);
|
|
continue;
|
|
}
|
|
if (buffer[4] == 'j')
|
|
{
|
|
strcpy(qtv->status, "Waiting for gamestate\n");
|
|
Netchan_Setup(qtv->sourcesock, &qtv->netchan, qtv->serveraddress, qtv->qport, true);
|
|
|
|
qtv->map.trackplayer = -1;
|
|
|
|
qtv->isconnected = true;
|
|
qtv->timeout = qtv->curtime + UDPTIMEOUT_LENGTH;
|
|
SendClientCommand(qtv, "new");
|
|
Sys_Printf(qtv->cluster, "Stream %i: Connected!\n", qtv->streamid);
|
|
continue;
|
|
}
|
|
Sys_Printf(qtv->cluster, "Stream %i: %s: unrecognized connectionless packet:\n%s\n", qtv->streamid, qtv->server, buffer+4);
|
|
continue;
|
|
}
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.cursize = readlen;
|
|
msg.data = buffer;
|
|
msg.maxsize = readlen;
|
|
qtv->timeout = qtv->curtime + UDPTIMEOUT_LENGTH;
|
|
if (!Netchan_Process(&qtv->netchan, &msg))
|
|
continue;
|
|
ParseMessage(qtv, (char*)msg.data + msg.readpos, msg.cursize - msg.readpos, dem_all, -1);
|
|
|
|
qtv->oldpackettime = qtv->nextpackettime;
|
|
qtv->nextpackettime = qtv->parsetime;
|
|
qtv->parsetime = qtv->curtime;
|
|
|
|
if (qtv->simtime < qtv->oldpackettime)
|
|
qtv->simtime = qtv->oldpackettime; //too old
|
|
|
|
if (qtv->controller)
|
|
{
|
|
qtv->controller->maysend = true;
|
|
//if (qtv->controller->netchan.outgoing_sequence != qtv->controller->netchan.incoming_sequence)
|
|
//printf("bug is here\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef COMMENTARY
|
|
#include <speex/speex.h>
|
|
#endif
|
|
|
|
void QTV_CollectCommentry(sv_t *qtv)
|
|
{
|
|
#define usespeex 0
|
|
#ifdef COMMENTARY
|
|
int samps;
|
|
unsigned char buffer[8192+6];
|
|
unsigned char *uchar;
|
|
signed char *schar;
|
|
int bytesleft;
|
|
if (!qtv->comentrycapture)
|
|
{
|
|
if (0)
|
|
{
|
|
// if (usespeex)
|
|
// qtv->comentrycapture = SND_InitCapture(11025, 16);
|
|
// else
|
|
qtv->comentrycapture = SND_InitCapture(11025, 8);
|
|
}
|
|
return;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
//the protocol WILL be different. Don't add compatibility for this code.
|
|
buffer[0] = 0;
|
|
buffer[1] = dem_audio;
|
|
buffer[2] = 255;
|
|
buffer[3] = 255;
|
|
buffer[4] = 8;
|
|
buffer[5] = 11*5;
|
|
|
|
/* if (usespeex)
|
|
{
|
|
|
|
SpeexBits bits;
|
|
void *enc_state;
|
|
|
|
int frame_size;
|
|
|
|
spx_int16_t pcmdata[8192/2];
|
|
|
|
samps=qtv->comentrycapture->update(qtv->comentrycapture, 2048, (char*)pcmdata);
|
|
|
|
|
|
speex_bits_init(&bits);
|
|
|
|
enc_state = speex_encoder_init(&speex_nb_mode);
|
|
|
|
|
|
speex_encoder_ctl(enc_state,SPEEX_GET_FRAME_SIZE,&frame_size);
|
|
|
|
|
|
speex_bits_reset(&bits);
|
|
|
|
speex_encode_int(enc_state, (spx_int16_t*)pcmdata, &bits);
|
|
|
|
samps = speex_bits_write(&bits, buffer+6, sizeof(buffer)-6);
|
|
|
|
|
|
speex_bits_destroy(&bits);
|
|
|
|
speex_encoder_destroy(enc_state);
|
|
|
|
}
|
|
else*/
|
|
{
|
|
samps=qtv->comentrycapture->update(qtv->comentrycapture, 2048, buffer+6);
|
|
|
|
bytesleft = samps;
|
|
schar = buffer+6;
|
|
uchar = buffer+6;
|
|
while(bytesleft-->0)
|
|
{
|
|
*schar++ = *uchar++ - 128;
|
|
}
|
|
}
|
|
|
|
buffer[2] = samps&255;
|
|
buffer[3] = samps>>8;
|
|
|
|
if (samps)
|
|
SV_ForwardStream(qtv, buffer, 6 + samps);
|
|
|
|
if (samps < 64)
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QTV_Run(sv_t *qtv)
|
|
{
|
|
int from;
|
|
int to;
|
|
int lengthofs;
|
|
unsigned int length;
|
|
unsigned char *buffer;
|
|
int oldcurtime;
|
|
int packettime;
|
|
|
|
if (qtv->numviewers == 0 && qtv->proxies == NULL)
|
|
{
|
|
if (qtv->autodisconnect == AD_WHENEMPTY)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: %s became inactive\n", qtv->streamid, qtv->server);
|
|
qtv->errored = ERR_DROP;
|
|
}
|
|
else if (qtv->autodisconnect == AD_STATUSPOLL && qtv->isconnected)
|
|
{
|
|
/*switch to status polling instead of packet spamming*/
|
|
qtv->errored = ERR_RECONNECT;
|
|
}
|
|
}
|
|
if (qtv->errored)
|
|
{
|
|
if (qtv->errored == ERR_DISABLED)
|
|
{
|
|
//this keeps any connected proxies ticking over.
|
|
//probably we should drop them instead - the connection will only be revived if one of them reconnects.
|
|
SV_ForwardStream(qtv, NULL, 0);
|
|
return;
|
|
}
|
|
else if (qtv->errored == ERR_PERMANENT)
|
|
{
|
|
QTV_Cleanup(qtv, false); //frees various pieces of context
|
|
qtv->errored = ERR_DISABLED;
|
|
return;
|
|
}
|
|
else if (qtv->errored == ERR_DROP)
|
|
{
|
|
QTV_Shutdown(qtv); //destroys the stream
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//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)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Time wrapped\n");
|
|
qtv->parsetime = qtv->curtime;
|
|
}
|
|
|
|
|
|
if (qtv->errored == ERR_PAUSED)
|
|
{
|
|
if (!qtv->parsingconnectiondata)
|
|
qtv->parsetime = qtv->curtime;
|
|
}
|
|
|
|
if (qtv->errored == ERR_RECONNECT)
|
|
{
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
QTV_DisconnectFromSource(qtv);
|
|
qtv->isconnected = 0;
|
|
qtv->errored = ERR_NONE;
|
|
qtv->nextconnectattempt = qtv->curtime; //make the reconnect happen _now_
|
|
}
|
|
|
|
|
|
if (qtv->sourcetype == SRC_UDP)
|
|
{
|
|
qtv->simtime += qtv->curtime - oldcurtime;
|
|
|
|
if (qtv->simtime > qtv->nextpackettime)
|
|
qtv->simtime = qtv->nextpackettime; //too old
|
|
|
|
if (!qtv->isconnected && (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - (UDPRECONNECT_TIME+STATUSPOLL_TIME)))
|
|
{
|
|
if (qtv->errored == ERR_DISABLED)
|
|
{
|
|
strcpy(qtv->status, "Given up connecting\n");
|
|
}
|
|
else if (qtv->autodisconnect == AD_STATUSPOLL)
|
|
{
|
|
QTV_DisconnectFromSource(qtv);
|
|
Netchan_OutOfBand(qtv->cluster, NET_ChooseSocket(qtv->cluster->qwdsocket, &qtv->serveraddress), qtv->serveraddress, 13, "status\n");
|
|
qtv->nextconnectattempt = qtv->curtime + STATUSPOLL_TIME;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
strcpy(qtv->status, "Attemping challenge\n");
|
|
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
|
|
{
|
|
if (!QTV_Connect(qtv, qtv->server)) //reconnect it
|
|
{
|
|
qtv->errored = ERR_PERMANENT;
|
|
}
|
|
}
|
|
if (qtv->errored == ERR_NONE)
|
|
Netchan_OutOfBand(qtv->cluster, qtv->sourcesock, qtv->serveraddress, 13, "getchallenge\n");
|
|
}
|
|
qtv->nextconnectattempt = qtv->curtime + UDPRECONNECT_TIME;
|
|
}
|
|
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
|
|
return;
|
|
|
|
QTV_ParseQWStream(qtv);
|
|
|
|
if (qtv->isconnected)
|
|
{
|
|
char buffer[128];
|
|
netmsg_t msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.data = buffer;
|
|
msg.maxsize = sizeof(buffer);
|
|
|
|
if (qtv->curtime >= qtv->timeout || qtv->curtime < qtv->timeout - UDPTIMEOUT_LENGTH*2)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Timeout\n", qtv->streamid);
|
|
qtv->isconnected = false;
|
|
return;
|
|
}
|
|
|
|
if (qtv->controller && !qtv->controller->netchan.isnqprotocol)
|
|
{
|
|
qtv->netchan.outgoing_sequence = qtv->controller->netchan.incoming_sequence;
|
|
if (qtv->maysend)
|
|
{
|
|
qtv->maysend = false;
|
|
qtv->packetratelimiter = qtv->curtime;
|
|
}
|
|
else
|
|
qtv->packetratelimiter = qtv->curtime + 1;
|
|
}
|
|
else
|
|
{
|
|
if (qtv->curtime < qtv->packetratelimiter - UDPPACKETINTERVAL*2)
|
|
qtv->packetratelimiter = qtv->curtime;
|
|
}
|
|
if (qtv->curtime >= qtv->packetratelimiter)
|
|
{
|
|
if (qtv->curtime >= qtv->nextsendpings || qtv->curtime < qtv->nextsendpings - PINGSINTERVAL_TIME*2)
|
|
{
|
|
qtv->nextsendpings = qtv->curtime + PINGSINTERVAL_TIME;
|
|
SendClientCommand(qtv, "pings\n");
|
|
|
|
}
|
|
ChooseFavoriteTrack(qtv);
|
|
|
|
if (qtv->map.trackplayer >= 0)
|
|
{
|
|
qtv->packetratelimiter += UDPPACKETINTERVAL;
|
|
|
|
WriteByte(&msg, clc_tmove);
|
|
WriteShort(&msg, qtv->map.players[qtv->map.trackplayer].current.origin[0]);
|
|
WriteShort(&msg, qtv->map.players[qtv->map.trackplayer].current.origin[1]);
|
|
WriteShort(&msg, qtv->map.players[qtv->map.trackplayer].current.origin[2]);
|
|
}
|
|
else if (qtv->controller)
|
|
{
|
|
qtv->packetratelimiter += UDPPACKETINTERVAL;
|
|
|
|
WriteByte(&msg, clc_tmove);
|
|
WriteShort(&msg, qtv->controller->origin[0]);
|
|
WriteShort(&msg, qtv->controller->origin[1]);
|
|
WriteShort(&msg, qtv->controller->origin[2]);
|
|
|
|
/* qtv->controller->ucmds[0].angles[1] = qtv->curtime*120;
|
|
qtv->controller->ucmds[1].angles[1] = qtv->curtime*120;
|
|
qtv->controller->ucmds[2].angles[1] = qtv->curtime*120;
|
|
*/
|
|
msg.startpos = msg.cursize;
|
|
WriteByte(&msg, clc_move);
|
|
WriteByte(&msg, 0);
|
|
WriteByte(&msg, 0);
|
|
WriteDeltaUsercmd(&msg, &nullcmd, &qtv->controller->ucmds[0]);
|
|
WriteDeltaUsercmd(&msg, &qtv->controller->ucmds[0], &qtv->controller->ucmds[1]);
|
|
WriteDeltaUsercmd(&msg, &qtv->controller->ucmds[1], &qtv->controller->ucmds[2]);
|
|
|
|
SetMoveCRC(qtv, &msg);
|
|
}
|
|
else if (qtv->proxyplayer || qtv->map.trackplayer < 0)
|
|
{
|
|
usercmd_t *cmd[3];
|
|
cmd[0] = &qtv->proxyplayerucmds[(qtv->proxyplayerucmdnum-2)%3];
|
|
cmd[1] = &qtv->proxyplayerucmds[(qtv->proxyplayerucmdnum-1)%3];
|
|
cmd[2] = &qtv->proxyplayerucmds[(qtv->proxyplayerucmdnum-0)%3];
|
|
|
|
cmd[2]->angles[0] = qtv->proxyplayerangles[0]/360*65535;
|
|
cmd[2]->angles[1] = qtv->proxyplayerangles[1]/360*65535;
|
|
cmd[2]->angles[2] = qtv->proxyplayerangles[2]/360*65535;
|
|
cmd[2]->buttons = qtv->proxyplayerbuttons & 255;
|
|
cmd[2]->forwardmove = (qtv->proxyplayerbuttons & (1<<8))?800:0 + (qtv->proxyplayerbuttons & (1<<9))?-800:0;
|
|
cmd[2]->sidemove = (qtv->proxyplayerbuttons & (1<<11))?800:0 + (qtv->proxyplayerbuttons & (1<<10))?-800:0;
|
|
cmd[2]->msec = qtv->curtime - qtv->packetratelimiter;
|
|
cmd[2]->impulse = qtv->proxyplayerimpulse;
|
|
if (cmd[2]->msec < 13)
|
|
cmd[2]->msec = 13;
|
|
qtv->packetratelimiter += cmd[2]->msec;
|
|
qtv->proxyplayerimpulse = 0;
|
|
|
|
|
|
msg.startpos = msg.cursize;
|
|
WriteByte(&msg, clc_move);
|
|
WriteByte(&msg, 0);
|
|
WriteByte(&msg, 0);
|
|
WriteDeltaUsercmd(&msg, &nullcmd, cmd[0]);
|
|
WriteDeltaUsercmd(&msg, cmd[0], cmd[1]);
|
|
WriteDeltaUsercmd(&msg, cmd[1], cmd[2]);
|
|
qtv->proxyplayerucmdnum++;
|
|
|
|
SetMoveCRC(qtv, &msg);
|
|
}
|
|
|
|
to = qtv->netchan.outgoing_sequence & (ENTITY_FRAMES-1);
|
|
from = qtv->netchan.incoming_sequence & (ENTITY_FRAMES-1);
|
|
if (qtv->map.frame[from].numents)
|
|
{
|
|
//remember which one we came from
|
|
qtv->map.frame[to].oldframe = from;
|
|
|
|
WriteByte(&msg, clc_delta);
|
|
WriteByte(&msg, qtv->map.frame[to].oldframe); //let the server know
|
|
}
|
|
else
|
|
qtv->map.frame[to].oldframe = -1;
|
|
|
|
qtv->map.frame[to].numents = 0;
|
|
|
|
Netchan_Transmit(qtv->cluster, &qtv->netchan, msg.cursize, msg.data);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
qtv->simtime = qtv->curtime;
|
|
|
|
|
|
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
|
|
{
|
|
if (qtv->errored == ERR_DISABLED)
|
|
return;
|
|
|
|
if (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - RECONNECT_TIME*2)
|
|
{
|
|
if (qtv->autodisconnect == AD_REVERSECONNECT) //2 means a reverse connection
|
|
{
|
|
qtv->errored = ERR_DROP;
|
|
return;
|
|
}
|
|
if (!QTV_Connect(qtv, qtv->server)) //reconnect it
|
|
{
|
|
qtv->errored = ERR_PERMANENT;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// SV_FindProxies(qtv->tcpsocket, qtv->cluster, qtv); //look for any other proxies wanting to muscle in on the action.
|
|
|
|
if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
if (!Net_ReadStream(qtv))
|
|
{ //if we have an error reading it
|
|
//if it's valid, give up
|
|
//what should we do here?
|
|
//obviously, we need to keep reading the stream to keep things smooth
|
|
}
|
|
|
|
Net_WriteUpstream(qtv);
|
|
}
|
|
|
|
|
|
if (qtv->parsingqtvheader)
|
|
{
|
|
float svversion;
|
|
int length;
|
|
char *start;
|
|
char *nl;
|
|
char *colon;
|
|
char *end;
|
|
char value[128];
|
|
char challenge[128];
|
|
char authmethod[128];
|
|
|
|
// qtv->buffer[qtv->buffersize] = 0;
|
|
// Sys_Printf(qtv->cluster, "msg: ---%s---\n", qtv->buffer);
|
|
|
|
*authmethod = 0;
|
|
|
|
qtv->parsetime = qtv->curtime;
|
|
length = qtv->buffersize;
|
|
if (length > 6)
|
|
length = 6;
|
|
if (ustrncmp(qtv->buffer, "QTVSV ", length))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: Server is not a QTV server (or is incompatible)\n", qtv->streamid);
|
|
//printf("%i, %s\n", qtv->buffersize, qtv->buffer);
|
|
qtv->errored = ERR_PERMANENT;
|
|
return;
|
|
}
|
|
if (length < 6)
|
|
return; //not ready yet
|
|
end = (char*)qtv->buffer + qtv->buffersize - 1;
|
|
for (nl = (char*)qtv->buffer; nl < end; nl++)
|
|
{
|
|
if (nl[0] == '\n' && nl[1] == '\n')
|
|
break;
|
|
}
|
|
if (nl == end)
|
|
return; //we need more header still
|
|
|
|
//we now have a complete packet.
|
|
|
|
svversion = atof((char*)qtv->buffer + 6);
|
|
if ((int)svversion != 1)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: QTV server doesn't support a compatible protocol version (returned %i)\n", qtv->streamid, atoi((char*)qtv->buffer + 6));
|
|
qtv->errored = ERR_PERMANENT;
|
|
return;
|
|
}
|
|
|
|
|
|
qtv->upstreamacceptschat = svversion>=1.1;
|
|
qtv->upstreamacceptsdownload = svversion>=1.1;
|
|
|
|
length = (nl - (char*)qtv->buffer) + 2;
|
|
end = nl;
|
|
nl[1] = '\0';
|
|
start = strchr((char*)qtv->buffer, '\n')+1;
|
|
|
|
while((nl = strchr(start, '\n')))
|
|
{
|
|
*nl = '\0';
|
|
colon = strchr(start, ':');
|
|
if (colon)
|
|
{
|
|
*colon = '\0';
|
|
colon++;
|
|
while (*colon == ' ')
|
|
colon++;
|
|
COM_ParseToken(colon, value, sizeof(value), NULL);
|
|
}
|
|
else
|
|
{
|
|
colon = "";
|
|
*value = '\0';
|
|
}
|
|
|
|
|
|
//read the notes at the top of this file for which messages to expect
|
|
if (!strcmp(start, "AUTH"))
|
|
strcpy(authmethod, value);
|
|
else if (!strcmp(start, "CHALLENGE"))
|
|
strcpy(challenge, colon);
|
|
else if (!strcmp(start, "COMPRESSION"))
|
|
{ //we don't support compression, we didn't ask for it.
|
|
Sys_Printf(qtv->cluster, "Stream %i: QTV server wrongly used compression\n", qtv->streamid);
|
|
qtv->errored = ERR_PERMANENT;
|
|
return;
|
|
}
|
|
else if (!strcmp(start, "PERROR"))
|
|
{
|
|
Sys_Printf(qtv->cluster, "\nStream %i: Server PERROR from %s: %s\n\n", qtv->streamid, qtv->server, colon);
|
|
qtv->errored = ERR_PERMANENT;
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
return;
|
|
}
|
|
else if (!strcmp(start, "TERROR") || !strcmp(start, "ERROR"))
|
|
{ //we don't support compression, we didn't ask for it.
|
|
Sys_Printf(qtv->cluster, "\nStream %i: Server TERROR from %s: %s\n\n", qtv->streamid, qtv->server, colon);
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
|
|
if (qtv->autodisconnect == AD_WHENEMPTY || qtv->autodisconnect == AD_REVERSECONNECT)
|
|
qtv->errored = ERR_DROP; //if its a user registered stream, drop it immediatly
|
|
else
|
|
{ //otherwise close the socket (this will result in a timeout and reconnect)
|
|
if (qtv->sourcesock != INVALID_SOCKET)
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if (!strcmp(start, "ASOURCE"))
|
|
{
|
|
Sys_Printf(qtv->cluster, "SRC: %s\n", colon);
|
|
}
|
|
else if (!strcmp(start, "ADEMO"))
|
|
{
|
|
int size;
|
|
size = atoi(colon);
|
|
colon = strchr(colon, ':');
|
|
if (!colon)
|
|
colon = "";
|
|
else
|
|
colon = colon+1;
|
|
while(*colon == ' ')
|
|
colon++;
|
|
if (size > 1024*1024)
|
|
Sys_Printf(qtv->cluster, "DEMO: (%3imb) %s\n", size/(1024*1024), colon);
|
|
else
|
|
Sys_Printf(qtv->cluster, "DEMO: (%3ikb) %s\n", size/1024, colon);
|
|
}
|
|
else if (!strcmp(start, "PRINT"))
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: QTV server: %s\n", qtv->streamid, colon);
|
|
}
|
|
else if (!strcmp(start, "BEGIN"))
|
|
{
|
|
qtv->parsingqtvheader = false;
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf(qtv->cluster, "DBG: QTV server responded with a %s key\n", start);
|
|
}
|
|
|
|
start = nl+1;
|
|
}
|
|
|
|
qtv->buffersize -= length;
|
|
memmove(qtv->buffer, qtv->buffer + length, qtv->buffersize);
|
|
|
|
if (qtv->serverquery)
|
|
{
|
|
Sys_Printf(qtv->cluster, "End of list\n");
|
|
qtv->errored = ERR_DROP;
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
return;
|
|
}
|
|
else if (*authmethod)
|
|
{ //we need to send a challenge response now.
|
|
Net_SendQTVConnectionRequest(qtv, authmethod, challenge);
|
|
return;
|
|
}
|
|
else if (qtv->parsingqtvheader)
|
|
{
|
|
Sys_Printf(qtv->cluster, "Stream %i: QTV server sent no begin command - assuming incompatible\n\n", qtv->streamid);
|
|
qtv->errored = ERR_PERMANENT;
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
return;
|
|
}
|
|
|
|
qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000;
|
|
if (!qtv->usequakeworldprotocols)
|
|
Sys_Printf(qtv->cluster, "Stream %i: Connection established, buffering for %i seconds\n", qtv->streamid, BUFFERTIME);
|
|
|
|
SV_ForwardStream(qtv, qtv->buffer, qtv->forwardpoint);
|
|
}
|
|
|
|
QTV_CollectCommentry(qtv);
|
|
|
|
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
|
|
if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET)
|
|
QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid);
|
|
}
|
|
break;
|
|
}
|
|
|
|
buffer = qtv->buffer;
|
|
|
|
switch (qtv->buffer[1]&dem_mask)
|
|
{
|
|
case dem_set:
|
|
length = 10;
|
|
if (qtv->buffersize < length)
|
|
{ //not enough stuff to play.
|
|
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
|
|
if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET)
|
|
QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid);
|
|
continue;
|
|
}
|
|
qtv->parsetime += buffer[0]; //well this was pointless
|
|
|
|
if (qtv->forwardpoint < length) //we're about to destroy this data, so it had better be forwarded by now!
|
|
{
|
|
SV_ForwardStream(qtv, qtv->buffer, length);
|
|
qtv->forwardpoint += length;
|
|
}
|
|
|
|
memmove(qtv->buffer, qtv->buffer+10, qtv->buffersize-(length));
|
|
qtv->buffersize -= length;
|
|
qtv->forwardpoint -= length;
|
|
continue;
|
|
case dem_multiple:
|
|
lengthofs = 6;
|
|
break;
|
|
default:
|
|
lengthofs = 2;
|
|
break;
|
|
}
|
|
|
|
if (qtv->buffersize < lengthofs+4)
|
|
{ //the size parameter doesn't fit.
|
|
if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET)
|
|
QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid);
|
|
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 > MAX_MSGLEN)
|
|
{ //THIS SHOULDN'T HAPPEN!
|
|
//Blame the upstream proxy!
|
|
Sys_Printf(qtv->cluster, "Stream %i: Warning: corrupt input packet (%i bytes) too big! Flushing and reconnecting!\n", qtv->streamid, length);
|
|
if (qtv->sourcefile)
|
|
{
|
|
fclose(qtv->sourcefile);
|
|
qtv->sourcefile = NULL;
|
|
}
|
|
else
|
|
{
|
|
closesocket(qtv->sourcesock);
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
}
|
|
qtv->buffersize = 0;
|
|
qtv->forwardpoint = 0;
|
|
break;
|
|
}
|
|
|
|
if (length+lengthofs+4 > qtv->buffersize)
|
|
{
|
|
if (qtv->sourcefile || qtv->sourcesock != INVALID_SOCKET)
|
|
QTV_Printf(qtv, "Stream %i: Not enough buffered\n", qtv->streamid);
|
|
qtv->parsetime = qtv->curtime + 2*1000; //add two seconds
|
|
break; //can't parse it yet.
|
|
}
|
|
|
|
// if (qtv->sourcesock != INVALID_SOCKET)
|
|
// {
|
|
// QTV_Printf(qtv, "Forcing demo speed to play at 100% speed\n");
|
|
// qtv->parsespeed = 1000; //no speeding up/slowing down routed demos
|
|
// }
|
|
|
|
packettime = buffer[0];
|
|
if (qtv->parsespeed>0)
|
|
packettime = ((1000*packettime) / qtv->parsespeed);
|
|
qtv->nextpackettime = qtv->parsetime + packettime;
|
|
|
|
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_all:
|
|
if (qtv->buffer[1] & ~dem_mask) //dem_qtvdata
|
|
if (qtv->sourcetype != SRC_DEMO)
|
|
break;
|
|
//fallthrough
|
|
case dem_read:
|
|
ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1], 0xffffffff);
|
|
break;
|
|
default:
|
|
Sys_Printf(qtv->cluster, "Message type %i\n", qtv->buffer[1]&dem_mask);
|
|
break;
|
|
}
|
|
|
|
length = lengthofs+4+length; //make length be the length of the entire packet
|
|
|
|
qtv->oldpackettime = qtv->curtime;
|
|
|
|
if (qtv->buffersize)
|
|
{ //svc_disconnect can flush our input buffer (to prevent the EndOfDemo part from interfering)
|
|
|
|
if (qtv->forwardpoint < length) //we're about to destroy this data, so it had better be forwarded by now!
|
|
{
|
|
SV_ForwardStream(qtv, qtv->buffer, length);
|
|
qtv->forwardpoint += length;
|
|
}
|
|
|
|
memmove(qtv->buffer, qtv->buffer+length, qtv->buffersize-(length));
|
|
qtv->buffersize -= length;
|
|
qtv->forwardpoint -= length;
|
|
}
|
|
|
|
if (qtv->sourcefile)
|
|
Net_ReadStream(qtv);
|
|
|
|
qtv->parsetime += packettime;
|
|
|
|
qtv->nextconnectattempt = qtv->curtime + RECONNECT_TIME;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
sv_t *QTV_NewServerConnection(cluster_t *cluster, int newstreamid, char *server, char *password, qboolean force, enum autodisconnect_e autoclose, qboolean noduplicates, qboolean query)
|
|
{
|
|
sv_t *qtv;
|
|
|
|
if (noduplicates)
|
|
{
|
|
for (qtv = cluster->servers; qtv; qtv = qtv->next)
|
|
{
|
|
if (!strcmp(qtv->server, server))
|
|
{ //if the stream detected some permanent/config error, try reconnecting again (of course this only happens when someone tries using the stream)
|
|
//warning review this logic
|
|
if (qtv->errored == ERR_DISABLED)
|
|
{
|
|
if (!(!QTV_Connect(qtv, server) && !force)) //try and wake it up
|
|
qtv->errored = ERR_NONE;
|
|
}
|
|
return qtv;
|
|
}
|
|
}
|
|
}
|
|
if (!newstreamid) //no fixed id? generate a default id
|
|
newstreamid = 100;
|
|
//make sure it doesn't conflict
|
|
for(;;newstreamid++)
|
|
{
|
|
for (qtv = cluster->servers; qtv; qtv = qtv->next)
|
|
{
|
|
if (qtv->streamid == newstreamid)
|
|
break;
|
|
}
|
|
if (!qtv)
|
|
break;
|
|
}
|
|
|
|
if (autoclose)
|
|
if (cluster->nouserconnects)
|
|
return NULL;
|
|
|
|
qtv = malloc(sizeof(sv_t));
|
|
if (!qtv)
|
|
return NULL;
|
|
|
|
memset(qtv, 0, sizeof(*qtv));
|
|
//set up a default config
|
|
// qtv->tcplistenportnum = PROX_DEFAULTLISTENPORT;
|
|
strcpy(qtv->server, PROX_DEFAULTSERVER);
|
|
|
|
memcpy(qtv->connectpassword, password, sizeof(qtv->connectpassword)-1);
|
|
|
|
// qtv->tcpsocket = INVALID_SOCKET;
|
|
qtv->sourcesock = INVALID_SOCKET;
|
|
qtv->autodisconnect = autoclose;
|
|
qtv->parsingconnectiondata = true;
|
|
qtv->serverquery = query;
|
|
qtv->silentstream = true;
|
|
qtv->parsespeed = 1000;
|
|
|
|
qtv->streamid = newstreamid;
|
|
|
|
qtv->cluster = cluster;
|
|
qtv->next = cluster->servers;
|
|
|
|
if (autoclose != AD_REVERSECONNECT) //2 means reverse connection (don't ever try reconnecting)
|
|
{
|
|
if (!QTV_Connect(qtv, server) && !force)
|
|
{
|
|
QTV_Cleanup(qtv, false);
|
|
free(qtv);
|
|
return NULL;
|
|
}
|
|
}
|
|
cluster->servers = qtv;
|
|
cluster->numservers++;
|
|
|
|
return qtv;
|
|
}
|