From 1d4ada39d2c27b4578ff40a330bc8d97a6e1f111 Mon Sep 17 00:00:00 2001 From: Spoike Date: Sun, 17 Sep 2006 01:27:32 +0000 Subject: [PATCH] WARNING: Includes protocol changes. Protocol changes will allow for passwords/commentator access. Restructured some of the code to clean it up. Added mini-http server for serving up qtv files for streams. Mostly supports acting as an nq server, not totally finished yet. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2380 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- fteqtv/Makefile | 8 +- fteqtv/bsp.c | 99 +--- fteqtv/control.c | 441 ++++++++++++++++ fteqtv/forward.c | 840 ++++++++++++++++++++++++++++++ fteqtv/netchan.c | 177 ++++++- fteqtv/parse.c | 179 +++++-- fteqtv/qtv.h | 188 +++++-- fteqtv/qtvprox.dsp | 188 +++++++ fteqtv/qw.c | 1018 ++++++++++++++++++++++++++++++++---- fteqtv/rcon.c | 449 +++++++++++++++- fteqtv/source.c | 1240 ++++++++++++-------------------------------- 11 files changed, 3626 insertions(+), 1201 deletions(-) create mode 100644 fteqtv/control.c create mode 100644 fteqtv/forward.c diff --git a/fteqtv/Makefile b/fteqtv/Makefile index 0eaa1701c..a0aae08d7 100644 --- a/fteqtv/Makefile +++ b/fteqtv/Makefile @@ -4,15 +4,15 @@ STRIP=strip STRIPFLAGS=--strip-unneeded --remove-section=.comment -OBJS = netchan.o parse.o qw.o source.o bsp.o rcon.o mdfour.o crc.o +OBJS = netchan.o parse.o qw.o source.o bsp.o rcon.o mdfour.o crc.o control.o forward.o qtv: $(OBJS) qtv.h - $(CC) $(CFLAGS) $(OBJS) -o $@.db -lm $(LDFLAGS) + $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@.db -lm $(STRIP) $(STRIPFLAGS) $@.db -o $@ -qtv.exe: +qtv.exe: *.c *.h $(MAKE) qtv CFLAGS=-mno-cygwin LDFLAGS="-lwsock32 -lwinmm" mv qtv qtv.exe clean: - rm -rf qtv qtv.db *.o + rm -rf qtv qtv.exe qtv.db *.o diff --git a/fteqtv/bsp.c b/fteqtv/bsp.c index a2925a8b4..5eb42bf0e 100644 --- a/fteqtv/bsp.c +++ b/fteqtv/bsp.c @@ -113,92 +113,6 @@ void DecompressVis(unsigned char *in, unsigned char *out, int bytecount) } } -typedef struct { - char name[56]; - int offset; - int length; -} pakfile; -// PACK, offset, lengthofpakfiles -FILE *FindInPaks(char *gamedir, char *filename, int *size) -{ - FILE *f; - char fname[1024]; - int i, j; - int numfiles; - unsigned int header[3]; - - pakfile pf; - - for (i = 0; ; i++) - { - sprintf(fname, "%s/pak%i.pak", gamedir, i); - f = fopen(fname, "rb"); - if (!f) - return NULL; //ran out of possible pak files. - - fread(header, 1, sizeof(header), f); - if (header[0] != *(unsigned int*)"PACK") - { //err... hmm. - fclose(f); - continue; - } - numfiles = LittleLong(header[2])/sizeof(pakfile); - fseek(f, LittleLong(header[1]), SEEK_SET); - for (j = 0; j < numfiles; j++) - { - fread(&pf, 1, sizeof(pf), f); - if (!strcmp(pf.name, filename)) - { - fseek(f, LittleLong(pf.offset), 0); - *size = LittleLong(pf.length); - return f; - } - } - fclose(f); - //not found - } - return NULL; -} - -unsigned char *ReadFile_WINDOWSSUCKS(char *gamedir, char *filename, int *size) -{ - unsigned char *data; - - FILE *f; - char fname[1024]; - - if (!*filename) - return NULL; - - //try and read it straight out of the file system - sprintf(fname, "%s/%s", gamedir, filename); - f = fopen(fname, "rb"); - if (!f) - f = fopen(filename, "rb"); //see if we're being run from inside the gamedir - if (!f) - { - f = FindInPaks(gamedir, filename, size); - if (!f) - f = FindInPaks("id1", filename, size); - if (!f) - { - return NULL; - } - } - else - { - fseek(f, 0, SEEK_END); - *size = ftell(f); - fseek(f, 0, SEEK_SET); - } - data = malloc(*size); - if (data) - fread(data, 1, *size, f); - fclose(f); - - return data; -} - bsp_t *BSP_LoadModel(cluster_t *cluster, char *gamedir, char *bspname) { unsigned char *data; @@ -215,18 +129,11 @@ bsp_t *BSP_LoadModel(cluster_t *cluster, char *gamedir, char *bspname) bsp_t *bsp; - if (!gamedir || !*gamedir) - gamedir = "qw"; - - data = ReadFile_WINDOWSSUCKS(gamedir, bspname, &size); + data = FS_ReadFile(gamedir, bspname, &size); if (!data) { - data = ReadFile_WINDOWSSUCKS("id1", bspname, &size); - if (!data) - { - Sys_Printf(cluster, "Couldn't open bsp file \"%s\" (gamedir \"%s\")\n", bspname, gamedir); - return NULL; - } + Sys_Printf(cluster, "Couldn't open bsp file \"%s\" (gamedir \"%s\")\n", bspname, gamedir); + return NULL; } diff --git a/fteqtv/control.c b/fteqtv/control.c new file mode 100644 index 000000000..f6bbb0f78 --- /dev/null +++ b/fteqtv/control.c @@ -0,0 +1,441 @@ +/* +Contains the control routines that handle both incoming and outgoing stuff +*/ + +#include "qtv.h" + + + +typedef struct { + char name[56]; + int offset; + int length; +} pakfile; +// PACK, offset, lengthofpakfiles +FILE *FindInPaks(char *gamedir, char *filename, int *size) +{ + FILE *f; + char fname[1024]; + int i, j; + int numfiles; + unsigned int header[3]; + + pakfile pf; + + for (i = 0; ; i++) + { + sprintf(fname, "%s/pak%i.pak", gamedir, i); + f = fopen(fname, "rb"); + if (!f) + return NULL; //ran out of possible pak files. + + fread(header, 1, sizeof(header), f); + if (header[0] != *(unsigned int*)"PACK") + { //err... hmm. + fclose(f); + continue; + } + numfiles = LittleLong(header[2])/sizeof(pakfile); + fseek(f, LittleLong(header[1]), SEEK_SET); + for (j = 0; j < numfiles; j++) + { + fread(&pf, 1, sizeof(pf), f); + if (!strcmp(pf.name, filename)) + { + fseek(f, LittleLong(pf.offset), 0); + if (size) + *size = LittleLong(pf.length); + return f; + } + } + fclose(f); + //not found + } + return NULL; +} + +unsigned char *FS_ReadFile2(char *gamedir, char *filename, unsigned int *sizep) +{ + int size; + unsigned char *data; + + FILE *f; + char fname[1024]; + + if (!*filename) + return NULL; + + //try and read it straight out of the file system + sprintf(fname, "%s/%s", gamedir, filename); + f = fopen(fname, "rb"); + if (!f) + f = fopen(filename, "rb"); //see if we're being run from inside the gamedir + if (!f) + { + f = FindInPaks(gamedir, filename, &size); + if (!f) + f = FindInPaks("id1", filename, &size); + if (!f) + { + return NULL; + } + } + else + { + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + } + data = malloc(size); + if (data) + fread(data, 1, size, f); + fclose(f); + + if (sizep) + *sizep = size; + return data; +} + +unsigned char *FS_ReadFile(char *gamedir, char *filename, unsigned int *size) +{ + char *data; + if (!gamedir || !*gamedir || !strcmp(gamedir, "qw")) + data = NULL; + else + data = FS_ReadFile2(gamedir, filename, size); + if (!data) + { + data = FS_ReadFile2("qw", filename, size); + if (!data) + { + data = FS_ReadFile2("id1", filename, size); + if (!data) + { + return NULL; + } + } + } + return data; +} + + +void Cluster_Run(cluster_t *cluster, qboolean dowait) +{ + oproxy_t *pend, *pend2, *pend3; + sv_t *sv, *old; + + int m; + struct timeval timeout; + fd_set socketset; + + if (dowait) + { + + FD_ZERO(&socketset); + m = 0; + if (cluster->qwdsocket != INVALID_SOCKET) + { + FD_SET(cluster->qwdsocket, &socketset); + if (cluster->qwdsocket >= m) + m = cluster->qwdsocket+1; + } + + for (sv = cluster->servers; sv; sv = sv->next) + { + if (sv->usequkeworldprotocols && sv->sourcesock != INVALID_SOCKET) + { + FD_SET(sv->sourcesock, &socketset); + if (sv->sourcesock >= m) + m = sv->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; + + m = 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') + { + Sys_Printf(cluster, "\n"); + if (cluster->inputlength) + { + cluster->commandinput[cluster->inputlength] = '\0'; + result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + cluster->inputlength = 0; + cluster->commandinput[0] = '\0'; + } + } + else if (c == '\b') + { + if (cluster->inputlength > 0) + { + Sys_Printf(cluster, "%c", c); + Sys_Printf(cluster, " ", c); + Sys_Printf(cluster, "%c", c); + + cluster->inputlength--; + cluster->commandinput[cluster->inputlength] = '\0'; + } + } + else + { + Sys_Printf(cluster, "%c", c); + if (cluster->inputlength < sizeof(cluster->commandinput)-1) + { + cluster->commandinput[cluster->inputlength++] = c; + cluster->commandinput[cluster->inputlength] = '\0'; + } + } + } +#else + if (FD_ISSET(STDIN, &socketset)) + { + char buffer[8192]; + char *result; + cluster->inputlength = read (0, cluster->commandinput, sizeof(cluster->commandinput)); + if (cluster->inputlength >= 1) + { + cluster->commandinput[cluster->inputlength-1] = 0; // rip off the /n and terminate + + if (cluster->inputlength) + { + cluster->commandinput[cluster->inputlength] = '\0'; + result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); + printf("%s", result); + cluster->inputlength = 0; + cluster->commandinput[0] = '\0'; + } + } + } +#endif + } + + + + cluster->curtime = Sys_Milliseconds(); + + for (sv = cluster->servers; sv; ) + { + old = sv; + sv = sv->next; + QTV_Run(old); + } + + SV_FindProxies(cluster->tcpsocket, cluster, NULL); //look for any other proxies wanting to muscle in on the action. + + QW_UpdateUDPStuff(cluster); + + while(cluster->pendingproxies) + { + pend2 = cluster->pendingproxies->next; + if (SV_ReadPendingProxy(cluster, cluster->pendingproxies)) + cluster->pendingproxies = pend2; + else + break; + } + if (cluster->pendingproxies) + { + for(pend = cluster->pendingproxies; pend && pend->next; ) + { + pend2 = pend->next; + pend3 = pend2->next; + if (SV_ReadPendingProxy(cluster, pend2)) + { + pend->next = pend3; + pend = pend3; + } + else + { + pend = pend2; + } + } + } +} + + + + + +void DoCommandLine(cluster_t *cluster, int argc, char **argv) +{ + int i; + char commandline[8192]; + char *start, *end, *result; + char buffer[8192]; + + commandline[0] = '\0'; + + //build a block of strings. + for (i = 1; i < argc; i++) + { + strcat(commandline, argv[i]); + strcat(commandline, " "); + } + strcat(commandline, "+"); + + start = commandline; + while(start) + { + end = strchr(start+1, '+'); + if (end) + *end = '\0'; + if (start[1]) + { + result = Rcon_Command(cluster, NULL, start+1, buffer, sizeof(buffer), true); + Sys_Printf(cluster, "%s", result); + } + + start = end; + } +} + +int main(int argc, char **argv) +{ + cluster_t cluster; + +#ifndef _WIN32 +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif +#endif + +#ifdef _WIN32 + { + WSADATA discard; + WSAStartup(MAKEWORD(2,0), &discard); + } +#endif + + memset(&cluster, 0, sizeof(cluster)); + + cluster.qwdsocket = INVALID_SOCKET; + cluster.tcpsocket = INVALID_SOCKET; + cluster.qwlistenportnum = 0; + strcpy(cluster.hostname, DEFAULT_HOSTNAME); + + + if (argc >= 2 && (!strcmp(argv[1], "-view") || !strcmp(argv[1], "-play"))) + { +#ifdef VIEWER + sv_t *sv; + sv_t *svtest; + char sourcename[256]; + char *s; + printf("Please enter a QTV source\n"); + printf("eg: file:test.mvd\n"); + printf("eg: udp:localhost:27500\n"); + printf("eg: tcp:localhost:27599\n"); + fgets(sourcename, sizeof(sourcename), stdin); + for (s = sourcename + strlen(sourcename)-1; s>=sourcename; s--) + { + if (*s == '\r' || *s == '\n') + *s = '\0'; + else + break; + } + + sv = QTV_NewServerConnection(&cluster, sourcename, "", false, false, true); + if (!sv) + { + printf("Unable to connect\n"); + return 0; + } + if (!strcmp(argv[1], "-play")) + sv->proxyplayer = true; + DemoViewer_Init(); + while (!cluster.wanttoexit) + { + Cluster_Run(&cluster, false); + + for (svtest = cluster.servers; svtest; svtest = svtest->next) + { //not the cleanest way to do this, of course + if (svtest == sv) + break; + } + if (svtest) + DemoViewer_Update(svtest); + else + cluster.wanttoexit = true; + } + DemoViewer_Shutdown(); + while(cluster.viewers) + QW_FreeViewer(&cluster, cluster.viewers); + while(cluster.servers) + QTV_Shutdown(cluster.servers); +#else + Sys_Printf(&cluster, "Demo viewer is not enabled in this build. Sorry.\n"); +#endif + return 0; + } + + + DoCommandLine(&cluster, argc, argv); + + if (!cluster.numservers) + { //probably running on a home user's computer + if (cluster.qwdsocket == INVALID_SOCKET && !cluster.qwlistenportnum) + { + cluster.qwdsocket = QW_InitUDPSocket(cluster.qwlistenportnum = 27599); + if (cluster.qwdsocket != INVALID_SOCKET) + Sys_Printf(&cluster, "opened udp port %i\n", cluster.qwlistenportnum); + } + if (cluster.tcpsocket == INVALID_SOCKET && !cluster.tcplistenportnum) + { + cluster.tcpsocket = Net_MVDListen(cluster.tcplistenportnum = 27599); + if (cluster.tcpsocket != INVALID_SOCKET) + Sys_Printf(&cluster, "opened tcp port %i\n", cluster.tcplistenportnum); + } + + Sys_Printf(&cluster, "\n" + "Welcome to FTEQTV\n" + "Please type\n" + "qtv server:port\n" + " to connect to a tcp server.\n" + "qw server:port\n" + " to connect to a regular qw server.\n" + "demo qw/example.mvd\n" + " to play a demo from an mvd.\n" + "\n"); + } + + while (!cluster.wanttoexit) + Cluster_Run(&cluster, true); + + return 0; +} + + +void Sys_Printf(cluster_t *cluster, char *fmt, ...) +{ + va_list argptr; + char string[2024]; + + va_start (argptr, fmt); + vsnprintf (string, sizeof(string), fmt,argptr); + va_end (argptr); + + printf("%s", string); +} + diff --git a/fteqtv/forward.c b/fteqtv/forward.c new file mode 100644 index 000000000..2cce0005e --- /dev/null +++ b/fteqtv/forward.c @@ -0,0 +1,840 @@ +/* +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 is server side stuff. + +*/ + +#include "qtv.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 > 1450) + 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) +{ + oproxy_t *prox; + + sock = accept(sock, NULL, NULL); + if (sock == INVALID_SOCKET) + return; + + if (cluster->numproxies >= cluster->maxproxies && 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++; + +#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 Net_TryFlushProxyBuffer(cluster_t *cluster, oproxy_t *prox) +{ + 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; + break; + case -1: + if (qerrno != EWOULDBLOCK) //not a problem, so long as we can flush it later. + prox->drop = true; //drop them if we get any errors + break; + default: + prox->bufferpos += length; + } +} + +void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, char *buffer, int length) +{ + int wrap; + + 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 + prox->flushing = true; + return; + } + } +#if 1 + //just simple + prox->buffersize+=length; + for (wrap = prox->buffersize-length; wrap < prox->buffersize; wrap++) + prox->buffer[wrap&(MAX_PROXY_BUFFER-1)] = *buffer++; +#else + //we don't do multiple wrappings, the above check cannot succeed if it were required. + + //find the wrap point + wrap = prox->buffersize-(prox->buffersize&(MAX_PROXY_BUFFER-1)) + MAX_PROXY_BUFFER; + wrap = wrap - (prox->buffersize&(MAX_PROXY_BUFFER-1)); //the ammount of data we can fit before wrapping. + + if (wrap > length) + { //we don't wrap afterall + memcpy(prox->buffer+(prox->buffersize)&(MAX_PROXY_BUFFER-1), buffer, length); + prox->buffersize+=length; + return; + } + memcpy(prox->buffer+prox->buffersize&(MAX_PROXY_BUFFER-1), buffer, wrap); + buffer += wrap; + length -= wrap; + memcpy(prox->buffer, buffer, length); + + prox->buffersize+=length; +#endif +} + +void Prox_SendMessage(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 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->players[player].stats[snum]) + { + if ((unsigned)qtv->players[player].stats[snum] > 255) + { + WriteByte(&msg, svc_updatestatlong); + WriteByte(&msg, snum); + WriteLong(&msg, qtv->players[player].stats[snum]); + } + else + { + WriteByte(&msg, svc_updatestat); + WriteByte(&msg, snum); + WriteByte(&msg, qtv->players[player].stats[snum]); + } + } + } + + if (msg.cursize) + { +// Prox_SendMessage(prox, msg.data, msg.cursize, dem_stats|(player<<3), (1<flushing = false; + + BuildServerData(qtv, &msg, true, false, 0); + 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->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->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 arn't actually delta-compressed, so the first send (simply forwarded from server) entirly replaces the old. + + //we do need to send entity states. + Prox_SendInitialEnts(qtv, prox, &msg); + Prox_SendMessage(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->buffersize); //send all the info we've not yet processed. + + + 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, char *buffer, int length) +{ //forward the stream on to connected clients + oproxy_t *prox, *next, *fre; + + CheckMVDConsistancy(buffer, 0, length); + + + while (qtv->proxies && qtv->proxies->drop) + { + next = qtv->proxies->next; + fre = qtv->proxies; + 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); + 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); + } +} + +void SV_GenerateNowPlayingHTTP(cluster_t *cluster, oproxy_t *dest) +{ + int player; + char *s; + char buffer[1024]; + char plname[64]; + sv_t *streams; + + s = "HTTP/1.1 200 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + sprintf(buffer, "" + "QuakeTV: Now Playing" + "" + ""); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + for (streams = cluster->servers; streams; streams = streams->next) + { + sprintf(buffer, "%s (%s: %s)
", streams->streamid, streams->server, streams->gamedir, streams->mapname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + for (player = 0; player < MAX_CLIENTS; player++) + { + if (*streams->players[player].userinfo) + { + Info_ValueForKey(streams->players[player].userinfo, "name", plname, sizeof(plname)); + sprintf(buffer, " %s
", plname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + } + } + } + sprintf(buffer, ""); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +qboolean SV_GetHTTPHeaderField(char *s, char *field, char *buffer, int buffersize) +{ + char *e; + char *colon; + int fieldnamelen = strlen(field); + + buffer[0] = 0; + + e = s; + while(*e) + { + if (*e == '\n') + { + *e = '\0'; + colon = strchr(s, ':'); + if (!colon) + { + if (!strncmp(field, s, fieldnamelen)) + { + if (s[fieldnamelen] <= ' ') + { + return true; + } + } + } + else + { + if (fieldnamelen == colon - s) + { + if (!strncmp(field, s, colon-s)) + { + colon++; + while (*colon == ' ') + colon++; + while (buffersize > 1) + { + if (*colon == '\r' || *colon == '\n') + break; + *buffer++ = *colon++; + } + *buffer = 0; + return true; + } + } + + } + s = e+1; + } + + e++; + } + return false; +} + +void SV_GenerateQTVStub(cluster_t *cluster, oproxy_t *dest, int streamid) +{ + char *s; + char hostname[64]; + char buffer[1024]; + + if (!SV_GetHTTPHeaderField(dest->inbuffer, "Host", hostname, sizeof(hostname))) + { + s = "HTTP/1.1 400 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + + "" + "" + "QuakeTV: Now Playing" + "" + "" + "Your client did not send a Host field\n" + "" + ""; + + Net_ProxySend(cluster, dest, s, strlen(s)); + return; + } + + s = "HTTP/1.1 200 OK\n" + "Content-Type: text/x-quaketvident\n" + "Connection: close\n" + "\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + + sprintf(buffer, "[QTV]\r\n" + "Stream: %i@%s\r\n" + "", + streamid, hostname); + + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +//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) +{ + char *s; + char *e; + char *colon; + int usableversion = 0; + int len; + qboolean raw; + sv_t *qtv; + + if (pend->drop) + { + closesocket(pend->sock); + free(pend); + return true; + } + + Net_TryFlushProxyBuffer(cluster, pend); + + if (pend->flushing) + { + if (pend->bufferpos == pend->buffersize) + pend->drop = true; + return false; + } + + len = sizeof(pend->inbuffer) - pend->inbuffersize - 1; + len = recv(pend->sock, pend->inbuffer+pend->inbuffersize, len, 0); + if (len == 0) + { + pend->drop = true; + return false; + } + if (len < 0) + return false; + + pend->inbuffersize += len; + pend->inbuffer[pend->inbuffersize] = '\0'; + + if (pend->inbuffersize >= 4) + { + if (strncmp(pend->inbuffer, "QTV\n", 4) && strncmp(pend->inbuffer, "GET ", 4)) + { //I have no idea what the smeg you are. + pend->drop = true; + return false; + } + } + + //make sure there's a double \n somewhere + + for (s = pend->inbuffer; *s; s++) + { + if (s[0] == '\n' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n'))) + break; + } + if (!*s) + return false; //don't have enough yet + + if (!strncmp(pend->inbuffer, "GET ", 4)) + { + if (!strncmp(pend->inbuffer+4, "/nowplaying", 11)) + { + SV_GenerateNowPlayingHTTP(cluster, pend); + } + else if (!strncmp(pend->inbuffer+4, "/watch.qtv?sid=", 15)) + { + SV_GenerateQTVStub(cluster, pend, atoi(pend->inbuffer+19)); + } + else if (!strncmp(pend->inbuffer+4, "/about", 6)) + { + s = "HTTP/1.0 302 Found\n" + "Location: http://www.fteqw.com/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp(pend->inbuffer+4, "/ ", 2)) + { + s = "HTTP/1.0 302 Found\n" + "Location: /nowplaying.html\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp(pend->inbuffer+4, "/demos", 6)) + { + s = "HTTP/1.0 302 Found\n" + "Location: http://www.fteqw.com/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } +/* else + { + s = "HTTP/0.9 200 OK\n" + "Content-Type: text/plain\n" + "Content-Length: 12\n" + "\n" + "Hello World\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + }*/ + else + { + s = "HTTP/1.1 404 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + "QuakeTVThe url you have specified was not recognised.\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + pend->flushing = true; + return false; + } + + raw = false; + + qtv = pend->defaultstream; + + e = pend->inbuffer; + s = e; + while(*e) + { + if (*e == '\n') + { + *e = '\0'; + colon = strchr(s, ':'); + if (!colon) + { + if (!strcmp(s, "SOURCELIST")) + { //lists sources that are currently playing + s = "QTVSV 1\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + if (!cluster->servers) + { + s = "PERROR: No sources currently available\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else + { + for (qtv = cluster->servers; qtv; qtv = qtv->next) + { + s = "ASOURCE: "; + Net_ProxySend(cluster, pend, s, strlen(s)); + s = qtv->hostname; + if (!s || !*s) + s = qtv->server; + Net_ProxySend(cluster, pend, s, strlen(s)); + s = "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + qtv = NULL; + } + s = "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + else if (!strcmp(s, "DEMOLIST")) + { //lists the demos available on this proxy + + s = "QTVSV 1\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + s = "PERROR: DEMOLIST command not yet implemented\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + s = "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + } + else + { + *colon++ = '\0'; + if (!strcmp(s, "VERSION")) + { + switch(atoi(colon)) + { + case 1: + //got a usable version + usableversion = 1; + break; + default: + //not recognised. + break; + } + } + else if (!strcmp(s, "RAW")) + raw = atoi(colon); + /*else if (!strcmp(s, "ROUTE")) + { //pure rewroute... + //is this safe? probably not. + s = "QTVSV 1\n" + "PERROR: ROUTE command not yet implemented\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + */ + else if (!strcmp(s, "SOURCE")) + { //connects, creating a new source + while (*colon == ' ') + colon++; + for (s = colon; *s; s++) + if (*s < '0' || *s > '9') + break; + if (*s) + qtv = QTV_NewServerConnection(cluster, colon, "", false, true, true); + else + { + //numerical source, use a stream id. + for (qtv = cluster->servers; qtv; qtv = qtv->next) + if (qtv->streamid == atoi(colon)) + break; + } +// s = "QTVSV 1\n" +// "PERROR: SOURCE command not yet implemented\n" +// "\n"; +// Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strcmp(s, "DEMO")) + { //starts a demo off the server... source does the same thing though... + s = "QTVSV 1\n" + "PERROR: DEMO command not yet implemented\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + } + s = e+1; + } + + e++; + } + + if (!pend->flushing) + { + if (!usableversion) + { + s = "QTVSV 1\n" + "PERROR: Requested protocol version not supported\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + if (!qtv) + { + s = "QTVSV 1\n" + "PERROR: No stream selected\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + } + } + if (pend->flushing) + return false; + + pend->next = qtv->proxies; + qtv->proxies = pend; + + if (!raw) + { + s = "QTVSV 1\n\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } +// else if (passwordprotected) //raw mode doesn't support passwords, so reject them +// { +// pend->flushing = true; +// return; +// } + + + Net_SendConnectionMVD(qtv, pend); + + return true; +} diff --git a/fteqtv/netchan.c b/fteqtv/netchan.c index da285a75f..1f29d26ab 100644 --- a/fteqtv/netchan.c +++ b/fteqtv/netchan.c @@ -240,10 +240,81 @@ A 0 length will still generate a packet and deal with the reliable messages. void Netchan_Transmit (cluster_t *cluster, netchan_t *chan, int length, const unsigned char *data) { netmsg_t send; - unsigned char send_buf[MAX_MSGLEN + PACKET_HEADER]; + unsigned char send_buf[MAX_NQMSGLEN + PACKET_HEADER]; qboolean send_reliable; unsigned w1, w2; + if (chan->isnqprotocol) + { + int i; + + send.data = send_buf; + send.maxsize = MAX_NQMSGLEN + PACKET_HEADER; + send.cursize = 0; + + if (!chan->reliable_length && chan->message.cursize) + { + memcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize); + chan->reliable_length = chan->message.cursize; + chan->reliable_start = 0; + chan->message.cursize = 0; + } + + i = chan->reliable_length - chan->reliable_start; + if (i>0) + { + WriteLong(&send, 0); + WriteLong(&send, SwapLong(chan->reliable_sequence)); + if (i > MAX_NQDATAGRAM) + i = MAX_NQDATAGRAM; + + WriteData (&send, chan->reliable_buf+chan->reliable_start, i); + if (length && send.cursize + length < send.maxsize) + { //throw the unreliable packet into the same one as the reliable (but not sent reliably) + WriteData (&send, data, length); + length = 0; + } + + + if (chan->reliable_start+i == chan->reliable_length) + *(int*)send_buf = BigLong(NETFLAG_DATA | NETFLAG_EOM | send.cursize); + else + *(int*)send_buf = BigLong(NETFLAG_DATA | send.cursize); + NET_SendPacket(cluster, chan->sock, send.cursize, send.data, chan->remote_address); + + if (chan->cleartime < curtime) + chan->cleartime = curtime + send.cursize*chan->rate; + else + chan->cleartime += send.cursize*chan->rate; + } + + //send out the unreliable (if still unsent) + if (length) + { + WriteLong(&send, 0); + WriteLong(&send, SwapLong(chan->outgoing_unreliable)); + chan->outgoing_unreliable++; + + WriteData (&send, data, length); + + *(int*)send_buf = BigLong(NETFLAG_UNRELIABLE | send.cursize); + NET_SendPacket (cluster, chan->sock, send.cursize, send.data, chan->remote_address); + + if (chan->cleartime < curtime) + chan->cleartime = curtime + send.cursize*chan->rate; + else + chan->cleartime += send.cursize*chan->rate; + + send.cursize = 0; + } + return; + } + + + + + + // check for message overflow if (chan->message.overflowed) { @@ -324,6 +395,110 @@ void Netchan_Transmit (cluster_t *cluster, netchan_t *chan, int length, const un */ } + + + +qboolean NQNetchan_Process(cluster_t *cluster, netchan_t *chan, netmsg_t *msg) +{ + int header; + int sequence; + int drop; + + msg->readpos = 0; + + header = SwapLong(ReadLong(msg)); + if (msg->cursize != (header & NETFLAG_LENGTH_MASK)) + return false; //size was wrong, couldn't have been ours. + + if (header & NETFLAG_CTL) + return false; //huh? + + sequence = SwapLong(ReadLong(msg)); + + if (header & NETFLAG_ACK) + { + if (sequence == chan->reliable_sequence) + { + chan->reliable_start += MAX_NQDATAGRAM; + if (chan->reliable_start >= chan->reliable_length) + { + chan->reliable_length = 0; //they got the entire message + chan->reliable_start = 0; + } + chan->incoming_reliable_acknowledged = chan->reliable_sequence; + chan->reliable_sequence++; + + chan->last_received = curtime; + } +// else if (sequence < chan->reliable_sequence) +// Con_DPrintf("Stale ack recieved\n"); +// else if (sequence > chan->reliable_sequence) +// Con_Printf("Future ack recieved\n"); + + return false; //don't try execing the 'payload'. I hate ack packets. + } + + if (header & NETFLAG_UNRELIABLE) + { + if (sequence < chan->incoming_unreliable) + { +// Con_DPrintf("Stale datagram recieved\n"); + return false; + } + drop = sequence - chan->incoming_unreliable - 1; + if (drop > 0) + { +// Con_DPrintf("Dropped %i datagrams\n", drop); +// chan->drop_count += drop; + } + chan->incoming_unreliable = sequence; + + chan->last_received = curtime; + + chan->incoming_acknowledged++; +// chan->good_count++; + return 1; + } + if (header & NETFLAG_DATA) + { + int runt[2]; + //always reply. a stale sequence probably means our ack got lost. + runt[0] = BigLong(NETFLAG_ACK | 8); + runt[1] = BigLong(sequence); + NET_SendPacket (cluster, chan->sock, 8, (void*)runt, chan->remote_address); + + chan->last_received = curtime; + if (sequence == chan->incoming_reliable_sequence) + { + chan->incoming_reliable_sequence++; + + if (chan->in_fragment_length + msg->cursize-8 >= sizeof(chan->in_fragment_buf)) + { + chan->drop = true; + return false; + } + + memcpy(chan->in_fragment_buf + chan->in_fragment_length, msg->data+8, msg->cursize-8); + chan->in_fragment_length += msg->cursize-8; + + if (header & NETFLAG_EOM) + { + msg->cursize = 0; + WriteData(msg, chan->in_fragment_buf, chan->in_fragment_length); + chan->in_fragment_length = 0; + msg->readpos = 0; + return 2; //we can read it now + } + } +// else +// Con_DPrintf("Stale reliable (%i)\n", sequence); + return false; + } + + return false; //not supported. +} + + /* ================= Netchan_Process diff --git a/fteqtv/parse.c b/fteqtv/parse.c index 64f926a3b..3211e6d68 100644 --- a/fteqtv/parse.c +++ b/fteqtv/parse.c @@ -56,6 +56,38 @@ unsigned int ReadLong(netmsg_t *b) return s1 | (s2<<16); } + +unsigned int BigLong(unsigned int val) +{ + union { + unsigned int i; + unsigned char c[4]; + } v; + unsigned char s; + + v.i = val; + return (v.c[0]<<24) | (v.c[1] << 16) | (v.c[2] << 8) | (v.c[3] << 0); +} + +unsigned int SwapLong(unsigned int val) +{ + union { + unsigned int i; + unsigned char c[4]; + } v; + unsigned char s; + + v.i = val; + s = v.c[0]; + v.c[0] = v.c[3]; + v.c[3] = s; + s = v.c[1]; + v.c[1] = v.c[2]; + v.c[2] = s; + + return v.i; +} + float ReadFloat(netmsg_t *b) { union { @@ -166,7 +198,7 @@ void SendBufferToViewer(viewer_t *v, const char *buffer, int length, qboolean re } } -void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playermask) +void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playermask, int suitablefor) { viewer_t *v; switch(to) @@ -180,7 +212,10 @@ void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playerma if (v->server == tv) if (v->trackplayer>=0) if ((1<trackplayer)&playermask) - SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type + { + if (suitablefor&(v->netchan.isnqprotocol?NQ:QW)) + SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type + } } break; default: @@ -188,17 +223,19 @@ void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playerma for (v = tv->cluster->viewers; v; v = v->next) { if (v->server == tv) - SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type + if (suitablefor&(v->netchan.isnqprotocol?NQ:QW)) + SendBufferToViewer(v, buffer, length, true); //FIXME: change the reliable depending on message type } break; } } -void Broadcast(cluster_t *cluster, char *buffer, int length) +void Broadcast(cluster_t *cluster, char *buffer, int length, int suitablefor) { viewer_t *v; for (v = cluster->viewers; v; v = v->next) { - SendBufferToViewer(v, buffer, length, true); + if (suitablefor&(v->netchan.isnqprotocol?NQ:QW)) + SendBufferToViewer(v, buffer, length, true); } } @@ -255,6 +292,7 @@ static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playerma } tv->maxents = 0; //clear these + tv->spawnstatic_count = 0; memset(tv->modellist, 0, sizeof(tv->modellist)); memset(tv->soundlist, 0, sizeof(tv->soundlist)); memset(tv->lightstyle, 0, sizeof(tv->lightstyle)); @@ -275,10 +313,18 @@ static void ParseServerData(sv_t *tv, netmsg_t *m, int to, unsigned int playerma static void ParseCDTrack(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { + char nqversion[3]; tv->cdtrack = ReadByte(m); if (!tv->parsingconnectiondata) - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + { + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); + + nqversion[0] = svc_cdtrack; + nqversion[1] = tv->cdtrack; + nqversion[2] = tv->cdtrack; + Multicast(tv, nqversion, 3, to, mask, NQ); + } } static void ParseStufftext(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { @@ -384,12 +430,12 @@ static void ParseStufftext(sv_t *tv, netmsg_t *m, int to, unsigned int mask) } else if (tv->usequkeworldprotocols && !strncmp(text, "setinfo ", 8)) { - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1); SendClientCommand(tv, text); } else { - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1); return; } } @@ -407,7 +453,7 @@ static void ParseSetInfo(sv_t *tv, netmsg_t *m) Info_SetValueForStarKey(tv->players[pnum].userinfo, key, value, sizeof(tv->players[pnum].userinfo)); if (!tv->parsingconnectiondata) - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1, QW); } static void ParseServerinfo(sv_t *tv, netmsg_t *m) @@ -421,17 +467,29 @@ static void ParseServerinfo(sv_t *tv, netmsg_t *m) Info_SetValueForStarKey(tv->serverinfo, key, value, sizeof(tv->serverinfo)); if (!tv->parsingconnectiondata) - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_all, (unsigned)-1, QW); } static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { unsigned char *t; char text[1024]; + char buffer[1024]; int level; level = ReadByte(m); - ReadString(m, text, sizeof(text)); + ReadString(m, text, sizeof(text)-2); + + if (level == 3) + { + strcpy(buffer+2, text); + buffer[1] = 1; + } + else + { + strcpy(buffer+1, text); + } + buffer[0] = svc_print; if (to == dem_all || to == dem_read) { @@ -464,14 +522,15 @@ static void ParsePrint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) } } - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); + Multicast(tv, buffer, strlen(buffer), to, mask, NQ); } static void ParseCenterprint(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { char text[1024]; ReadString(m, text, sizeof(text)); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1); } static int ParseList(sv_t *tv, netmsg_t *m, filename_t *list, int to, unsigned int mask) { @@ -533,7 +592,7 @@ static void ParseStaticSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask) tv->staticsound_count++; if (!tv->parsingconnectiondata) - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1); } static void ParseIntermission(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -545,7 +604,7 @@ static void ParseIntermission(sv_t *tv, netmsg_t *m, int to, unsigned int mask) ReadByte(m); ReadByte(m); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } void ParseSpawnStatic(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -561,7 +620,7 @@ void ParseSpawnStatic(sv_t *tv, netmsg_t *m, int to, unsigned int mask) tv->spawnstatic_count++; if (!tv->parsingconnectiondata) - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, Q1); } extern const usercmd_t nullcmd; @@ -610,6 +669,12 @@ static void ParsePlayerInfo(sv_t *tv, netmsg_t *m, qboolean clearoldplayers) tv->players[num].current.angles[1] = nonnullcmd.angles[1]; tv->players[num].current.angles[2] = nonnullcmd.angles[2]; } + else + { + tv->players[num].current.angles[0] = tv->proxyplayerangles[0]/360*65535; + tv->players[num].current.angles[1] = tv->proxyplayerangles[1]/360*65535; + tv->players[num].current.angles[2] = tv->proxyplayerangles[2]/360*65535; + } for (i=0 ; i<3 ; i++) { @@ -786,7 +851,7 @@ static void ParseUpdatePing(sv_t *tv, netmsg_t *m, int to, unsigned int mask) else Sys_Printf(tv->cluster, "svc_updateping: invalid player number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseUpdateFrags(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -801,7 +866,7 @@ static void ParseUpdateFrags(sv_t *tv, netmsg_t *m, int to, unsigned int mask) else Sys_Printf(tv->cluster, "svc_updatefrags: invalid player number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, (pnum < 16)?Q1:QW); } static void ParseUpdateStat(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -824,7 +889,7 @@ static void ParseUpdateStat(sv_t *tv, netmsg_t *m, int to, unsigned int mask) else Sys_Printf(tv->cluster, "svc_updatestat: invalid stat number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseUpdateStatLong(sv_t *tv, netmsg_t *m, int to, unsigned int mask) { @@ -846,7 +911,7 @@ static void ParseUpdateStatLong(sv_t *tv, netmsg_t *m, int to, unsigned int mask else Sys_Printf(tv->cluster, "svc_updatestatlong: invalid stat number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseUpdateUserinfo(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -864,7 +929,7 @@ static void ParseUpdateUserinfo(sv_t *tv, netmsg_t *m, int to, unsigned int mask } } - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParsePacketloss(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -880,7 +945,7 @@ static void ParsePacketloss(sv_t *tv, netmsg_t *m, int to, unsigned int mask) else Sys_Printf(tv->cluster, "svc_updatepl: invalid player number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseUpdateEnterTime(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -896,7 +961,7 @@ static void ParseUpdateEnterTime(sv_t *tv, netmsg_t *m, int to, unsigned int mas else Sys_Printf(tv->cluster, "svc_updateentertime: invalid player number\n"); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -930,7 +995,7 @@ static void ParseSound(sv_t *tv, netmsg_t *m, int to, unsigned int mask) for (i=0 ; i<3 ; i++) org[i] = ReadShort (m); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } static void ParseDamage(sv_t *tv, netmsg_t *m, int to, unsigned int mask) @@ -940,7 +1005,7 @@ static void ParseDamage(sv_t *tv, netmsg_t *m, int to, unsigned int mask) ReadShort (m); ReadShort (m); ReadShort (m); - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } enum { @@ -1041,7 +1106,7 @@ static void ParseTempEntity(sv_t *tv, netmsg_t *m, int to, unsigned int mask) return; } - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, to, mask, QW); } void ParseLightstyle(sv_t *tv, netmsg_t *m) @@ -1058,7 +1123,7 @@ void ParseLightstyle(sv_t *tv, netmsg_t *m) } } - Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_read, (unsigned)-1); + Multicast(tv, m->data+m->startpos, m->readpos - m->startpos, dem_read, (unsigned)-1, Q1); } void ParseNails(sv_t *tv, netmsg_t *m, qboolean nails2) @@ -1121,6 +1186,10 @@ void ParseDownload(sv_t *tv, netmsg_t *m) { fclose(tv->file); tv->file = NULL; + + snprintf(buffer, sizeof(buffer), "%s/%s", (tv->gamedir&&*tv->gamedir)?tv->gamedir:"id1", tv->modellist[1].name); + rename(tv->downloadname, buffer); + Sys_Printf(tv->cluster, "Download complete\n"); tv->bsp = BSP_LoadModel(tv->cluster, tv->gamedir, tv->modellist[1].name); @@ -1187,6 +1256,7 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) ParseSound(tv, &buf, to, mask); break; //#define svc_time 7 // [float] server time + case svc_print: ParsePrint(tv, &buf, to, mask); break; @@ -1198,12 +1268,21 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) case svc_setangle: if (!tv->usequkeworldprotocols) ReadByte(&buf); - ReadByte(&buf); - ReadByte(&buf); - ReadByte(&buf); + tv->proxyplayerangles[0] = ReadByte(&buf)*360.0/255; + tv->proxyplayerangles[1] = ReadByte(&buf)*360.0/255; + tv->proxyplayerangles[2] = ReadByte(&buf)*360.0/255; if (tv->usequkeworldprotocols && tv->controller) SendBufferToViewer(tv->controller, buf.data+buf.startpos, buf.readpos - buf.startpos, true); + + { + char nq[4]; + nq[0] = svc_setangle; + nq[1] = tv->proxyplayerangles[0]; + nq[2] = tv->proxyplayerangles[1]; + nq[3] = tv->proxyplayerangles[2]; + Multicast(tv, nq, 4, to, mask, Q1); + } break; case svc_serverdata: @@ -1215,9 +1294,11 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) break; //#define svc_updatename 13 // [qbyte] [string] + case svc_updatefrags: ParseUpdateFrags(tv, &buf, to, mask); break; + //#define svc_clientdata 15 // //#define svc_stopsound 16 // //#define svc_updatecolors 17 // [qbyte] [qbyte] [qbyte] @@ -1231,8 +1312,9 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) ReadByte(&buf); ReadByte(&buf); ReadByte(&buf); - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1); break; + case svc_damage: ParseDamage(tv, &buf, to, mask); break; @@ -1252,7 +1334,7 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) case svc_setpause: // [qbyte] on / off tv->ispaused = ReadByte(&buf); - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, dem_read, (unsigned)-1, Q1); break; //#define svc_signonnum 25 // [qbyte] used for the signon sequence @@ -1277,13 +1359,14 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) case svc_cdtrack: ParseCDTrack(tv, &buf, to, mask); break; + //#define svc_sellscreen 33 //#define svc_cutscene 34 //hmm... nq only... added after qw tree splitt? case svc_smallkick: case svc_bigkick: - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW); break; case svc_updateping: @@ -1300,7 +1383,7 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) case svc_muzzleflash: ReadShort(&buf); - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW); break; case svc_updateuserinfo: @@ -1310,16 +1393,19 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) case svc_download: // [short] size [size bytes] ParseDownload(tv, &buf); break; + case svc_playerinfo: ParsePlayerInfo(tv, &buf, clearoldplayers); clearoldplayers = false; break; + case svc_nails: ParseNails(tv, &buf, false); break; case svc_chokecount: ReadByte(&buf); break; + case svc_modellist: i = ParseList(tv, &buf, tv->modellist, to, mask); if (!i) @@ -1328,7 +1414,7 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) if (tv->bsp) BSP_Free(tv->bsp); - if (tv->cluster->nobsp && !tv->usequkeworldprotocols) + if (tv->cluster->nobsp || !tv->usequkeworldprotocols) tv->bsp = NULL; else tv->bsp = BSP_LoadModel(tv->cluster, tv->gamedir, tv->modellist[1].name); @@ -1352,10 +1438,10 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) { fclose(tv->file); unlink(tv->downloadname); - Sys_Printf(tv->cluster, "Was already downloading %s\nOld download canceled\n"); + Sys_Printf(tv->cluster, "Was already downloading %s\nOld download canceled\n", tv->downloadname); tv->file = NULL; } - snprintf(tv->downloadname, sizeof(tv->downloadname), "%s/%s", (tv->gamedir&&*tv->gamedir)?tv->gamedir:"qw", tv->modellist[1].name, sizeof(tv->downloadname)); + snprintf(tv->downloadname, sizeof(tv->downloadname), "%s/%s.tmp", (tv->gamedir&&*tv->gamedir)?tv->gamedir:"id1", tv->modellist[1].name); tv->file = fopen(tv->downloadname, "wb"); if (!tv->file) tv->drop = true; @@ -1384,6 +1470,7 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) SendClientCommand(tv, "modellist %i 0\n", tv->clservercount); } break; + case svc_packetentities: FlushPacketEntities(tv); ParsePacketEntities(tv, &buf); @@ -1392,15 +1479,16 @@ void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask) ReadByte(&buf); ParsePacketEntities(tv, &buf); break; + //#define svc_maxspeed 49 // maxspeed change, for prediction -case svc_entgravity: // gravity change, for prediction - ReadFloat(&buf); - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask); - break; -case svc_maxspeed: - ReadFloat(&buf); - Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask); - break; + case svc_entgravity: // gravity change, for prediction + ReadFloat(&buf); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW); + break; + case svc_maxspeed: + ReadFloat(&buf); + Multicast(tv, buf.data+buf.startpos, buf.readpos - buf.startpos, to, mask, QW); + break; case svc_setinfo: ParseSetInfo(tv, &buf); break; @@ -1413,6 +1501,7 @@ case svc_maxspeed: case svc_nails2: ParseNails(tv, &buf, true); break; + default: buf.readpos = buf.startpos; Sys_Printf(tv->cluster, "Can't handle svc %i\n", (unsigned int)ReadByte(&buf)); diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index a2c9ae590..ccc3dccd8 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -42,13 +42,28 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef _WIN32 #include - #include - #pragma comment (lib, "wsock32.lib") + #include //this includes windows.h and is the reason for much compiling slowness with windows builds. + #ifdef _MSC_VER + #pragma comment (lib, "wsock32.lib") + #endif #define qerrno WSAGetLastError() #define EWOULDBLOCK WSAEWOULDBLOCK #define EINPROGRESS WSAEINPROGRESS + #define ECONNREFUSED WSAECONNREFUSED #define ENOTCONN WSAENOTCONN + //we have special functions to properly terminate sprintf buffers in windows. + //we assume other systems are designed with even a minor thought to security. + #if !defined(__MINGW32_VERSION) + #define unlink _unlink //why do MS have to be so awkward? + int snprintf(char *buffer, int buffersize, char *format, ...); + #if !defined(_VC80_UPGRADE) + int vsnprintf(char *buffer, int buffersize, char *format, va_list argptr); + #endif + #else + #define unlink remove //seems mingw misses something + #endif + #ifdef _MSC_VER //okay, so warnings are here to help... they're ugly though. #pragma warning(disable: 4761) //integral size mismatch in argument @@ -121,7 +136,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define MAX_SERVERINFO_STRING 1024 //standard quake has 512 here. #define MAX_USERINFO 192 #define MAX_CLIENTS 32 -#define MAX_STATS 32 #define MAX_LIST 256 #define MAX_MODELS MAX_LIST #define MAX_SOUNDS MAX_LIST @@ -131,6 +145,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define MAX_LIGHTSTYLES 64 #define DEFAULT_HOSTNAME "FTEQTV" +#define MAX_PROXY_INBUFFER 4096 #define MAX_PROXY_BUFFER (1<<14) //must be power-of-two #define PREFERED_PROXY_BUFFER 8192 //the ammount of data we try to leave in our input buffer (must be large enough to contain any single mvd frame) @@ -141,16 +156,66 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define Z_EXT_SERVERTIME (1<<3) // STAT_TIME #define Z_EXT_STRING "8" + + +#define MAX_STATS 32 +#define STAT_HEALTH 0 +#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 +#define STAT_WEAPONFRAME 5 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 +#define STAT_ROCKETS 8 +#define STAT_CELLS 9 +#define STAT_ACTIVEWEAPON 10 +#define STAT_TOTALSECRETS 11 +#define STAT_TOTALMONSTERS 12 +#define STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define STAT_MONSTERS 14 // bumped by svc_killedmonster +#define STAT_ITEMS 15 + #define STAT_TIME 17 //A ZQ hack, sending time via a stat. //this allows l33t engines to interpolate properly without spamming at a silly high fps. + + + +#ifndef __cplusplus typedef enum {false, true} qboolean; +#else +typedef int qboolean; +extern "C" { +#endif typedef unsigned char netadr_t[64]; +#define NQ_PACKETS_PER_SECOND 100 +#define MAX_NQMSGLEN 8000 #define MAX_MSGLEN 1400 +#define MAX_NQDATAGRAM 1024 #define MAX_BACKBUF_SIZE 1000 //this is smaller so we don't loose entities when lagging + +//NQ transport layer defines +#define NETFLAG_LENGTH_MASK 0x0000ffff +#define NETFLAG_DATA 0x00010000 +#define NETFLAG_ACK 0x00020000 +#define NETFLAG_NAK 0x00040000 +#define NETFLAG_EOM 0x00080000 +#define NETFLAG_UNRELIABLE 0x00100000 +#define NETFLAG_CTL 0x80000000 + +#define CCREQ_CONNECT 0x01 + +#define CCREP_ACCEPT 0x81 +#define CCREP_REJECT 0x82 + +#define NET_GAMENAME_NQ "QUAKE" +#define NET_PROTOCOL_VERSION 3 + + typedef struct { unsigned int readpos; unsigned int cursize; @@ -171,10 +236,11 @@ typedef struct { int reliable_length; qboolean drop; qboolean isclient; + qboolean isnqprotocol; netmsg_t message; - char message_buf[MAX_MSGLEN]; //reliable message being built - char reliable_buf[MAX_MSGLEN]; //reliable message that we're making sure arrives. + char message_buf[MAX_NQMSGLEN]; //reliable message being built + char reliable_buf[MAX_NQMSGLEN]; //reliable message that we're making sure arrives. float rate; @@ -186,6 +252,15 @@ typedef struct { unsigned int incoming_sequence; unsigned int outgoing_sequence; + + + + unsigned int reliable_start; + unsigned int outgoing_unreliable; + unsigned int incoming_unreliable; + unsigned int in_fragment_length; + + char in_fragment_buf[MAX_NQMSGLEN]; } netchan_t; typedef struct { @@ -199,7 +274,7 @@ typedef struct { unsigned char colormap; unsigned char skinnum; short origin[3]; - unsigned char angles[3]; + char angles[3]; unsigned char effects; } entity_state_t; typedef struct { @@ -208,7 +283,7 @@ typedef struct { unsigned char skinnum; short origin[3]; short velocity[3]; - unsigned short angles[3]; + short angles[3]; unsigned char effects; unsigned char weaponframe; } player_state_t; @@ -251,6 +326,8 @@ typedef struct sv_s sv_t; typedef struct cluster_s cluster_t; typedef struct viewer_s { qboolean drop; + unsigned int timeout; + unsigned int nextpacket; //for nq clients netchan_t netchan; qboolean maysend; qboolean chokeme; @@ -291,12 +368,21 @@ typedef struct viewer_s { int fwdval; //for scrolling up/down the menu using +forward/+back :) } viewer_t; +//'other proxy', these are mvd stream clients. typedef struct oproxy_s { + int authkey; + unsigned int droptime; + qboolean flushing; qboolean drop; - FILE *file; - SOCKET sock; + sv_t *defaultstream; + + FILE *file; //recording a demo + SOCKET sock; //playing to a proxy + + unsigned char inbuffer[MAX_PROXY_INBUFFER]; + unsigned int inbuffersize; //amount of data available. unsigned char buffer[MAX_PROXY_BUFFER]; unsigned int buffersize; //use cyclic buffering. @@ -329,11 +415,16 @@ typedef struct { } nail_t; struct sv_s { + char connectpassword[64]; //password given to server netadr_t serveraddress; netchan_t netchan; unsigned char buffer[MAX_PROXY_BUFFER]; //this doesn't cycle. int buffersize; //it memmoves down + qboolean parsingqtvheader; + + unsigned char upstreambuffer[2048]; + int upstreambuffersize; unsigned int parsetime; @@ -389,17 +480,24 @@ struct sv_s { qboolean ispaused; unsigned int packetratelimiter; viewer_t *controller; + + qboolean proxyplayer; //a player is actually playing on the proxy. + usercmd_t proxyplayerucmds[3]; + int proxyplayerucmdnum; + int proxyplayerbuttons; + float proxyplayerangles[3]; + float proxyplayerimpulse; + qboolean maysend; FILE *file; unsigned int filelength; SOCKET sourcesock; - SOCKET listenmvd; //tcp + mvd protocol + SOCKET tcpsocket; //tcp + mvd protocol int tcplistenportnum; oproxy_t *proxies; - int numproxies; qboolean parsingconnectiondata; //so reject any new connects for now @@ -424,10 +522,12 @@ struct sv_s { //options: char server[MAX_QPATH]; + int streamid; }; struct cluster_s { SOCKET qwdsocket; //udp + quakeworld protocols + SOCKET tcpsocket; //tcp listening socket (for mvd and listings and stuff) char commandinput[512]; int inputlength; @@ -440,21 +540,28 @@ struct cluster_s { int numviewers; sv_t *servers; int numservers; + int nextstreamid; //options int qwlistenportnum; - char password[256]; + int tcplistenportnum; + char adminpassword[256];//password required for rcon etc + char qtvpassword[256]; //password required to connect a proxy char hostname[256]; char master[MAX_QPATH]; qboolean chokeonnotupdated; qboolean lateforward; qboolean notalking; qboolean nobsp; + int maxviewers; + + int numproxies; int maxproxies; qboolean wanttoexit; + oproxy_t *pendingproxies; }; @@ -475,7 +582,7 @@ void ReadString(netmsg_t *b, char *string, int maxlen); #define clc_bad 0 #define clc_nop 1 -//define clc_doublemove 2 +#define clc_disconnect 2 //NQ only #define clc_move 3 // [[usercmd_t] #define clc_stringcmd 4 // [string] message #define clc_delta 5 // [byte] sequence number, requests delta compression of message @@ -492,9 +599,9 @@ void ReadString(netmsg_t *b, char *string, int maxlen); #define svc_disconnect 2 #define svc_updatestat 3 // [qbyte] [qbyte] //#define svc_version 4 // [long] server version -//#define svc_setview 5 // [short] entity number +#define svc_nqsetview 5 // [short] entity number #define svc_sound 6 // -//#define svc_time 7 // [float] server time +#define svc_nqtime 7 // [float] server time #define svc_print 8 // [qbyte] id [string] null terminated string #define svc_stufftext 9 // [string] stuffed into client's console buffer // the string should be \n terminated @@ -502,11 +609,11 @@ void ReadString(netmsg_t *b, char *string, int maxlen); #define svc_serverdata 11 // [long] protocol ... #define svc_lightstyle 12 // [qbyte] [string] -//#define svc_updatename 13 // [qbyte] [string] +#define svc_nqupdatename 13 // [qbyte] [string] #define svc_updatefrags 14 // [qbyte] [short] -//#define svc_clientdata 15 // +#define svc_nqclientdata 15 // //#define svc_stopsound 16 // -//#define svc_updatecolors 17 // [qbyte] [qbyte] [qbyte] +#define svc_nqupdatecolors 17 // [qbyte] [qbyte] [qbyte] #define svc_particle 18 // [vec3] #define svc_damage 19 @@ -516,7 +623,7 @@ void ReadString(netmsg_t *b, char *string, int maxlen); #define svc_temp_entity 23 // variable #define svc_setpause 24 // [qbyte] on / off -//#define svc_signonnum 25 // [qbyte] used for the signon sequence +#define svc_nqsignonnum 25 // [qbyte] used for the signon sequence #define svc_centerprint 26 // [string] to put in center of the screen @@ -577,6 +684,7 @@ void ReadString(netmsg_t *b, char *string, int maxlen); #define dem_mask 7 +#define PROTOCOL_VERSION_NQ 15 #define PROTOCOL_VERSION 28 @@ -617,8 +725,10 @@ void ReadString(netmsg_t *b, char *string, int maxlen); - - +//flags for where a message can be sent, for easy broadcasting +#define Q1 (NQ|QW) +#define QW 1 +#define NQ 2 @@ -636,10 +746,11 @@ void WriteString2(netmsg_t *b, const char *str); void WriteString(netmsg_t *b, const char *str); void WriteData(netmsg_t *b, const char *data, int length); -void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playermask); -void Broadcast(cluster_t *cluster, char *buffer, int length); +void Multicast(sv_t *tv, char *buffer, int length, int to, unsigned int playermask,int suitablefor); +void Broadcast(cluster_t *cluster, char *buffer, int length, int suitablefor); void ParseMessage(sv_t *tv, char *buffer, int length, int to, int mask); void BuildServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount); +void BuildNQServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount); SOCKET QW_InitUDPSocket(int port); void QW_UpdateUDPStuff(cluster_t *qtv); unsigned int Sys_Milliseconds(void); @@ -658,6 +769,7 @@ int Netchan_IsLocal (netadr_t adr); void NET_SendPacket(cluster_t *cluster, SOCKET sock, int length, char *data, netadr_t adr); qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2); qboolean Netchan_Process (netchan_t *chan, netmsg_t *msg); +qboolean NQNetChan_Process(cluster_t *cluster, netchan_t *chan, netmsg_t *msg); void Netchan_Transmit (cluster_t *cluster, netchan_t *chan, int length, const unsigned char *data); int SendList(sv_t *qtv, int first, const filename_t *list, int svc, netmsg_t *msg); int Prespawn(sv_t *qtv, int curmsgsize, netmsg_t *msg, int bufnum, int thisplayer); @@ -671,6 +783,7 @@ qboolean BSP_Visible(bsp_t *bsp, int leafcount, unsigned short *list); void BSP_SetupForPosition(bsp_t *bsp, float x, float y, float z); void QW_SetViewersServer(viewer_t *viewer, sv_t *sv); unsigned short QCRC_Block (unsigned char *start, int count); +unsigned short QCRC_Value(unsigned short crcvalue); void Netchan_OutOfBand (cluster_t *cluster, SOCKET sock, netadr_t adr, int length, unsigned char *data); void WriteDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move); void SendClientCommand(sv_t *qtv, char *fmt, ...); @@ -682,16 +795,29 @@ char *Info_ValueForKey (char *s, const char *key, char *buffer, int buffersize); void Info_SetValueForStarKey (char *s, const char *key, const char *value, int maxsize); void ReadDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move); unsigned Com_BlockChecksum (void *buffer, int length); +void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); void Sys_Printf(cluster_t *cluster, char *fmt, ...); -#ifdef _WIN32 -int snprintf(char *buffer, int buffersize, char *format, ...); -#endif -#if (defined(_WIN32) && !defined(_VC80_UPGRADE)) -int vsnprintf(char *buffer, int buffersize, char *format, va_list argptr); -#endif -qboolean Net_FileProxy(sv_t *qtv, char *filename); -sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, qboolean force, qboolean autoclose, qboolean noduplicates); +oproxy_t *Net_FileProxy(sv_t *qtv, char *filename); +sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password, qboolean force, qboolean autoclose, qboolean noduplicates); SOCKET Net_MVDListen(int port); qboolean Net_StopFileProxy(sv_t *qtv); + + +void SV_FindProxies(SOCKET sock, cluster_t *cluster, sv_t *defaultqtv); +qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend); +void SV_ForwardStream(sv_t *qtv, char *buffer, int length); + +unsigned char *FS_ReadFile(char *gamedir, char *filename, unsigned int *size); + +void ChooseFavoriteTrack(sv_t *tv); + +void DemoViewer_Init(void); +void DemoViewer_Update(sv_t *svtest); +void DemoViewer_Shutdown(void); + + +#ifdef __cplusplus +} +#endif diff --git a/fteqtv/qtvprox.dsp b/fteqtv/qtvprox.dsp index 0887fd437..7e013fba1 100644 --- a/fteqtv/qtvprox.dsp +++ b/fteqtv/qtvprox.dsp @@ -63,6 +63,7 @@ LINK32=link.exe # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug" # PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c # ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /Yu"qtv.h" /FD /GZ /c @@ -90,10 +91,18 @@ SOURCE=.\bsp.c # End Source File # Begin Source File +SOURCE=.\control.c +# End Source File +# Begin Source File + SOURCE=.\crc.c # End Source File # Begin Source File +SOURCE=.\forward.c +# End Source File +# Begin Source File + SOURCE=.\mdfour.c # End Source File # Begin Source File @@ -165,5 +174,184 @@ SOURCE=.\qtv.h # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # End Group +# Begin Group "viewer" + +# PROP Default_Filter "" +# Begin Group "d3d" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\viewer\d3drend\d3d_backend.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\d3drend\d3d_image.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\d3drend\d3d_video.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# End Group +# Begin Source File + +SOURCE=.\viewer\cvar.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_backend.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_bsp29.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_image.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_mdl.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_testgrid.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\gl_vidsdl.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\matrix.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\model.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewer\renderer.cpp + +!IF "$(CFG)" == "qtvprox - Win32 Release" + +!ELSEIF "$(CFG)" == "qtvprox - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# End Group # End Target # End Project diff --git a/fteqtv/qw.c b/fteqtv/qw.c index d074fb0ab..dfb15e81e 100644 --- a/fteqtv/qw.c +++ b/fteqtv/qw.c @@ -20,6 +20,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "qtv.h" +static const filename_t ConnectionlessModelList[] = {{""}, {"maps/start.bsp"}, {"progs/player.mdl"}, {""}}; +static const filename_t ConnectionlessSoundList[] = {{""}, {""}}; + + #define MENU_NONE 0 #define MENU_SERVERS 1 #define MENU_ADMIN 2 @@ -28,7 +32,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum); void QW_SetMenu(viewer_t *v, int menunum); -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32_VERSION) int snprintf(char *buffer, int buffersize, char *format, ...) { va_list argptr; @@ -41,7 +45,7 @@ int snprintf(char *buffer, int buffersize, char *format, ...) return ret; } #endif -#if (defined(_WIN32) && !defined(_VC80_UPGRADE)) +#if (defined(_WIN32) && !defined(_VC80_UPGRADE) && !defined(__MINGW32_VERSION)) int vsnprintf(char *buffer, int buffersize, char *format, va_list argptr) { int ret; @@ -262,18 +266,95 @@ void BuildServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount) WriteString(msg, "\"\n"); } } +void BuildNQServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount) +{ + int i; + WriteByte(msg, svc_serverdata); + WriteLong(msg, PROTOCOL_VERSION_NQ); + WriteByte(msg, 16); //MAX_CLIENTS + WriteByte(msg, 0); //game type + + + if (!tv || tv->parsingconnectiondata ) + { + //dummy connection, for choosing a game to watch. + WriteString(msg, "FTEQTV Proxy"); + + + //modellist + for (i = 1; *ConnectionlessModelList[i].name; i++) + { + WriteString(msg, ConnectionlessModelList[i].name); + } + WriteString(msg, ""); + + //soundlist + for (i = 1; *ConnectionlessSoundList[i].name; i++) + { + WriteString(msg, ConnectionlessSoundList[i].name); + } + WriteString(msg, ""); + + WriteByte(msg, svc_cdtrack); + WriteByte(msg, 0); //two of them, yeah... weird, eh? + WriteByte(msg, 0); + + WriteByte(msg, svc_nqsetview); + WriteShort(msg, 1); + + WriteByte(msg, svc_nqsignonnum); + WriteByte(msg, 1); + } + else + { + //dummy connection, for choosing a game to watch. + WriteString(msg, "FTEQTV Proxy"); + + + //modellist + for (i = 1; *tv->modellist[i].name; i++) + { + WriteString(msg, tv->modellist[i].name); + } + WriteString(msg, ""); + + //soundlist + for (i = 1; *tv->soundlist[i].name; i++) + { + WriteString(msg, tv->soundlist[i].name); + } + WriteString(msg, ""); + + WriteByte(msg, svc_cdtrack); + WriteByte(msg, tv->cdtrack); //two of them, yeah... weird, eh? + WriteByte(msg, tv->cdtrack); + + WriteByte(msg, svc_nqsetview); + WriteShort(msg, 1); + + WriteByte(msg, svc_nqsignonnum); + WriteByte(msg, 1); + } +} void SendServerData(sv_t *tv, viewer_t *viewer) { netmsg_t msg; - char buffer[1024]; - InitNetMsg(&msg, buffer, sizeof(buffer)); + char buffer[MAX_NQMSGLEN]; + + if (viewer->netchan.isnqprotocol) + InitNetMsg(&msg, buffer, sizeof(buffer)); + else + InitNetMsg(&msg, buffer,1024); if (tv && (tv->controller == viewer || !tv->controller)) viewer->thisplayer = tv->thisplayer; else viewer->thisplayer = MAX_CLIENTS-1; - BuildServerData(tv, &msg, false, viewer->servercount); + if (viewer->netchan.isnqprotocol) + BuildNQServerData(tv, &msg, false, viewer->servercount); + else + BuildServerData(tv, &msg, false, viewer->servercount); SendBufferToViewer(viewer, msg.data, msg.cursize, true); @@ -282,6 +363,48 @@ void SendServerData(sv_t *tv, viewer_t *viewer) memset(viewer->currentstats, 0, sizeof(viewer->currentstats)); } +void SendNQSpawnInfoToViewer(cluster_t *cluster, viewer_t *viewer, netmsg_t *msg) +{ + int i; + sv_t *tv = viewer->server; + WriteByte(msg, svc_nqtime); + WriteFloat(msg, cluster->curtime/1000.0f); + + if (tv) + { + for (i=0; iplayers[i].frags); + WriteByte (msg, svc_nqupdatecolors); + WriteByte (msg, i); + WriteByte (msg, 0); //fixme + } + } + else + { + for (i=0; i < 16; i++) + { + WriteByte (msg, svc_nqupdatename); + WriteByte (msg, i); + WriteString (msg, ""); + WriteByte (msg, svc_updatefrags); + WriteByte (msg, i); + WriteShort (msg, 0); + WriteByte (msg, svc_nqupdatecolors); + WriteByte (msg, i); + WriteByte (msg, 0); + } + } + + WriteByte(msg, svc_nqsignonnum); + WriteByte(msg, 3); +} + int SendCurrentUserinfos(sv_t *tv, int cursize, netmsg_t *msg, int i, int thisplayer) { if (i < 0) @@ -291,14 +414,14 @@ int SendCurrentUserinfos(sv_t *tv, int cursize, netmsg_t *msg, int i, int thispl for (; i < MAX_CLIENTS; i++) { - if (i == thisplayer && (!tv || !(tv->controller))) + if (i == thisplayer && (!tv || !(tv->controller || tv->proxyplayer))) { WriteByte(msg, svc_updateuserinfo); WriteByte(msg, i); WriteLong(msg, i); WriteString2(msg, "\\*spectator\\1\\name\\"); - if (tv && tv->hostname && tv->hostname[0]) + if (tv && tv->hostname[0]) WriteString(msg, tv->hostname); else WriteString(msg, "FTEQTV"); @@ -359,7 +482,7 @@ void WriteEntityState(netmsg_t *msg, entity_state_t *es) WriteByte(msg, es->angles[i]); } } -int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int i) +int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int maxbuffersize, int i) { if (i < 0 || i >= MAX_ENTITIES) @@ -367,25 +490,29 @@ int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int i) for (; i < MAX_ENTITIES; i++) { - if (msg->cursize+cursize+16 > 768) + if (msg->cursize+cursize+16 > maxbuffersize) { return i; } - WriteByte(msg, svc_spawnbaseline); - WriteShort(msg, i); - WriteEntityState(msg, &tv->entity[i].baseline); + + if (tv->entity[i].baseline.modelindex) + { + WriteByte(msg, svc_spawnbaseline); + WriteShort(msg, i); + WriteEntityState(msg, &tv->entity[i].baseline); + } } return i; } -int SendCurrentLightmaps(sv_t *tv, int cursize, netmsg_t *msg, int i) +int SendCurrentLightmaps(sv_t *tv, int cursize, netmsg_t *msg, int maxbuffersize, int i) { if (i < 0 || i >= MAX_LIGHTSTYLES) return i; for (; i < MAX_LIGHTSTYLES; i++) { - if (msg->cursize+cursize+strlen(tv->lightstyle[i].name) > 768) + if (msg->cursize+cursize+strlen(tv->lightstyle[i].name) > maxbuffersize) { return i; } @@ -395,14 +522,14 @@ int SendCurrentLightmaps(sv_t *tv, int cursize, netmsg_t *msg, int i) } return i; } -int SendStaticSounds(sv_t *tv, int cursize, netmsg_t *msg, int i) +int SendStaticSounds(sv_t *tv, int cursize, netmsg_t *msg, int maxbuffersize, int i) { if (i < 0 || i >= MAX_STATICSOUNDS) return i; for (; i < MAX_STATICSOUNDS; i++) { - if (msg->cursize+cursize+16 > 768) + if (msg->cursize+cursize+16 > maxbuffersize) { return i; } @@ -420,14 +547,14 @@ int SendStaticSounds(sv_t *tv, int cursize, netmsg_t *msg, int i) return i; } -int SendStaticEntities(sv_t *tv, int cursize, netmsg_t *msg, int i) +int SendStaticEntities(sv_t *tv, int cursize, netmsg_t *msg, int maxbuffersize, int i) { if (i < 0 || i >= MAX_STATICENTITIES) return i; for (; i < MAX_STATICENTITIES; i++) { - if (msg->cursize+cursize+16 > 768) + if (msg->cursize+cursize+16 > maxbuffersize) { return i; } @@ -498,6 +625,121 @@ qboolean ChallengePasses(netadr_t *addr, int challenge) return false; } +void NewClient(cluster_t *cluster, viewer_t *viewer) +{ + viewer->trackplayer = -1; + + + if (!viewer->server) + { + QW_SetMenu(viewer, MENU_SERVERS); + } + else + { + viewer->menunum = -1; + QW_SetMenu(viewer, MENU_NONE); + } + + + QW_PrintfToViewer(viewer, "Welcome to FTEQTV\n"); + QW_StuffcmdToViewer(viewer, "alias admin \"cmd admin\"\n"); + + QW_StuffcmdToViewer(viewer, "alias \"proxy:up\" \"say proxy:menu up\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:down\" \"say proxy:menu down\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:right\" \"say proxy:menu right\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:left\" \"say proxy:menu left\"\n"); + + QW_StuffcmdToViewer(viewer, "alias \"proxy:select\" \"say proxy:menu select\"\n"); + + QW_StuffcmdToViewer(viewer, "alias \"proxy:home\" \"say proxy:menu home\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:end\" \"say proxy:menu end\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:menu\" \"say proxy:menu\"\n"); + QW_StuffcmdToViewer(viewer, "alias \"proxy:backspace\" \"say proxy:menu backspace\"\n"); + + QW_PrintfToViewer(viewer, "Type admin for the admin menu\n"); +} + +void NewNQClient(cluster_t *cluster, netadr_t *addr) +{ + sv_t *initialserver; + int header; + int len; + unsigned char buffer[64]; + viewer_t *viewer = NULL;; + + + if (cluster->numviewers >= cluster->maxviewers && cluster->maxviewers) + { + buffer[4] = CCREP_REJECT; + strcpy(buffer+5, "Sorry, proxy is full.\n"); + len = strlen(buffer+5)+5; + } +/* else + { + buffer[4] = CCREP_REJECT; + strcpy(buffer+5, "NQ not supported yet\n"); + len = strlen(buffer+5)+5; + }*/ + else if (!(viewer = malloc(sizeof(viewer_t)))) + { + buffer[4] = CCREP_REJECT; + strcpy(buffer+5, "Out of memory\n"); + len = strlen(buffer+5)+5; + } + else + { + buffer[4] = CCREP_ACCEPT; + buffer[5] = (cluster->qwlistenportnum&0x00ff)>>0; + buffer[6] = (cluster->qwlistenportnum&0xff00)>>8; + buffer[7] = 0; + buffer[8] = 0; + len = 4+1+4; + } + + *(int*)buffer = NETFLAG_CTL | len; + header = (buffer[0]<<24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; + *(int*)buffer = header; + + NET_SendPacket (cluster, cluster->qwdsocket, len, buffer, *addr); + + if (!viewer) + return; + + + memset(viewer, 0, sizeof(*viewer)); + + + Netchan_Setup (cluster->qwdsocket, &viewer->netchan, *addr, 0, false); + viewer->netchan.isnqprotocol = true; + + viewer->next = cluster->viewers; + cluster->viewers = viewer; + viewer->delta_frame = -1; + + initialserver = NULL; + if (cluster->numservers == 1) + { + initialserver = cluster->servers; + if (!initialserver->modellist[1].name[0]) + initialserver = NULL; //damn, that server isn't ready + } + + viewer->server = initialserver; + if (viewer->server) + viewer->server->numviewers++; + + cluster->numviewers++; + + strcpy(viewer->name, "unnamed"); + sprintf(viewer->userinfo, "\\name\\%s", viewer->name); + + NewClient(cluster, viewer); + + QW_StuffcmdToViewer(viewer, "cmd new\n"); + + Sys_Printf(cluster, "New NQ client connected\n"); +} + void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) { sv_t *initialserver; @@ -528,7 +770,6 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) } memset(viewer, 0, sizeof(viewer_t)); - viewer->trackplayer = -1; Netchan_Setup (cluster->qwdsocket, &viewer->netchan, *addr, atoi(qport), false); viewer->next = cluster->viewers; @@ -547,16 +788,6 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) if (viewer->server) viewer->server->numviewers++; - if (!initialserver) - { - QW_SetMenu(viewer, MENU_SERVERS); - } - else - { - viewer->menunum = -1; - QW_SetMenu(viewer, MENU_NONE); - } - cluster->numviewers++; Info_ValueForKey(infostring, "name", viewer->name, sizeof(viewer->name)); @@ -564,24 +795,7 @@ void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage) Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "j"); - QW_PrintfToViewer(viewer, "Welcome to FTEQTV\n"); - QW_StuffcmdToViewer(viewer, "alias admin \"cmd admin\"\n"); - - QW_StuffcmdToViewer(viewer, "alias \"proxy:up\" \"say proxy:menu up\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:down\" \"say proxy:menu down\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:right\" \"say proxy:menu right\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:left\" \"say proxy:menu left\"\n"); - - QW_StuffcmdToViewer(viewer, "alias \"proxy:select\" \"say proxy:menu select\"\n"); - - QW_StuffcmdToViewer(viewer, "alias \"proxy:home\" \"say proxy:menu home\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:end\" \"say proxy:menu end\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:menu\" \"say proxy:menu\"\n"); - QW_StuffcmdToViewer(viewer, "alias \"proxy:backspace\" \"say proxy:menu backspace\"\n"); - - QW_PrintfToViewer(viewer, "Type admin for the admin menu\n"); - -// if + NewClient(cluster, viewer); } void QW_SetMenu(viewer_t *v, int menunum) @@ -625,7 +839,7 @@ void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from) char *command; int passlen; - if (!*cluster->password) + if (!*cluster->adminpassword) { Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n"); return; @@ -636,7 +850,7 @@ void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from) command = strchr(message, ' '); passlen = command-message; - if (passlen != strlen(cluster->password) || strncmp(message, cluster->password, passlen)) + if (passlen != strlen(cluster->adminpassword) || strncmp(message, cluster->adminpassword, passlen)) { Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n"); return; @@ -997,6 +1211,375 @@ void SendLocalPlayerState(sv_t *tv, viewer_t *v, int playernum, netmsg_t *msg) } } +#define UNQ_MOREBITS (1<<0) +#define UNQ_ORIGIN1 (1<<1) +#define UNQ_ORIGIN2 (1<<2) +#define UNQ_ORIGIN3 (1<<3) +#define UNQ_ANGLE2 (1<<4) +#define UNQ_NOLERP (1<<5) // don't interpolate movement +#define UNQ_FRAME (1<<6) +#define UNQ_SIGNAL (1<<7) // just differentiates from other updates + +// svc_update can pass all of the fast update bits, plus more +#define UNQ_ANGLE1 (1<<8) +#define UNQ_ANGLE3 (1<<9) +#define UNQ_MODEL (1<<10) +#define UNQ_COLORMAP (1<<11) +#define UNQ_SKIN (1<<12) +#define UNQ_EFFECTS (1<<13) +#define UNQ_LONGENTITY (1<<14) +#define UNQ_UNUSED (1<<15) + + +#define SU_VIEWHEIGHT (1<<0) +#define SU_IDEALPITCH (1<<1) +#define SU_PUNCH1 (1<<2) +#define SU_PUNCH2 (1<<3) +#define SU_PUNCH3 (1<<4) +#define SU_VELOCITY1 (1<<5) +#define SU_VELOCITY2 (1<<6) +#define SU_VELOCITY3 (1<<7) +//define SU_AIMENT (1<<8) AVAILABLE BIT +#define SU_ITEMS (1<<9) +#define SU_ONGROUND (1<<10) // no data follows, the bit is it +#define SU_INWATER (1<<11) // no data follows, the bit is it +#define SU_WEAPONFRAME (1<<12) +#define SU_ARMOR (1<<13) +#define SU_WEAPON (1<<14) + +void SendNQClientData(sv_t *tv, viewer_t *v, netmsg_t *msg) +{ + playerinfo_t *pl; + int bits; + int i; + + if (!tv) + return; + + if (v->trackplayer < 0) + pl = &tv->players[v->thisplayer]; + else + pl = &tv->players[v->trackplayer]; + + bits = 0; + + if (!pl->dead) + bits |= SU_VIEWHEIGHT; + + if (0) + bits |= SU_IDEALPITCH; + + bits |= SU_ITEMS; + + if ( 0) + bits |= SU_ONGROUND; + + if ( 0 ) + bits |= SU_INWATER; + + for (i=0 ; i<3 ; i++) + { + if (0) + bits |= (SU_PUNCH1<current.weaponframe) + bits |= SU_WEAPONFRAME; + + if (pl->stats[STAT_ARMOR]) + bits |= SU_ARMOR; + +// if (pl->stats[STAT_WEAPON]) + bits |= SU_WEAPON; + +// send the data + + WriteByte (msg, svc_nqclientdata); + WriteShort (msg, bits); + + if (bits & SU_VIEWHEIGHT) + WriteByte (msg, 22); + + if (bits & SU_IDEALPITCH) + WriteByte (msg, 0); + + for (i=0 ; i<3 ; i++) + { + if (bits & (SU_PUNCH1<stats[STAT_ITEMS]); + + if (bits & SU_WEAPONFRAME) + WriteByte (msg, pl->current.weaponframe); + if (bits & SU_ARMOR) + WriteByte (msg, pl->stats[STAT_ARMOR]); + if (bits & SU_WEAPON) + WriteByte (msg, pl->stats[STAT_WEAPON]); + + WriteShort (msg, pl->stats[STAT_HEALTH]); + WriteByte (msg, pl->stats[STAT_AMMO]); + WriteByte (msg, pl->stats[STAT_SHELLS]); + WriteByte (msg, pl->stats[STAT_NAILS]); + WriteByte (msg, pl->stats[STAT_ROCKETS]); + WriteByte (msg, pl->stats[STAT_CELLS]); + + WriteByte (msg, pl->stats[STAT_ACTIVEWEAPON]); +} + +void SendNQPlayerStates(cluster_t *cluster, sv_t *tv, viewer_t *v, netmsg_t *msg) +{ + int miss; + int e; + int i; + usercmd_t to; + unsigned short flags; + short interp; + float lerp; + int bits; + unsigned short org[3]; + entity_t *ent; + playerinfo_t *pl; + + + memset(&to, 0, sizeof(to)); + + if (tv) + { + WriteByte(msg, svc_nqtime); + WriteFloat(msg, tv->physicstime/1000.0f); + + BSP_SetupForPosition(tv->bsp, v->origin[0], v->origin[1], v->origin[2]); + + lerp = ((tv->simtime - tv->oldpackettime)/1000.0f) / ((tv->nextpackettime - tv->oldpackettime)/1000.0f); + if (lerp < 0) + lerp = 0; + if (lerp > 1) + lerp = 1; + + if (tv->controller == v) + lerp = 1; + } + else + { + WriteByte(msg, svc_nqtime); + WriteFloat(msg, cluster->curtime/1000.0f); + + lerp = 1; + } + + SendNQClientData(tv, v, msg); + + if (tv) + { + + if (v->trackplayer >= 0) + { + WriteByte(msg, svc_nqsetview); + WriteShort(msg, v->trackplayer+1); + + WriteByte(msg, svc_setangle); + WriteByte(msg, tv->players[v->trackplayer].current.angles[0]>>8); + WriteByte(msg, tv->players[v->trackplayer].current.angles[1]>>8); + WriteByte(msg, tv->players[v->trackplayer].current.angles[2]>>8); + } + + + for (e = 0; e < MAX_CLIENTS; e++) + { + pl = &tv->players[e]; + ent = &tv->entity[e+1]; + if (!pl->active) + continue; + + if (pl->current.modelindex >= tv->numinlines && !BSP_Visible(tv->bsp, pl->leafcount, pl->leafs)) + continue; + + pl->current.modelindex = 8; + +// send an update + bits = 0; + + for (i=0 ; i<3 ; i++) + { + org[i] = (lerp)*pl->current.origin[i] + (1-lerp)*pl->old.origin[i]; + bits |= UNQ_ORIGIN1<current.angles[0]>>8 != ent->baseline.angles[0] ) + bits |= UNQ_ANGLE1; + + if ( pl->current.angles[1]>>8 != ent->baseline.angles[1] ) + bits |= UNQ_ANGLE2; + + if ( pl->current.angles[2]>>8 != ent->baseline.angles[2] ) + bits |= UNQ_ANGLE3; + +// if (pl->v.movetype == MOVETYPE_STEP) +// bits |= UNQ_NOLERP; // don't mess up the step animation + + if (ent->baseline.colormap != e+1) + bits |= UNQ_COLORMAP; + + if (ent->baseline.skinnum != pl->current.skinnum) + bits |= UNQ_SKIN; + + if (ent->baseline.frame != pl->current.frame) + bits |= UNQ_FRAME; + + if (ent->baseline.effects != pl->current.effects) + bits |= UNQ_EFFECTS; + + if (ent->baseline.modelindex != pl->current.modelindex) + bits |= UNQ_MODEL; + + if (e+1 >= 256) + bits |= UNQ_LONGENTITY; + + if (bits >= 256) + bits |= UNQ_MOREBITS; + + // + // write the message + // + WriteByte (msg,bits | UNQ_SIGNAL); + + if (bits & UNQ_MOREBITS) + WriteByte (msg, bits>>8); + if (bits & UNQ_LONGENTITY) + WriteShort (msg,e+1); + else + WriteByte (msg,e+1); + + if (bits & UNQ_MODEL) + WriteByte (msg, pl->current.modelindex); + if (bits & UNQ_FRAME) + WriteByte (msg, pl->current.frame); + if (bits & UNQ_COLORMAP) + WriteByte (msg, (e>=15)?0:(e+1)); + if (bits & UNQ_SKIN) + WriteByte (msg, pl->current.skinnum); + if (bits & UNQ_EFFECTS) + WriteByte (msg, pl->current.effects); + if (bits & UNQ_ORIGIN1) + WriteShort (msg, org[0]); + if (bits & UNQ_ANGLE1) + WriteByte(msg, -(pl->current.angles[0]>>8)); + if (bits & UNQ_ORIGIN2) + WriteShort (msg, org[1]); + if (bits & UNQ_ANGLE2) + WriteByte(msg, pl->current.angles[1]>>8); + if (bits & UNQ_ORIGIN3) + WriteShort (msg, org[2]); + if (bits & UNQ_ANGLE3) + WriteByte(msg, pl->current.angles[2]>>8); + } + for (e = 0; e < tv->maxents; e++) + { + ent = &tv->entity[e]; + if (!ent->current.modelindex) + continue; + + if (ent->current.modelindex >= tv->numinlines && !BSP_Visible(tv->bsp, ent->leafcount, ent->leafs)) + continue; + +// send an update + bits = 0; + + for (i=0 ; i<3 ; i++) + { + org[i] = (lerp)*ent->current.origin[i] + (1-lerp)*ent->old.origin[i]; + miss = org[i] - ent->baseline.origin[i]; + // if ( miss < -1 || miss > 1 ) + bits |= UNQ_ORIGIN1<current.angles[0] != ent->baseline.angles[0] ) + bits |= UNQ_ANGLE1; + + if ( ent->current.angles[1] != ent->baseline.angles[1] ) + bits |= UNQ_ANGLE2; + + if ( ent->current.angles[2] != ent->baseline.angles[2] ) + bits |= UNQ_ANGLE3; + +// if (ent->v.movetype == MOVETYPE_STEP) +// bits |= UNQ_NOLERP; // don't mess up the step animation + + if (ent->baseline.colormap != ent->current.colormap) + bits |= UNQ_COLORMAP; + + if (ent->baseline.skinnum != ent->current.skinnum) + bits |= UNQ_SKIN; + + if (ent->baseline.frame != ent->current.frame) + bits |= UNQ_FRAME; + + if (ent->baseline.effects != ent->current.effects) + bits |= UNQ_EFFECTS; + + if (ent->baseline.modelindex != ent->current.modelindex) + bits |= UNQ_MODEL; + + if (e >= 256) + bits |= UNQ_LONGENTITY; + + if (bits >= 256) + bits |= UNQ_MOREBITS; + + // + // write the message + // + WriteByte (msg,bits | UNQ_SIGNAL); + + if (bits & UNQ_MOREBITS) + WriteByte (msg, bits>>8); + if (bits & UNQ_LONGENTITY) + WriteShort (msg,e); + else + WriteByte (msg,e); + + if (bits & UNQ_MODEL) + WriteByte (msg, ent->current.modelindex); + if (bits & UNQ_FRAME) + WriteByte (msg, ent->current.frame); + if (bits & UNQ_COLORMAP) + WriteByte (msg, (ent->current.colormap>15)?0:(ent->current.colormap)); + if (bits & UNQ_SKIN) + WriteByte (msg, ent->current.skinnum); + if (bits & UNQ_EFFECTS) + WriteByte (msg, ent->current.effects); + if (bits & UNQ_ORIGIN1) + WriteShort (msg, org[0]); + if (bits & UNQ_ANGLE1) + WriteByte(msg, ent->current.angles[0]); + if (bits & UNQ_ORIGIN2) + WriteShort (msg, org[1]); + if (bits & UNQ_ANGLE2) + WriteByte(msg, ent->current.angles[1]); + if (bits & UNQ_ORIGIN3) + WriteShort (msg, org[2]); + if (bits & UNQ_ANGLE3) + WriteByte(msg, ent->current.angles[2]); + } + } + else + { + WriteShort (msg,UNQ_MOREBITS|UNQ_MODEL|UNQ_ORIGIN1 | UNQ_ORIGIN2 | UNQ_ORIGIN3 | UNQ_SIGNAL); + WriteByte (msg, 1); + WriteByte (msg, 2); //model + WriteShort (msg, v->origin[0]); + WriteShort (msg, v->origin[1]); + WriteShort (msg, v->origin[2]); + } +} + void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) { packet_entities_t *e; @@ -1078,6 +1661,8 @@ void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg) } //vel //model + if (flags & PF_MODEL) + WriteByte(msg, tv->players[i].current.modelindex); //skin //effects //weaponframe @@ -1160,7 +1745,13 @@ void UpdateStats(sv_t *qtv, viewer_t *v) { if (v->currentstats[i] != stats[i]) { - if (stats[i] < 256) + if (v->netchan.isnqprotocol) + { //nq only supports 32bit stats + WriteByte(&msg, svc_updatestat); + WriteByte(&msg, i); + WriteLong(&msg, stats[i]); + } + else if (stats[i] < 256) { WriteByte(&msg, svc_updatestat); WriteByte(&msg, i); @@ -1190,22 +1781,22 @@ int Prespawn(sv_t *qtv, int curmsgsize, netmsg_t *msg, int bufnum, int thisplaye bufnum = ni; bufnum -= MAX_CLIENTS; - ni = SendCurrentBaselines(qtv, curmsgsize, msg, bufnum); + ni = SendCurrentBaselines(qtv, curmsgsize, msg, 768, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_ENTITIES; - ni = SendCurrentLightmaps(qtv, curmsgsize, msg, bufnum); + ni = SendCurrentLightmaps(qtv, curmsgsize, msg, 768, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_LIGHTSTYLES; - ni = SendStaticSounds(qtv, curmsgsize, msg, bufnum); + ni = SendStaticSounds(qtv, curmsgsize, msg, 768, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_STATICSOUNDS; - ni = SendStaticEntities(qtv, curmsgsize, msg, bufnum); + ni = SendStaticEntities(qtv, curmsgsize, msg, 768, bufnum); r += ni - bufnum; bufnum = ni; bufnum -= MAX_STATICENTITIES; @@ -1288,7 +1879,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean else if (!strcmp(v->expectcommand, "addserver")) { snprintf(buf, sizeof(buf), "tcp:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, false, false); + qtv = QTV_NewServerConnection(cluster, buf, "", false, false, false); if (qtv) { QW_SetViewersServer(v, qtv); @@ -1299,7 +1890,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean } else if (!strcmp(v->expectcommand, "admin")) { - if (!strcmp(message, cluster->password)) + if (!strcmp(message, cluster->adminpassword)) { QW_SetMenu(v, MENU_ADMIN); v->isadmin = true; @@ -1314,7 +1905,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean else if (!strcmp(v->expectcommand, "adddemo")) { snprintf(buf, sizeof(buf), "file:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, false, false); + qtv = QTV_NewServerConnection(cluster, buf, "", false, false, false); if (!qtv) QW_PrintfToViewer(v, "Failed to play demo \"%s\"\n", message); else @@ -1337,17 +1928,17 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean if (news != INVALID_SOCKET) { - if (qtv->listenmvd != INVALID_SOCKET) - closesocket(qtv->listenmvd); - qtv->listenmvd = news; + if (qtv->tcpsocket != INVALID_SOCKET) + closesocket(qtv->tcpsocket); + qtv->tcpsocket = news; qtv->tcplistenportnum = newp; qtv->disconnectwhennooneiswatching = false; } } - else if (qtv->listenmvd != INVALID_SOCKET) + else if (qtv->tcpsocket != INVALID_SOCKET) { - closesocket(qtv->listenmvd); - qtv->listenmvd = INVALID_SOCKET; + closesocket(qtv->tcpsocket); + qtv->tcpsocket = INVALID_SOCKET; } } else @@ -1377,7 +1968,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean else message += 9; snprintf(buf, sizeof(buf), "udp:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, true, true); + qtv = QTV_NewServerConnection(cluster, buf, "", false, true, true); if (qtv) { QW_SetMenu(v, MENU_NONE); @@ -1391,7 +1982,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean { message += 6; snprintf(buf, sizeof(buf), "udp:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, true, false); + qtv = QTV_NewServerConnection(cluster, buf, "", false, true, false); if (qtv) { QW_SetMenu(v, MENU_NONE); @@ -1406,7 +1997,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean { message += 5; snprintf(buf, sizeof(buf), "tcp:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, true, true); + qtv = QTV_NewServerConnection(cluster, buf, "", false, true, true); if (qtv) { QW_SetMenu(v, MENU_NONE); @@ -1420,7 +2011,7 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean { message += 6; snprintf(buf, sizeof(buf), "file:%s", message); - qtv = QTV_NewServerConnection(cluster, buf, false, true, true); + qtv = QTV_NewServerConnection(cluster, buf, "", false, true, true); if (qtv) { QW_SetMenu(v, MENU_NONE); @@ -1518,7 +2109,19 @@ void QTV_Say(cluster_t *cluster, sv_t *qtv, viewer_t *v, char *message, qboolean WriteString2(&msg, message); WriteString(&msg, "\n"); - Broadcast(cluster, msg.data, msg.cursize); + Broadcast(cluster, msg.data, msg.cursize, QW); + + + InitNetMsg(&msg, buf, sizeof(buf)); + + WriteByte(&msg, svc_print); + WriteByte(&msg, 1); + WriteString2(&msg, v->name); + WriteString2(&msg, "\x8d "); + WriteString2(&msg, message); + WriteString(&msg, "\n"); + + Broadcast(cluster, msg.data, msg.cursize, NQ); } } } @@ -1562,10 +2165,124 @@ void QW_StuffcmdToViewer(viewer_t *v, char *format, ...) SendBufferToViewer(v, buf, strlen(buf)+1, true); } -static const filename_t ConnectionlessModelList[] = {{""}, {"maps/start.bsp"}, {"progs/player.mdl"}, {""}}; -static const filename_t ConnectionlessSoundList[] = {{""}, {""}}; +void ParseNQC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m) +{ + char buf[MAX_NQMSGLEN]; + netmsg_t msg; + int mtype; + + while (m->readpos < m->cursize) + { + switch ((mtype=ReadByte(m))) + { + case clc_nop: + break; + case clc_stringcmd: + ReadString (m, buf, sizeof(buf)); + printf("stringcmd: %s\n", buf); + + if (!strcmp(buf, "new")) + { + if (qtv && qtv->parsingconnectiondata) + QW_StuffcmdToViewer(v, "cmd new\n"); + else + { + SendServerData(qtv, v); + } + } + else if (!strncmp(buf, "prespawn", 8)) + { + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + msg.overflowed = 0; + + if (qtv) + { + SendCurrentBaselines(qtv, 64, &msg, msg.maxsize, 0); + SendCurrentLightmaps(qtv, 64, &msg, msg.maxsize, 0); + + SendStaticSounds(qtv, 64, &msg, msg.maxsize, 0); + + SendStaticEntities(qtv, 64, &msg, msg.maxsize, 0); + } + WriteByte (&msg, svc_nqsignonnum); + WriteByte (&msg, 2); + SendBufferToViewer(v, msg.data, msg.cursize, true); + } + else if (!strncmp(buf, "name ", 5)) + { + /* + fixme + */ + } + else if (!strncmp(buf, "color ", 6)) + { + /* + fixme + */ + } + else if (!strncmp(buf, "spawn ", 6)) + { + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + msg.overflowed = 0; + SendNQSpawnInfoToViewer(cluster, v, &msg); + SendBufferToViewer(v, msg.data, msg.cursize, true); + + v->thinksitsconnected = true; + } + else if (!strncmp(buf, "begin", 5)) + { + v->thinksitsconnected = true; + } + + else if (!strncmp(buf, "say \"", 5)) + QTV_Say(cluster, qtv, v, buf+5, false); + else if (!strncmp(buf, "say ", 4)) + QTV_Say(cluster, qtv, v, buf+4, false); + + else if (!strncmp(buf, "say_team \"", 10)) + QTV_Say(cluster, qtv, v, buf+10, true); + else if (!strncmp(buf, "say_team ", 9)) + QTV_Say(cluster, qtv, v, buf+9, true); + else + { + QW_PrintfToViewer(v, "Command not recognised\n"); + Sys_Printf(cluster, "NQ client sent unrecognised stringcmd %s\n", buf); + } + break; + case clc_disconnect: + v->drop = true; + return; + case clc_move: + ReadFloat(m); //time, for pings + //three angles + v->ucmds[2].angles[0] = ReadByte(m)*256; + v->ucmds[2].angles[1] = ReadByte(m)*256; + v->ucmds[2].angles[2] = ReadByte(m)*256; + //three direction values + v->ucmds[2].forwardmove = ReadShort(m); + v->ucmds[2].sidemove = ReadShort(m); + v->ucmds[2].upmove = ReadShort(m); + + //one button + v->ucmds[2].buttons = ReadByte(m); + //one impulse + v->ucmds[2].impulse = ReadByte(m); + + v->ucmds[2].msec = 1000/NQ_PACKETS_PER_SECOND; + PMove(v, &v->ucmds[2]); + break; + default: + Sys_Printf(cluster, "Bad message type %i\n", mtype); + return; + } + } +} void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m) { // usercmd_t oldest, oldcmd, newcmd; @@ -1585,7 +2302,7 @@ void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m) break; case clc_stringcmd: ReadString (m, buf, sizeof(buf)); - printf("stringcmd: %s\n", buf); +// printf("stringcmd: %s\n", buf); if (!strcmp(buf, "new")) { @@ -1655,7 +2372,9 @@ void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m) int crc; int r; char *s; - s = buf+9; + s = buf+8; + while(*s == ' ') + s++; while((*s >= '0' && *s <= '9') || *s == '-') s++; while(*s == ' ') @@ -1780,7 +2499,7 @@ void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m) } else if (!strncmp(buf, "admin", 5)) { - if (!*cluster->password) + if (!*cluster->adminpassword) { if (Netchan_IsLocal(v->netchan.remote_address)) { @@ -2103,7 +2822,7 @@ void Menu_Draw(cluster_t *cluster, viewer_t *viewer) i = 0; WriteString2(&m, " port"); WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : "); - if (sv->listenmvd == INVALID_SOCKET) + if (sv->tcpsocket == INVALID_SOCKET) sprintf(str, "!%-19i", sv->tcplistenportnum); else sprintf(str, "%-20i", sv->tcplistenportnum); @@ -2276,10 +2995,13 @@ void QW_FreeViewer(cluster_t *cluster, viewer_t *viewer) free(viewer->backbuf[i].data); } - if (viewer->server->controller == viewer) - viewer->server->drop = true; if (viewer->server) + { + if (viewer->server->controller == viewer) + viewer->server->drop = true; + viewer->server->numviewers--; + } free(viewer); @@ -2289,6 +3011,7 @@ void QW_FreeViewer(cluster_t *cluster, viewer_t *viewer) void QW_UpdateUDPStuff(cluster_t *cluster) { char buffer[MAX_MSGLEN*2]; + char tempbuffer[256]; netadr_t from; int fromsize = sizeof(from); int read; @@ -2339,47 +3062,115 @@ void QW_UpdateUDPStuff(cluster_t *cluster) continue; } - //read the qport - ReadLong(&m); - ReadLong(&m); - qport = ReadShort(&m); + if (read < 10) //otherwise it's a runt or bad. + { + if (read < 0) //it's bad. + break; + + qport = 0; + } + else + { + //read the qport + ReadLong(&m); + ReadLong(&m); + qport = ReadShort(&m); + } for (v = cluster->viewers; v; v = v->next) { - if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport)) + if (v->netchan.isnqprotocol) { - if (Netchan_Process(&v->netchan, &m)) + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) { - useserver = v->server; - if (useserver && useserver->parsingconnectiondata) - useserver = NULL; + if (NQNetchan_Process(cluster, &v->netchan, &m)) + { + useserver = v->server; + if (useserver && useserver->parsingconnectiondata) + useserver = NULL; - v->netchan.outgoing_sequence = v->netchan.incoming_sequence; //compensate for client->server packetloss. - if (v->server && v->server->controller == v) - { -// v->maysend = true; - v->server->maysend = true; -// v->server->netchan.outgoing_sequence = v->netchan.incoming_sequence; - } - else - { - if (!v->server) - v->maysend = true; - else if (!v->chokeme || !cluster->chokeonnotupdated) + ParseNQC(cluster, useserver, v, &m); + + if (v->server && v->server->controller == v) { - v->maysend = true; - v->chokeme = cluster->chokeonnotupdated; + QTV_Run(v->server); } } - - ParseQWC(cluster, useserver, v, &m); - - if (v->server && v->server->controller == v) - { - QTV_Run(v->server); - } } - break; + } + else + { + if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport)) + { + if (Netchan_Process(&v->netchan, &m)) + { + useserver = v->server; + if (useserver && useserver->parsingconnectiondata) + useserver = NULL; + + v->netchan.outgoing_sequence = v->netchan.incoming_sequence; //compensate for client->server packetloss. + if (v->server && v->server->controller == v) + { + // v->maysend = true; + v->server->maysend = true; + // v->server->netchan.outgoing_sequence = v->netchan.incoming_sequence; + } + else + { + if (!v->server) + v->maysend = true; + else if (!v->chokeme || !cluster->chokeonnotupdated) + { + v->maysend = true; + v->chokeme = cluster->chokeonnotupdated; + } + } + + ParseQWC(cluster, useserver, v, &m); + + if (v->server && v->server->controller == v) + { + QTV_Run(v->server); + } + } + break; + } + } + } + if (!v) + { + //NQ connectionless packet? + m.readpos = 0; + read = ReadLong(&m); + read = SwapLong(read); + if (read & NETFLAG_CTL) + { //looks hopeful + switch(ReadByte(&m)) + { + case CCREQ_CONNECT: + ReadString(&m, tempbuffer, sizeof(tempbuffer)); + if (!strcmp(tempbuffer, NET_GAMENAME_NQ)) + { + if (ReadByte(&m) == NET_PROTOCOL_VERSION) + { + //drop any old nq clients from this address + for (v = cluster->viewers; v; v = v->next) + { + if (v->netchan.isnqprotocol) + { + if (Net_CompareAddress(&v->netchan.remote_address, &from, 0, 0)) + { + v->drop = true; + } + } + } + NewNQClient(cluster, &from); + } + } + break; + default: + break; + } } } } @@ -2405,8 +3196,16 @@ void QW_UpdateUDPStuff(cluster_t *cluster) QW_FreeViewer(cluster, f); } + v->drop |= v->netchan.drop; + + if (v->netchan.isnqprotocol) + { + v->maysend = (v->nextpacket < cluster->curtime); + } if (v->maysend) //don't send incompleate connection data. { + v->nextpacket = cluster->curtime + 1000/NQ_PACKETS_PER_SECOND; + useserver = v->server; if (useserver && useserver->parsingconnectiondata) useserver = NULL; @@ -2415,7 +3214,10 @@ void QW_UpdateUDPStuff(cluster_t *cluster) m.cursize = 0; if (v->thinksitsconnected) { - SendPlayerStates(useserver, v, &m); + if (v->netchan.isnqprotocol) + SendNQPlayerStates(cluster, useserver, v, &m); + else + SendPlayerStates(useserver, v, &m); UpdateStats(useserver, v); if (v->menunum) diff --git a/fteqtv/rcon.c b/fteqtv/rcon.c index 25b40bed1..14b7220e2 100644 --- a/fteqtv/rcon.c +++ b/fteqtv/rcon.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define MAX_INFO_KEY 64 +//I apologise for this if it breaks your formatting or anything #define HELPSTRING "\ FTEQTV proxy commands: (build "__DATE__")\n\ ----------------------\n\ @@ -39,6 +40,10 @@ maxviewers, maxproxies\n\ - limit number of connections\n\ status, choke, late, talking, nobsp, reconnect, exec, password, master, hostname, record, stop, quit\n\n" + + + + char *Info_ValueForKey (char *s, const char *key, char *buffer, int buffersize) { char pkey[1024]; @@ -309,9 +314,400 @@ skipwhite: return data; } + + + + #define MAX_ARGS 8 #define ARG_LEN 512 -char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +typedef char *(*dispatchrconcommand_t)(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand); + +char *Cmd_Hostname(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + strncpy(cluster->hostname, arg[1], sizeof(cluster->hostname)-1); + return "hostname will change at start of next map\n"; //I'm too lazy to alter the serverinfo here. +} +char *Cmd_Master(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + netadr_t addr; + + strncpy(cluster->master, arg[1], sizeof(cluster->master)-1); + cluster->mastersendtime = cluster->curtime; + + if (NET_StringToAddr(arg[1], &addr, 27000)) //send a ping like a qw server does. this is kinda pointless of course. + NET_SendPacket (cluster, cluster->qwdsocket, 1, "k", addr); + + return "Master server set.\n"; +} + + + +char *Cmd_UDPPort(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + int news; + int newp = atoi(arg[1]); + news = QW_InitUDPSocket(newp); + + if (news != INVALID_SOCKET) + { + cluster->mastersendtime = cluster->curtime; + closesocket(cluster->qwdsocket); + cluster->qwdsocket = news; + cluster->qwlistenportnum = newp; + return "Opened udp port (all connected qw clients will time out)\n"; + } + else + return "Failed to open udp port\n"; +} +char *Cmd_AdminPassword(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!localcommand) + return "Rejecting remote password change.\n"; + + strncpy(cluster->adminpassword, arg[1], sizeof(cluster->adminpassword)-1); + return "Password changed.\n"; +} +char *Cmd_QTVConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!*arg[1]) + return "connect requires an ip:port parameter\n"; + + memmove(arg[1]+4, arg[1], ARG_LEN-5); + strncpy(arg[1], "tcp:", 4); + + if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false)) + return "Failed to connect to server, connection aborted\n"; + return "Source registered\n"; +} +char *Cmd_QWConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!*arg[1]) + return "connect requires an ip:port parameter\n"; + + memmove(arg[1]+4, arg[1], ARG_LEN-5); + strncpy(arg[1], "udp:", 4); + + if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false)) + return "Failed to connect to server, connection aborted\n"; + return "Source registered\n"; +} +char *Cmd_MVDConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!*arg[1]) + return "adddemo requires an filename parameter\n"; + + if (!localcommand) + if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':') + return "Absolute paths are prohibited.\n"; + + memmove(arg[1]+5, arg[1], ARG_LEN-6); + strncpy(arg[1], "file:", 5); + + if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false)) + return "Failed to connect to server, connection aborted\n"; + return "Source registered\n"; +} +char *Cmd_Exec(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + FILE *f; + char line[512], *res; + + if (!localcommand) + if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':') + return "Absolute paths are prohibited.\n"; + + f = fopen(arg[1], "rt"); + if (!f) + { + snprintf(buffer, sizeofbuffer, "Couldn't exec \"%s\"\n", arg[1]); + return buffer; + } + else + { + while(fgets(line, sizeof(line)-1, f)) + { + res = Rcon_Command(cluster, qtv, line, buffer, sizeofbuffer, localcommand); + Sys_Printf(cluster, "%s", res); //this is perhaps wrong. + } + fclose(f); + return "Execed\n"; + } +} +char *Cmd_Status(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + buffer[0] = '\0'; + + sprintf(buffer, "%i connections\n", cluster->numservers); + + strcat(buffer, "Options:\n"); + strcat(buffer, " Hostname "); + strcat(buffer, cluster->hostname); + strcat(buffer, "\n"); + if (cluster->chokeonnotupdated) + strcat(buffer, " Choke\n"); + if (cluster->lateforward) + strcat(buffer, " Late forwarding\n"); + if (!cluster->notalking) + strcat(buffer, " Talking allowed\n"); + if (cluster->nobsp) + strcat(buffer, " No BSP loading\n"); + strcat(buffer, "\n"); + + + if (qtv) + { + strcat(buffer, "Selected server: "); + strcat(buffer, qtv->server); + strcat(buffer, "\n"); + if (qtv->file) + strcat(buffer, "Playing from file\n"); + if (qtv->sourcesock != INVALID_SOCKET) + strcat(buffer, "Connected\n"); + if (qtv->parsingqtvheader || qtv->parsingconnectiondata) + strcat(buffer, "Waiting for gamestate\n"); + if (qtv->controller) + { + strcat(buffer, "Spectating through "); + strcat(buffer, qtv->controller->name); + strcat(buffer, "\n"); + } + if (*qtv->modellist[1].name) + { + strcat(buffer, "Map name "); + strcat(buffer, qtv->modellist[1].name); + strcat(buffer, "\n"); + } + if (qtv->connectpassword) + strcat(buffer, "Using a password\n"); + + if (qtv->tcpsocket != INVALID_SOCKET) + { + strcat(buffer, "Listening for proxies ("); + sprintf(arg[0], "%i", qtv->tcplistenportnum); + strcat(buffer, arg[0]); + strcat(buffer, ")\n"); + } + + if (qtv->bsp) + { + strcat(buffer, "BSP ("); + strcat(buffer, qtv->mapname); + strcat(buffer, ") is loaded\n"); + } + } + + return buffer; +} +char *Cmd_Choke(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->chokeonnotupdated = !!atoi(arg[1]); + return "choke-until-update set\n"; +} +char *Cmd_Late(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->lateforward = !!atoi(arg[1]); + return "late forwarding set\n"; +} +char *Cmd_Talking(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->notalking = !atoi(arg[1]); + return "talking permissions set\n"; +} +char *Cmd_NoBSP(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->nobsp = !!atoi(arg[1]); + return "nobsp will change at start of next map\n"; +} + +char *Cmd_MaxViewers(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->maxviewers = atoi(arg[2]); + return "maxviewers set\n"; +} +char *Cmd_MaxProxies(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + cluster->maxproxies = atoi(arg[2]); + return "maxproxies set\n"; +} + + +char *Cmd_Ping(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + netadr_t addr; + if (NET_StringToAddr(arg[1], &addr, 27500)) + { + NET_SendPacket (cluster, cluster->qwdsocket, 1, "k", addr); + return "pinged\n"; + } + return "couldn't resolve\n"; +} + +char *Cmd_Help(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + return HELPSTRING; +} + +char *Cmd_Echo(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + return "Poly wants a cracker.\n"; +} + +char *Cmd_Quit(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!localcommand) + return "Remote shutdown refused.\n"; + cluster->wanttoexit = true; + return "Shutting down.\n"; +} + + + + + + + + + + + + + + + + + + + + +char *Cmd_Disconnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + QTV_Shutdown(qtv); + return "Disconnected\n"; +} + +char *Cmd_Record(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (!*arg[1]) + return "record requires a filename on the proxy's machine\n"; + + if (!localcommand) + if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':') + return "Absolute paths are prohibited.\n"; + + if (Net_FileProxy(qtv, arg[1])) + return "Recording to disk\n"; + else + return "Failed to open file\n"; +} +char *Cmd_Stop(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (Net_StopFileProxy(qtv)) + return "stopped\n"; + else + return "not recording to disk\n"; +} + +char *Cmd_Reconnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + if (QTV_Connect(qtv, qtv->server)) + return "Reconnected\n"; + else + return "Failed to reconnect (will keep trying)\n"; +} + +char *Cmd_MVDPort(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + int news; + int newp = atoi(arg[1]); + + if (!newp) + { + if (qtv->tcpsocket != INVALID_SOCKET) + { + closesocket(qtv->tcpsocket); + qtv->tcpsocket = INVALID_SOCKET; + qtv->tcplistenportnum = 0; + + return "mvd port is now closed\n"; + } + return "Already closed\n"; + } + else + { + news = Net_MVDListen(newp); + + if (news != INVALID_SOCKET) + { + if (qtv->tcpsocket != INVALID_SOCKET) + closesocket(qtv->tcpsocket); + qtv->tcpsocket = news; + qtv->disconnectwhennooneiswatching = false; + qtv->tcplistenportnum = newp; + return "Opened tcp port\n"; + } + else + return "Failed to open tcp port\n"; + } +} + +char *Cmd_Commands(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +{ + return ""; +} + +typedef struct rconcommands_s { + char *name; + qboolean serverspecific; //works within a qtv context + qboolean clusterspecific; //works without a qtv context (ignores context) + dispatchrconcommand_t func; +} rconcommands_t; + +const rconcommands_t rconcommands[] = +{ + {"exec", 1, 1, Cmd_Exec}, + {"status", 1, 1, Cmd_Status}, + + {"help", 0, 1, Cmd_Help}, + {"commands", 0, 1, Cmd_Commands}, + {"hostname", 0, 1, Cmd_Hostname}, + {"master", 0, 1, Cmd_Master}, + {"udpport", 0, 1, Cmd_UDPPort}, + {"port", 0, 1, Cmd_UDPPort}, + {"adminpassword",0, 1, Cmd_AdminPassword}, + {"rconpassword",0, 1, Cmd_AdminPassword}, + {"qtv", 0, 1, Cmd_QTVConnect}, + {"addserver", 0, 1, Cmd_QTVConnect}, + {"connect", 0, 1, Cmd_QTVConnect}, + {"qw", 0, 1, Cmd_QWConnect}, + {"demo", 0, 1, Cmd_MVDConnect}, + {"playdemo", 0, 1, Cmd_MVDConnect}, + {"choke", 0, 1, Cmd_Choke}, + {"late", 0, 1, Cmd_Late}, + {"talking", 0, 1, Cmd_Talking}, + {"nobsp", 0, 1, Cmd_NoBSP}, + {"maxviewers", 0, 1, Cmd_MaxViewers}, + {"maxproxies", 0, 1, Cmd_MaxProxies}, + {"ping", 0, 1, Cmd_Ping}, + {"reconnect", 0, 1, Cmd_Reconnect}, + {"echo", 0, 1, Cmd_Echo}, + {"quit", 0, 1, Cmd_Quit}, + + + + + {"disconnect", 1, 0, Cmd_Disconnect}, + {"record", 1, 0, Cmd_Record}, + {"stop", 1, 0, Cmd_Stop}, + {"tcpport", 1, 0, Cmd_MVDPort}, + {"mvdport", 1, 0, Cmd_MVDPort}, + + {NULL} +}; + + + +/* +static char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) { if (!strcmp(arg[0], "hostname")) { @@ -363,9 +759,9 @@ char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffe memmove(arg[1]+4, arg[1], ARG_LEN-5); strncpy(arg[1], "tcp:", 4); - if (!QTV_NewServerConnection(cluster, arg[1], false, !strcmp(arg[0], "connect"), false)) + if (!QTV_NewServerConnection(cluster, arg[1], false, false, false)) return "Failed to connect to server, connection aborted\n"; - return "Connection registered\n"; + return "Source registered\n"; } else if (!strcmp(arg[0], "qw")) { @@ -377,7 +773,7 @@ char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffe if (!QTV_NewServerConnection(cluster, arg[1], false, false, false)) return "Failed to connect to server, connection aborted\n"; - return "Connection registered\n"; + return "Source registered\n"; } else if (!strcmp(arg[0], "demo") || !strcmp(arg[0], "adddemo") || !strcmp(arg[0], "addfile")) { @@ -393,7 +789,7 @@ char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffe if (!QTV_NewServerConnection(cluster, arg[1], false, false, false)) return "Failed to connect to server, connection aborted\n"; - return "Connection registered\n"; + return "Source registered\n"; } else if (!strcmp(arg[0], "exec")) { @@ -522,7 +918,7 @@ char *Cluster_Rcon_Dispatch(cluster_t *cluster, char *arg[MAX_ARGS], char *buffe return NULL; } -char *Server_Rcon_Dispatch(sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) +static char *Server_Rcon_Dispatch(sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand) { #define TOKENIZE_PUNCTUATION "" @@ -702,7 +1098,7 @@ char *Server_Rcon_Dispatch(sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int siz return NULL; } } - +*/ char *Rcon_Command(cluster_t *cluster, sv_t *qtv, char *command, char *buffer, int sizeofbuffer, qboolean localcommand) { #define TOKENIZE_PUNCTUATION "" @@ -710,7 +1106,6 @@ char *Rcon_Command(cluster_t *cluster, sv_t *qtv, char *command, char *buffer, i int i; char arg[MAX_ARGS][ARG_LEN]; char *argptrs[MAX_ARGS]; - char *result; for (i = 0; i < MAX_ARGS; i++) { @@ -718,22 +1113,36 @@ char *Rcon_Command(cluster_t *cluster, sv_t *qtv, char *command, char *buffer, i argptrs[i] = arg[i]; } + if (!qtv && cluster->numservers==1) + qtv = cluster->servers; + + buffer[0] = 0; + if (qtv) { //if there is a specific connection targetted - result = Server_Rcon_Dispatch(qtv, argptrs, buffer, sizeofbuffer, localcommand); - if (result) - return result; - } - else if (cluster->numservers == 1) - { //if it's a single-connection proxy - result = Server_Rcon_Dispatch(cluster->servers, argptrs, buffer, sizeofbuffer, localcommand); - if (result) - return result; + + for (i = 0; rconcommands[i].name; i++) + { + if (rconcommands[i].serverspecific) + if (!strcmp(rconcommands[i].name, argptrs[0])) + return rconcommands[i].func(cluster, qtv, argptrs, buffer, sizeofbuffer, localcommand); + } + } + + for (i = 0; rconcommands[i].name; i++) + { + if (!strcmp(rconcommands[i].name, argptrs[0])) + { + if (rconcommands[i].clusterspecific) + return rconcommands[i].func(cluster, NULL, argptrs, buffer, sizeofbuffer, localcommand); + else if (rconcommands[i].serverspecific) + { + snprintf(buffer, sizeofbuffer, "Command \"%s\" requires a targeted server.\n", arg[0]); + return buffer; + } + } } - result = Cluster_Rcon_Dispatch(cluster, argptrs, buffer, sizeofbuffer, localcommand); - if (result) - return result; snprintf(buffer, sizeofbuffer, "Command \"%s\" not recognised.\n", arg[0]); return buffer; diff --git a/fteqtv/source.c b/fteqtv/source.c index 1cbd4ab3b..297a32651 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -18,12 +18,46 @@ 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, seperated 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 prefered 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" + #ifndef _WIN32 #include #endif -#include "qtv.h" - #define RECONNECT_TIME (1000*30) #define UDPRECONNECT_TIME (1000) #define PINGSINTERVAL_TIME (1000*5) @@ -31,6 +65,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #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) @@ -177,7 +212,7 @@ qboolean Net_CompareAddress(netadr_t *s1, netadr_t *s2, int qp1, int qp2) SOCKET Net_MVDListen(int port) { - int sock; + SOCKET sock; struct sockaddr_in address; // int fromlen; @@ -212,8 +247,87 @@ SOCKET Net_MVDListen(int port) return sock; } +void Net_SendQTVConnectionRequest(sv_t *qtv, char *authmethod, char *challenge) +{ + 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 = !!*qtv->connectpassword; + + + + str = "QTV\n"; Net_QueueUpstream(qtv, strlen(str), str); + str = "VERSION: 1\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->drop = true; + 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; @@ -244,13 +358,17 @@ qboolean Net_ConnectToTCPServer(sv_t *qtv, char *ip) if (connect(qtv->sourcesock, (struct sockaddr *)&qtv->serveraddress, sizeof(qtv->serveraddress)) == INVALID_SOCKET) { - if (qerrno != EINPROGRESS && qerrno != EWOULDBLOCK) //bsd sockets are meant to return EINPROGRESS, but some winsock drivers use EWOULDBLOCK instead. *sigh*... + err = qerrno; + if (err != EINPROGRESS && err != EWOULDBLOCK) //bsd sockets are meant to return EINPROGRESS, but some winsock drivers use EWOULDBLOCK instead. *sigh*... { closesocket(qtv->sourcesock); qtv->sourcesock = INVALID_SOCKET; return false; } } + + //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) @@ -323,385 +441,42 @@ qboolean Net_ConnectToServer(sv_t *qtv, char *ip) } } -void Net_FindProxies(sv_t *qtv) +void Net_QueueUpstream(sv_t *qtv, int size, char *buffer) { - oproxy_t *prox; - int sock; - - sock = accept(qtv->listenmvd, NULL, NULL); - if (sock == INVALID_SOCKET) + if (qtv->usequkeworldprotocols) return; - if (qtv->numproxies >= qtv->cluster->maxproxies && qtv->cluster->maxproxies) + if (qtv->upstreambuffersize + size > sizeof(qtv->upstreambuffer)) { - 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); + qtv->drop = true; return; } - - prox = malloc(sizeof(*prox)); - if (!prox) - {//out of mem? - closesocket(sock); - return; - } - memset(prox, 0, sizeof(*prox)); - prox->sock = sock; - prox->file = NULL; - - prox->next = qtv->proxies; - qtv->proxies = prox; - - qtv->numproxies++; - - Net_SendConnectionMVD(qtv, prox); + memcpy(qtv->upstreambuffer + qtv->upstreambuffersize, buffer, size); + qtv->upstreambuffersize += size; } -qboolean Net_FileProxy(sv_t *qtv, char *filename) +qboolean Net_WriteUpStream(sv_t *qtv) { - oproxy_t *prox; - FILE *f; - - f = fopen(filename, "wb"); - if (!f) - return false; - - //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 false; - memset(prox, 0, sizeof(*prox)); - - prox->sock = INVALID_SOCKET; - prox->file = f; - - prox->next = qtv->proxies; - qtv->proxies = prox; - - qtv->numproxies++; - - Net_SendConnectionMVD(qtv, prox); + int len; + if (qtv->upstreambuffersize) + { + len = send(qtv->sourcesock, qtv->upstreambuffer, qtv->upstreambuffersize, 0); + if (len == 0) + return false; + if (len < 0) + { + int err = qerrno; + if (err != EWOULDBLOCK && err != ENOTCONN) + qtv->drop = true; + return false; + } + qtv->upstreambuffersize -= len; + memmove(qtv->upstreambuffer + len, qtv->upstreambuffer, qtv->upstreambuffersize); + } return true; } -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; -} - -#undef IN -#define IN(x) buffer[(x)&(MAX_PROXY_BUFFER-1)] - -void CheckMVDConsistancy(unsigned char *buffer, int pos, int size) -{ -/* - int length; - int msec, type; - while(pos < size) - { - msec = IN(pos++); - type = IN(pos++); - if (type == dem_set) - { - pos+=8; - continue; - } - if (type == dem_multiple) - pos+=4; - length = (IN(pos+0)<<0) + (IN(pos+1)<<8) + (IN(pos+2)<<16) + (IN(pos+3)<<24); - pos+=4; - if (length > 1450) - printf("too big (%i)\n", length); - pos += length; - } - if (pos != size) - printf("pos != size\n"); - */ -} - -void Net_TryFlushProxyBuffer(cluster_t *cluster, oproxy_t *prox) -{ - 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; - break; - case -1: - if (qerrno != EWOULDBLOCK) //not a problem, so long as we can flush it later. - prox->drop = true; //drop them if we get any errors - break; - default: - prox->bufferpos += length; - } -} - -void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, char *buffer, int length) -{ - int wrap; - - 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 - prox->flushing = true; - return; - } - } -#if 1 - //just simple - prox->buffersize+=length; - for (wrap = prox->buffersize-length; wrap < prox->buffersize; wrap++) - prox->buffer[wrap&(MAX_PROXY_BUFFER-1)] = *buffer++; -#else - //we don't do multiple wrappings, the above check cannot succeed if it were required. - - //find the wrap point - wrap = prox->buffersize-(prox->buffersize&(MAX_PROXY_BUFFER-1)) + MAX_PROXY_BUFFER; - wrap = wrap - (prox->buffersize&(MAX_PROXY_BUFFER-1)); //the ammount of data we can fit before wrapping. - - if (wrap > length) - { //we don't wrap afterall - memcpy(prox->buffer+(prox->buffersize)&(MAX_PROXY_BUFFER-1), buffer, length); - prox->buffersize+=length; - return; - } - memcpy(prox->buffer+prox->buffersize&(MAX_PROXY_BUFFER-1), buffer, wrap); - buffer += wrap; - length -= wrap; - memcpy(prox->buffer, buffer, length); - - prox->buffersize+=length; -#endif -} - -void Prox_SendMessage(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); - - - Net_ProxySend(cluster, prox, msg.data, msg.cursize); - - Net_ProxySend(cluster, prox, buf, length); -} - -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->players[player].stats[snum]) - { - if ((unsigned)qtv->players[player].stats[snum] > 255) - { - WriteByte(&msg, svc_updatestatlong); - WriteByte(&msg, snum); - WriteLong(&msg, qtv->players[player].stats[snum]); - } - else - { - WriteByte(&msg, svc_updatestat); - WriteByte(&msg, snum); - WriteByte(&msg, qtv->players[player].stats[snum]); - } - } - } - - if (msg.cursize) - { -// Prox_SendMessage(prox, msg.data, msg.cursize, dem_stats|(player<<3), (1<flushing = false; - - BuildServerData(qtv, &msg, true, 0); - 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->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->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 arn't actually delta-compressed, so the first send (simply forwarded from server) entirly replaces the old. - - //we do need to send entity states. - Prox_SendInitialEnts(qtv, prox, &msg); - Prox_SendMessage(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->buffersize); //send all the info we've not yet processed. - - - 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); -} - -void Net_ForwardStream(sv_t *qtv, char *buffer, int length) -{ //forward the stream on to connected clients - oproxy_t *prox, *next, *fre; - - CheckMVDConsistancy(buffer, 0, length); - - - while (qtv->proxies && qtv->proxies->drop) - { - next = qtv->proxies->next; - fre = qtv->proxies; - if (fre->file) - fclose(fre->file); - else - closesocket(fre->sock); - free(fre); - qtv->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); - free(fre); - qtv->numproxies--; - prox->next = next; - } - - if (prox->flushing) //don't sent it if we're trying to empty thier buffer. - { - if (prox->buffersize == prox->bufferpos) - { - if (!qtv->parsingconnectiondata) - Net_SendConnectionMVD(qtv, prox); //they're up to date, resend the connection info. - } - else - { - Net_TryFlushProxyBuffer(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); - } -} - qboolean Net_ReadStream(sv_t *qtv) { int maxreadable; @@ -724,27 +499,40 @@ qboolean Net_ReadStream(sv_t *qtv) } else { + read = sizeof(err); + err = 0; + getsockopt(qtv->sourcesock, SOL_SOCKET, SO_ERROR, (char*)&err, &read); + if (err == ECONNREFUSED) + { + Sys_Printf(qtv->cluster, "Error: server %s refused connection\n", qtv->server); + closesocket(qtv->sourcesock); + qtv->sourcesock = INVALID_SOCKET; + return false; + } + read = recv(qtv->sourcesock, buffer, maxreadable, 0); } if (read > 0) { qtv->buffersize += read; - if (!qtv->cluster->lateforward) - Net_ForwardStream(qtv, buffer, read); + if (!qtv->cluster->lateforward && !qtv->parsingqtvheader) + SV_ForwardStream(qtv, buffer, read); } else { err = qerrno; if (read == 0 || (err != EWOULDBLOCK && err != ENOTCONN)) //ENOTCONN can be returned whilst waiting for a connect to finish. { + if (qtv->file) + Sys_Printf(qtv->cluster, "Error: End of file\n"); + else if (read) + Sys_Printf(qtv->cluster, "Error: source socket error %i\n", qerrno); + else + Sys_Printf(qtv->cluster, "Error: server disconnected\n"); if (qtv->sourcesock != INVALID_SOCKET) { int err; err = qerrno; - if (qerrno) - Sys_Printf(qtv->cluster, "Error: source socket error %i\n", qerrno); - else - Sys_Printf(qtv->cluster, "Error: server disconnected\n"); closesocket(qtv->sourcesock); qtv->sourcesock = INVALID_SOCKET; } @@ -753,7 +541,6 @@ qboolean Net_ReadStream(sv_t *qtv) fclose(qtv->file); qtv->file = NULL; } - Sys_Printf(qtv->cluster, "Read error or eof to %s\n", qtv->server); return false; } } @@ -765,7 +552,9 @@ qboolean Net_ReadStream(sv_t *qtv) unsigned int Sys_Milliseconds(void) { #ifdef _WIN32 -#pragma comment(lib, "winmm.lib") + #ifdef _MSC_VER + #pragma comment(lib, "winmm.lib") + #endif return timeGetTime(); #else //assume every other system follows standards. @@ -918,7 +707,6 @@ qboolean QTV_Connect(sv_t *qtv, char *serverurl) Sys_Printf(qtv->cluster, "Couldn't connect (%s)\n", qtv->server); return false; } - Sys_Printf(qtv->cluster, "Connected\n"); if (qtv->sourcesock == INVALID_SOCKET) { @@ -928,8 +716,6 @@ qboolean QTV_Connect(sv_t *qtv, char *serverurl) else { qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000; - if (!qtv->usequkeworldprotocols) - Sys_Printf(qtv->cluster, "Buffering for %i seconds\n", BUFFERTIME); } return true; } @@ -941,7 +727,7 @@ void QTV_Shutdown(sv_t *qtv) viewer_t *v; sv_t *peer; cluster_t *cluster; - Sys_Printf(qtv->cluster, "Shutting down %s\n", qtv->server); + Sys_Printf(qtv->cluster, "Closing source %s\n", qtv->server); if (qtv->sourcesock != INVALID_SOCKET) { @@ -959,8 +745,8 @@ void QTV_Shutdown(sv_t *qtv) fclose(qtv->file); qtv->file = NULL; } - if (qtv->listenmvd != INVALID_SOCKET) - closesocket(qtv->listenmvd); + if (qtv->tcpsocket != INVALID_SOCKET) + closesocket(qtv->tcpsocket); BSP_Free(qtv->bsp); qtv->bsp = NULL; @@ -1004,278 +790,6 @@ void QTV_Shutdown(sv_t *qtv) cluster->numservers--; } -/* -void QTV_Run(sv_t *qtv) -{ - int lengthofs; - unsigned int length; - unsigned char *buffer; - int oldcurtime; - int packettime; - - while(1) - { - if (MAX_PROXY_BUFFER == qtv->buffersize) - { //our input buffer is full - //so our receiving tcp socket probably has something waiting on it - //so our select calls will never wait - //so we're using close to 100% cpu - //so we add some extra sleeping here. -#ifdef _WIN32 - Sleep(5); -#else - usleep(5000); -#endif - } - - NetSleep(qtv); - - if (qtv->sourcesock == INVALID_SOCKET && !qtv->file) - if (!QTV_Connect(qtv, qtv->server)) - { - QW_UpdateUDPStuff(qtv); - continue; - } - - Net_FindProxies(qtv); - - if (!Net_ReadStream(qtv)) - { - QW_UpdateUDPStuff(qtv); - continue; - } - -//we will read out as many packets as we can until we're up to date -//note: this can cause real issues when we're overloaded for any length of time -//each new packet comes with a leading msec byte (msecs from last packet) -//then a type, an optional destination mask, and a 4byte size. -//the 4 byte size is probably excessive, a short would do. -//some of the types have thier destination mask encoded inside the type byte, yielding 8 types, and 32 max players. - -//if we've no got enough data to read a new packet, we print a message and wait an extra two seconds. this will add a pause, connected clients will get the same pause, and we'll just try to buffer more of the game before playing. -//we'll stay 2 secs behind when the tcp stream catches up, however. This could be bad especially with long up-time. -//All timings are in msecs, which is in keeping with the mvd times, but means we might have issues after 72 or so days. -//the following if statement will reset the parse timer. It might cause the game to play too soon, the buffersize checks in the rest of the function will hopefully put it back to something sensible. - - oldcurtime = qtv->curtime; - qtv->curtime = Sys_Milliseconds(); - if (oldcurtime > qtv->curtime) - { - printf("Time wrapped\n"); - qtv->parsetime = qtv->curtime; - } - - - while (qtv->curtime >= qtv->parsetime) - { - if (qtv->buffersize < 2) - { //not enough stuff to play. - if (qtv->parsetime < qtv->curtime) - { - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - printf("Not enough buffered\n"); - } - break; - } - - buffer = qtv->buffer; - - switch (qtv->buffer[1]&dem_mask) - { - case dem_set: - if (qtv->buffersize < 10) - { //not enough stuff to play. - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - printf("Not enough buffered\n"); - continue; - } - qtv->parsetime += buffer[0]; //well this was pointless - memmove(qtv->buffer, qtv->buffer+10, qtv->buffersize-(10)); - qtv->buffersize -= 10; - continue; - case dem_multiple: - lengthofs = 6; - break; - default: - lengthofs = 2; - break; - } - - if (qtv->buffersize < lengthofs+4) - { //the size parameter doesn't fit. - printf("Not enough buffered\n"); - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - break; - } - - - length = (buffer[lengthofs]<<0) + (buffer[lengthofs+1]<<8) + (buffer[lengthofs+2]<<16) + (buffer[lengthofs+3]<<24); - if (length > 1450) - { //FIXME: THIS SHOULDN'T HAPPEN! - //Blame the upstream proxy! - printf("EGad! input packet (%i) too big! Flushing and reconnecting!\n", length); - if (qtv->file) - { - fclose(qtv->file); - qtv->file = NULL; - } - else - { - closesocket(qtv->sourcesock); - qtv->sourcesock = INVALID_SOCKET; - } - qtv->buffersize = 0; - break; - } - - if (length+lengthofs+4 > qtv->buffersize) - { - printf("Not enough buffered\n"); - qtv->parsetime = qtv->curtime + 2*1000; //add two seconds - break; //can't parse it yet. - } - - qtv->nextpackettime = qtv->parsetime+buffer[0]; - - if (qtv->nextpackettime < qtv->curtime) - { - if (qtv->lateforward) - Net_ForwardStream(qtv, qtv->buffer, lengthofs+4+length); - - switch(qtv->buffer[1]&dem_mask) - { - case dem_multiple: - ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, (buffer[lengthofs-4]<<0) + (buffer[lengthofs+3]<<8) + (buffer[lengthofs-2]<<16) + (buffer[lengthofs-1]<<24)); - break; - case dem_single: - case dem_stats: - ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, 1<<(qtv->buffer[1]>>3)); - break; - case dem_read: - case dem_all: - ParseMessage(qtv, buffer+lengthofs+4, length, qtv->buffer[1]&dem_mask, 0xffffffff); - break; - default: - printf("Message type %i\n", qtv->buffer[1]&dem_mask); - break; - } - - qtv->oldpackettime = qtv->curtime; - - packettime = buffer[0]; - if (qtv->buffersize) - { //svc_disconnect can flush our input buffer (to prevent the EndOfDemo part from interfering) - memmove(qtv->buffer, qtv->buffer+lengthofs+4+length, qtv->buffersize-(lengthofs+length+4)); - qtv->buffersize -= lengthofs+4+length; - } - - if (qtv->file) - Net_ReadStream(qtv); - - qtv->parsetime += packettime; - } - else - break; - } - - QW_UpdateUDPStuff(qtv); - - - } -} - - -int main(int argc, char **argv) -{ - FILE *f; - - - sv_t qtv; - - - char line[1024]; - char buffer[8192]; - char *res; - -#ifdef _WIN32 - { - WSADATA discard; - WSAStartup(MAKEWORD(2,0), &discard); - } -#endif - - - memset(&qtv, 0, sizeof(qtv)); - //set up a default config - qtv.tcplistenportnum = PROX_DEFAULTLISTENPORT; - qtv.qwlistenportnum = PROX_DEFAULTLISTENPORT; - strcpy(qtv.server, PROX_DEFAULTSERVER); - strcpy(qtv.hostname, DEFAULT_HOSTNAME); - qtv.chokeonnotupdated = true; - - qtv.qwdsocket = INVALID_SOCKET; - qtv.listenmvd = INVALID_SOCKET; - qtv.sourcesock = INVALID_SOCKET; - - qtv.maxviewers = 100; - qtv.maxproxies = 100; - - - line[sizeof(line)-1] = '\0'; - if (argc < 2) - res = "ftv.cfg"; - else - res = argv[1]; - f = fopen(res, "rt"); - if (!f) - printf("Couldn't open config file \"%s\"\n", res); - else - { - while(fgets(line, sizeof(line)-1, f)) - { - res = Rcon_Command(&qtv, line, buffer, sizeof(buffer), true); - printf("%s", res); - } - fclose(f); - } - - - if (qtv.qwdsocket == INVALID_SOCKET && qtv.listenmvd == INVALID_SOCKET) - { - //make sure there's a use for this proxy. - if (qtv.qwdsocket == INVALID_SOCKET) - { //still not opened one? try again - qtv.qwdsocket = QW_InitUDPSocket(qtv.qwlistenportnum); - if (qtv.qwdsocket == INVALID_SOCKET) - printf("Warning: couldn't open udp socket\n"); - else - printf("Opened udp port %i\n", qtv.qwlistenportnum); - } - if (qtv.listenmvd == INVALID_SOCKET) - { - qtv.listenmvd = Net_MVDListen(qtv.tcplistenportnum); - if (qtv.listenmvd == INVALID_SOCKET) - printf("Warning: couldn't open mvd socket\n"); - else - printf("Opened tcp port %i\n", qtv.tcplistenportnum); - } - if (qtv.qwdsocket == INVALID_SOCKET && qtv.listenmvd == INVALID_SOCKET) - { - printf("Shutting down, couldn't open listening ports (useless proxy)\n"); - return 0; - } - } - - qtv.parsingconnectiondata = true; - QTV_Run(&qtv); - - return 0; -} - -*/ - - - @@ -1290,12 +804,7 @@ void SendClientCommand(sv_t *qtv, char *fmt, ...) char buf[1024]; va_start (argptr, fmt); -#ifdef _WIN32 - _vsnprintf (buf, sizeof(buf) - 1, fmt, argptr); - buf[sizeof(buf) - 1] = '\0'; -#else vsnprintf (buf, sizeof(buf), fmt, argptr); -#endif // _WIN32 va_end (argptr); WriteByte(&qtv->netchan.message, clc_stringcmd); @@ -1314,7 +823,7 @@ void ChooseFavoriteTrack(sv_t *tv) frags = -10000; best = -1; - if (tv->controller) + if (tv->controller || tv->proxyplayer) best = tv->trackplayer; else { @@ -1444,7 +953,9 @@ void QTV_ParseQWStream(sv_t *qtv) strcpy(qtv->status, "Attemping connection\n"); qtv->challenge = atoi(buffer+5); if (qtv->controller) - sprintf(buffer, "connect %i %i %i \"%s\"", 28, qtv->qport, qtv->challenge, qtv->controller->userinfo); + sprintf(buffer, "connect %i %i %i \"%s\\*qtv\\1\\\"", 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); @@ -1506,7 +1017,7 @@ void QTV_Run(sv_t *qtv) int oldcurtime; int packettime; - if (qtv->drop || (qtv->disconnectwhennooneiswatching && qtv->numviewers == 0)) + if (qtv->drop || (qtv->disconnectwhennooneiswatching && qtv->numviewers == 0 && qtv->proxies == NULL)) { QTV_Shutdown(qtv); return; @@ -1567,7 +1078,7 @@ void QTV_Run(sv_t *qtv) return; } - if (qtv->controller) + if (qtv->controller && !qtv->controller->netchan.isnqprotocol) { qtv->netchan.outgoing_sequence = qtv->controller->netchan.incoming_sequence; qtv->netchan.incoming_sequence = qtv->controller->netchan.incoming_acknowledged; @@ -1586,8 +1097,6 @@ void QTV_Run(sv_t *qtv) } if (qtv->curtime >= qtv->packetratelimiter) { - qtv->packetratelimiter += UDPPACKETINTERVAL; - if (qtv->curtime >= qtv->nextsendpings || qtv->curtime < qtv->nextsendpings - PINGSINTERVAL_TIME*2) { qtv->nextsendpings = qtv->curtime + PINGSINTERVAL_TIME; @@ -1598,6 +1107,8 @@ void QTV_Run(sv_t *qtv) if (qtv->trackplayer >= 0) { + qtv->packetratelimiter += UDPPACKETINTERVAL; + WriteByte(&msg, clc_tmove); WriteShort(&msg, qtv->players[qtv->trackplayer].current.origin[0]); WriteShort(&msg, qtv->players[qtv->trackplayer].current.origin[1]); @@ -1605,6 +1116,8 @@ void QTV_Run(sv_t *qtv) } else if (qtv->controller) { + qtv->packetratelimiter += UDPPACKETINTERVAL; + WriteByte(&msg, clc_tmove); WriteShort(&msg, qtv->controller->origin[0]); WriteShort(&msg, qtv->controller->origin[1]); @@ -1624,6 +1137,38 @@ void QTV_Run(sv_t *qtv) SetMoveCRC(qtv, &msg); } + else if (qtv->proxyplayer) + { + 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); + } Netchan_Transmit(qtv->cluster, &qtv->netchan, msg.cursize, msg.data); } @@ -1644,7 +1189,7 @@ void QTV_Run(sv_t *qtv) } - Net_FindProxies(qtv); //look for any other proxies wanting to muscle in on the action. + SV_FindProxies(qtv->tcpsocket, qtv->cluster, qtv); //look for any other proxies wanting to muscle in on the action. if (qtv->file || qtv->sourcesock != INVALID_SOCKET) { @@ -1654,9 +1199,133 @@ void QTV_Run(sv_t *qtv) //what should we do here? //obviously, we need to keep reading the stream to keep things smooth } + + Net_WriteUpStream(qtv); } + if (qtv->parsingqtvheader) + { + 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 (strncmp(qtv->buffer, "QTVSV ", length)) + { + Sys_Printf(qtv->cluster, "Server is not a QTV server (or is incompatable)\n"); + qtv->drop = true; + return; + } + if (length < 6) + return; //not ready yet + end = qtv->buffer + qtv->buffersize - 1; + for (nl = 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. + + if (atoi(qtv->buffer + 6) != 1) + { + Sys_Printf(qtv->cluster, "QTV server doesn't support a compatable protocol version (returned %i)\n", atoi(qtv->buffer + 6)); + qtv->drop = true; + return; + } + + length = (nl - (char*)qtv->buffer) + 2; + end = nl; + nl[1] = '\0'; + start = strchr(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, "QTV server wrongly used compression\n"); + qtv->drop = true; + return; + } + else if (!strcmp(start, "PERROR")) + { //we don't support compression, we didn't ask for it. + Sys_Printf(qtv->cluster, "\nQTV server error: %s\n\n", colon); + qtv->drop = true; + qtv->buffersize = 0; + return; + } + else if (!strcmp(start, "PRINT")) + { //we don't support compression, we didn't ask for it. + Sys_Printf(qtv->cluster, "QTV server: \n"); + return; + } + 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 (*authmethod == '\0') + qtv->parsingqtvheader = false; + else + { //we need to send a challenge response now. + Net_SendQTVConnectionRequest(qtv, authmethod, challenge); + } + + if (qtv->parsingqtvheader) + return; + + qtv->parsetime = Sys_Milliseconds() + BUFFERTIME*1000; + if (!qtv->usequkeworldprotocols) + Sys_Printf(qtv->cluster, "Connection established, buffering for %i seconds\n", BUFFERTIME); + + if (!qtv->cluster->lateforward) + SV_ForwardStream(qtv, qtv->buffer, qtv->buffersize); + } + while (qtv->curtime >= qtv->parsetime) { @@ -1736,7 +1405,7 @@ void QTV_Run(sv_t *qtv) if (qtv->nextpackettime < qtv->curtime) { if (qtv->cluster->lateforward) - Net_ForwardStream(qtv, qtv->buffer, lengthofs+4+length); + SV_ForwardStream(qtv, qtv->buffer, lengthofs+4+length); switch(qtv->buffer[1]&dem_mask) { @@ -1775,132 +1444,7 @@ void QTV_Run(sv_t *qtv) } } - - -void Cluster_Run(cluster_t *cluster) -{ - sv_t *sv, *old; - - int m; - struct timeval timeout; - fd_set socketset; - - FD_ZERO(&socketset); - m = 0; - if (cluster->qwdsocket != INVALID_SOCKET) - { - FD_SET(cluster->qwdsocket, &socketset); - if (cluster->qwdsocket >= m) - m = cluster->qwdsocket+1; - } - - for (sv = cluster->servers; sv; sv = sv->next) - { - if (sv->usequkeworldprotocols && sv->sourcesock != INVALID_SOCKET) - { - FD_SET(sv->sourcesock, &socketset); - if (sv->sourcesock >= m) - m = sv->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; - - m = 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') - { - Sys_Printf(cluster, "\n"); - if (cluster->inputlength) - { - cluster->commandinput[cluster->inputlength] = '\0'; - result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - cluster->inputlength = 0; - cluster->commandinput[0] = '\0'; - } - } - else if (c == '\b') - { - if (cluster->inputlength > 0) - { - Sys_Printf(cluster, "%c", c); - Sys_Printf(cluster, " ", c); - Sys_Printf(cluster, "%c", c); - - cluster->inputlength--; - cluster->commandinput[cluster->inputlength] = '\0'; - } - } - else - { - Sys_Printf(cluster, "%c", c); - if (cluster->inputlength < sizeof(cluster->commandinput)-1) - { - cluster->commandinput[cluster->inputlength++] = c; - cluster->commandinput[cluster->inputlength] = '\0'; - } - } - } -#else - if (FD_ISSET(STDIN, &socketset)) - { - char buffer[8192]; - char *result; - cluster->inputlength = read (0, cluster->commandinput, sizeof(cluster->commandinput)); - if (cluster->inputlength >= 1) - { - cluster->commandinput[cluster->inputlength-1] = 0; // rip off the /n and terminate - - if (cluster->inputlength) - { - cluster->commandinput[cluster->inputlength] = '\0'; - result = Rcon_Command(cluster, NULL, cluster->commandinput, buffer, sizeof(buffer), true); - printf("%s", result); - cluster->inputlength = 0; - cluster->commandinput[0] = '\0'; - } - } - } -#endif - - - - - - for (sv = cluster->servers; sv; ) - { - old = sv; - sv = sv->next; - QTV_Run(old); - } - - QW_UpdateUDPStuff(cluster); -} - -sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, qboolean force, qboolean autoclose, qboolean noduplicates) +sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password, qboolean force, qboolean autoclose, qboolean noduplicates) { sv_t *qtv; @@ -1922,11 +1466,15 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, qboolean force, qtv->tcplistenportnum = PROX_DEFAULTLISTENPORT; strcpy(qtv->server, PROX_DEFAULTSERVER); - qtv->listenmvd = INVALID_SOCKET; + memcpy(qtv->connectpassword, password, sizeof(qtv->connectpassword)-1); + + qtv->tcpsocket = INVALID_SOCKET; qtv->sourcesock = INVALID_SOCKET; qtv->disconnectwhennooneiswatching = autoclose; qtv->parsingconnectiondata = true; + qtv->streamid = ++cluster->nextstreamid; + qtv->cluster = cluster; qtv->next = cluster->servers; @@ -1940,103 +1488,3 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, qboolean force, return qtv; } - -void DoCommandLine(cluster_t *cluster, int argc, char **argv) -{ - int i; - char commandline[8192]; - char *start, *end, *result; - char buffer[8192]; - - commandline[0] = '\0'; - - //build a block of strings. - for (i = 1; i < argc; i++) - { - strcat(commandline, argv[i]); - strcat(commandline, " "); - } - strcat(commandline, "+"); - - start = commandline; - while(start) - { - end = strchr(start+1, '+'); - if (end) - *end = '\0'; - if (start[1]) - { - result = Rcon_Command(cluster, NULL, start+1, buffer, sizeof(buffer), true); - Sys_Printf(cluster, "%s", result); - } - - start = end; - } -} - -int main(int argc, char **argv) -{ - cluster_t cluster; - -#ifndef _WIN32 -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif -#endif - -#ifdef _WIN32 - { - WSADATA discard; - WSAStartup(MAKEWORD(2,0), &discard); - } -#endif - - memset(&cluster, 0, sizeof(cluster)); - - cluster.qwdsocket = INVALID_SOCKET; - cluster.qwlistenportnum = 0; - strcpy(cluster.hostname, DEFAULT_HOSTNAME); - - - DoCommandLine(&cluster, argc, argv); - - if (!cluster.numservers) - { //probably running on a home user's computer - if (cluster.qwdsocket == INVALID_SOCKET && !cluster.qwlistenportnum) - { - cluster.qwdsocket = QW_InitUDPSocket(cluster.qwlistenportnum = 27599); - if (cluster.qwdsocket != INVALID_SOCKET) - Sys_Printf(&cluster, "opened port %i\n", cluster.qwlistenportnum); - } - - Sys_Printf(&cluster, "\n" - "Welcome to FTEQTV\n" - "Please type\n" - "qtv server:port\n" - " to connect to a tcp server.\n" - "qw server:port\n" - " to connect to a regular qw server.\n" - "demo qw/example.mvd\n" - " to play a demo from an mvd.\n" - "\n"); - } - - while (!cluster.wanttoexit) - Cluster_Run(&cluster); - - return 0; -} - - -void Sys_Printf(cluster_t *cluster, char *fmt, ...) -{ - va_list argptr; - char string[2024]; - - va_start (argptr, fmt); - vsnprintf (string, sizeof(string), fmt,argptr); - va_end (argptr); - - printf("%s", string); -} -