diff --git a/CMakeLists.txt b/CMakeLists.txt index a7ad3394..76fcc0dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -829,6 +829,7 @@ ELSE() ADD_EXECUTABLE(ftemaster ${FTESV_ARCH_FILES} engine/server/sv_master.c + engine/common/net_ice.c #for the stun responses. engine/common/net_wins.c engine/common/cvar.c engine/common/cmd.c diff --git a/engine/Makefile b/engine/Makefile index 09ad66de..57fe5474 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -2328,10 +2328,13 @@ $(RELEASE_DIR)/imgtool$(BITS)$(EXEPOSTFIX): $(IMGTOOL_OBJECTS) imgtool-rel: $(RELEASE_DIR)/imgtool$(BITS)$(EXEPOSTFIX) imgtool: imgtool-rel -MASTER_OBJECTS=server/sv_sys_unix.c common/sys_linux_threads.c common/net_ssl_gnutls.c server/sv_master.c common/net_wins.c common/cvar.c common/cmd.c common/sha1.c http/httpclient.c common/log.c common/fs.c common/fs_stdio.c common/common.c common/translate.c common/zone.c qclib/hash.c +MASTER_OBJECTS=server/sv_sys_unix.c common/sys_linux_threads.c common/net_ssl_gnutls.c server/sv_master.c common/net_wins.c common/net_ice.c common/cvar.c common/cmd.c common/sha1.c http/httpclient.c common/log.c common/fs.c common/fs_stdio.c common/common.c common/translate.c common/zone.c qclib/hash.c $(RELEASE_DIR)/ftemaster$(BITS)$(EXEPOSTFIX): $(MASTER_OBJECTS) - $(CC) -o $@ $(MASTER_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver -DMASTERONLY -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -lm -ldl + $(CC) -o $@ $(MASTER_OBJECTS) -flto=jobserver -fvisibility=hidden -Icommon -Iclient -Iqclib -Igl -Iserver -DMASTERONLY -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -lm -ldl -lz $(RELEASE_CFLAGS) $(RELEASE_LDFLAGS) +$(DEBUG_DIR)/ftemaster$(BITS)$(EXEPOSTFIX): $(MASTER_OBJECTS) + $(CC) -o $@ $(MASTER_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver -DMASTERONLY -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -lm -ldl -lz $(DEBUG_CFLAGS) $(DEBUG_LDFLAGS) master-rel: $(RELEASE_DIR)/ftemaster$(BITS)$(EXEPOSTFIX) +master-dbg: $(DEBUG_DIR)/ftemaster$(BITS)$(EXEPOSTFIX) master: master-rel QTV_OBJECTS= \ diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index 633fa5f7..7bc5e0d5 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -951,7 +951,7 @@ void Cam_SetModAutoTrack(int userid) Con_Printf("//at: invalid userid\n"); } -void Cam_TrackCrosshairedPlayer(playerview_t *pv) +/*static void Cam_TrackCrosshairedPlayer(playerview_t *pv) { inframe_t *frame; player_state_t *player; @@ -978,11 +978,11 @@ void Cam_TrackCrosshairedPlayer(playerview_t *pv) } } // Con_Printf("Track %i? %f\n", best, bestdot); - if (best != -1) //did we actually get someone? + if (best > .707) //did we actually get someone? { Cam_Lock(pv, best); } -} +}*/ void Cam_FinishMove(playerview_t *pv, usercmd_t *cmd) { @@ -1091,8 +1091,8 @@ void Cam_FinishMove(playerview_t *pv, usercmd_t *cmd) pv->cam_oldbuttons &= ~BUTTON_ATTACK; if (pv->cam_state == CAM_FREECAM && autotrackmode == TM_USER) { - if ((cmd->buttons & BUTTON_JUMP) && !(pv->cam_oldbuttons & BUTTON_JUMP)) - Cam_TrackCrosshairedPlayer(pv); +// if ((cmd->buttons & BUTTON_JUMP) && !(pv->cam_oldbuttons & BUTTON_JUMP)) +// Cam_TrackCrosshairedPlayer(pv); pv->cam_oldbuttons = (pv->cam_oldbuttons&~BUTTON_JUMP) | (cmd->buttons & BUTTON_JUMP); return; } diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 7a1324e0..16419906 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -2632,7 +2632,8 @@ void CL_QTVPoll (void) qboolean init_numviewers = false; qboolean iseztv = false; char srchost[256]; - + char auth[64]; + char challenge[128]; if (!qtvrequest) return; @@ -2706,6 +2707,8 @@ void CL_QTVPoll (void) } s[1] = '\0'; //make sure its null terminated before the data payload s = qtvrequestbuffer; + + *auth = *challenge = 0; for (e = s; *e; ) { if (*e == '\r') @@ -2735,6 +2738,14 @@ void CL_QTVPoll (void) { //printable error Con_Printf("Demo%s is available\n", colon); } + else if (!strcmp(s, "AUTH")) + { + while (*colon && *(unsigned char*)colon <= ' ') + colon++; + Q_strncpyz(auth, colon, sizeof(auth)); + } + else if (!strcmp(s, "CHALLENGE")) + Q_strncpyz(challenge, colon, sizeof(challenge)); //generic sourcelist responce else if (!strcmp(s, "ASOURCE")) @@ -2830,6 +2841,14 @@ void CL_QTVPoll (void) return; } + if (!strcmp(auth, "NONE")) + ; +// else if (!strcmp(auth, "PLAIN")) +// else if (!strcmp(auth, "MD4")) +// else if (!strcmp(auth, "SHA1")) + else if (*auth) + Con_Printf("Server requires unsupported auth method: %s\n", auth); + SCR_SetLoadingStage(LS_NONE); VFS_CLOSE(qtvrequest); qtvrequest = NULL; @@ -3013,6 +3032,7 @@ void CL_ParseQTVDescriptor(vfsfile_t *f, const char *name) VFS_CLOSE(f); } +#include "netinc.h" void CL_QTVPlay_f (void) { qboolean raw=0; @@ -3021,11 +3041,11 @@ void CL_QTVPlay_f (void) char *host; char msg[4096]; int msglen=0; -// char *password; + char *password; if (Cmd_Argc() < 2) { - Con_Printf("Usage: qtvplay [stream@]hostname[:port] [password]\n"); + Con_Printf("Usage: qtvplay [stream@][tls://]hostname[:port] [password]\n"); return; } @@ -3047,8 +3067,23 @@ void CL_QTVPlay_f (void) connrequest = strchrrev(connrequest, '@'); if (connrequest) host = connrequest+1; - Q_strncpyz(qtvhostname, host, sizeof(qtvhostname)); - newf = FS_OpenTCP(qtvhostname, 27599); +#ifdef HAVE_SSL + if (!strncmp(host, "tls://", 6)) + { + char *colon; + Q_strncpyz(qtvhostname, host+6, sizeof(qtvhostname)); + colon = strchr(qtvhostname, ':'); + newf = FS_OpenTCP(qtvhostname, 27599); + if (colon) *colon = 0; + newf = FS_OpenSSL(qtvhostname, newf, false); + if (colon) *colon = ':'; + } + else +#endif + { + Q_strncpyz(qtvhostname, host, sizeof(qtvhostname)); + newf = FS_OpenTCP(qtvhostname, 27599); + } if (!newf) { @@ -3063,7 +3098,7 @@ void CL_QTVPlay_f (void) else host = NULL; -// password = Cmd_Argv(2); + password = Cmd_Argv(2); if (qtvcl_forceversion1.ival) { @@ -3079,6 +3114,33 @@ void CL_QTVPlay_f (void) } msglen += strlen(msg+msglen); + if (password) + { +#if 0 + //just send it directly, we can't handle the tripple handshake for the challenge info + Q_snprintfz(msg+msglen, sizeof(msg)-msglen, + "AUTH: PLAIN\n" + "PASSWORD: %s\n" + , password); +#else + //report supported auth methods to the server. it'll pick one and send us a challenge. + Q_snprintfz(msg+msglen, sizeof(msg)-msglen, +// "AUTH: SHA1\n" +// "AUTH: MD4\n" +// "AUTH: CCITT\n" + "AUTH: PLAIN\n"); +#endif + msglen += strlen(msg+msglen); + } + else + { + //include supported auth methods, so server can pick one (and give suitable challenge in its response) + Q_snprintfz(msg+msglen, sizeof(msg)-msglen, + "AUTH: NONE\n" + ""); + msglen += strlen(msg+msglen); + } + if (raw) { Q_snprintfz(msg+msglen, sizeof(msg)-msglen, diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 01082212..0f00cd0d 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -4646,17 +4646,17 @@ void CLQW_ParsePlayerinfo (void) if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) { - player_state_t *prevstate, dummy; + player_state_t dummy; if (!cl.parsecount || info->prevcount > cl.parsecount || cl.parsecount - info->prevcount >= UPDATE_BACKUP - 1) { memset(&dummy, 0, sizeof(dummy)); - prevstate = &dummy; + oldstate = &dummy; } else { - prevstate = &cl.inframes[info->prevcount & UPDATE_MASK].playerstate[num]; + oldstate = &cl.inframes[info->prevcount & UPDATE_MASK].playerstate[num]; } - memcpy(state, prevstate, sizeof(player_state_t)); + memcpy(state, oldstate, sizeof(player_state_t)); info->prevcount = cl.parsecount; #ifdef QUAKESTATS @@ -4689,7 +4689,7 @@ void CLQW_ParsePlayerinfo (void) } } - VectorSubtract(state->origin, prevstate->origin, dist); + VectorSubtract(state->origin, oldstate->origin, dist); VectorScale(dist, 1/(cl.inframes[parsecountmod].packet_entities.servertime - cl.inframes[oldparsecountmod].packet_entities.servertime), state->velocity); VectorCopy (state->origin, state->predorigin); diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index bb0e8489..2ff19718 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -64,10 +64,8 @@ int CL_TargettedSplit(qboolean nowrap) int mod; //explicitly targetted at some seat number from the server - if (Cmd_ExecLevel > RESTRICT_SERVER) - return Cmd_ExecLevel - RESTRICT_SERVER-1; - if (Cmd_ExecLevel == RESTRICT_SERVER) - return 0; + if (Cmd_ExecLevel >= RESTRICT_SERVER) + return Cmd_ExecLevel - RESTRICT_SERVER; //locally executed command. if (nowrap) @@ -531,31 +529,32 @@ static void IN_UseDown (void) {KeyDown(&in_use, NULL);} static void IN_UseUp (void) {KeyUp(&in_use);} static void IN_JumpDown (void) { - qboolean condition; - - + qboolean up; int pnum = CL_TargettedSplit(false); playerview_t *pv = &cl.playerview[pnum]; - condition = (cls.state == ca_active && cl_smartjump.ival && !prox_inmenu.ival); + up = (cls.state == ca_active && cl_smartjump.ival && !prox_inmenu.ival); + if (!up) + up = false; #ifdef Q2CLIENT - if (condition && cls.protocol == CP_QUAKE2) - KeyDown(&in_up, &in_down); - else + else if (cls.protocol == CP_QUAKE2) + up = true; //always smartjump in q2. #endif + else if (pv->spectator && pv->cam_state != CAM_FREECAM) + up = false; //if we're tracking, don't confuse stuff. #ifdef QUAKESTATS - if (condition && cl.playerview[pnum].stats[STAT_HEALTH] > 0 && !cls.demoplayback && !pv->spectator && - (cls.protocol==CP_NETQUAKE || cl.inframes[cl.validsequence&UPDATE_MASK].playerstate[pv->playernum].messagenum == cl.validsequence) - && cl.playerview[pnum].waterlevel >= 2 && (!cl.teamfortress || !(in_forward.state[pnum] & 1)) - ) - KeyDown(&in_up, &in_down); - else + else if (!pv->spectator && pv->stats[STAT_HEALTH] <= 0) + up = false; //don't ever 'swim' when dead. + else if (pv->pmovetype == PM_FLY || pv->pmovetype == PM_6DOF || pv->pmovetype == PM_SPECTATOR || pv->pmovetype == PM_OLD_SPECTATOR) + up = true; //fling/spectating + else if ((pv->pmovetype == PM_NORMAL || pv->pmovetype == PM_WALLWALK) && pv->waterlevel >= 2 && (!cl.teamfortress || !(in_forward.state[pnum] & 1))) + up = true; //swimming. TF only (silently) smartjumps when NOT moving. #endif - if (condition && pv->spectator && pv->cam_state == CAM_FREECAM) - KeyDown(&in_up, &in_down); else - KeyDown(&in_jump, &in_down); + up = false; + + KeyDown((up?&in_up:&in_jump), &in_down); } static void IN_JumpUp (void) { diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index ce14296e..986c9dce 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -102,7 +102,7 @@ cvar_t m_forward = CVARF("m_forward","1", CVAR_ARCHIVE); cvar_t m_side = CVARF("m_side","0.8", CVAR_ARCHIVE); cvar_t cl_lerp_maxinterval = CVARD("cl_lerp_maxinterval", "0.3", "Maximum interval between keyframes, in seconds. Larger values can result in entities drifting very slowly when they move sporadically."); -cvar_t cl_lerp_players = CVARD("cl_lerp_players", "1", "Set this to make other players smoother, though it may increase effective latency. Affects only QuakeWorld."); +cvar_t cl_lerp_players = CVARD("cl_lerp_players", "0", "Set this to make other players smoother, though it may increase effective latency. Affects only QuakeWorld."); cvar_t cl_predict_players = CVARD("cl_predict_players", "1", "Clear this cvar to see ents exactly how they are on the server."); cvar_t cl_predict_players_frac = CVARD("cl_predict_players_frac", "0.9", "How much of other players to predict. Values less than 1 will help minimize overruns."); cvar_t cl_predict_players_latency = CVARD("cl_predict_players_latency", "1.0", "Push the player back according to your latency, to give a smooth consistent simulation of the server."); @@ -206,6 +206,7 @@ cvar_t host_mapname = CVARAF("mapname", "", #define RULESETADVICE " You should not normally change this cvar from its permissive default, instead impose limits on yourself only through the 'ruleset' cvar." cvar_t ruleset_allow_playercount = CVARD("ruleset_allow_playercount", "1", "Specifies whether teamplay triggers that count nearby players are allowed in the current ruleset."RULESETADVICE); cvar_t ruleset_allow_frj = CVARD("ruleset_allow_frj", "1", "Specifies whether Forward-Rocket-Jump scripts are allowed in the current ruleset. If 0, limits on yaw speed will be imposed so they cannot be scripted."RULESETADVICE); + //FIXME: rename ruleset_allow_frj to allow_scripts to match ezquake - 0: block multiple commands in binds, 1: cap angle speed changes, 2: vanilla quake cvar_t ruleset_allow_semicheats = CVARD("ruleset_allow_semicheats", "1", "If 0, this blocks a range of cvars that are marked as semi-cheats. Such cvars will be locked to their empty/0 value."RULESETADVICE); cvar_t ruleset_allow_packet = CVARD("ruleset_allow_packet", "1", "If 0, network packets sent via the 'packet' command will be blocked. This makes scripting timers a little harder."RULESETADVICE); cvar_t ruleset_allow_particle_lightning = CVARD("ruleset_allow_particle_lightning", "1", "A setting of 0 blocks using the particle system to replace lightning gun trails. This prevents making the trails thinner thus preventing them from obscuring your view of your enemies."RULESETADVICE); @@ -264,9 +265,9 @@ unsigned int cl_maxstris; static struct { - qboolean trying; - qboolean istransfer; //ignore the user's desired server (don't change connect.adr). - netadr_t adr; //address that we're trying to transfer to. FIXME: support multiple resolved addresses, eg both ::1 AND 127.0.0.1 + qboolean trying; + qboolean istransfer; //ignore the user's desired server (don't change connect.adr). + netadr_t adr; //address that we're trying to transfer to. FIXME: support multiple resolved addresses, eg both ::1 AND 127.0.0.1 #ifdef HAVE_DTLS enum { @@ -276,19 +277,20 @@ static struct DTLS_ACTIVE } dtlsupgrade; #endif - int mtu; - unsigned int compresscrc; - int protocol; //nq/qw/q2/q3. guessed based upon server replies - int subprotocol; //the monkeys are trying to eat me. - unsigned int fteext1; - unsigned int fteext2; - unsigned int ezext1; - int qport; - int challenge; //tracked as part of guesswork based upon what replies we get. - double time; //for connection retransmits - int defaultport; - int tries; //increased each try, every fourth trys nq connect packets. - unsigned char guid[64]; + int mtu; + unsigned int compresscrc; + int protocol; //nq/qw/q2/q3. guessed based upon server replies + int subprotocol; //the monkeys are trying to eat me. + unsigned int fteext1; + unsigned int fteext2; + unsigned int ezext1; + int qport; + int challenge; //tracked as part of guesswork based upon what replies we get. + double time; //for connection retransmits + int defaultport; + int tries; //increased each try, every fourth trys nq connect packets. + unsigned char guid[64]; +// qbyte fingerprint[5*4]; //sha1 hash of accepted dtls certs } connectinfo; quakeparms_t host_parms; @@ -1033,22 +1035,6 @@ void CL_CheckForResend (void) if (startuppending || r_blockvidrestart) return; //don't send connect requests until we've actually initialised fully. this isn't a huge issue, but makes the startup prints a little more sane. - /* -#ifdef NQPROT - if (connect_type) - { - if (!connect_time || !(realtime - connect_time < 5.0)) - { - connect_time = realtime; - NQ_BeginConnect(cls.servername); - NQ_ContinueConnect(cls.servername); - } - else - NQ_ContinueConnect(cls.servername); - return; - } -#endif - */ if (connectinfo.time && realtime - connectinfo.time < 5.0) return; @@ -1228,6 +1214,7 @@ void CL_BeginServerConnect(const char *host, int port, qboolean noproxy) connectinfo.trying = true; connectinfo.defaultport = port; connectinfo.protocol = CP_UNKNOWN; + SCR_SetLoadingStage(LS_CONNECTION); CL_CheckForResend(); } @@ -1276,11 +1263,9 @@ void CL_Transfer_f(void) memset(&connectinfo, 0, sizeof(connectinfo)); if (NET_StringToAdr(server, 0, &connectinfo.adr)) { - if (cls.state) - { - connectinfo.istransfer = true; - Q_strncpyz(connectinfo.guid, oldguid, sizeof(oldguid)); //retain the same guid on transfers - } + connectinfo.istransfer = true; + Q_strncpyz(connectinfo.guid, oldguid, sizeof(oldguid)); //retain the same guid on transfers + Cvar_Set(&cl_disconnectreason, "Transferring...."); connectinfo.trying = true; connectinfo.defaultport = cl_defaultport.value; @@ -2161,7 +2146,7 @@ void CL_Color_f (void) if (top == 0) *num = '\0'; if (Cmd_ExecLevel>RESTRICT_SERVER) //colour command came from server for a split client - Cbuf_AddText(va("p%i cmd setinfo topcolor \"%s\"\n", Cmd_ExecLevel-RESTRICT_SERVER-1, num), Cmd_ExecLevel); + Cbuf_AddText(va("p%i cmd setinfo topcolor \"%s\"\n", pnum+1, num), Cmd_ExecLevel); // else if (server_owns_colour) // Cvar_LockFromServer(&topcolor, num); else @@ -2170,7 +2155,7 @@ void CL_Color_f (void) if (bottom == 0) *num = '\0'; if (Cmd_ExecLevel>RESTRICT_SERVER) //colour command came from server for a split client - Cbuf_AddText(va("p%i cmd setinfo bottomcolor \"%s\"\n", Cmd_ExecLevel-RESTRICT_SERVER-1, num), Cmd_ExecLevel); + Cbuf_AddText(va("p%i cmd setinfo bottomcolor \"%s\"\n", pnum+1, num), Cmd_ExecLevel); else if (server_owns_colour) Cvar_LockFromServer(&bottomcolor, num); else @@ -2535,6 +2520,8 @@ void CL_SetInfoBlob (int pnum, const char *key, const char *value, size_t values return; } } + else if (pnum < 0 || pnum >= MAX_SPLITS) + return; InfoBuf_SetStarBlobKey(&cls.userinfo[pnum], key, value, valuesize); } @@ -2960,6 +2947,7 @@ void CL_Reconnect_f (void) } CL_Disconnect(NULL); + connectinfo.tries = 0; //re-ensure routes. CL_BeginServerReconnect(); } @@ -3142,6 +3130,13 @@ void CL_ConnectionlessPacket (void) Con_TPrintf ("challenge\n"); + if (!NET_CompareAdr(&connectinfo.adr, &net_from)) + { + if (connectinfo.adr.prot != NP_RTC_TCP && connectinfo.adr.prot != NP_RTC_TLS) + Con_Printf("Challenge from wrong server, ignoring\n"); + return; + } + if (!strcmp(com_token, "hallengeResponse")) { /*Quake3*/ @@ -3589,6 +3584,7 @@ client_connect: //fixme: make function cls.netchan.qportsize = 1; } cls.netchan.pext_fragmentation = connectinfo.mtu?true:false; + cls.netchan.pext_stunaware = !!(connectinfo.fteext2&PEXT2_STUNAWARE); if (connectinfo.mtu >= 64) { cls.netchan.mtu = connectinfo.mtu; @@ -3907,6 +3903,10 @@ void CL_ReadPackets (void) continue; } + if (cls.netchan.pext_stunaware) //should be safe to do this here. + if (NET_WasSpecialPacket(cls.sockets)) + continue; + switch(cls.protocol) { case CP_NETQUAKE: @@ -4450,6 +4450,7 @@ void CL_Status_f(void) char adr[128]; float pi, po, bi, bo; NET_PrintAddresses(cls.sockets); + NET_PrintConnectionsStatus(cls.sockets); if (NET_GetRates(cls.sockets, &pi, &po, &bi, &bo)) Con_Printf("packets,bytes/sec: in: %g %g out: %g %g\n", pi, bi, po, bo); //not relevent as a limit. @@ -5954,7 +5955,9 @@ double Host_Frame (double time) return; // framerate is too high */ - Mod_Think(); //think even on idle (which means small walls and a fast cpu can get more surfaces done. +#ifdef RUNTIMELIGHTING + RelightThink(); //think even on idle (which means small walls and a fast cpu can get more surfaces done. +#endif #ifndef CLIENTONLY if (sv.state && cls.state != ca_active) diff --git a/engine/client/cl_master.h b/engine/client/cl_master.h index a7ee1e99..84a2b141 100644 --- a/engine/client/cl_master.h +++ b/engine/client/cl_master.h @@ -35,14 +35,15 @@ enum masterprotocol_e #define SS_PROXY (1<<7u) #define PING_DEAD 0xffff //default ping value to denote servers that are not responding. -#define PING_MAX 0xfffe //default ping value to denote servers that are not responding. +#define PING_UNKNOWN 0xfffe //these servers are considered up, but we can't query them directly so can't determine the final ping from here. +#define PING_MAX 0xfffd //highest 'valid' ping value. //despite not supporting nq or q2, we still load them. We just filter them. This is to make sure we properly write the listing files. enum mastertype_e { MT_BAD, //this would be an error - MT_MASTERHTTPJSON, +// MT_MASTERHTTPJSON, MT_MASTERHTTP, MT_MASTERUDP, MT_BCAST, @@ -127,6 +128,7 @@ typedef struct serverinfo_s { char name[64]; //hostname. netadr_t adr; + char brokerid[64]; //'rtc[s]://adr//brokerid' short special; //flags short protocol; @@ -191,6 +193,7 @@ extern struct selectedserver_s { qboolean inuse; netadr_t adr; + char brokerid[64]; float refreshtime; int lastplayer; char lastrule[64]; @@ -224,8 +227,7 @@ qboolean CL_QueryServers(void); int Master_CheckPollSockets(void); void MasterInfo_Shutdown(void); void MasterInfo_WriteServers(void); -void MasterInfo_Request(master_t *mast); -serverinfo_t *Master_InfoForServer (netadr_t *addr); +serverinfo_t *Master_InfoForServer (netadr_t *addr, const char *brokerid); serverinfo_t *Master_InfoForNum (int num); unsigned int Master_TotalCount(void); unsigned int Master_NumPolled(void); //progress indicator @@ -234,6 +236,7 @@ void Master_SetupSockets(void); void MasterInfo_Refresh(qboolean doreset); void Master_QueryServer(serverinfo_t *server); void MasterInfo_WriteServers(void); +char *Master_ServerToString (char *s, int len, serverinfo_t *a); //like NET_AdrToString, but handles more complex addresses. hostcachekey_t Master_KeyForName(const char *keyname); float Master_ReadKeyFloat(serverinfo_t *server, hostcachekey_t keynum); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index b5deae0f..ee006d6e 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -6238,7 +6238,7 @@ static void CL_ParsePrint(char *msg, int level) } else strcat(printtext, msg); //safe due to size on if. - while((msg = strchr(printtext, '\n'))) + while((msg = strchr(printtext, '\n')) || (msg = strchr(printtext, '\r'))) { n = msg[1]; msg[1] = 0; @@ -6399,6 +6399,7 @@ static void CL_ParseTeamInfo(void) static char stufftext[4096]; static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds from network segregation. { + int cbuflevel; #ifdef NQPROT if (!*stufftext && *msg == 1 && !cls.allow_csqc) { @@ -6411,7 +6412,8 @@ static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds while((msg = strchr(stufftext, '\n'))) { *msg = '\0'; - Con_DLPrintf((cls.state==ca_active)?1:2, "stufftext: %s\n", stufftext); + cbuflevel = RESTRICT_SERVERSEAT(destsplit); + Con_DLPrintf((cls.state==ca_active)?1:2, "stufftext%i: %s\n", destsplit, stufftext); if (!strncmp(stufftext, "fullserverinfo ", 15) || !strncmp(stufftext, "//fullserverinfo ", 17)) { Cmd_TokenizeString(stufftext+2, false, false); @@ -6532,9 +6534,9 @@ static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds Q_strncatz(newdemo, Cmd_Argv(1), sizeof(newdemo)); } - Cbuf_AddText ("playdemo ", RESTRICT_SERVER+destsplit); - Cbuf_AddText (COM_QuotedString(newdemo, temp, sizeof(temp), false), RESTRICT_SERVER+destsplit); - Cbuf_AddText ("\n", RESTRICT_SERVER+destsplit); + Cbuf_AddText ("playdemo ", cbuflevel); + Cbuf_AddText (COM_QuotedString(newdemo, temp, sizeof(temp), false), cbuflevel); + Cbuf_AddText ("\n", cbuflevel); } #ifdef CSQC_DAT else if (CSQC_StuffCmd(destsplit, stufftext, msg)) @@ -6546,20 +6548,20 @@ static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds COM_Parse(stufftext + 11); if (Cmd_Exists(com_token)) { - Cbuf_AddText ("cmd cmdsupported ", RESTRICT_SERVER+destsplit); - Cbuf_AddText (com_token, RESTRICT_SERVER+destsplit); - Cbuf_AddText ("\n", RESTRICT_SERVER+destsplit); + Cbuf_AddText ("cmd cmdsupported ", cbuflevel); + Cbuf_AddText (com_token, cbuflevel); + Cbuf_AddText ("\n", cbuflevel); } } else if (!strncmp(stufftext, "//exectrigger ", 14)) //so that mods can add whatever 'alias grabbedarmour' or whatever triggers that users might want to script responses for, without errors about unknown commands { COM_Parse(stufftext + 14); - if (Cmd_AliasExist(com_token, RESTRICT_SERVER)) - Cmd_ExecuteString(com_token, RESTRICT_SERVER); //do this NOW so that it's done before any models or anything are loaded + if (Cmd_AliasExist(com_token, cbuflevel)) + Cmd_ExecuteString(com_token, cbuflevel); //do this NOW so that it's done before any models or anything are loaded } else if (!strncmp(stufftext, "//set ", 6)) //equivelent to regular set, except non-spammy if it doesn't exist, and happens instantly without extra latency. { - Cmd_ExecuteString(stufftext+2, RESTRICT_SERVER+destsplit); //do this NOW so that it's done before any models or anything are loaded + Cmd_ExecuteString(stufftext+2, cbuflevel); //do this NOW so that it's done before any models or anything are loaded } else if (!strncmp(stufftext, "//at ", 5)) //ktx autotrack hints { @@ -6635,9 +6637,9 @@ static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds else { if (!strncmp(stufftext, "cmd ", 4)) - Cbuf_AddText (va("p%i ", destsplit+1), RESTRICT_SERVER+destsplit); //without this, in_forceseat can break directed cmds. - Cbuf_AddText (stufftext, RESTRICT_SERVER+destsplit); - Cbuf_AddText ("\n", RESTRICT_SERVER+destsplit); + Cbuf_AddText (va("p%i ", destsplit+1), cbuflevel); //without this, in_forceseat can break directed cmds. + Cbuf_AddText (stufftext, cbuflevel); + Cbuf_AddText ("\n", cbuflevel); } msg++; diff --git a/engine/client/cl_plugin.inc b/engine/client/cl_plugin.inc index dafe40f3..43f7ac44 100644 --- a/engine/client/cl_plugin.inc +++ b/engine/client/cl_plugin.inc @@ -646,6 +646,11 @@ static float QDECL Plug_GetTrackerOwnFrags(int seat, char *outptr, size_t outlen else return Stats_GetLastOwnFrag(seat, outptr, outlen); } +static void QDECL Plug_GetPredInfo(int seat, vec3_t outvel) +{ + if ((unsigned)seat < MAX_SPLITS) + VectorCopy(cl.playerview[seat].simvel, outvel); +} #endif static void QDECL Plug_GetLocationName(const float *locpoint, char *outbuffer, size_t bufferlen) diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index 9293a97d..6042578c 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -486,17 +486,8 @@ void CL_CalcCrouch (playerview_t *pv) return; } -/*fixme: this helps lifts with cl_nopred, but seems to harm ramps slightly, might just be my imagination. I guess we need to check last frame too. - //check if we moved in the x/y axis. if we didn't then we're on a vertically moving platform and shouldn't be crouching. - VectorMA(pv->oldorigin, pv->oldz-orgz, pv->gravitydir, pv->oldorigin); - VectorSubtract(pv->simorg, pv->oldorigin, delta); - if (Length(delta)<0.001) - pv->oldz = orgz; -*/ - VectorCopy (pv->simorg, pv->oldorigin); - if (pv->onground && orgz - pv->oldz) { if (pv->oldz > orgz) diff --git a/engine/client/cl_ui.c b/engine/client/cl_ui.c index 42ff2c1f..1e83c805 100644 --- a/engine/client/cl_ui.c +++ b/engine/client/cl_ui.c @@ -297,6 +297,7 @@ struct { unsigned int startms; netadr_t adr; + char brokername[64]; } ui_pings[MAX_PINGREQUESTS]; #define UITAGNUM 2452 @@ -879,11 +880,14 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con if (ui_pings[i].adr.type == NA_INVALID) { serverinfo_t *info; + const char *p = NULL; COM_Parse(cmdtext + 5); ui_pings[i].startms = Sys_Milliseconds(); - if (NET_StringToAdr(com_token, 0, &ui_pings[i].adr)) + if (NET_StringToAdr2(com_token, 0, &ui_pings[i].adr, 1, &p)) { - info = Master_InfoForServer(&ui_pings[i].adr); + if (p && *p=='/') + p++; + info = Master_InfoForServer(&ui_pings[i].adr, p); if (info) { info->special |= SS_KEEPINFO; @@ -891,6 +895,7 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con Master_QueryServer(info); } } + Q_strncpyz(ui_pings[i].brokername, p?p:"", sizeof(ui_pings[i].brokername)); break; } } @@ -1159,13 +1164,16 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con char *buf = VM_POINTER(arg[1]); size_t bufsize = VM_LONG(arg[2]); int *ping = VM_POINTER(arg[3]); - serverinfo_t *info = Master_InfoForServer(&ui_pings[i].adr); - NET_AdrToString(buf, bufsize, &ui_pings[i].adr); + serverinfo_t *info = Master_InfoForServer(&ui_pings[i].adr, ui_pings[i].brokername); + if (info) + Master_ServerToString(buf, bufsize, info); + else + NET_AdrToString(buf, bufsize, &ui_pings[i].adr); - if (info && (info->status & SRVSTATUS_ALIVE) && info->moreinfo) + if (info && /*(info->status & SRVSTATUS_ALIVE) &&*/ info->moreinfo) { VM_LONG(ret) = true; - *ping = info->ping; + *ping = (info->ping == PING_UNKNOWN)?1:info->ping; break; } i = Sys_Milliseconds()-ui_pings[i].startms; @@ -1188,8 +1196,8 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con char *buf = VM_POINTER(arg[1]); size_t bufsize = VM_LONG(arg[2]); char *adr; - serverinfo_t *info = Master_InfoForServer(&ui_pings[i].adr); - if (info && (info->status & SRVSTATUS_ALIVE) && info->moreinfo) + serverinfo_t *info = Master_InfoForServer(&ui_pings[i].adr, ui_pings[i].brokername); + if (info && /*(info->status & SRVSTATUS_ALIVE) &&*/ info->moreinfo) { adr = info->moreinfo->info; if (!adr) @@ -1269,7 +1277,7 @@ static qintptr_t UI_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con serverinfo_t *info = Master_InfoForNum(VM_LONG(arg[1])); if (info) { - adr = NET_AdrToString(adrbuf, sizeof(adrbuf), &info->adr); + adr = Master_ServerToString(adrbuf, sizeof(adrbuf), info); if (strlen(adr) < VM_LONG(arg[3])) { strcpy(buf, adr); diff --git a/engine/client/client.h b/engine/client/client.h index d351d702..ed7138ce 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -358,10 +358,6 @@ typedef struct int colourkey; //compacted version of the colours, for comparison against caches } lightstyle_t; - - -#define MAX_EFRAGS 512 - #define MAX_DEMOS 8 #define MAX_DEMONAME 16 @@ -531,7 +527,7 @@ typedef struct float latency; // rolling average qboolean allow_anyparticles; - qboolean allow_skyboxes; + qboolean allow_skyboxes; //skyboxes/domes do not need to be depth-masked when set. FIXME: we treat this as an optimisation hint, but some hl/q2/q3 maps require strict do-not-mask rules to look right. qboolean allow_watervis; //fixme: not checked any more float allow_fbskins; //fraction of allowance qboolean allow_cheats; @@ -865,6 +861,7 @@ typedef struct float maxpitch; qboolean paused; // send over by server + qboolean implicitpause; //for csqc. only a hint, respected only in singleplayer. enum { @@ -1528,7 +1525,6 @@ void Cam_Lock(playerview_t *pv, int playernum); //attempt to lock on to the give void Cam_NowLocked(playerview_t *pv); //player was located, track them now void Cam_SelfTrack(playerview_t *pv); void Cam_Track(playerview_t *pv, usercmd_t *cmd); -void Cam_TrackCrosshairedPlayer(playerview_t *pv); void Cam_SetModAutoTrack(int userid); void Cam_FinishMove(playerview_t *pv, usercmd_t *cmd); void Cam_Reset(void); diff --git a/engine/client/clq2_ents.c b/engine/client/clq2_ents.c index 6519962a..eea86fb4 100644 --- a/engine/client/clq2_ents.c +++ b/engine/client/clq2_ents.c @@ -2458,7 +2458,7 @@ void CLQ2_CalcViewValues (int seat) for (i=0 ; i<3 ; i++) { pv->simorg[i] = pv->predicted_origin[i] + ops->viewoffset[i] - + cl.lerpfrac * (ps->viewoffset[i] - ops->viewoffset[i]) + + lerp * (ps->viewoffset[i] - ops->viewoffset[i]) - backlerp * pv->prediction_error[i]; } diff --git a/engine/client/console.c b/engine/client/console.c index c102da04..a7279a79 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1887,8 +1887,8 @@ static int Con_DrawProgress(int left, int right, int y) { conchar_t dlbar[1024], *chr; unsigned char progresspercenttext[128]; - char *progresstext = NULL; - char *txt; + const char *progresstext = NULL; + const char *txt; int x, tw; int i; int barwidth, barleft; @@ -1937,14 +1937,9 @@ static int Con_DrawProgress(int left, int right, int y) } } #ifdef RUNTIMELIGHTING - else if (lightmodel) + else if ((progresstext=RelightGetProgress(&progresspercent))) { - if (relitsurface < lightmodel->numsurfaces) - { - progresstext = "light"; - progresspercent = (relitsurface*100.0f) / lightmodel->numsurfaces; - sprintf(progresspercenttext, " %02d%%", (int)progresspercent); - } + sprintf(progresspercenttext, " %02d%%", (int)progresspercent); } #endif diff --git a/engine/client/image.c b/engine/client/image.c index 12645ac7..145f4d5d 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -7320,10 +7320,12 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) switch(mips->encoding) { + case TF_TRANS8: + case TF_H2_TRANS8_0: case PTI_P8: if (sh_config.can_mipcap) return; //if we can cap mips, do that. it'll save lots of expensive lookups and uglyness. - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7346,7 +7348,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) case PTI_R8_SNORM: case PTI_L8: case PTI_L8_SRGB: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7369,7 +7371,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) case PTI_RG8_SNORM: case PTI_L8A8: case PTI_L8A8_SRGB: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7389,7 +7391,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) } return; case PTI_RGBA32F: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7409,7 +7411,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) } break; case PTI_RGBA16F: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7429,7 +7431,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) } break; case PTI_RGBA16: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7452,7 +7454,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) case PTI_BGR8_SRGB: case PTI_RGB8: case PTI_BGR8: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7479,7 +7481,7 @@ void Image_GenerateMips(struct pendingtextureinfo *mips, unsigned int flags) case PTI_RGBX8: case PTI_BGRA8: case PTI_BGRX8: - for (mip = mips->mipcount; mip < 32; mip++) + for (mip = mips->mipcount; mip < countof(mips->mip); mip++) { mips->mip[mip].width = mips->mip[mip-1].width >> 1; mips->mip[mip].height = mips->mip[mip-1].height >> 1; @@ -7963,15 +7965,16 @@ static void Image_Tr_NoTransform(struct pendingtextureinfo *mips, int dummy) { } -static void Image_Tr_PalettedtoRGBX8(struct pendingtextureinfo *mips, int dummy) +static void Image_Tr_PalettedtoRGBX8(struct pendingtextureinfo *mips, int alphapix) { unsigned int mip; for (mip = 0; mip < mips->mipcount; mip++) { qbyte *in = mips->mip[mip].data; unsigned int p = mips->mip[mip].width*mips->mip[mip].height*mips->mip[mip].depth; - qbyte *out = BZ_Malloc(sizeof(*out)*4*p); - void *newdata = out; + qbyte *out; + size_t datasize = sizeof(*out)*4*p; + void *newdata = out = BZ_Malloc(datasize); while(p-->0) { @@ -7979,13 +7982,13 @@ static void Image_Tr_PalettedtoRGBX8(struct pendingtextureinfo *mips, int dummy) *out++ = host_basepal[l*3+0]; *out++ = host_basepal[l*3+1]; *out++ = host_basepal[l*3+2]; - *out++ = (l==255)?255:0; + *out++ = (l==alphapix)?0:255; } if (mips->mip[mip].needfree) BZ_Free(mips->mip[mip].data); mips->mip[mip].needfree = true; mips->mip[mip].data = newdata; - mips->mip[mip].datasize = sizeof(*out)*4*p; + mips->mip[mip].datasize = datasize; } } @@ -10711,7 +10714,24 @@ const char *Image_FormatName(uploadfmt_t fmt) #ifdef FTE_TARGET_WEB case PTI_WHOLEFILE: return "Whole File"; #endif - case PTI_EMULATED: + case TF_INVALID: return "INVALID"; + case TF_BGR24_FLIP: return "BGR24_FLIP"; + case TF_MIP4_P8: return "MIP4_P8"; + case TF_MIP4_SOLID8: return "MIP4_SOLID8"; + case TF_MIP4_8PAL24: return "MIP4_8PAL24"; + case TF_MIP4_8PAL24_T255: return "MIP4_8PAL24_T255"; + case TF_SOLID8: return "SOLID8"; + case TF_TRANS8: return "TRANS8_255"; + case TF_TRANS8_FULLBRIGHT: return "TRANS8_FULLBRIGHT"; + case TF_HEIGHT8: return "HEIGHT8"; + case TF_HEIGHT8PAL: return "HEIGHT8PAL"; + case TF_H2_T7G1: return "H2_T7G1"; + case TF_H2_TRANS8_0: return "TRANS8_0"; + case TF_H2_T4A4: return "H2_T4A4"; + case TF_8PAL24: return "8PAL24"; + case TF_8PAL32: return "8PAL32"; + case PTI_LLLX8: return "LLLX8"; + case PTI_LLLA8: return "LLLA8"; case PTI_MAX: break; } @@ -11223,7 +11243,9 @@ static struct {PTI_RG8, PTI_RGBX8, Image_Tr_RG8ToRGXX8}, {PTI_RGBX8, PTI_P8, Image_Tr_RGBX8toPaletted}, - {PTI_P8, PTI_RGBX8, Image_Tr_PalettedtoRGBX8}, + {PTI_P8, PTI_RGBX8, Image_Tr_PalettedtoRGBX8, -1}, + {TF_H2_TRANS8_0,PTI_RGBA8, Image_Tr_PalettedtoRGBX8, 0}, + {TF_TRANS8, PTI_RGBA8, Image_Tr_PalettedtoRGBX8, 255}, }; void Image_ChangeFormat(struct pendingtextureinfo *mips, qboolean *allowedformats, uploadfmt_t origfmt, const char *imagename) { diff --git a/engine/client/keys.c b/engine/client/keys.c index 8af5e3f7..c3296f69 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -1209,7 +1209,7 @@ void Key_ConsoleRelease(console_t *con, int key, unsigned int unicode) if ((key == K_MOUSE1 && con->buttonsdown == CB_SCROLL) || (key == K_MOUSE2 && con->buttonsdown == CB_SCROLL_R)) { con->buttonsdown = CB_NONE; - if (abs(con->mousedown[0] - con->mousecursor[0]) < 5 && abs(con->mousedown[1] - con->mousecursor[1]) < 5) + if (fabs(con->mousedown[0] - con->mousecursor[0]) < 5 && fabs(con->mousedown[1] - con->mousecursor[1]) < 5) { buffer = Con_CopyConsole(con, false, false, false); Con_Footerf(con, false, ""); @@ -2652,7 +2652,7 @@ void Key_Bind_f (void) if (bindcmdlevel[b][modifier] != level) { if (Cmd_ExecLevel > RESTRICT_SERVER) - Q_snprintfz(leveldesc, sizeof(leveldesc), ", for seat %i", Cmd_ExecLevel - RESTRICT_SERVER-1); + Q_snprintfz(leveldesc, sizeof(leveldesc), ", for seat %i", Cmd_ExecLevel - RESTRICT_SERVER); else if (Cmd_ExecLevel == RESTRICT_SERVER) Q_snprintfz(leveldesc, sizeof(leveldesc), ", bound by server"); else if (bindcmdlevel[b][modifier]>=RESTRICT_INSECURE) diff --git a/engine/client/m_master.c b/engine/client/m_master.c index e8829e2a..991526b7 100644 --- a/engine/client/m_master.c +++ b/engine/client/m_master.c @@ -281,7 +281,7 @@ static void SL_ServerDraw (int x, int y, menucustom_t *ths, emenu_t *menu) } else if (thisone == info->scrollpos + (int)(mousecursor_y-info->servers_top)/8 && mousecursor_x < x) R2D_ImageColours(SRGBA((sin(realtime*4.4)*0.25)+0.5, (sin(realtime*4.4)*0.25)+0.5, 0.08, sb_alpha.value)); - else if (selectedserver.inuse && NET_CompareAdr(&si->adr, &selectedserver.adr)) + else if (selectedserver.inuse && NET_CompareAdr(&si->adr, &selectedserver.adr) && !strcmp(si->brokerid, selectedserver.brokerid)) R2D_ImageColours(SRGBA(((sin(realtime*4.4)*0.25)+0.5) * 0.5, ((sin(realtime*4.4)*0.25)+0.5)*0.5, 0.08*0.5, sb_alpha.value)); else { @@ -299,8 +299,8 @@ static void SL_ServerDraw (int x, int y, menucustom_t *ths, emenu_t *menu) if (sb_showplayers.value) {Draw_FunStringWidth((x-5*8), y, va("%2i/%2i", si->numhumans, si->maxplayers), 5*8, false, false); x-=6*8;} if (sb_showmap.value) {Draw_FunStringWidth((x-8*8), y, si->map, 8*8, false, false); x-=9*8;} if (sb_showgamedir.value) {Draw_FunStringWidth((x-8*8), y, si->gamedir, 8*8, false, false); x-=9*8;} - if (sb_showping.value) {Draw_FunStringWidth((x-3*8), y, va("%i", si->ping), 3*8, false, false); x-=4*8;} - if (sb_showaddress.value) {Draw_FunStringWidth((x-21*8), y, NET_AdrToString(adr, sizeof(adr), &si->adr), 21*8, false, false); x-=22*8;} + if (sb_showping.value) {Draw_FunStringWidth((x-3*8), y, *si->brokerid?"---":va("%i", si->ping), 3*8, false, false); x-=4*8;} + if (sb_showaddress.value) {Draw_FunStringWidth((x-21*8), y, Master_ServerToString(adr, sizeof(adr), si), 21*8, false, false); x-=22*8;} Draw_FunStringWidth(0, y, si->name, x, false, false); } } @@ -431,7 +431,7 @@ static void SL_PostDraw (emenu_t *menu) if (serverpreview) { - serverinfo_t *server = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr):NULL; + serverinfo_t *server = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr, selectedserver.brokerid):NULL; int h = 0; int w = 240; if (server && selectedserver.refreshtime < realtime) @@ -520,7 +520,7 @@ static void SL_PostDraw (emenu_t *menu) y += 8; Draw_FunStringWidth (x, y, Info_ValueForKey(server->moreinfo->info, "status"), w, 2, false); y += 8; - Draw_FunStringWidth (x, y, NET_AdrToString(buf, sizeof(buf), &server->adr), w, 2, false); + Draw_FunStringWidth (x, y, Master_ServerToString(buf, sizeof(buf), server), w, 2, false); y += 8; Draw_FunStringWidth (x, y, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f", w, 2, false); @@ -532,7 +532,7 @@ static void SL_PostDraw (emenu_t *menu) for (prox = server; prox; prox = prox->prevpeer) { Draw_FunStringWidth (x, y, va("%i", prox->cost), 32-8, true, false); - Draw_FunStringWidth (x + 32, y, NET_AdrToString(buf, sizeof(buf), &prox->adr), w/2 - 8 - 32, true, false); + Draw_FunStringWidth (x + 32, y, Master_ServerToString(buf, sizeof(buf), prox), w/2 - 8 - 32, true, false); Draw_FunStringWidth (x + w/2, y, prox->name, w/2, false, false); y += 8; } @@ -641,7 +641,7 @@ static void SL_PostDraw (emenu_t *menu) Draw_FunStringWidth(vid.width/2 - 100, vid.height/2 + 0, "Please wait", 200, 2, false); } - if ((server->special & SS_PROTOCOLMASK) == SS_QUAKEWORLD) + if (server && (server->special & SS_PROTOCOLMASK) == SS_QUAKEWORLD) { int lx = vid.width/2 - w/2; int y = vid.height/2 - h/2 - 4 + h; @@ -732,7 +732,7 @@ static qboolean SL_Key (int key, emenu_t *menu) if (serverpreview) { char buf[64]; - serverinfo_t *server = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr):NULL; + serverinfo_t *server = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr, selectedserver.brokerid):NULL; extern qboolean keydown[]; qboolean ctrldown = keydown[K_LCTRL] || keydown[K_RCTRL]; @@ -772,7 +772,7 @@ static qboolean SL_Key (int key, emenu_t *menu) if (--serverpreview < 1) serverpreview = 4; - if (serverpreview == 4) + if (serverpreview == 4 && server) Master_FindRoute(server->adr); return true; } @@ -781,13 +781,14 @@ static qboolean SL_Key (int key, emenu_t *menu) if (++serverpreview > 4) serverpreview = 1; - if (serverpreview == 4) + if (serverpreview == 4 && server) Master_FindRoute(server->adr); return true; } else if (key == 'b' && serverpreview != 4) { - Master_FindRoute(server->adr); + if (server) + Master_FindRoute(server->adr); serverpreview = 4; } else if (key == 'b' || key == 'o' || key == 'j' || key == K_ENTER || key == K_KP_ENTER || key == K_GP_START) //join @@ -810,13 +811,13 @@ dojoin: Cbuf_AddText("connect ", RESTRICT_LOCAL); //output the server's address - Cbuf_AddText(va("%s", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("%s", Master_ServerToString(buf, sizeof(buf), server)), RESTRICT_LOCAL); if (serverpreview == 4 || key == 'b') { //and postfix it with routing info if we're going for a proxied route. if (serverpreview != 4) Master_FindRoute(server->adr); for (server = server->prevpeer; server; server = server->prevpeer) - Cbuf_AddText(va("@%s", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("@%s", Master_ServerToString(buf, sizeof(buf), server)), RESTRICT_LOCAL); } Cbuf_AddText("\n", RESTRICT_LOCAL); @@ -826,7 +827,7 @@ dojoin: } else if (server && key == 'c' && ctrldown) //copy to clip { - Sys_SaveClipboard(CBT_CLIPBOARD, NET_AdrToString(buf, sizeof(buf), &server->adr)); + Sys_SaveClipboard(CBT_CLIPBOARD, Master_ServerToString(buf, sizeof(buf), server)); return true; } else if (server && (key == 'v' || key == 'c')) //say to current server @@ -840,11 +841,11 @@ dojoin: while((s = strchr(safename, '\n'))) *s = ' '; if (key == 'c') - Sys_SaveClipboard(CBT_CLIPBOARD, va("%s - %s\n", server->name, NET_AdrToString(buf, sizeof(buf), &server->adr))); + Sys_SaveClipboard(CBT_CLIPBOARD, va("%s - %s\n", server->name, Master_ServerToString(buf, sizeof(buf), server))); else if (ctrldown) - Cbuf_AddText(va("say_team %s - %s\n", server->name, NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("say_team %s - %s\n", server->name, Master_ServerToString(buf, sizeof(buf), server)), RESTRICT_LOCAL); else - Cbuf_AddText(va("say %s - %s\n", server->name, NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("say %s - %s\n", server->name, Master_ServerToString(buf, sizeof(buf), server)), RESTRICT_LOCAL); return true; } //eat (nearly) all keys @@ -1303,9 +1304,9 @@ static void M_QuickConnect_PreDraw(emenu_t *menu) Con_Printf("Quick connect found %s (gamedir %s, players %i/%i/%i, ping %ims)\n", best->name, best->gamedir, best->numhumans, best->players, best->maxplayers, best->ping); if ((best->special & SS_PROTOCOLMASK) == SS_NETQUAKE) - Cbuf_AddText(va("nqconnect %s\n", NET_AdrToString(adr, sizeof(adr), &best->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("nqconnect %s\n", Master_ServerToString(adr, sizeof(adr), best)), RESTRICT_LOCAL); else - Cbuf_AddText(va("join %s\n", NET_AdrToString(adr, sizeof(adr), &best->adr)), RESTRICT_LOCAL); + Cbuf_AddText(va("join %s\n", Master_ServerToString(adr, sizeof(adr), best)), RESTRICT_LOCAL); M_ToggleMenu_f(); return; diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index 36dd75fb..c9a76927 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -1854,7 +1854,7 @@ static cin_t *Media_WinAvi_TryLoad(char *name) if(qAVIStreamReadFormat(cin->avi.pavisound, qAVIStreamStart(cin->avi.pavisound), pChunk, &lSize)) { // error - Con_Printf("Failiure reading sound info\n"); + Con_Printf("Failure reading sound info\n"); } cin->avi.pWaveFormat = (LPWAVEFORMATEX)pChunk; } diff --git a/engine/client/m_multi.c b/engine/client/m_multi.c index 65a6ff3b..078a0c2c 100644 --- a/engine/client/m_multi.c +++ b/engine/client/m_multi.c @@ -533,6 +533,7 @@ void M_Menu_GameOptions_f (void) typedef struct { menuedit_t *hostnameedit; + menucombo_t *publicgame; menucombo_t *deathmatch; menucombo_t *numplayers; menucombo_t *teamplay; @@ -582,6 +583,7 @@ qboolean MultiBeginGame (union menuoption_s *option,struct emenu_s *menu, int ke Cbuf_AddText(va("skill %i\n", info->skill->selectedoption), RESTRICT_LOCAL); Cbuf_AddText(va("timelimit %i\n", info->timelimit->selectedoption*5), RESTRICT_LOCAL); Cbuf_AddText(va("fraglimit %i\n", info->fraglimit->selectedoption*10), RESTRICT_LOCAL); + Cbuf_AddText(va("sv_public %i\n", info->publicgame->selectedoption-1), RESTRICT_LOCAL); Cbuf_AddText(va("map \"%s\"\n", info->mapnameedit->text), RESTRICT_LOCAL); if (info->rundedicated->value) @@ -647,6 +649,14 @@ void M_Menu_GameOptions_f (void) "100 frags", NULL }; + static const char *publicoptions[] = { + "Disabled", + "Private/LAN", + "Public (Manual)", + "Public (Holepunch)", + NULL + }; + extern cvar_t sv_public; newmultimenu_t *info; emenu_t *menu; int y = 40; @@ -677,7 +687,10 @@ void M_Menu_GameOptions_f (void) menu->selecteditem = (menuoption_t*) MC_AddCommand (menu, 64, 160, y, "Start game", MultiBeginGame);y+=16; - info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=16; + y+=4; + info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=16; + info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8; + y+=4; for (players = 0; players < sizeof(numplayeroptions)/ sizeof(numplayeroptions[0]); players++) { diff --git a/engine/client/m_options.c b/engine/client/m_options.c index b1e6cab4..c3ba7b98 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -911,6 +911,7 @@ const char *presetexec[] = "seta cl_nolerp 1;" "seta r_lerpmuzzlehack 0;" "seta v_gunkick 0;" + "seta r_shadows 0;" "seta v_viewmodel_quake 0;" "seta cl_rollangle 0;" "seta cl_bob 0;" diff --git a/engine/client/merged.h b/engine/client/merged.h index da68fd4c..fe9af7a4 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -188,9 +188,6 @@ extern void Mod_TouchModel (const char *name); extern const char *Mod_FixName (const char *modname, const char *worldname); //remaps the name appropriately const char *Mod_ParseWorldspawnKey (struct model_s *mod, const char *key, char *buffer, size_t sizeofbuffer); -extern long relitsurface; -extern struct model_s *lightmodel; -extern void Mod_Think (void); extern qboolean Mod_GetModelEvent (struct model_s *model, int animation, int eventidx, float *timestamp, int *eventcode, char **eventdata); extern int Mod_SkinNumForName (struct model_s *model, int surfaceidx, const char *name); extern int Mod_FrameNumForName (struct model_s *model, int surfaceidx, const char *name); @@ -295,21 +292,21 @@ struct pendingtextureinfo { enum { - //formats are all w*h*(d||l) - PTI_2D, //w*h*1 - PTI_3D, //w*h*d - only format which actually changes mip depths - PTI_CUBE, //w*h*6 - PTI_2D_ARRAY, //w*h*layers - PTI_CUBE_ARRAY, //w*h*(layers*6) + //formats are all w*h*d (where depth has limitations according to type) + PTI_2D, //w*h*1 - depth MUST be 1 + PTI_3D, //w*h*d - we can't generate 3d mips + PTI_CUBE, //w*h*6 - depth MUST be 6 (faces must be tightly packed) + PTI_2D_ARRAY, //w*h*layers - depth is =layers + PTI_CUBE_ARRAY, //w*h*(layers*6) - depth is =(layers*6). } type; - uploadfmt_t encoding; //0 - void *extrafree; + uploadfmt_t encoding; //PTI_* formats + void *extrafree; //avoids some memcpys int mipcount; struct { void *data; - size_t datasize; + size_t datasize; //ceil(width/blockwidth)*ceil(height/blockheight)*ceil(depth/blockdepth)*blocksize - except that blockdepth is always considered 1 for now. int width; int height; int depth; diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 0f9cf45b..0db00770 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -102,6 +102,10 @@ typedef struct { netadr_t adr[MAX_MASTER_ADDRESSES]; } net_masterlist_t; static net_masterlist_t net_masterlist[] = { +#if 0 //for debugging + {MP_DPMASTER, CVARFC("net_masterextra1", "localhost:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Eukara +#else + #ifndef QUAKETC //user-specified master lists. {MP_QUAKEWORLD, CVARC("net_qwmaster1", "", Net_Masterlist_Callback)}, @@ -115,7 +119,7 @@ static net_masterlist_t net_masterlist[] = { #endif //dpmaster is the generic non-quake-specific master protocol that we use for custom stand-alone mods. - {MP_DPMASTER, CVARAFC("net_master1", "", "sv_master1", 0, Net_Masterlist_Callback)}, + {MP_DPMASTER, CVARAFC("net_master1", "localhost", "sv_master1", 0, Net_Masterlist_Callback)}, {MP_DPMASTER, CVARAFC("net_master2", "", "sv_master2", 0, Net_Masterlist_Callback)}, {MP_DPMASTER, CVARAFC("net_master3", "", "sv_master3", 0, Net_Masterlist_Callback)}, {MP_DPMASTER, CVARAFC("net_master4", "", "sv_master4", 0, Net_Masterlist_Callback)}, @@ -163,9 +167,10 @@ static net_masterlist_t net_masterlist[] = { // {MP_QUAKEWORLD, CVARFC("net_qwmasterextraHistoric", "master.teamdamage.com:27000", CVAR_NOSAVE, Net_Masterlist_Callback), "master.teamdamage.com"}, //Total conversions will need to define their own in defaults.cfg or whatever. - {MP_DPMASTER, CVARFC("net_masterextra1", "ghdigital.com:27950 207.55.114.154:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //207.55.114.154 (was 69.59.212.88 (admin: LordHavoc) - {MP_DPMASTER, CVARFC("net_masterextra2", "dpmaster.deathmask.net:27950 107.161.23.68:27950 [2604:180::4ac:98c1]:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //107.161.23.68 (admin: Willis) - {MP_DPMASTER, CVARFC("net_masterextra3", "dpmaster.tchr.no:27950 92.62.40.73:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //92.62.40.73 (admin: tChr) + {MP_DPMASTER, CVARFC("net_masterextra1", "frag-net.com:27950 198.58.111.37:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Eukara +// {MP_DPMASTER, CVARFC("net_masterextra1", ""/*"ghdigital.com:27950 207.55.114.154:27950"*/, CVAR_NOSAVE, Net_Masterlist_Callback)}, //(was 69.59.212.88) admin: LordHavoc + {MP_DPMASTER, CVARFC("net_masterextra2", "dpmaster.deathmask.net:27950 107.161.23.68:27950 [2604:180::4ac:98c1]:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: Willis + {MP_DPMASTER, CVARFC("net_masterextra3", "dpmaster.tchr.no:27950 92.62.40.73:27950", CVAR_NOSAVE, Net_Masterlist_Callback)}, //admin: tChr #else {MP_DPMASTER, CVARFC("net_masterextra1", "", CVAR_NOSAVE, Net_Masterlist_Callback)}, {MP_DPMASTER, CVARFC("net_masterextra2", "", CVAR_NOSAVE, Net_Masterlist_Callback)}, @@ -195,6 +200,7 @@ static net_masterlist_t net_masterlist[] = { {MP_QUAKE3, CVARFC("net_q3masterextra6", "dpmaster.deathmask.net:27950", CVAR_NOSAVE, Net_Masterlist_Callback), "US: DeathMask.net"}, {MP_QUAKE3, CVARFC("net_q3masterextra8", "master3.idsoftware.com:27950", CVAR_NOSAVE, Net_Masterlist_Callback), "US: id Software Quake III Master"}, #endif +#endif #endif {MP_UNSPECIFIED, CVAR(NULL, NULL)} @@ -477,7 +483,7 @@ void SV_Master_Worker_Resolve(void *ctx, void *data, size_t a, size_t b) { str = COM_ParseOut(str, token, sizeof(token)); if (*token) - found += NET_StringToAdr2(token, 0, &work->na[found], MAX_MASTER_ADDRESSES-found); + found += NET_StringToAdr2(token, 0, &work->na[found], MAX_MASTER_ADDRESSES-found, NULL); if (first && found) break; //if we found one by name, don't try any fallback ip addresses. first = false; @@ -501,6 +507,10 @@ void SV_Master_Heartbeat (void) if (sv_public.ival<=0 || SSV_IsSubServer()) return; +#ifdef SUPPORT_ICE + if (sv_public.ival == 2) + return; //using our broker service. we're configured as behind a nat so these addresses won't work anyway. +#endif if (realtime-interval - svs.last_heartbeat < interval) return; // not time to send yet @@ -776,7 +786,7 @@ void Master_SetupSockets(void) pollsocketsList[i] = INVALID_SOCKET; } -void Master_HideServer(serverinfo_t *server) +static void Master_HideServer(serverinfo_t *server) { int i, j; for (i = 0; i < numvisibleservers;) @@ -793,7 +803,7 @@ void Master_HideServer(serverinfo_t *server) server->status &= ~SRVSTATUS_DISPLAYED; } -void Master_InsertAt(serverinfo_t *server, int pos) +static void Master_InsertAt(serverinfo_t *server, int pos) { int i; if (numvisibleservers >= maxvisibleservers) @@ -811,7 +821,7 @@ void Master_InsertAt(serverinfo_t *server, int pos) server->status |= SRVSTATUS_DISPLAYED; } -qboolean Master_CompareInteger(int a, int b, slist_test_t rule) +static qboolean Master_CompareInteger(int a, int b, slist_test_t rule) { switch(rule) { @@ -836,7 +846,7 @@ qboolean Master_CompareInteger(int a, int b, slist_test_t rule) } return false; } -qboolean Master_CompareString(const char *a, const char *b, slist_test_t rule) +static qboolean Master_CompareString(const char *a, const char *b, slist_test_t rule) { switch(rule) { @@ -864,7 +874,22 @@ qboolean Master_CompareString(const char *a, const char *b, slist_test_t rule) return false; } -qboolean Master_ServerIsGreater(serverinfo_t *a, serverinfo_t *b) +char *Master_ServerToString (char *s, int len, serverinfo_t *a) +{ + if (*a->brokerid) + { + if (a->adr.type==NA_INVALID) + *s = 0; //default broker... skip it for brevity. + else + NET_AdrToString(s, len, &a->adr); + Q_strncatz(s, "/", len); + Q_strncatz(s, a->brokerid, len); + return s; + } + return NET_AdrToString(s, len, &a->adr); +} + +static qboolean Master_ServerIsGreater(serverinfo_t *a, serverinfo_t *b) { if (sort_categories) if (a->qccategory != b->qccategory) @@ -1287,7 +1312,7 @@ char *Master_ReadKeyString(serverinfo_t *server, hostcachekey_t keynum) case SLKEY_NAME: return server->name; case SLKEY_ADDRESS: - return NET_AdrToString(adr, sizeof(adr), &server->adr); + return Master_ServerToString(adr, sizeof(adr), server); case SLKEY_GAMEDIR: return server->gamedir; @@ -1397,7 +1422,7 @@ serverinfo_t *Master_FindRoute(netadr_t target) { serverinfo_t *info, *targ, *prox; extern cvar_t cl_proxyaddr; - targ = Master_InfoForServer(&target); + targ = Master_InfoForServer(&target, NULL); if (!targ) //you wot? return NULL; @@ -1412,7 +1437,7 @@ serverinfo_t *Master_FindRoute(netadr_t target) *chain = 0; if (NET_StringToAdr(cl_proxyaddr.string, 0, &pa)) - prox = Master_InfoForServer(&pa); + prox = Master_InfoForServer(&pa, NULL); else prox = NULL; if (chain) @@ -1479,9 +1504,9 @@ int Master_FindBestRoute(char *server, char *out, size_t outsize, int *directcos *directcost = route->ping; *chainedcost = route->cost; - Q_strncatz(out, NET_AdrToString(buf, sizeof(buf), &route->adr), outsize); + Q_strncatz(out, Master_ServerToString(buf, sizeof(buf), route), outsize); for (ret = 0, route = route->prevpeer; route; route = route->prevpeer, ret++) - Q_strncatz(out, va("@%s", NET_AdrToString(buf, sizeof(buf), &route->adr)), outsize); + Q_strncatz(out, va("@%s", Master_ServerToString(buf, sizeof(buf), route)), outsize); return ret; } @@ -1581,7 +1606,7 @@ void CLMaster_AddMaster_Worker_Resolve(void *ctx, void *data, size_t a, size_t b { str = COM_ParseOut(str, token, sizeof(token)); if (*token) - found += NET_StringToAdr2(token, 0, &adrs[found], MAX_MASTER_ADDRESSES-found); + found += NET_StringToAdr2(token, 0, &adrs[found], MAX_MASTER_ADDRESSES-found, NULL); //we don't do this logic because windows doesn't look up ipv6 names if it only has teredo //this means an ipv4+teredo client cannot see ivp6-only servers. and that sucks. // if (first && found) @@ -1787,8 +1812,6 @@ qboolean Master_LoadMasterList (char *filename, qboolean withcomment, int defaul servertype = MT_MASTERUDP; else if (!strcmp(sep, "masterhttp")) servertype = MT_MASTERHTTP; - else if (!strcmp(sep, "masterhttpjson")) - servertype = MT_MASTERHTTPJSON; else if (!strcmp(sep, "bcast")) servertype = MT_BCAST; @@ -1813,11 +1836,6 @@ qboolean Master_LoadMasterList (char *filename, qboolean withcomment, int defaul //legacy compat #ifdef NQPROT - else if (!strcmp(com_token, "httpjson")) - { - servertype = MT_MASTERHTTPJSON; - protocoltype = MP_NETQUAKE; - } else if (!strcmp(com_token, "httpnq")) { servertype = MT_MASTERHTTP; @@ -1861,7 +1879,6 @@ qboolean Master_LoadMasterList (char *filename, qboolean withcomment, int defaul switch (servertype) { - case MT_MASTERHTTPJSON: case MT_MASTERHTTP: Master_AddMasterHTTP(entry, servertype, protocoltype, name); break; @@ -2150,9 +2167,9 @@ int Master_CheckPollSockets(void) if (ccrep == CCREP_PLAYER_INFO) { - serverinfo_t *selserver = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr):NULL; - serverinfo_t *info = Master_InfoForServer(&net_from); - info = Master_InfoForServer(&net_from); + serverinfo_t *selserver = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr, selectedserver.brokerid):NULL; + serverinfo_t *info = Master_InfoForServer(&net_from, NULL); + info = Master_InfoForServer(&net_from, NULL); if (selserver == info) { char playeraddrbuf[256]; @@ -2195,10 +2212,10 @@ int Master_CheckPollSockets(void) } else if (ccrep == CCREP_RULE_INFO) { - serverinfo_t *selserver = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr):NULL; - serverinfo_t *info = Master_InfoForServer(&net_from); + serverinfo_t *selserver = selectedserver.inuse?Master_InfoForServer(&selectedserver.adr, selectedserver.brokerid):NULL; + serverinfo_t *info = Master_InfoForServer(&net_from, NULL); char *s, *old; - info = Master_InfoForServer(&net_from); + info = Master_InfoForServer(&net_from, NULL); if (selserver == info) { s = MSG_ReadString(); @@ -2267,9 +2284,9 @@ void SListOptionChanged(serverinfo_t *newserver) { for (oldserver = firstserver; oldserver; oldserver=oldserver->next) { - if (NET_CompareAdr(&selectedserver.adr, &oldserver->adr))//*(int*)selectedserver.ipaddress == *(int*)server->ipaddress && selectedserver.port == server->port) + if (NET_CompareAdr(&selectedserver.adr, &oldserver->adr) && !strcmp(selectedserver.brokerid, oldserver->brokerid)) { - if (oldserver->moreinfo) + if (oldserver->moreinfo && oldserver->ping!=PING_UNKNOWN) { Z_Free(oldserver->moreinfo); oldserver->moreinfo = NULL; @@ -2283,6 +2300,7 @@ void SListOptionChanged(serverinfo_t *newserver) return; selectedserver.adr = newserver->adr; + strcpy(selectedserver.brokerid, newserver->brokerid); if (newserver->moreinfo) //we cached it. { @@ -2327,22 +2345,78 @@ void SListOptionChanged(serverinfo_t *newserver) } #ifdef WEBCLIENT -void MasterInfo_ProcessHTTP(struct dl_download *dl) +static void MasterInfo_ProcessHTTPInfo(serverinfo_t *srv, const char *info) +{ + char adrbuf[MAX_ADR_SIZE]; + if (info && (!(srv->status & SRVSTATUS_ALIVE) || srv->ping == PING_UNKNOWN)) + { + if (srv->adr.prot == NP_RTC_TCP || srv->adr.prot == NP_RTC_TCP) + { + srv->sends = 0; //no point pinging it, it won't work. + srv->ping = PING_UNKNOWN; + srv->status |= SRVSTATUS_ALIVE; //or at least wouldn't have been reported this time around. + } + else + srv->sends = 1; //no point pinging it, it won't work. + Q_strncpyz(srv->name, Info_ValueForKey(info, "hostname"), sizeof(srv->name)); + Q_strncpyz(srv->gamedir, Info_ValueForKey(info, "modname"), sizeof(srv->gamedir)); + Q_strncpyz(srv->map, Info_ValueForKey(info, "mapname"), sizeof(srv->map)); + srv->players = atoi(Info_ValueForKey(info, "clients")); + srv->maxplayers = atoi(Info_ValueForKey(info, "maxclients")); + + srv->numbots = 0; + srv->numhumans = srv->players - srv->numbots; + srv->freeslots = srv->maxplayers - srv->players; + + + if (!srv->moreinfo)// && ((slist_cacheinfo.value == 2 || (NET_CompareAdr(&srv->adr, &selectedserver.adr)&&!strcmp(srv->brokerid,selectedserver.brokerid))) || (srv->special & SS_KEEPINFO))) + srv->moreinfo = Z_Malloc(sizeof(serverdetailedinfo_t)); + if (srv->moreinfo) + Q_strncpyz(srv->moreinfo->info, info, sizeof(srv->moreinfo->info)); + } + if (!*srv->name) + Q_snprintfz(srv->name, sizeof(srv->name), "%s h", Master_ServerToString(adrbuf, sizeof(adrbuf), srv)); +} +static void MasterInfo_ProcessHTTP(struct dl_download *dl) { master_t *mast = dl->user_ctx; vfsfile_t *file = dl->file; - int protocoltype = mast->protocoltype; + int protocoltype; netadr_t adr; char *s; char *el; serverinfo_t *info; - char adrbuf[MAX_ADR_SIZE]; char linebuffer[2048]; - mast->dl = NULL; + char *brokerid; + char *infostring; + netadr_t brokeradr; + + if (mast) + { + brokeradr = mast->adr; + mast->dl = NULL; + protocoltype = mast->protocoltype; + } + else + { + NET_StringToAdr("/", PORT_ICEBROKER, &brokeradr); + protocoltype = MP_QUAKE3; + } if (!file) return; + brokeradr.type = NA_INVALID; //should be the default broker... + brokeradr.prot = NP_RTC_TLS; + for (info = firstserver; info; info = info->next) + { + if (NET_CompareAdr(&info->adr, &brokeradr)) + { + info->ping = PING_DEAD; + info->status &= ~SRVSTATUS_ALIVE; + } + } + while(VFS_GETS(file, linebuffer, sizeof(linebuffer))) { s = linebuffer; @@ -2356,18 +2430,41 @@ void MasterInfo_ProcessHTTP(struct dl_download *dl) if (*s == '#') //hash is a comment, apparently. continue; - if (!NET_StringToAdr(s, 80, &adr)) - continue; + for (infostring = s; *infostring && *infostring != ' '; ) + infostring++; + if (*infostring == ' ') + *infostring++ = 0; + else + infostring = NULL; + if (!strncmp(s, "ice:///", 7) || !strncmp(s, "ices:///", 8) || !strncmp(s, "rtc:///", 7) || !strncmp(s, "rtcs:///", 8)) + { + brokerid = s+7; + while (*brokerid == '/') + brokerid++; + adr = brokeradr; + if (!*brokerid) + continue; //invalid... + } + else + { + brokerid = ""; + if (!NET_StringToAdr(s, 80, &adr)) + continue; + } - if ((info = Master_InfoForServer(&adr))) //remove if the server already exists. + if ((info = Master_InfoForServer(&adr, brokerid))) //remove if the server already exists. { info->sends = 1; //reset. + MasterInfo_ProcessHTTPInfo(info, infostring); + + Master_ResortServer(info); } else { info = Z_Malloc(sizeof(serverinfo_t)); info->adr = adr; info->sends = 1; + Q_strncpyz(info->brokerid, brokerid, sizeof(info->brokerid)); info->special = 0; if (protocoltype == MP_QUAKEWORLD) @@ -2390,7 +2487,7 @@ void MasterInfo_ProcessHTTP(struct dl_download *dl) info->refreshtime = 0; info->ping = PING_DEAD; - snprintf(info->name, sizeof(info->name), "%s h", NET_AdrToString(adrbuf, sizeof(adrbuf), &info->adr)); + MasterInfo_ProcessHTTPInfo(info, infostring); info->next = firstserver; firstserver = info; @@ -2400,141 +2497,10 @@ void MasterInfo_ProcessHTTP(struct dl_download *dl) info->status |= SRVSTATUS_GLOBAL; } } - -char *jsonnode(int level, char *node) -{ - netadr_t adr = {NA_INVALID}; - char servername[256] = {0}; - char key[256]; - int flags = SS_NETQUAKE; //assumption - int port = 0; - int cp = 0, mp = 0; - if (*node != '{') - return node; - do - { - node++; - node = COM_ParseToken(node, ",:{}[]"); - if (*node != ':') - continue; - node++; - if (*node == '[') - { - do - { - node++; - node = jsonnode(level+1, node); - if (!node) - return NULL; - if (*node == ']') - { - break; - } - } while(*node == ','); - if (*node != ']') - return NULL; - node++; - } - else - { - Q_strncpyz(key, com_token, sizeof(key)); - node = COM_ParseToken(node, ",:{}[]"); - - if (level == 1) - { - if (!strcmp(key, "IPAddress")) - { - if (!NET_StringToAdr(com_token, 0, &adr)) - adr.type = NA_INVALID; - } - if (!strcmp(key, "Port")) - port = atoi(com_token); - if (!strcmp(key, "DNS")) - Q_strncpyz(servername, com_token, sizeof(servername)); - if (!strcmp(key, "CurrentPlayerCount")) - cp = atoi(com_token); - if (!strcmp(key, "MaxPlayers")) - mp = atoi(com_token); - if (!strcmp(key, "Game")) - { - flags &= ~SS_PROTOCOLMASK; - if (!strcmp(com_token, "NetQuake")) - flags |= SS_NETQUAKE; - if (!strcmp(com_token, "QuakeWorld")) - flags |= SS_QUAKEWORLD; - if (!strcmp(com_token, "Quake2")) - flags |= SS_QUAKE2; - if (!strcmp(com_token, "Quake3")) - flags |= SS_QUAKE3; - } - } - } - } while(*node == ','); - - if (*node == '}') - node++; - - if (adr.type != NA_INVALID) - { - serverinfo_t *info; - - if (port) - adr.port = htons(port); - - if ((info = Master_InfoForServer(&adr))) //remove if the server already exists. - { - if (!info->special) - info->special = flags; - info->sends = 1; //reset. - } - else - { - info = Z_Malloc(sizeof(serverinfo_t)); - info->ping = PING_DEAD; - info->adr = adr; - info->sends = 1; - info->special = flags; - info->refreshtime = 0; - info->players = cp; - info->maxplayers = mp; - - snprintf(info->name, sizeof(info->name), "%s j", *servername?servername:NET_AdrToString(servername, sizeof(servername), &info->adr)); - - info->next = firstserver; - firstserver = info; - - Master_ResortServer(info); - } - info->status |= SRVSTATUS_GLOBAL; - } - - return node; -} - -void MasterInfo_ProcessHTTPJSON(struct dl_download *dl) -{ - int len; - char *buf; - master_t *mast = dl->user_ctx; - mast->dl = NULL; - if (dl->file) - { - len = VFS_GETLEN(dl->file); - buf = malloc(len + 1); - VFS_READ(dl->file, buf, len); - buf[len] = 0; - jsonnode(0, buf); - free(buf); - } - else - { - Con_Printf("Unable to query master at \"%s\"\n", dl->url); - } -} #endif //don't try sending to servers we don't support -void MasterInfo_Request(master_t *mast) +static void MasterInfo_Request(master_t *mast) { if (!mast) return; @@ -2546,17 +2512,6 @@ void MasterInfo_Request(master_t *mast) switch(mast->mastertype) { #ifdef WEBCLIENT - case MT_MASTERHTTPJSON: - if (!mast->dl) - { - mast->dl = HTTP_CL_Get(mast->address, NULL, MasterInfo_ProcessHTTPJSON); - if (mast->dl) - { - mast->dl->user_ctx = mast; - mast->dl->isquery = true; - } - } - break; case MT_MASTERHTTP: if (!mast->dl) { @@ -2683,19 +2638,19 @@ void MasterInfo_WriteServers(void) switch(server->special & SS_PROTOCOLMASK) { case SS_QUAKE3: - VFS_PUTS(qws, va("%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), &server->adr), "favorite:q3", server->name)); + VFS_PUTS(qws, va("%s\t%s\t%s\n", Master_ServerToString(adr, sizeof(adr), server), "favorite:q3", server->name)); break; case SS_QUAKE2: - VFS_PUTS(qws, va("%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), &server->adr), "favorite:q2", server->name)); + VFS_PUTS(qws, va("%s\t%s\t%s\n", Master_ServerToString(adr, sizeof(adr), server), "favorite:q2", server->name)); break; case SS_NETQUAKE: - VFS_PUTS(qws, va("%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), &server->adr), "favorite:nq", server->name)); + VFS_PUTS(qws, va("%s\t%s\t%s\n", Master_ServerToString(adr, sizeof(adr), server), "favorite:nq", server->name)); break; // case SS_DARKPLACES: -// VFS_PUTS(qws, va("%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), &server->adr), "favorite:dp", server->name)); +// VFS_PUTS(qws, va("%s\t%s\t%s\n", Master_ServerToString(adr, sizeof(adr), server), "favorite:dp", server->name)); // break; case SS_QUAKEWORLD: - VFS_PUTS(qws, va("%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), &server->adr), "favorite:qw", server->name)); + VFS_PUTS(qws, va("%s\t%s\t%s\n", Master_ServerToString(adr, sizeof(adr), server), "favorite:qw", server->name)); break; } } @@ -2753,6 +2708,21 @@ void MasterInfo_Refresh(qboolean doreset) Master_AddMaster("255.255.255.255:"STRINGIFY(PORT_Q3SERVER), MT_BCAST, MP_QUAKE3, "Nearby Quake3 UDP servers."); #endif + if (!fs_manifest->rtcbroker || !*fs_manifest->rtcbroker) + ; //nope, sorry, not configured. + else + { + char *url; + COM_Parse(com_protocolname.string); + if (!strncmp(fs_manifest->rtcbroker, "tls://", 6)) + url = va("https://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); + else if (!strncmp(fs_manifest->rtcbroker, "tcp://", 6)) + url = va("http://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); + else + url = va("http://%s/raw/%s", fs_manifest->rtcbroker, com_token); + Master_AddMasterHTTP(url, MT_MASTERHTTP, MP_QUAKEWORLD, "Public Servers Potentially Behind A NAT."); + } + for (i = 0; net_masterlist[i].cv.name; i++) { Master_AddMaster(net_masterlist[i].cv.string, MT_MASTERUDP, net_masterlist[i].protocol, net_masterlist[i].comment); @@ -2772,6 +2742,8 @@ void Master_QueryServer(serverinfo_t *server) { char data[2048]; server->sends--; + if (*server->brokerid) + return; //don't even try. we have no direct route. server->refreshtime = Sys_DoubleTime(); switch(server->special & SS_PROTOCOLMASK) @@ -2972,13 +2944,15 @@ unsigned int Master_NumAlive(void) } //true if server is on a different master's list. -serverinfo_t *Master_InfoForServer (netadr_t *addr) +serverinfo_t *Master_InfoForServer (netadr_t *addr, const char *brokerid) { serverinfo_t *info; + if (!brokerid) + brokerid=""; for (info = firstserver; info; info = info->next) { - if (NET_CompareAdr(&info->adr, addr)) + if (!strcmp(info->brokerid, brokerid) && NET_CompareAdr(&info->adr, addr)) return info; } return NULL; @@ -3056,7 +3030,7 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor serverinfo_t *info; char adr[MAX_ADR_SIZE]; - info = Master_InfoForServer(&net_from); + info = Master_InfoForServer(&net_from, NULL); if (!info) //not found... { @@ -3069,7 +3043,7 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor info->adr = net_from; - snprintf(info->name, sizeof(info->name), "%s ?", NET_AdrToString(adr, sizeof(adr), &info->adr)); + Q_snprintfz(info->name, sizeof(info->name), "%s ?", Master_ServerToString(adr, sizeof(adr), info)); info->next = firstserver; firstserver = info; @@ -3114,7 +3088,7 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor memset(&pa, 0, sizeof(pa)); remaining /= 8; - NET_AdrToString(adr, sizeof(adr), &info->adr); + //Master_ServerToString(adr, sizeof(adr), info); Z_Free(info->peers); info->numpeers = 0; @@ -3135,7 +3109,7 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor if (NET_ClassifyAddress(&pa, NULL) >= ASCOPE_NET) { - peer->peer = Master_InfoForServer(&pa); + peer->peer = Master_InfoForServer(&pa, NULL); if (!peer->peer) { //generate some lame peer node that we can use. @@ -3146,7 +3120,7 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor peer->peer->refreshtime = 0; peer->peer->ping = PING_DEAD; peer->peer->next = firstserver; - snprintf(peer->peer->name, sizeof(peer->peer->name), "%s p", NET_AdrToString(adr, sizeof(adr), &pa)); + Q_snprintfz(peer->peer->name, sizeof(peer->peer->name), "%s p", Master_ServerToString(adr, sizeof(adr), peer->peer)); firstserver = peer->peer; } peer++; @@ -3443,9 +3417,9 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor } - if (!info->moreinfo && ((slist_cacheinfo.value == 2 || NET_CompareAdr(&info->adr, &selectedserver.adr)) || (info->special & SS_KEEPINFO))) + if (!info->moreinfo && ((slist_cacheinfo.value == 2 || (NET_CompareAdr(&info->adr, &selectedserver.adr)&&!strcmp(info->brokerid,selectedserver.brokerid))) || (info->special & SS_KEEPINFO))) info->moreinfo = Z_Malloc(sizeof(serverdetailedinfo_t)); - if (NET_CompareAdr(&info->adr, &selectedserver.adr)) + if (NET_CompareAdr(&info->adr, &selectedserver.adr)&&!strcmp(info->brokerid,selectedserver.brokerid)) selectedserver.detail = info->moreinfo; if (info->moreinfo) @@ -3540,7 +3514,7 @@ void CL_MasterListParse(netadrtype_t adrtype, int type, qboolean slashpad) Z_Free(info); break; } - if ((old = Master_InfoForServer(&info->adr))) //remove if the server already exists. + if ((old = Master_InfoForServer(&info->adr, NULL))) //remove if the server already exists. { if ((old->special & (SS_PROTOCOLMASK)) != (type & (SS_PROTOCOLMASK))) old->special = type | (old->special & (SS_FAVORITE|SS_LOCAL)); @@ -3556,7 +3530,7 @@ void CL_MasterListParse(netadrtype_t adrtype, int type, qboolean slashpad) info->refreshtime = 0; info->status |= SRVSTATUS_GLOBAL; - Q_snprintfz(info->name, sizeof(info->name), "%s (via %s)", NET_AdrToString(adr, sizeof(adr), &info->adr), madr); + Q_snprintfz(info->name, sizeof(info->name), "%s (via %s)", Master_ServerToString(adr, sizeof(adr), info), madr); info->next = last; last = info; @@ -3586,11 +3560,11 @@ void CL_Connect_c(int argn, const char *partial, struct xcommandargcompletioncb_ { if (len && !Q_strncasecmp(partial, info->name, len)) { - ctx->cb(info->name, va("^[%s^], %i players, %i ping", info->name, info->players, info->ping), NET_AdrToString(buf, sizeof(buf), &info->adr), ctx); + ctx->cb(info->name, va("^[%s^], %i players, %i ping", info->name, info->players, info->ping), Master_ServerToString(buf, sizeof(buf), info), ctx); continue; } - NET_AdrToString(buf, sizeof(buf), &info->adr); + Master_ServerToString(buf, sizeof(buf), info); //there are too many meaningless servers out there, so only suggest IP addresses if those servers are actually significant (ie: active, or favourite) if (!strncmp(partial, buf, len)) { @@ -3612,23 +3586,52 @@ void CL_Connect_c(int argn, const char *partial, struct xcommandargcompletioncb_ #endif #ifdef Q3CLIENT +#if defined(CL_MASTER) static void NetQ3_LocalServers_f(void) { -#if defined(CL_MASTER) netadr_t na; MasterInfo_Refresh(true); if (NET_StringToAdr("255.255.255.255", PORT_Q3SERVER, &na)) NET_SendPollPacket (14, va("%c%c%c%cgetstatus\n", 255, 255, 255, 255), na); -#endif +} +static void NetQ3_GlobalServers_Request(size_t masternum, int protocol, const char *keywords) +{ + if (masternum == countof(net_masterlist)) + { + const char *url; + struct dl_download *dl; + COM_Parse(com_protocolname.string); + if (!strncmp(fs_manifest->rtcbroker, "tls://", 6)) + url = va("https://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); + else if (!strncmp(fs_manifest->rtcbroker, "tcp://", 6)) + url = va("http://%s/raw/%s", fs_manifest->rtcbroker+6, com_token); + else + url = va("http://%s/raw/%s", fs_manifest->rtcbroker, com_token); + + dl = HTTP_CL_Get(url, NULL, MasterInfo_ProcessHTTP); + if (dl) + dl->isquery = true; + } + if (masternum >= countof(net_masterlist)) + return; //erk + if (net_masterlist[masternum].protocol == MP_QUAKE3) + { + netadr_t adr[16]; + char *str; + size_t i, n; + COM_Parse(net_masterlist[masternum].cv.string); //only want the first one + n = NET_StringToAdr2(com_token, 0, adr, countof(adr), NULL); + str = va("%c%c%c%cgetservers %u %s\n", 255, 255, 255, 255, protocol, keywords); + for (i = 0; i < n; i++) + NET_SendPollPacket (strlen(str), str, adr[i]); + } } static void NetQ3_GlobalServers_f(void) { -#if defined(CL_MASTER) size_t masternum = atoi(Cmd_Argv(1)); int protocol = atoi(Cmd_Argv(2)); char *keywords; - size_t i; MasterInfo_Refresh(true); Cmd_ShiftArgs(2, false); @@ -3636,25 +3639,16 @@ static void NetQ3_GlobalServers_f(void) if (!masternum) { - for (i = 0; i < countof(net_masterlist); i++) - Cbuf_AddText(va("globalservers %u %i %s\n", (unsigned)(i+1), protocol, keywords), Cmd_ExecLevel); + for (masternum = 0; masternum <= countof(net_masterlist); masternum++) + NetQ3_GlobalServers_Request(masternum, protocol, keywords); } - masternum--; - if (masternum >= countof(net_masterlist)) - return; //erk - if (net_masterlist[masternum].protocol == MP_QUAKE3) - { - netadr_t adr[16]; - char *str; - size_t n; - COM_Parse(net_masterlist[masternum].cv.string); //only want the first one - n = NET_StringToAdr2(com_token, 0, adr, countof(adr)); - str = va("%c%c%c%cgetservers %u empty full\n", 255, 255, 255, 255, 68); - for (i = 0; i < n; i++) - NET_SendPollPacket (strlen(str), str, adr[i]); - } -#endif + else + NetQ3_GlobalServers_Request(masternum-1, protocol, keywords); } +#else +static void NetQ3_LocalServers_f(void){} +static void NetQ3_GlobalServers_f(void){} +#endif #endif void Net_Master_Init(void) { diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index a9b6798a..cb2c5eac 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -719,13 +719,13 @@ static void QCBUILTIN PF_cs_remove (pubprogfuncs_t *prinst, struct globalvars_s if (!ed->entnum) { - Con_Printf("Unable to remove the world. Try godmode."); + Con_Printf("Unable to remove the world. Try godmode.\n"); PR_StackTrace (prinst, false); return; } if (ed->readonly) { - Con_Printf("Entity %i is readonly.", ed->entnum); + Con_Printf("Entity %i is readonly.\n", ed->entnum); return; } @@ -734,6 +734,35 @@ static void QCBUILTIN PF_cs_remove (pubprogfuncs_t *prinst, struct globalvars_s World_UnlinkEdict((wedict_t*)ed); ED_Free (prinst, (void*)ed); } +static void QCBUILTIN PF_cs_removeinstant (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + csqcedict_t *ed; + + ed = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0); + + if (ED_ISFREE(ed)) + { + csqc_deprecated("Tried removing free entity"); + return; + } + + if (!ed->entnum) + { + Con_Printf("Unable to remove the world. Try godmode.\n"); + PR_StackTrace (prinst, false); + return; + } + if (ed->readonly) + { + Con_Printf("Entity %i is readonly.\n", ed->entnum); + return; + } + + if (pe) + pe->DelinkTrailstate(&ed->trailstate); + World_UnlinkEdict((wedict_t*)ed); + prinst->EntFree(prinst, (void*)ed, true); +} static void QCBUILTIN PF_cvar (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -2243,6 +2272,11 @@ nogameaccess: *r = r_refdef.useperspective; break; + case VF_PROJECTIONOFFSET: + r[0] = r_refdef.projectionoffset[0]; + r[1] = r_refdef.projectionoffset[1]; + break; + case VF_SCREENVSIZE: r[0] = vid.width; r[1] = vid.height; @@ -2458,6 +2492,11 @@ void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ r_refdef.useperspective = *p; break; + case VF_PROJECTIONOFFSET: + r_refdef.projectionoffset[0] = p[0]; + r_refdef.projectionoffset[1] = p[1]; + break; + case VF_RT_DESTCOLOUR0: case VF_RT_DESTCOLOUR1: case VF_RT_DESTCOLOUR2: @@ -3651,6 +3690,12 @@ static void cs_get_input_state (usercmd_t *cmd) cmd->cursor_entitynumber = *csqcg.input_cursor_entitynumber; } +//sets implicit pause (only works when singleplayer) +static void QCBUILTIN PF_cl_setpause (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + cl.implicitpause = !!G_FLOAT(OFS_PARM0); +} + //get the input commands, and stuff them into some globals. static void QCBUILTIN PF_cs_getinputstate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -6327,6 +6372,7 @@ static struct { {"vectoyaw", PF_vectoyaw, 13}, // #13 float(vector v) vectoyaw (QUAKE) {"spawn", PF_Spawn, 14}, // #14 entity() spawn (QUAKE) {"remove", PF_cs_remove, 15}, // #15 void(entity e) remove (QUAKE) + {"removeinstant", PF_cs_removeinstant, 0}, {"traceline", PF_cs_traceline, 16}, // #16 void(vector v1, vector v2, float nomonst, entity forent) traceline (QUAKE) {"checkclient", PF_NoCSQC, 17}, // #17 entity() checkclient (QUAKE) (don't support) {"find", PF_FindString, 18}, // #18 entity(entity start, .string fld, string match) findstring (QUAKE) @@ -6944,6 +6990,7 @@ static struct { {"loadfromdata", PF_loadfromdata, 529}, {"loadfromfile", PF_loadfromfile, 530}, + {"setpause", PF_cl_setpause, 531}, {"log", PF_Logarithm, 532}, {"stopsound", PF_stopsound, 0}, diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index b3956784..b3512ae8 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -1659,6 +1659,21 @@ static void QCBUILTIN PF_Remove_ (pubprogfuncs_t *prinst, struct globalvars_s *p ED_Free (prinst, (void*)ed); } +static void QCBUILTIN PF_RemoveInstant (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + menuedict_t *ed; + + ed = (void*)G_EDICT(prinst, OFS_PARM0); + + if (ed->ereftype == ER_FREE) + { + Con_DPrintf("Tried removing free entity\n"); + PR_StackTrace(prinst, false); + return; + } + + prinst->EntFree(prinst, (void*)ed, true); +} static void QCBUILTIN PF_CopyEntity (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -2238,6 +2253,7 @@ static struct { {"spawn", PF_Spawn, 22}, {"remove", PF_Remove_, 23}, + {"removeinstant", PF_RemoveInstant, 0}, {"find", PF_FindString, 24}, {"findfloat", PF_FindFloat, 25}, {"findentity", PF_FindFloat, 25}, diff --git a/engine/client/render.h b/engine/client/render.h index be7ba160..1effd33a 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -546,7 +546,6 @@ void Mod_LoadLighting (struct model_s *loadmodel, bspx_header_t *bspx, qbyte *mo struct mleaf_s *Mod_PointInLeaf (struct model_s *model, float *p); -void Mod_Think (void); void Mod_NowLoadExternal(struct model_s *loadmodel); void GLR_LoadSkys (void); void R_BloomRegister(void); @@ -560,12 +559,16 @@ void Mod_ModelLoaded(void *ctx, void *data, size_t a, size_t b); #ifdef RUNTIMELIGHTING struct relight_ctx_s; struct llightinfo_s; -void LightFace (struct relight_ctx_s *ctx, struct llightinfo_s *threadctx, int surfnum); //version that is aware of bsp trees void LightPlane (struct relight_ctx_s *ctx, struct llightinfo_s *threadctx, lightstyleindex_t surf_styles[4], unsigned int *surf_expsamples, qbyte *surf_rgbsamples, qbyte *surf_deluxesamples, vec4_t surf_plane, vec4_t surf_texplanes[2], vec2_t exactmins, vec2_t exactmaxs, int texmins[2], int texsize[2], float lmscale); //special version that doesn't know what a face is or anything. struct relight_ctx_s *LightStartup(struct relight_ctx_s *ctx, struct model_s *model, qboolean shadows, qboolean skiplit); void LightReloadEntities(struct relight_ctx_s *ctx, const char *entstring, qboolean ignorestyles); -void LightShutdown(struct relight_ctx_s *ctx, struct model_s *mod); +void LightShutdown(struct relight_ctx_s *ctx); extern const size_t lightthreadctxsize; + +qboolean RelightSetup (struct model_s *model, size_t lightsamples, qboolean generatelit); +void RelightThink (void); +const char *RelightGetProgress(float *progress); //reports filename and progress +void RelightTerminate(struct model_s *mod); //NULL acts as a wildcard #endif diff --git a/engine/client/renderer.c b/engine/client/renderer.c index e8aa7e0b..9ad1c645 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -1505,6 +1505,7 @@ qboolean R_ApplyRenderer_Load (rendererstate_t *newr) if (host_basepal) BZ_Free(host_basepal); host_basepal = (qbyte *)FS_LoadMallocFile ("gfx/palette.lmp", &sz); + vid.fullbright = host_basepal?32:0; //q1-like mods are assumed to have 32 fullbright pixels, even if the colormap is missing. if (!host_basepal) host_basepal = (qbyte *)FS_LoadMallocFile ("wad/playpal", &sz); if (!host_basepal || sz != 768) diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 90c139a7..17ed8a69 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -35,6 +35,7 @@ cvar_t scr_scoreboard_newstyle = CVARD("scr_scoreboard_newstyle", "1", "Display cvar_t scr_scoreboard_showfrags = CVARD("scr_scoreboard_showfrags", "0", "Display kills+deaths+teamkills, as determined by fragfile.dat-based conprint parsing. These may be inaccurate if you join mid-game."); cvar_t scr_scoreboard_showflags = CVARD("scr_scoreboard_showflags", "2", "Display flag caps+touches on the scoreboard, where our fragfile.dat supports them.\n0: off\n1: on\n2: on only if someone appears to have interacted with a flag."); cvar_t scr_scoreboard_fillalpha = CVARD("scr_scoreboard_fillalpha", "0.7", "Transparency amount for newstyle scoreboard."); +cvar_t scr_scoreboard_backgroundalpha = CVARD("scr_scoreboard_backgroundalpha", "0.5", "Further multiplier for the background alphas."); cvar_t scr_scoreboard_teamscores = CVARD("scr_scoreboard_teamscores", "1", "Makes +showscores act as +showteamscores. Because reasons."); cvar_t scr_scoreboard_teamsort = CVARD("scr_scoreboard_teamsort", "0", "On the scoreboard, sort players by their team BEFORE their personal score."); cvar_t scr_scoreboard_titleseperator = CVAR("scr_scoreboard_titleseperator", "1"); @@ -91,7 +92,7 @@ static apic_t *sb_ibar; static apic_t *sb_sbar; static apic_t *sb_scorebar; -static apic_t *sb_weapons[7][8]; // 0 is active, 1 is owned, 2-5 are flashes + apic_t *sb_weapons[7][8]; // 0 is active, 1 is owned, 2-5 are flashes static apic_t *sb_ammo[4]; static apic_t *sb_sigil[4]; static apic_t *sb_armor[3]; @@ -972,6 +973,7 @@ static apic_t *Sbar_PicFromWad(char *name) void Sbar_Flush (void) { sbar_loaded = false; + memset(sb_weapons, 0, sizeof(sb_weapons)); } void Sbar_Start (void) //if one of these fails, skip the entire status bar. { @@ -981,6 +983,8 @@ void Sbar_Start (void) //if one of these fails, skip the entire status bar. if (sbar_loaded) return; + memset(sb_weapons, 0, sizeof(sb_weapons)); + sbar_loaded = true; COM_FlushFSCache(false, true); //make sure the fs cache is built if needed. there's lots of loading here. @@ -1138,6 +1142,7 @@ void Sbar_Init (void) Cvar_Register(&scr_scoreboard_showfrags, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_showflags, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_fillalpha, "Scoreboard settings"); + Cvar_Register(&scr_scoreboard_backgroundalpha, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_teamscores, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_teamsort, "Scoreboard settings"); Cvar_Register(&scr_scoreboard_titleseperator, "Scoreboard settings"); @@ -3023,7 +3028,7 @@ void Sbar_IntermissionNumber (float x, float y, int num, int digits, int color, #define COL_TEAM_LOWAVGHIGH COLUMN("low/avg/high", 12*8, {sprintf (num, "%3i/%3i/%3i", plow, pavg, phigh); Draw_FunString ( x, y, num); }) #define COL_TEAM_TEAM COLUMN("team", 4*8, {Draw_FunStringWidth ( x, y, tm->team, 4*8, false, false); \ - if (!strncmp(cl.players[trackplayer].team, tm->team, 16))\ + if (ourteam)\ {\ Draw_FunString ( x - 1*8, y, "^Ue010");\ Draw_FunString ( x + 4*8, y, "^Ue011");\ @@ -3057,6 +3062,8 @@ void Sbar_TeamOverlay (playerview_t *pv) int startx; int trackplayer; + qboolean ourteam; + if (!pv) pv = &cl.playerview[0]; @@ -3146,11 +3153,14 @@ void Sbar_TeamOverlay (playerview_t *pv) k = teamsort[i]; tm = teams + k; + ourteam = !strncmp(cl.players[trackplayer].team, tm->team, 16); + if (scr_scoreboard_newstyle.ival) { // Electro's scoreboard eyecandy: Render the main background transparencies behind players row // TODO: Alpha values on the background int background_color; + float backalpha; if (!(strcmp("red", tm->team))) background_color = 4; // forced red @@ -3159,7 +3169,11 @@ void Sbar_TeamOverlay (playerview_t *pv) else background_color = tm->bottomcolour; - Sbar_FillPCDark (startx - 2, y, rank_width - 3, 8, background_color, scr_scoreboard_fillalpha.value); + backalpha = scr_scoreboard_backgroundalpha.value*scr_scoreboard_fillalpha.value; + if (ourteam) + backalpha *= 1.7; + + Sbar_FillPCDark (startx - 2, y, rank_width - 3, 8, background_color, backalpha); R2D_ImagePaletteColour (0, scr_scoreboard_fillalpha.value); R2D_FillBlock (startx - 3, y, 1, 8); // Electro - Border - Left @@ -3275,8 +3289,7 @@ ping time frags name Font_BeginString(font_default, x+24, y, &cx, &cy); \ Font_DrawChar(cx, cy, CON_WHITEMASK, num[2] | 0xe000); \ \ - if ((pv->cam_state == CAM_FREECAM && k == pv->playernum) || \ - (pv->cam_state != CAM_FREECAM && k == pv->cam_spec_track)) \ + if (isme) \ { \ Font_BeginString(font_default, x, y, &cx, &cy); \ Font_DrawChar(cx, cy, CON_WHITEMASK, 16 | 0xe000); \ @@ -3335,9 +3348,11 @@ void Sbar_DeathmatchOverlay (playerview_t *pv, int start) int skip = 10; int showcolumns; int startx, rank_width; + qboolean isme; vrect_t gr = r_refdef.grect; int namesize = (cl.teamplay ? 12*8 : 16*8); + float backalpha; if (!pv) return; @@ -3525,6 +3540,8 @@ if (showcolumns & (1< vid.height-10) break; + isme = (pv->cam_state == CAM_FREECAM && k == pv->playernum) || + (pv->cam_state != CAM_FREECAM && k == pv->cam_spec_track); // Electro's scoreboard eyecandy: Moved this up here for usage with the row background color top = Sbar_TopColour(s); @@ -3532,6 +3549,10 @@ if (showcolumns & (1<spectator)) @@ -3549,13 +3570,13 @@ if (showcolumns & (1< vid.height-10) break; + isme = (pv->cam_state == CAM_FREECAM && k == pv->playernum) || + (pv->cam_state != CAM_FREECAM && k == pv->cam_spec_track); x = startx; #define COLUMN(title, width, code, fills) \ diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index a15980f3..64a19a41 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -3435,7 +3435,7 @@ void S_UpdateAmbientSounds (soundcardinfo_t *sc) for (i = MUSIC_FIRST; i < MUSIC_STOP; i++) { - chanupdatereason_t changed = false; + chanupdatereason_t changed = CUR_SPACIALISEONLY; chan = &sc->channel[i]; if (!chan->sfx) { @@ -4218,6 +4218,9 @@ void S_RawAudio(int sourceid, qbyte *data, int speed, int samples, int channels, qbyte *newcache; streaming_t *s, *free=NULL; + if (!sound_started) + return; + for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++) { if (!s->inuse) diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index a918123b..8abe6945 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -614,7 +614,7 @@ static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char * { if (wildcmp(match, ent->d_name)) { - Q_snprintfz(file, sizeof(file), "%s/%s", truepath, ent->d_name); + Q_snprintfz(file, sizeof(file), "%s%s", truepath, ent->d_name); if (stat(file, &st) == 0) { @@ -628,7 +628,7 @@ static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char * } } else - printf("Stat failed for \"%s\"\n", file); + Con_DPrintf("Stat failed for \"%s\"\n", file); //can happen with dead symlinks } } } while(1); @@ -640,6 +640,7 @@ int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const char apath[MAX_OSPATH]; char truepath[MAX_OSPATH]; char *s; + int suboffset; if (!gpath) gpath = ""; @@ -658,8 +659,15 @@ int Sys_EnumerateFiles (const char *gpath, const char *match, int (*func)(const if (s < apath) //didn't find a '/' *apath = '\0'; - Q_snprintfz(truepath, sizeof(truepath), "%s/%s", gpath, apath); - return Sys_EnumerateFiles2(truepath, strlen(gpath)+1, match, func, parm, spath); + suboffset = strlen(gpath); + if (suboffset + 1 + strlen(apath) >= sizeof(truepath)) + return false; //overflow... + memcpy(truepath, gpath, suboffset); + if (suboffset && truepath[suboffset-1] != '/') + truepath[suboffset++] = '/'; + Q_strncpyz(truepath+suboffset, apath, sizeof(truepath)-suboffset); + + return Sys_EnumerateFiles2(truepath, suboffset, match, func, parm, spath); } int secbase; diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 8922621e..ac3d467f 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -990,9 +990,6 @@ int watchdogthreadfunction(void *arg) } #endif -int *debug; - - #ifndef SERVERONLY #if (_WIN32_WINNT < 0x0400) @@ -1012,7 +1009,16 @@ int *debug; #endif #endif -HHOOK llkeyboardhook; +static struct +{ + HHOOK llkeyboardhook; + + //windows hooks can be used for code injection etc. + //hide these symbols from shitty exports scanners so we don't look like the keylogger that we aren't. Note the 'vid.activeapp' requirement below - we are not a keylogger, we only see a limited set of keys and only when we already have focus. + HHOOK (WINAPI *pSetWindowsHookEx) (int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId); //W and A versions have the same signature. + LRESULT (WINAPI *pCallNextHookEx) (HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam); + WINBOOL (WINAPI *pUnhookWindowsHookEx) (HHOOK hhk); +} winkeys; cvar_t sys_disableWinKeys = CVAR("sys_disableWinKeys", "0"); cvar_t sys_disableTaskSwitch = CVARF("sys_disableTaskSwitch", "0", CVAR_NOTFROMSERVER); // please don't encourage people to use this... @@ -1071,7 +1077,7 @@ LRESULT CALLBACK LowLevelKeyboardProc (INT nCode, WPARAM wParam, LPARAM lParam) default: break; } - return CallNextHookEx (llkeyboardhook, nCode, wParam, lParam); + return winkeys.pCallNextHookEx (winkeys.llkeyboardhook, nCode, wParam, lParam); } void SetHookState(qboolean state) @@ -1079,16 +1085,27 @@ void SetHookState(qboolean state) if (!sys_disableTaskSwitch.ival && !sys_disableWinKeys.ival) state = false; - if (!state == !llkeyboardhook) //not so types are comparable + if (!state == !winkeys.llkeyboardhook) //not so types are comparable return; - - if (llkeyboardhook) + if (!winkeys.pSetWindowsHookEx) { - UnhookWindowsHookEx(llkeyboardhook); - llkeyboardhook = NULL; + HMODULE dll = LoadLibraryA("user32.dll"); + if (!dll) + return; + winkeys.pSetWindowsHookEx = (void*)GetProcAddress(dll, WinNT?"SetWindowsHookExW":"SetWindowsHookExA"); + winkeys.pCallNextHookEx = (void*)GetProcAddress(dll, "CallNextHookEx"); + winkeys.pUnhookWindowsHookEx = (void*)GetProcAddress(dll, "UnhookWindowsHookEx"); + if (!winkeys.pSetWindowsHookEx) + return; + } + + if (winkeys.llkeyboardhook) + { + winkeys.pUnhookWindowsHookEx(winkeys.llkeyboardhook); + winkeys.llkeyboardhook = NULL; } if (state) - llkeyboardhook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); + winkeys.llkeyboardhook = winkeys.pSetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); } #endif diff --git a/engine/client/view.c b/engine/client/view.c index 74219f00..d162bfdb 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -155,13 +155,16 @@ cvar_t v_viewheight = CVARF("v_viewheight", "0", CVAR_ARCHIVE); static cvar_t v_depthsortentities = CVARAD("v_depthsortentities", "0", "v_reorderentitiesrandomly", "Reorder entities for transparency such that the furthest entities are drawn first, allowing nearer transparent entities to draw over the top of them."); +#ifdef QUAKESTATS static cvar_t scr_autoid = CVARD("scr_autoid", "1", "Display nametags above all players while spectating."); -static cvar_t scr_autoid_team = CVARD("scr_autoid_team", "1", "Display nametags above team members. 0: off. 1: display with half-alpha if occluded. 2: hide when occluded."); +static cvar_t scr_autoid_team = CVARD("scr_autoid_team", "2", "Display nametags above team members. 0: off. 1: display with half-alpha if occluded. 2: hide when occluded."); static cvar_t scr_autoid_health = CVARD("scr_autoid_health", "1", "Display health as part of nametags (when known)."); static cvar_t scr_autoid_armour = CVARD("scr_autoid_armor", "1", "Display armour as part of nametags (when known)."); static cvar_t scr_autoid_weapon = CVARD("scr_autoid_weapon", "1", "Display the player's best weapon as part of their nametag (when known)."); +static cvar_t scr_autoid_weapon_mask = CVARD("scr_autoid_weapon_mask", "126", "Mask of bits for weapon icons to actually show.\n+1: Shotgun.\n+2: Super Shotgun.\n+4: Nail Gun.\n+8: Super Nail Gun.\n+16: Grenade Launcher.\n+32: Rocket Launcher.\n+64: Lightning\nShowing only RL and GL is 96\n"); static cvar_t scr_autoid_teamcolour = CVARD("scr_autoid_teamcolour", STRINGIFY(COLOR_BLUE), "The colour for the text on the nametags of team members."); static cvar_t scr_autoid_enemycolour = CVARD("scr_autoid_enemycolour", STRINGIFY(COLOR_WHITE), "The colour for the text on the nametags of non-team members."); +#endif cvar_t chase_active = CVAR("chase_active", "0"); cvar_t chase_back = CVAR("chase_back", "48"); @@ -1768,6 +1771,7 @@ static qboolean SCR_VRectForPlayer(vrect_t *vrect, int pnum, unsigned maxseats) void Draw_ExpandedString(struct font_s *font, float x, float y, conchar_t *str); +#ifdef QUAKESTATS static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) { conchar_t buffer[256]; @@ -1795,7 +1799,6 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) {1, 0.4, 0, 1}, {1, 1, 1, 1} }; -#ifdef QUAKESTATS static vec4_t armourcolours[] = { {25, 170, 0, 0.2}, @@ -1817,7 +1820,6 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) &tp_name_rl, &tp_name_lg }; -#endif struct font_s *font = font_default; VectorCopy(org, tagcenter); @@ -1848,7 +1850,6 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) x = center[0]*r_refdef.vrect.width+r_refdef.vrect.x; y = (1-center[1])*r_refdef.vrect.height+r_refdef.vrect.y; -#ifdef QUAKESTATS if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) { health = pl->statsf[STAT_HEALTH]; @@ -1858,7 +1859,6 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) haveinfo = true; } else -#endif { health = pl->tinfo.health; armour = pl->tinfo.armour; @@ -1897,16 +1897,18 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) h += 8; y -= 8; R2D_ImageColours(healthcolours[r][0], healthcolours[r][1], healthcolours[r][2], healthcolours[r][3]*alpha); - R2D_FillBlock(x - barwidth*0.5 + barwidth * frac, y, barwidth * (1-frac), 8); + R2D_FillBlock(x - barwidth*0.5 + barwidth * frac, y, barwidth * (1-frac), 4); r++; R2D_ImageColours(healthcolours[r][0], healthcolours[r][1], healthcolours[r][2], healthcolours[r][3]*alpha); - R2D_FillBlock(x - barwidth*0.5, y, barwidth * frac, 8); + R2D_FillBlock(x - barwidth*0.5, y, barwidth * frac, 4); } if (health <= 0) //armour+weapons are not relevant when dead + { + R2D_ImageColours(1, 1, 1, 1); return; + } -#ifdef QUAKESTATS if (scr_autoid_armour.ival) { //display armour bar above that @@ -1919,30 +1921,56 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) else r = -1; if (r >= 0) { - h += 8; - y -= 8; + h += 5; + y -= 5; armour = bound(0, armour, health); barwidth = 32; R2D_ImageColours(armourcolours[r][0], armourcolours[r][1], armourcolours[r][2], armourcolours[r][3]*alpha); - R2D_FillBlock(x - barwidth*0.5 + barwidth * armour/(float)health, y, barwidth * (health-armour)/(float)health, 8); + R2D_FillBlock(x - barwidth*0.5 + barwidth * armour/(float)health, y, barwidth * (health-armour)/(float)health, 4); r++; R2D_ImageColours(armourcolours[r][0], armourcolours[r][1], armourcolours[r][2], armourcolours[r][3]*alpha); - R2D_FillBlock(x - barwidth*0.5, y, barwidth * armour/(float)health, 8); + R2D_FillBlock(x - barwidth*0.5, y, barwidth * armour/(float)health, 4); } } + R2D_ImageColours(1, 1, 1, 1); if (scr_autoid_weapon.ival) { - if (h < 8) - h = 8; - y += (h-8)/2; - for (r = countof(wbitnames)-1; r>=0; r--) if (items & (1<= 0) + if (r >= 0 && (scr_autoid_weapon_mask.ival&(1<<1))) { + if (scr_autoid_weapon.ival==1) + { + extern apic_t *sb_weapons[7][8]; + float w = 0; + if (r < 8 && sb_weapons[0][r]) + { + + if (h < 16) + { + y-= 16-h; + h = 16; + } + y += (h-16)/2; + + if (sb_weapons[0][r]->width) + w = sb_weapons[0][r]->width; + else + w = sb_weapons[0][r]->atlas->width; + R2D_ImageAtlas(x-barwidth*.5-24-4, y, 24, 16, 0, 0, 24/w, 1, sb_weapons[0][r]); + return; + } + } + + if (h < 8) + { + y-= 8-h; + h = 8; + } + y += (h-8)/2; + len = COM_ParseFunString(textflags, wbitnames[r]->string, buffer, sizeof(buffer), false) - buffer; if (textflags & CON_HALFALPHA) { @@ -1953,17 +1981,15 @@ static void SCR_DrawAutoID(vec3_t org, player_info_t *pl, qboolean isteam) if (len && (buffer[0] & CON_CHARMASK) == '{' && (buffer[len-1] & CON_CHARMASK) == '}') { //these are often surrounded by {} to make them white in chat messages, and recoloured. buffer[len-1] = 0; - Draw_ExpandedString(font, x + barwidth*0.5 + 4, y, buffer+1); + len = 1; } else - Draw_ExpandedString(font, x + barwidth*0.5 + 4, y, buffer); + len = 0; + Draw_ExpandedString(font, x + barwidth*0.5 + 4, y, buffer+len); } } -#else - (void)items; - (void)armour; -#endif } +#endif #include "pr_common.h" msurface_t *Mod_GetSurfaceNearPoint(model_t *model, vec3_t point); @@ -1974,10 +2000,12 @@ extern cvar_t r_showshaders, r_showfields, r_projection; void R_DrawNameTags(void) { int i; +#ifdef QUAKESTATS lerpents_t *le; qboolean isteam; char *ourteam; int ourcolour; +#endif if (r_projection.ival) //we don't actually know how to transform the points unless the projection is coded in advance. and it isn't. return; @@ -2184,6 +2212,7 @@ void R_DrawNameTags(void) } } +#ifdef QUAKESTATS if (cls.protocol == CP_QUAKE2) return; //FIXME: q2 has its own ent logic, which messes stuff up here. @@ -2203,6 +2232,7 @@ void R_DrawNameTags(void) ourcolour = cl.players[r_refdef.playerview->playernum].rbottomcolor; } + pmove.skipent = r_refdef.playerview->playernum+1; for (i = 0; i < cl.allocated_client_slots; i++) { if (!*cl.players[i].name) @@ -2247,6 +2277,7 @@ void R_DrawNameTags(void) SCR_DrawAutoID(nametagorg[i], &cl.players[i], isteam); } +#endif } void R2D_PolyBlend (void); @@ -2615,13 +2646,16 @@ void V_Init (void) Cvar_Register (&v_deathtilt, VIEWVARS); Cvar_Register (&v_skyroom, VIEWVARS); +#ifdef QUAKESTATS Cvar_Register (&scr_autoid, VIEWVARS); Cvar_Register (&scr_autoid_team, VIEWVARS); Cvar_Register (&scr_autoid_health, VIEWVARS); Cvar_Register (&scr_autoid_armour, VIEWVARS); Cvar_Register (&scr_autoid_weapon, VIEWVARS); + Cvar_Register (&scr_autoid_weapon_mask, VIEWVARS); Cvar_Register (&scr_autoid_teamcolour, VIEWVARS); Cvar_Register (&scr_autoid_enemycolour, VIEWVARS); +#endif #ifdef SIDEVIEWS #define SECONDARYVIEWVARS "Secondary view vars" diff --git a/engine/client/wad.c b/engine/client/wad.c index b2e3351e..da6fe69e 100644 --- a/engine/client/wad.c +++ b/engine/client/wad.c @@ -470,11 +470,18 @@ qbyte *W_GetTexture(const char *name, int *width, int *height, uploadfmt_t *form p = W_GetLumpName(name+4, &lumpsize, &lumptype); if (p) { - if (/*lumptype == TYP_QPIC && */!strcmp(name+4, "conchars") && lumpsize==128*128) + if (/*lumptype == TYP_MIPTEX && */!strcmp(name+4, "conchars") && (lumpsize==128*128 +#ifdef HAVE_LEGACY + || (lumptype == TYP_QPIC&&lumpsize==8+128*128) +#endif + )) { //conchars has no header. qbyte *lump = (qbyte*)p; extern cvar_t con_ocranaleds; + if (lumpsize==8+128*128) + Con_Printf(CON_WARNING"WARNING: gfx.wad conchars lump has incorrect lump size.\n"); + if (con_ocranaleds.ival) { if (con_ocranaleds.ival != 2 || QCRC_Block(lump, 128*128) == 798) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 21ed5941..b5a54851 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -4262,7 +4262,7 @@ void Cmd_Init (void) #endif Cmd_AddCommandAD ("seta", Cmd_set_f, Cmd_Set_c, "Changes the current value of the named cvar, creating it if it doesn't yet exist. Also forces the archive flag so that the cvar will always be written into any saved configs."); Cmd_AddCommandAD ("seta_calc", Cmd_set_f, Cmd_Set_c, "Sets the named cvar to the result of a (complex) expression. Also forces the archive flag so that the cvar will always be written into any saved configs."); - Cmd_AddCommand ("vstr", Cmd_Vstr_f); + Cmd_AddCommandD ("vstr", Cmd_Vstr_f, "Executes the string value of the cvar, much like if it were an alias. For compatibility with q3."); Cmd_AddCommandAD ("inc", Cvar_Inc_f, Cmd_Set_c, "Adds a value to the named cvar. Use a negative value if you wish to decrease the cvar's value."); //FIXME: Add seta some time. Cmd_AddCommand ("if", Cmd_if_f); diff --git a/engine/common/cmd.h b/engine/common/cmd.h index e1f3ebd1..e65ee4f2 100644 --- a/engine/common/cmd.h +++ b/engine/common/cmd.h @@ -25,18 +25,53 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /*FIXME: rewrite this to use something like the following typedef struct { - qbyte level:8; - qbyte seat:4; //for splitscreen binds etc - qbyte pad:2; - qbyte insecure:1; //from gamecode, untrusted configs, stuffed binds, etc. flagged for many reasons. - qbyte cvarlatch:1; //latches cvars so the user cannot change them till next map. should be RESTRICT_LOCAL to avoid users cheating by restrict-to-block server commands. -} cmdpermissions_t; + qbyte groups:27; + qbyte cvarlatch:1; //latches cvars so the user cannot change them till next map. should be RESTRICT_LOCAL to avoid users cheating by restrict-to-block server commands. + qbyte seat:4; //for splitscreen binds etc. 1 based! 0 uses in_forceseat/first. +} cmdaccess_t; +enum { +//core groups + CAG_OWNER = 1<<0, //commands that came from the keyboard (or stdin). + CAG_MENUQC = 1<<1, //menuqc (mostly allowed to do stuff - but often also has CAG_DOWNLOADED) + CAG_CSQC = 1<<2, //csqc localcmds, nearly always has CAG_DOWNLOADED, so a load of denies + CAG_SSQC = 1<<3, //ssqc localcmds, able to poke quite a lot of things, generally unrestricted. + CAG_SERVER = 1<<4, //ssqc stuffcmds, typically same access as csqc +//special groups: + CAG_DOWNLOADED = 1<<5, //for execs that read from a downloaded(untrusted) package. also flagged by qc modules if they were from such packages (so usually included from csqc). + CAG_SCRIPT = 1<<6, //added for binds (with more than one command), or for aliases/configs. exists for +lookdown's denies (using explicit checks, to avoid cheat bypasses). +//custom groups: + CAG_RCON = 1<<7, //commands that came from rcon. + +//groups of groups, for default masks or special denies + CAG_DEFAULTALLOW = CAG_OWNER|CAG_MENUQC|CAG_CSQC|CAG_SSQC|CAG_SERVER|CAG_RCON, + CAG_INSECURE = CAG_DOWNLOADED|CAG_SERVER|CAG_CSQC, //csqc included mostly for consistency. + + // userN: defaults to no commands + // vip: BAN_VIP users + // mapper: BAN_MAPPER users +}; + + //stuff can be accessed when: + // if ((command.allows&cbuf.groups) && !(command.denies&cbuf.groups)) accessisallowed; + //cvars: + // separate allow-read, deny-read, allow-write, deny-write masks (set according to the older flags, to keep things simple) + //aliases: + // alias execution ors in the group(s) that created it (and 'script'). some execution chains could end up with a LOT of groups... FIXME: is that a problem? just don't put potentially restricted things in aliases? + //binds: + // also ors the creator's group - no `bind w doevil` and waiting. + //multiple cbufs: + // ssqc still has a dedicated cbuf, so that wait commands cause THAT cbuf to wait, not others. + // rcon+readcmd have a separate cbuf, to try to isolate prints + //seats: + // there's no security needed between seats, but server should maybe be blocked from seat switching commands (acting only as asserts), to catch bugs. + // + //'p2 nameofalias' sets seat to 2, overriding in_forceseat. + //+lookup etc is blocked when allow_scripts==0 and indirect is set. typedef struct { - cbuf_t *cbuf; //exec etc inserts into this cmdpermissions_t p; //access rights } cmdstate_t; -void Cbuf_AddText(const char *text, qboolean addnl, qboolean insert, const cmdstate_t *cstate); //null for local? +void Cbuf_AddText(const char *text, qboolean addnl, qboolean insert); //null for local? all commands must be \n terminated. char *Cbuf_GetNext(cmdpermissions_t permissions, qboolean ignoresemicolon); void Cbuf_Execute(cbuf_t *cbuf); void Cmd_ExecuteString (const char *text, const cmdstate_t *cstate); @@ -193,9 +228,10 @@ void Cmd_Args_Set(const char *newargs, size_t len); #define RESTRICT_MAX RESTRICT_MAX_USER -#define RESTRICT_LOCAL RESTRICT_MAX //commands typed at the console -#define RESTRICT_INSECURE RESTRICT_MAX+1 //commands from csqc or untrusted sources (really should be a separate flag, requires cbuf rewrite) -#define RESTRICT_SERVER RESTRICT_MAX+2 //commands from ssqc (untrusted, but allowed to lock cvars) +#define RESTRICT_LOCAL (RESTRICT_MAX) //commands typed at the console +#define RESTRICT_INSECURE (RESTRICT_MAX+1) //commands from csqc or untrusted sources (really should be a separate flag, requires cbuf rewrite) +#define RESTRICT_SERVER (RESTRICT_MAX+2) //commands from ssqc (untrusted, but allowed to lock cvars) +#define RESTRICT_SERVERSEAT(x) (RESTRICT_SERVER+x) #define RESTRICT_RCON rcon_level.ival //#define RESTRICT_SSQC RESTRICT_MAX-2 diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index bdf08762..9e1b2f08 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -3612,7 +3612,6 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model galiasskin_t *outskin = galias->ofsskins; const char *slash; unsigned int texflags; - const char *defaultshader = NULL; s = pq1inmodel->skinwidth*pq1inmodel->skinheight; for (i = 0; i < pq1inmodel->numskins; i++) @@ -3664,7 +3663,7 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model { default: //urk case TF_SOLID8: - frames[0].defaultshader = defaultshader; + frames[0].defaultshader = NULL; //default skin... break; case TF_H2_T7G1: frames[0].defaultshader = @@ -3682,6 +3681,7 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model case TF_H2_TRANS8_0: frames[0].defaultshader = "{\n" +// "program defaultskin#MASK=0.5#MASKLT\n" "{\n" "map $diffuse\n" "blendfunc gl_src_alpha gl_one_minus_src_alpha\n" @@ -3690,11 +3690,26 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model "alphagen entity\n" "depthwrite\n" "}\n" + "{\n" + "map $loweroverlay\n" + "rgbgen bottomcolor\n" + "blendfunc gl_src_alpha gl_one\n" + "}\n" + "{\n" + "map $upperoverlay\n" + "rgbgen topcolor\n" + "blendfunc gl_src_alpha gl_one\n" + "}\n" + "{\n" + "map $fullbright\n" + "blendfunc add\n" + "}\n" "}\n"; break; case TF_H2_T4A4: frames[0].defaultshader = "{\n" +// "program defaultskin\n" "cull disable\n" "{\n" "map $diffuse\n" @@ -3703,6 +3718,20 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model "rgbgen lightingDiffuse\n" "depthwrite\n" "}\n" + "{\n" + "map $loweroverlay\n" + "rgbgen bottomcolor\n" + "blendfunc gl_src_alpha gl_one\n" + "}\n" + "{\n" + "map $upperoverlay\n" + "rgbgen topcolor\n" + "blendfunc gl_src_alpha gl_one\n" + "}\n" + "{\n" + "map $fullbright\n" + "blendfunc add\n" + "}\n" "}\n"; break; } diff --git a/engine/common/common.c b/engine/common/common.c index 423c0f15..6ecc9e10 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -2804,6 +2804,7 @@ char *COM_DeFunString(conchar_t *str, conchar_t *stop, char *out, int outsize, q if (!outsize) Sys_Error("COM_DeFunString given outsize=0"); #endif + outsize--; /*if (ignoreflags) { diff --git a/engine/common/fs.c b/engine/common/fs.c index 8496e4d3..2399b1cc 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -396,6 +396,8 @@ static ftemanifest_t *FS_Manifest_Create(const char *syspath) #else man->mainconfig = Z_StrDup("fte.cfg"); #endif + + man->rtcbroker = Z_StrDup("tls://master.frag-net.com:27950"); //This is eukara's server. fixme: this really ought to be a cvar instead. return man; } @@ -3358,7 +3360,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) #define EZQUAKECOMPETITIVE "set ruleset_allow_fbmodels 1\nset sv_demoExtensions \"\"\n" /*quake requires a few settings for compatibility*/ #define QRPCOMPAT "set cl_cursor_scale 0.2\nset cl_cursor_bias_x 7.5\nset cl_cursor_bias_y 0.8\n" -#define QUAKESPASMSUCKS "mod_h2holey_bugged 1\n" +#define QUAKESPASMSUCKS "set mod_h2holey_bugged 1\n" #define QCFG "set v_gammainverted 1\nset con_stayhidden 0\nset com_parseutf8 0\nset allow_download_pakcontents 1\nset allow_download_refpackages 0\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" ZFIXHACK EZQUAKECOMPETITIVE QRPCOMPAT QUAKESPASMSUCKS /*NetQuake reconfiguration, to make certain people feel more at home...*/ #define NQCFG "//-nohome\ncfg_save_auto 1\n" QCFG "sv_nqplayerphysics 1\ncl_loopbackprotocol auto\ncl_sbar 1\nplug_sbar 0\nsv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\n" @@ -3470,13 +3472,13 @@ const gamemode_info_t gamemode_info[] = { {"-hexen2", "hexen2", "FTE-Hexen2", {"data1/pak0.pak"}, HEX2CFG,{"data1", "*fteh2"}, "Hexen II", UPDATEURL(H2)}, #endif #if defined(Q2CLIENT) || defined(Q2SERVER) - {"-quake2", "q2", "FTE-Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II", UPDATEURL(Q2)}, + {"-quake2", "q2", "Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II", UPDATEURL(Q2)}, //mods of the above that should generally work. - {"-dday", "dday", "FTE-Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, + {"-dday", "dday", "Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, #endif #if defined(Q3CLIENT) || defined(Q3SERVER) - {"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena", UPDATEURL(Q3)}, + {"-quake3", "q3", "Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena", UPDATEURL(Q3)}, //the rest are not supported in any real way. maps-only mostly, if that // {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"}, // {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"}, @@ -5897,12 +5899,14 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean char *oldprefix = "http://fte."; char *newprefix = "https://updates."; e = COM_ParseFunString(CON_WHITEMASK, ENGINEWEBSITE, musite, sizeof(musite), false); - COM_DeFunString(musite, e, site, sizeof(site), true, true); + COM_DeFunString(musite, e, site, sizeof(site)-1, true, true); + printf("site: %s\n", site); if (!strncmp(site, oldprefix, strlen(oldprefix))) { - memmove(site+strlen(newprefix), site+strlen(oldprefix), strlen(site)-strlen(oldprefix)); + memmove(site+strlen(newprefix), site+strlen(oldprefix), strlen(site)-strlen(oldprefix)+1); memcpy(site, newprefix, strlen(newprefix)); } + printf("site: %s\n", site); Cmd_TokenizeString(va("downloadsurl \"%s%s\"", site, gamemode_info[i].downloadsurl), false, false); } else diff --git a/engine/common/net.h b/engine/common/net.h index 0e47373d..de713669 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -56,6 +56,8 @@ typedef enum { NP_WS, NP_WSS, NP_NATPMP, //server-only scheme for registering public ports. + NP_RTC_TCP, + NP_RTC_TLS, //really need a better way to do this than two copies of every protocol... NP_INVALID } netproto_t; @@ -164,8 +166,8 @@ char *NET_SockadrToString (char *s, int len, struct sockaddr_qstorage *a, size_ char *NET_BaseAdrToString (char *s, int len, netadr_t *a); size_t NET_StringToSockaddr2 (const char *s, int defaultport, netadrtype_t afhint, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize, size_t addrcount); #define NET_StringToSockaddr(s,p,a,f,z) (NET_StringToSockaddr2(s,p,NA_INVALID,a,f,z,1)>0) -size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t addrcount); -#define NET_StringToAdr(s,p,a) NET_StringToAdr2(s,p,a,1) +size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t addrcount, const char **pathstart); +#define NET_StringToAdr(s,p,a) NET_StringToAdr2(s,p,a,1,NULL) qboolean NET_PortToAdr (netadrtype_t adrfamily, netproto_t adrprot, const char *s, netadr_t *a); qboolean NET_IsClientLegal(netadr_t *adr); @@ -211,6 +213,7 @@ typedef struct qbyte nqunreliableonly; //nq can't cope with certain reliables some times. if 2, we have a reliable that result in a block (that should be sent). if 1, we are blocking. if 0, we can send reliables freely. if 3, then we just want to ignore clc_moves #endif qboolean pext_fragmentation; //fte's packet fragmentation extension, to avoid issues with low mtus. + qboolean pext_stunaware; //prevent the two lead-bits of packets from being either 0(stun), so stray stun packets cannot mess things up for us. struct netprim_s netprim; int mtu; //the path mtu, if known int dupe; //how many times to dupe packets diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index c5cef630..82aaf273 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -28,6 +28,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PACKET_HEADER 8 +#define ANTISTUNBIAS 0x40000000 //adding this to sequences in the header ensures that we our packets will not get confused for stun or rtp packets. + /* packet header @@ -225,6 +227,8 @@ unsigned int Net_PextMask(unsigned int protover, qboolean fornq) if (mask & PEXT2_REPLACEMENTDELTAS) mask |= PEXT2_NEWSIZEENCODING; //use if we can + mask |= PEXT2_STUNAWARE; + if (fornq) { //only ones that are tested @@ -736,6 +740,12 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate) w1 = chan->outgoing_sequence | (send_reliable<<31); w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence<<31); + if (chan->pext_stunaware) + { + w1 = BigLong(w1+ANTISTUNBIAS); + w2 = BigLong(w2); + } + chan->outgoing_sequence++; MSG_WriteLong (&send, w1); @@ -945,6 +955,15 @@ qboolean Netchan_Process (netchan_t *chan) sequence = MSG_ReadLong (); sequence_ack = MSG_ReadLong (); + if (chan->pext_stunaware) + { + sequence = BigLong(sequence); + if (!(sequence&ANTISTUNBIAS)) + return false; + sequence -= ANTISTUNBIAS; + sequence_ack = BigLong(sequence_ack); + } + // skip over the qport if we are a server (its handled elsewhere) #ifndef CLIENTONLY if (chan->sock == NS_SERVER) diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index de8454bc..491dd871 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -13,7 +13,11 @@ typedef struct unsigned short attrtype; unsigned short attrlen; } stunattr_t; +#if defined(SUPPORT_ICE) || defined(MASTERONLY) +#include "zlib.h" +#endif #ifdef SUPPORT_ICE +cvar_t net_ice_exchangeprivateips = CVARD("net_ice_exchangeprivateips", "", "Boolean. When set to 0, hides private IP addresses from your peers. Only addresses determined from the other side of your router will be shared. Setting it to 0 may be desirable but it can cause connections to fail when your router does not support hairpinning, whereas 1 fixes that at the cost of exposing private IP addresses."); /* Interactive Connectivity Establishment (rfc 5245) find out your peer's potential ports. @@ -23,18 +27,20 @@ the 'controller' assigns some final candidate pair to ensure that both peers sen if no candidates are available, try using stun to find public nat addresses. in fte, a 'pair' is actually in terms of each local socket and remote address. hopefully that won't cause too much weirdness. +(this does limit which interfaces we can send packets from (probably only an issue with VPNs, which should negate the value of ICE), and prevents us from being able to report reladdr in candidate offers (although these are merely diagnostic rather than useful) stun test packets must contain all sorts of info. username+integrity+fingerprint for validation. priority+usecandidate+icecontrol(ing) to decree the priority of any new remote candidates, whether its finished, and just who decides whether its finished. peers don't like it when those are missing. -host candidates - addresses that are directly known -server reflexive candidates - addresses that we found from some public stun server +host candidates - addresses that are directly known (but are probably unroutable private things) +server reflexive candidates - addresses that we found from some public stun server (useful for NATs that use a single public port for each unique private port) peer reflexive candidates - addresses that our peer finds out about as we spam them relayed candidates - some sort of socks5 or something proxy. Note: Even after the ICE connection becomes active, you should continue to collect local candidates and transmit them to the peer out of band. this allows the connection to pick a new route if some router dies (like a relay kicking us). +FIXME: the client currently disconnects from the broker. the server tracks players via ip rather than ICE. tcp rtp framing should generally be done with a 16-bit network-endian length prefix followed by the data. */ @@ -128,7 +134,7 @@ struct rtpheader_s }; void S_Voip_RTP_Parse(unsigned short sequence, const char *codec, const unsigned char *data, unsigned int datalen); qboolean S_Voip_RTP_CodecOkay(const char *codec); -qboolean NET_RTP_Parse(void) +static qboolean NET_RTP_Parse(void) { struct rtpheader_s *rtpheader = (void*)net_message.data; if (net_message.cursize >= sizeof(*rtpheader) && (rtpheader->v2_p1_x1_cc4 & 0xc0) == 0x80) @@ -221,7 +227,7 @@ qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, const c -struct icestate_s *QDECL ICE_Find(void *module, char *conname) +static struct icestate_s *QDECL ICE_Find(void *module, const char *conname) { struct icestate_s *con; @@ -232,7 +238,7 @@ struct icestate_s *QDECL ICE_Find(void *module, char *conname) } return NULL; } -ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) +static ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) { if (con->connections) return con->connections; @@ -252,7 +258,7 @@ ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) } return NULL; } -struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const char *peername, enum icemode_e mode, enum iceproto_e proto) +static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const char *peername, enum icemode_e mode, enum iceproto_e proto) { ftenet_connections_t *collection; struct icestate_s *con; @@ -297,6 +303,9 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha Sys_RandomBytes((void*)rnd, sizeof(rnd)); conname = va("fte%08x%08x", rnd[0], rnd[1]); } + + if (ICE_Find(module, conname)) + return NULL; //don't allow dupes. con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); @@ -341,8 +350,9 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha netadr_t addr[64]; struct ftenet_generic_connection_s *gcon[sizeof(addr)/sizeof(addr[0])]; unsigned int flags[sizeof(addr)/sizeof(addr[0])]; + const char *params[sizeof(addr)/sizeof(addr[0])]; - m = NET_EnumerateAddresses(collection, gcon, flags, addr, sizeof(addr)/sizeof(addr[0])); + m = NET_EnumerateAddresses(collection, gcon, flags, addr, params, sizeof(addr)/sizeof(addr[0])); for (i = 0; i < m; i++) { @@ -358,7 +368,6 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha return con; } -#include "zlib.h" //if either remotecand is null, new packets will be sent to all. static qboolean ICE_SendSpam(struct icestate_s *con) { @@ -373,7 +382,7 @@ static qboolean ICE_SendSpam(struct icestate_s *con) //only send one ping to each. for (i = 0; i < MAX_CONNECTIONS; i++) { - if (collection->conn[i]) + if (collection->conn[i] && (collection->conn[i]->addrtype[0]==NA_IP||collection->conn[i]->addrtype[0]==NA_IPV6)) { for(rc = con->rc; rc; rc = rc->next) { @@ -408,7 +417,7 @@ static qboolean ICE_SendSpam(struct icestate_s *con) if (!NET_StringToAdr(bestpeer->info.addr, bestpeer->info.port, &to)) return true; - Con_DPrintf("Spam %i -> %s:%i\n", bestlocal, bestpeer->info.addr, bestpeer->info.port); + Con_DPrintf("ICE checking %s -> %s:%i\n", collection->conn[bestlocal]->name, bestpeer->info.addr, bestpeer->info.port); if (!con->controlled && NET_CompareAdr(&to, &con->chosenpeer)) usecandidate = true; @@ -489,7 +498,7 @@ static qboolean ICE_SendSpam(struct icestate_s *con) return false; } -void ICE_ToStunServer(struct icestate_s *con) +static void ICE_ToStunServer(struct icestate_s *con) { sizebuf_t buf; char data[512]; @@ -500,7 +509,7 @@ void ICE_ToStunServer(struct icestate_s *con) if (!con->stunrnd[0]) Sys_RandomBytes((char*)con->stunrnd, sizeof(con->stunrnd)); - Con_DPrintf("Spam stun %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); + Con_DPrintf("ICE: Checking public IP via %s\n", NET_AdrToString(data, sizeof(data), &con->pubstunserver)); memset(&buf, 0, sizeof(buf)); buf.maxsize = sizeof(data); @@ -528,7 +537,7 @@ void ICE_ToStunServer(struct icestate_s *con) NET_SendPacket(collection, buf.cursize, data, &con->pubstunserver); } -void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) +static void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) { struct icecandidate_s *o; qboolean isnew; @@ -548,12 +557,34 @@ void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n if (!peer.address.ip[0] && !peer.address.ip[1] && !peer.address.ip[2] && !peer.address.ip[3]) return; } - - for (o = con->rc; o; o = o->next) + else if (peer.type == NA_IPV6) { - //not sure that updating candidates is particuarly useful tbh, but hey. - if (!strcmp(o->info.candidateid, n->candidateid)) - break; + //ignore invalid addresses + int i; + for (i = 0; i < countof(peer.address.ip6); i++) + if (peer.address.ip6[i]) + break; + if (i == countof(peer.address.ip6)) + return; //all clear. in6_addr_any + } + + if (*n->candidateid) + { + for (o = con->rc; o; o = o->next) + { + //not sure that updating candidates is particuarly useful tbh, but hey. + if (!strcmp(o->info.candidateid, n->candidateid)) + break; + } + } + else + { + for (o = con->rc; o; o = o->next) + { + //avoid dupes. + if (!strcmp(o->info.addr, n->addr) && o->info.port == n->port) + break; + } } if (!o) { @@ -585,7 +616,104 @@ void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n Con_DPrintf("%s remote candidate %s: [%s]:%i\n", isnew?"Added":"Updated", o->info.candidateid, o->info.addr, o->info.port); } -qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *value) +static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *value); +static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) +{ + if (!strncmp(value, "a=ice-pwd:", 10)) + ICE_Set(con, "rpwd", value+10); + else if (!strncmp(value, "a=ice-ufrag:", 12)) + ICE_Set(con, "rufrag", value+12); + else if (!strncmp(value, "a=rtpmap:", 9)) + { + char name[64]; + int codec; + char *sl; + value += 9; + codec = strtoul(value, (char**)&value, 0); + if (*value == ' ') value++; + + COM_ParseOut(value, name, sizeof(name)); + sl = strchr(name, '/'); + if (sl) + *sl = '@'; + ICE_Set(con, va("codec%i", codec), name); + } + else if (!strncmp(value, "a=candidate:", 12)) + { + struct icecandinfo_s n; + memset(&n, 0, sizeof(n)); + + value += 12; + n.foundation = strtoul(value, (char**)&value, 0); + + if(*value == ' ')value++; + n.component = strtoul(value, (char**)&value, 0); + + if(*value == ' ')value++; + if (!strncmp(value, "UDP ", 4)) + { + n.transport = 0; + value += 3; + } + else + return; + + if(*value == ' ')value++; + n.priority = strtoul(value, (char**)&value, 0); + + if(*value == ' ')value++; + value = COM_ParseOut(value, n.addr, sizeof(n.addr)); + if (!value) return; + + if(*value == ' ')value++; + n.port = strtoul(value, (char**)&value, 0); + + if(*value == ' ')value++; + if (strncmp(value, "typ ", 4)) return; + value += 3; + + if(*value == ' ')value++; + if (!strncmp(value, "host", 4)) + n.type = ICE_HOST; + else if (!strncmp(value, "srflx", 4)) + n.type = ICE_SRFLX; + else if (!strncmp(value, "prflx", 4)) + n.type = ICE_PRFLX; + else if (!strncmp(value, "relay", 4)) + n.type = ICE_RELAY; + else + return; + + while (*value) + { + if(*value == ' ')value++; + if (!strncmp(value, "raddr ", 6)) + { + value += 6; + value = COM_ParseOut(value, n.reladdr, sizeof(n.reladdr)); + if (!value) + break; + } + else if (!strncmp(value, "rport ", 6)) + { + value += 6; + n.relport = strtoul(value, (char**)&value, 0); + } + else + { + //this is meant to be extensible. + while (*value && *value != ' ') + value++; + if(*value == ' ')value++; + while (*value && *value != ' ') + value++; + } + } + ICE_AddRCandidateInfo(con, &n); + } +} + +static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *value) { if (!strcmp(prop, "state")) { @@ -609,11 +737,13 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val #ifndef SERVERONLY if (con->state == ICE_CONNECTING && (con->proto == ICEP_QWCLIENT || con->proto == ICEP_VOICE)) - NET_InitClient(false); + if (!cls.sockets) + NET_InitClient(false); #endif if (oldstate != con->state && con->state == ICE_CONNECTED) { + char msg[256]; if (con->chosenpeer.type == NA_INVALID) { con->state = ICE_FAILED; @@ -622,10 +752,9 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val #ifndef SERVERONLY else if (con->proto == ICEP_QWCLIENT) { - char msg[256]; //FIXME: should make a proper connection type for this so we can switch to other candidates if one route goes down // Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); - Cbuf_AddText(va("\nconnect \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); + Cbuf_AddText(va("\ncl_transfer \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); } #endif #ifndef CLIENTONLY @@ -636,7 +765,7 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val } #endif if (con->state == ICE_CONNECTED) - Con_Printf("%s connection established.\n", con->proto == ICEP_VOICE?"voice":"Quake"); + Con_Printf("%s connection established (peer %s).\n", con->proto == ICEP_VOICE?"voice":"data", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); } #if !defined(SERVERONLY) && defined(VOICECHAT) @@ -690,107 +819,23 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val } else if (!strcmp(prop, "sdp")) { + char line[8192]; const char *eol; for (; *value; value = eol) { eol = strchr(value, '\n'); if (!eol) eol = value+strlen(value); - else + + if (eol-value < sizeof(line)) + { + memcpy(line, value, eol-value); + line[eol-value] = 0; + ICE_ParseSDPLine(con, line); + } + + if (eol) eol++; - - if (!strncmp(value, "a=ice-pwd:", 10)) - ICE_Set(con, "rpwd", value+10); - else if (!strncmp(value, "a=ice-ufrag:", 12)) - ICE_Set(con, "rufrag", value+12); - else if (!strncmp(value, "a=rtpmap:", 9)) - { - char name[64]; - int codec; - char *sl; - value += 9; - codec = strtoul(value, (char**)&value, 0); - if (*value == ' ') value++; - - COM_ParseOut(value, name, sizeof(name)); - sl = strchr(name, '/'); - if (sl) - *sl = '@'; - ICE_Set(con, va("codec%i", codec), name); - } - else if (!strncmp(value, "a=candidate:", 12)) - { - struct icecandinfo_s n; - memset(&n, 0, sizeof(n)); - - value += 12; - n.foundation = strtoul(value, (char**)&value, 0); - - if(*value == ' ')value++; - n.component = strtoul(value, (char**)&value, 0); - - if(*value == ' ')value++; - if (!strncmp(value, "UDP ", 4)) - { - n.transport = 0; - value += 3; - } - else - break; - - if(*value == ' ')value++; - n.priority = strtoul(value, (char**)&value, 0); - - if(*value == ' ')value++; - value = COM_ParseOut(value, n.addr, sizeof(n.addr)); - if (!value) break; - - if(*value == ' ')value++; - n.port = strtoul(value, (char**)&value, 0); - - if(*value == ' ')value++; - if (strncmp(value, "typ ", 4)) break; - value += 3; - - if(*value == ' ')value++; - if (!strncmp(value, "host", 4)) - n.type = ICE_HOST; - else if (!strncmp(value, "srflx", 4)) - n.type = ICE_SRFLX; - else if (!strncmp(value, "prflx", 4)) - n.type = ICE_PRFLX; - else if (!strncmp(value, "relay", 4)) - n.type = ICE_RELAY; - else - break; - - while (value < eol) - { - if(*value == ' ')value++; - if (!strncmp(value, "raddr ", 6)) - { - value += 6; - value = COM_ParseOut(value, n.reladdr, sizeof(n.reladdr)); - if (!value) - break; - } - else if (!strncmp(value, "rport ", 6)) - { - value += 6; - n.relport = strtoul(value, (char**)&value, 0); - } - else - { - //this is meant to be extensible. - while (*value && value < eol && *value != ' ') - value++; - if(*value == ' ')value++; - while (*value && value < eol && *value != ' ') - value++; - } - } - ICE_AddRCandidateInfo(con, &n); - } } } else @@ -808,10 +853,10 @@ static char *ICE_CandidateToSDP(struct icecandidate_s *can, char *value, size_t case ICE_PRFLX: ctype = "prflx"; break; case ICE_RELAY: ctype = "relay"; break; } - Q_snprintfz(value, valuelen, "candidate:%i %i %s %i %s %i typ %s", + Q_snprintfz(value, valuelen, "a=candidate:%i %i %s %i %s %i typ %s", can->info.foundation, can->info.component, - can->info.transport==0?"udp":"ERROR", + can->info.transport==0?"UDP":"ERROR", can->info.priority, can->info.addr, can->info.port, @@ -820,18 +865,35 @@ static char *ICE_CandidateToSDP(struct icecandidate_s *can, char *value, size_t Q_strncatz(value, va(" generation %i", can->info.generation), valuelen); if (can->info.type != ICE_HOST) { - Q_strncatz(value, va(" raddr %s", can->info.reladdr), valuelen); + if (*can->info.reladdr) + Q_strncatz(value, va(" raddr %s", can->info.reladdr), valuelen); Q_strncatz(value, va(" rport %i", can->info.relport), valuelen); } return value; } -qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, size_t valuelen) +static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, size_t valuelen) { if (!strcmp(prop, "sid")) Q_strncpyz(value, con->conname, valuelen); else if (!strcmp(prop, "state")) - Q_snprintfz(value, valuelen, "%i", con->state); + { + switch(con->state) + { + case ICE_INACTIVE: + Q_strncpyz(value, STRINGIFY(ICE_INACTIVE), valuelen); + break; + case ICE_FAILED: + Q_strncpyz(value, STRINGIFY(ICE_FAILED), valuelen); + break; + case ICE_CONNECTING: + Q_strncpyz(value, STRINGIFY(ICE_CONNECTING), valuelen); + break; + case ICE_CONNECTED: + Q_strncpyz(value, STRINGIFY(ICE_CONNECTED), valuelen); + break; + } + } else if (!strcmp(prop, "lufrag")) Q_strncpyz(value, con->lufrag, valuelen); else if (!strcmp(prop, "lpwd")) @@ -871,8 +933,9 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, si netadr_t addr[1]; struct ftenet_generic_connection_s *gcon[countof(addr)]; int flags[countof(addr)]; + const char *params[countof(addr)]; - if (!NET_EnumerateAddresses(ICE_PickConnection(con), gcon, flags, addr, countof(addr))) + if (!NET_EnumerateAddresses(ICE_PickConnection(con), gcon, flags, addr, params, countof(addr))) sender.type = NA_INVALID; else sender = *addr; @@ -886,10 +949,12 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, si Q_strncatz(value, va("a=ice-pwd:%s\n", con->lpwd), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); +// if (net_enable_dtls.ival) +// Q_strncatz(value, va("a=fingerprint:SHA-1 XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX\n", con->fingerprint), valuelen); if (con->proto == ICEP_QWSERVER || con->proto == ICEP_QWCLIENT) { #ifdef HAVE_DTLS - Q_strncatz(value, "m=application 9 DTLS/SCTP 5000\n", valuelen); +// Q_strncatz(value, "m=application 9 DTLS/SCTP 5000\n", valuelen); #endif } @@ -917,7 +982,6 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, si { char canline[256]; can->dirty = false; //doesn't matter now. - Q_strncatz(value, "a=\n", valuelen); ICE_CandidateToSDP(can, canline, sizeof(canline)); Q_strncatz(value, canline, valuelen); Q_strncatz(value, "\n", valuelen); @@ -928,7 +992,36 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, si return false; return true; } -qboolean QDECL ICE_GetLCandidateSDP(struct icestate_s *con, char *out, size_t outsize) + +static void ICE_Debug(struct icestate_s *con) +{ + struct icecandidate_s *can; + char buf[65536]; + ICE_Get(con, "state", buf, sizeof(buf)); + Con_Printf("ICE \"%s\" (%s):\n", con->friendlyname, buf); + ICE_Get(con, "sdp", buf, sizeof(buf)); + Con_Printf(S_COLOR_YELLOW "%s\n", buf); + + Con_Printf("local:\n"); + for (can = con->lc; can; can = can->next) + { + ICE_CandidateToSDP(can, buf, sizeof(buf)); + if (can->dirty) + Con_Printf(S_COLOR_RED" %s\n", buf); + else + Con_Printf(S_COLOR_YELLOW" %s\n", buf); + } + Con_Printf("remote:\n"); + for (can = con->rc; can; can = can->next) + { + ICE_CandidateToSDP(can, buf, sizeof(buf)); + if (can->dirty) + Con_Printf(S_COLOR_RED" %s\n", buf); + else + Con_Printf(S_COLOR_YELLOW" %s\n", buf); + } +} +static qboolean QDECL ICE_GetLCandidateSDP(struct icestate_s *con, char *out, size_t outsize) { struct icecandidate_s *can; for (can = con->lc; can; can = can->next) @@ -943,7 +1036,7 @@ qboolean QDECL ICE_GetLCandidateSDP(struct icestate_s *con, char *out, size_t ou } return false; } -struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) +static struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) { struct icecandidate_s *can; for (can = con->lc; can; can = can->next) @@ -964,6 +1057,34 @@ void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrn if (!con) return; + switch(adr->type) + { + case NA_IP: + if (adr->address.ip[0] == 127) + return; //Addresses from a loopback interface MUST NOT be included in the candidate addresses + break; + case NA_IPV6: + if (!memcmp(adr->address.ip6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", 16)) + return; //Addresses from a loopback interface MUST NOT be included in the candidate addresses + if (adr->address.ip6[0] == 0xfe && (adr->address.ip6[1]&0xc0)==0x80) + return; //fe80::/10 link local addresses should also not be reported. + break; + default: + return; //no, just no. + } + switch(adr->prot) + { + case NP_DTLS: + case NP_DGRAM: + break; + default: + return; //don't report any tcp/etc connections... + } + + //only consider private IP addresses when we're allowed to do so (ignore completely so we don't ignore them if they're srflx). + if (!net_ice_exchangeprivateips.ival && type == ICE_HOST) + return; + for (cand = con->lc; cand; cand = cand->next) { if (NET_CompareAdr(adr, &cand->peer)) @@ -1082,7 +1203,7 @@ void ICE_Tick(void) } } } -void QDECL ICE_Close(struct icestate_s *con) +static void QDECL ICE_Close(struct icestate_s *con) { struct icestate_s **link; @@ -1100,7 +1221,7 @@ void QDECL ICE_Close(struct icestate_s *con) link = &(*link)->next; } } -void QDECL ICE_CloseModule(void *module) +static void QDECL ICE_CloseModule(void *module) { struct icestate_s **link, *con; @@ -1128,14 +1249,16 @@ icefuncs_t iceapi = ICE_CloseModule, ICE_GetLCandidateSDP }; +#endif +#if defined(SUPPORT_ICE) || defined(MASTERONLY) qboolean ICE_WasStun(ftenet_connections_t *col) { #if defined(HAVE_CLIENT) && defined(VOICECHAT) if (col == cls.sockets) { if (NET_RTP_Parse()) - return true; + return true; } #endif @@ -1143,6 +1266,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) { stunhdr_t *stun = (stunhdr_t*)net_message.data; int stunlen = BigShort(stun->msglen); +#ifdef SUPPORT_ICE if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) { //binding reply (or error) @@ -1204,9 +1328,8 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->mode != ICEM_ICE) continue; - //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. if (NET_CompareAdr(&net_from, &con->pubstunserver)) - { + { //check to see if this is a new server-reflexive address, which happens when the peer is behind a nat. for (rc = con->lc; rc; rc = rc->next) { if (NET_CompareAdr(&adr, &rc->peer)) @@ -1214,24 +1337,34 @@ qboolean ICE_WasStun(ftenet_connections_t *col) } if (!rc) { - struct icecandidate_s *rc; - rc = Z_Malloc(sizeof(*rc)); - rc->next = con->lc; - con->lc = rc; - rc->peer = adr; - NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &adr); - rc->info.port = ntohs(adr.port); - rc->info.type = ICE_SRFLX; - rc->info.component = 1; - rc->dirty = true; - rc->info.priority = 1; //FIXME + netadr_t reladdr; + int relflags; + const char *relpath; + int rnd[2]; + struct icecandidate_s *src; //server Reflexive Candidate + src = Z_Malloc(sizeof(*src)); + src->next = con->lc; + con->lc = src; + src->peer = adr; + NET_BaseAdrToString(src->info.addr, sizeof(src->info.addr), &adr); + src->info.port = ntohs(adr.port); + col->conn[net_from.connum-1]->GetLocalAddresses(col->conn[net_from.connum-1], &relflags, &reladdr, &relpath, 1); + //FIXME: we don't really know which one... NET_BaseAdrToString(src->info.reladdr, sizeof(src->info.reladdr), &reladdr); + src->info.relport = ntohs(reladdr.port); + src->info.type = ICE_SRFLX; + src->info.component = 1; + src->dirty = true; + src->info.priority = 1; //FIXME - Con_DPrintf("ICE: Public address: %s\n", rc->info.addr); + Sys_RandomBytes((void*)rnd, sizeof(rnd)); + Q_strncpyz(src->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(src->info.candidateid)); + + Con_DPrintf("ICE: Public address: %s\n", NET_AdrToString(str, sizeof(str), &adr)); } con->stunretry = Sys_Milliseconds() + 60*1000; } else - { + { //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. for (rc = con->rc; rc; rc = rc->next) { if (NET_CompareAdr(&net_from, &rc->peer)) @@ -1282,22 +1415,30 @@ qboolean ICE_WasStun(ftenet_connections_t *col) //binding indication. used as an rtp keepalive. return true; } - else if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + else +#endif + if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) { char username[256]; char integrity[20]; - char *integritypos = NULL; - int role = 0; +#ifdef SUPPORT_ICE struct icestate_s *con; + int role = 0; unsigned int tiehigh = 0; unsigned int tielow = 0; qboolean usecandidate = false; - int error = 0; unsigned int priority = 0; +#endif + char *integritypos = NULL; + int error = 0; + + sizebuf_t buf; + char data[512]; + int alen = 0, atype = 0, aofs = 0; + int crc; //binding request stunattr_t *attr = (stunattr_t*)(stun+1); - int alen; *username = 0; while(stunlen) { @@ -1326,6 +1467,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) memcpy(integrity, attr+1, sizeof(integrity)); integritypos = (char*)(attr+1); break; +#ifdef SUPPORT_ICE case 0x24: //priority // Con_Printf("priority = \"%i\"\n", priority); @@ -1335,10 +1477,12 @@ qboolean ICE_WasStun(ftenet_connections_t *col) //USE-CANDIDATE usecandidate = true; break; +#endif case 0x8028: //fingerprint // Con_Printf("fingerprint = \"%08x\"\n", BigLong(*(int*)(attr+1))); break; +#ifdef SUPPORT_ICE case 0x8029://ice controlled case 0x802A://ice controlling role = (unsigned short)BigShort(attr->attrtype); @@ -1346,48 +1490,45 @@ qboolean ICE_WasStun(ftenet_connections_t *col) tiehigh = BigLong(((int*)(attr+1))[0]); tielow = BigLong(((int*)(attr+1))[1]); break; +#endif } alen = (alen+3)&~3; attr = (stunattr_t*)((char*)(attr+1) + alen); stunlen -= alen+sizeof(*attr); } - //we need to know which connection its from in order to validate the integrity - for (con = icelist; con; con = con->next) +#ifdef SUPPORT_ICE + if (*username || integritypos) { - if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) - break; - } - if (!con) - { - Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); - } - else - { - if (integritypos) + //we need to know which connection its from in order to validate the integrity + for (con = icelist; con; con = con->next) { - char key[20]; - //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. - stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); - HMAC(SHA1_m, key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); - if (memcmp(key, integrity, sizeof(integrity))) - { - Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); - return true; - } + if (!strcmp(va("%s:%s", con->lufrag, con->rufrag), username)) + break; } - - if (con->state != ICE_INACTIVE) + if (!con) + { + Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); + return true; + } + else if (con->state == ICE_INACTIVE) + return true; //bad timing + else { - sizebuf_t buf; - char data[512]; - int alen = 0, atype = 0, aofs = 0; - int crc; struct icecandidate_s *rc; - memset(&buf, 0, sizeof(buf)); - buf.maxsize = sizeof(data); - buf.cursize = 0; - buf.data = data; + + if (integritypos) + { + char key[20]; + //the hmac is a bit weird. the header length includes the integrity attribute's length, but the checksum doesn't even consider the attribute header. + stun->msglen = BigShort(integritypos+sizeof(integrity) - (char*)stun - sizeof(*stun)); + HMAC(SHA1_m, key, sizeof(key), (qbyte*)stun, integritypos-4 - (char*)stun, con->lpwd, strlen(con->lpwd)); + if (memcmp(key, integrity, sizeof(integrity))) + { + Con_DPrintf("Integrity is bad! needed %x got %x\n", *(int*)key, *(int*)integrity); + return true; + } + } //check to see if this is a new peer-reflexive address, which happens when the peer is behind a nat. for (rc = con->rc; rc; rc = rc->next) @@ -1397,13 +1538,20 @@ qboolean ICE_WasStun(ftenet_connections_t *col) } if (!rc) { + netadr_t reladdr; + int relflags; + const char *relpath; struct icecandidate_s *rc; rc = Z_Malloc(sizeof(*rc)); rc->next = con->rc; con->rc = rc; + rc->peer = net_from; NET_BaseAdrToString(rc->info.addr, sizeof(rc->info.addr), &net_from); rc->info.port = ntohs(net_from.port); + col->conn[net_from.connum-1]->GetLocalAddresses(col->conn[net_from.connum-1], &relflags, &reladdr, &relpath, 1); + //FIXME: we don't really know which one... NET_BaseAdrToString(rc->info.reladdr, sizeof(rc->info.reladdr), &reladdr); + rc->info.relport = ntohs(reladdr.port); rc->info.type = ICE_PRFLX; rc->dirty = true; rc->info.priority = priority; @@ -1444,97 +1592,662 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (con->state == ICE_CONNECTING) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } - - if (net_from.type == NA_IP) - { - alen = 4; - atype = 1; - aofs = 0; - } - else if (net_from.type == NA_IPV6 && - !*(int*)&net_from.address.ip6[0] && - !*(int*)&net_from.address.ip6[4] && - !*(short*)&net_from.address.ip6[8] && - *(short*)&net_from.address.ip6[10] == (short)0xffff) - { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... - alen = 4; - atype = 1; - aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); - } - else if (net_from.type == NA_IPV6) - { - alen = 16; - atype = 2; - aofs = 0; - } - - MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); - MSG_WriteShort(&buf, BigShort(0)); //fill in later - MSG_WriteLong(&buf, stun->magiccookie); - MSG_WriteLong(&buf, stun->transactid[0]); - MSG_WriteLong(&buf, stun->transactid[1]); - MSG_WriteLong(&buf, stun->transactid[2]); - - if (error == 87) - { - char *txt = "Role Conflict"; - MSG_WriteShort(&buf, BigShort(0x0009)); - MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); - MSG_WriteShort(&buf, 0); //reserved - MSG_WriteByte(&buf, 0); //class - MSG_WriteByte(&buf, error); //code - SZ_Write(&buf, txt, strlen(txt)); //readable - while(buf.cursize&3) //padding - MSG_WriteChar(&buf, 0); - } - else if (1) - { //xor mapped - MSG_WriteShort(&buf, BigShort(0x0020)); - MSG_WriteShort(&buf, BigShort(4+alen)); - MSG_WriteShort(&buf, BigShort(atype)); - MSG_WriteShort(&buf, net_from.port); - SZ_Write(&buf, (char*)&net_from.address + aofs, alen); - } - else - { //non-xor mapped - MSG_WriteShort(&buf, BigShort(0x0001)); - MSG_WriteShort(&buf, BigShort(4+alen)); - MSG_WriteShort(&buf, BigShort(atype)); - MSG_WriteShort(&buf, net_from.port); - SZ_Write(&buf, (char*)&net_from.address + aofs, alen); - } - - MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME - MSG_WriteShort(&buf, BigShort(strlen(username))); - SZ_Write(&buf, username, strlen(username)); - while(buf.cursize&3) - MSG_WriteChar(&buf, 0); - - //message integrity is a bit annoying - data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute - data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; - //but the hash is to the start of the attribute's header - HMAC(SHA1_m, integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); - MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY - MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length - SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data - - data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length - data[3] = ((buf.cursize+8-20)>>0)&0xff; - crc = crc32(0, data, buf.cursize)^0x5354554e; - MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT - MSG_WriteShort(&buf, BigShort(sizeof(crc))); - MSG_WriteLong(&buf, BigLong(crc)); - - data[2] = ((buf.cursize-20)>>8)&0xff; - data[3] = ((buf.cursize-20)>>0)&0xff; - NET_SendPacket(col, buf.cursize, data, &net_from); } + }//otherwise its just an ip check + else + con = NULL; +#else + (void)integritypos; +#endif + + memset(&buf, 0, sizeof(buf)); + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + if (net_from.type == NA_IP) + { + alen = 4; + atype = 1; + aofs = 0; } + else if (net_from.type == NA_IPV6 && + !*(int*)&net_from.address.ip6[0] && + !*(int*)&net_from.address.ip6[4] && + !*(short*)&net_from.address.ip6[8] && + *(short*)&net_from.address.ip6[10] == (short)0xffff) + { //just because we use an ipv6 address for ipv4 internally doesn't mean we should tell the peer that they're on ipv6... + alen = 4; + atype = 1; + aofs = sizeof(net_from.address.ip6) - sizeof(net_from.address.ip); + } + else if (net_from.type == NA_IPV6) + { + alen = 16; + atype = 2; + aofs = 0; + } + else + { + alen = 0; + atype = 0; + } + +//Con_DPrintf("STUN from %s\n", NET_AdrToString(data, sizeof(data), &net_from)); + + MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); + MSG_WriteShort(&buf, BigShort(0)); //fill in later + MSG_WriteLong(&buf, stun->magiccookie); + MSG_WriteLong(&buf, stun->transactid[0]); + MSG_WriteLong(&buf, stun->transactid[1]); + MSG_WriteLong(&buf, stun->transactid[2]); + + if (error == 87) + { + char *txt = "Role Conflict"; + MSG_WriteShort(&buf, BigShort(0x0009)); + MSG_WriteShort(&buf, BigShort(4 + strlen(txt))); + MSG_WriteShort(&buf, 0); //reserved + MSG_WriteByte(&buf, 0); //class + MSG_WriteByte(&buf, error); //code + SZ_Write(&buf, txt, strlen(txt)); //readable + while(buf.cursize&3) //padding + MSG_WriteChar(&buf, 0); + } + else if (1) + { //xor mapped + netadr_t xored = net_from; + int i; + xored.port ^= *(short*)(data+4); + for (i = 0; i < alen; i++) + ((qbyte*)&xored.address)[aofs+i] ^= ((qbyte*)data+4)[i]; + MSG_WriteShort(&buf, BigShort(0x0020)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, xored.port); + SZ_Write(&buf, (char*)&xored.address + aofs, alen); + } + else + { //non-xor mapped + MSG_WriteShort(&buf, BigShort(0x0001)); + MSG_WriteShort(&buf, BigShort(4+alen)); + MSG_WriteShort(&buf, BigShort(atype)); + MSG_WriteShort(&buf, net_from.port); + SZ_Write(&buf, (char*)&net_from.address + aofs, alen); + } + + MSG_WriteShort(&buf, BigShort(0x6)); //USERNAME + MSG_WriteShort(&buf, BigShort(strlen(username))); + SZ_Write(&buf, username, strlen(username)); + while(buf.cursize&3) + MSG_WriteChar(&buf, 0); + +#ifdef SUPPORT_ICE + if (con) + { + //message integrity is a bit annoying + data[2] = ((buf.cursize+4+sizeof(integrity)-20)>>8)&0xff; //hashed header length is up to the end of the hmac attribute + data[3] = ((buf.cursize+4+sizeof(integrity)-20)>>0)&0xff; + //but the hash is to the start of the attribute's header + HMAC(SHA1_m, integrity, sizeof(integrity), data, buf.cursize, con->lpwd, strlen(con->lpwd)); + MSG_WriteShort(&buf, BigShort(0x8)); //MESSAGE-INTEGRITY + MSG_WriteShort(&buf, BigShort(sizeof(integrity))); //sha1 key length + SZ_Write(&buf, integrity, sizeof(integrity)); //integrity data + } +#endif + + data[2] = ((buf.cursize+8-20)>>8)&0xff; //dummy length + data[3] = ((buf.cursize+8-20)>>0)&0xff; + crc = crc32(0, data, buf.cursize)^0x5354554e; + MSG_WriteShort(&buf, BigShort(0x8028)); //FINGERPRINT + MSG_WriteShort(&buf, BigShort(sizeof(crc))); + MSG_WriteLong(&buf, BigLong(crc)); + + data[2] = ((buf.cursize-20)>>8)&0xff; + data[3] = ((buf.cursize-20)>>0)&0xff; + NET_SendPacket(col, buf.cursize, data, &net_from); return true; } } return false; } +#endif + + +#ifdef SUPPORT_ICE +//this is the clientside part of our custom accountless broker protocol +//basically just keeps the broker processing, but doesn't send/receive actual game packets. +//inbound messages can change ice connection states. +//clients only handle one connection. servers need to handle multiple +typedef struct { + ftenet_generic_connection_t generic; + + //config state + char brokername[64]; //dns name + netadr_t brokeradr; //actual ip + char gamename[64]; //what we're trying to register as/for with the broker + + //broker connection state + vfsfile_t *broker; + qboolean handshaking; + double nextping; //send heartbeats every now and then + double heartbeat; + double timeout; //detect if the broker goes dead, so we can reconnect reliably (instead of living for two hours without anyone able to connect). + qbyte in[8192]; + size_t insize; + qbyte out[8192]; + size_t outsize; + int error; //outgoing data is corrupt. kill it. + + //client state... + struct icestate_s *ice; + int serverid; + + //server state... + struct + { + struct icestate_s *ice; + } *clients; + size_t numclients; +} ftenet_ice_connection_t; +static void FTENET_ICE_Close(ftenet_generic_connection_t *gcon) +{ + ftenet_ice_connection_t *b = (void*)gcon; + int cl; + if (b->broker) + VFS_CLOSE(b->broker); + + for (cl = 0; cl < b->numclients; cl++) + if (b->clients[cl].ice) + iceapi.ICE_Close(b->clients[cl].ice); + Z_Free(b->clients); + if (b->ice) + iceapi.ICE_Close(b->ice); + + Z_Free(b); +} + +static void FTENET_ICE_Flush(ftenet_ice_connection_t *b) +{ + int r; + if (!b->outsize || b->error || !b->broker) + return; + r = VFS_WRITE(b->broker, b->out, b->outsize); + if (r > 0) + { + b->outsize -= r; + memmove(b->out, b->out+r, b->outsize); + } + if (r < 0) + b->error = true; +} +static neterr_t FTENET_ICE_SendPacket(ftenet_generic_connection_t *gcon, int length, const void *data, netadr_t *to) +{ + ftenet_ice_connection_t *b = (void*)gcon; + if (to->prot != NP_RTC_TCP && to->prot != NP_RTC_TLS) + return NETERR_NOROUTE; + if (!NET_CompareAdr(to, &b->brokeradr)) + return NETERR_NOROUTE; //its using some other broker, don't bother trying to handle it here. + if (b->error) + return NETERR_DISCONNECTED; + return NETERR_CLOGGED; //we'll switch to a connect localcmd when the connection completes, so we don't really need to send any packets when they're using ICE. Just make sure the client doesn't give up. +} + +static void FTENET_ICE_SplurgeRaw(ftenet_ice_connection_t *b, const qbyte *data, size_t len) +{ +//0: dropclient (cl=-1 drops entire connection) + if (b->outsize+len > sizeof(b->out)) + b->error = true; + else + { + memcpy(b->out+b->outsize, data, len); + b->outsize += len; + } +} +static void FTENET_ICE_SplurgeWS(ftenet_ice_connection_t *b, enum websocketpackettype_e pkttype, const qbyte *data1, size_t len1, const qbyte *data2, size_t len2) +{ + size_t tlen = len1+len2; + qbyte header[8]; + header[0] = 0x80|pkttype; + if (tlen >= 126) + { + header[1] = 126; + header[2] = tlen>>8; //bigendian + header[3] = tlen&0xff; + FTENET_ICE_SplurgeRaw(b, header, 4); + } + else + { //small data + header[1] = tlen; + FTENET_ICE_SplurgeRaw(b, header, 2); + } + FTENET_ICE_SplurgeRaw(b, data1, len1); + FTENET_ICE_SplurgeRaw(b, data2, len2); +} +static void FTENET_ICE_SplurgeCmd(ftenet_ice_connection_t *b, int icemsg, int cl, const char *data) +{ + qbyte msg[3] = {icemsg, cl&0xff, (cl>>8)&0xff}; //little endian... + FTENET_ICE_SplurgeWS(b, WS_PACKETTYPE_BINARYFRAME, msg, sizeof(msg), data, strlen(data)); +} +static void FTENET_ICE_Heartbeat(ftenet_ice_connection_t *b) +{ + b->heartbeat = realtime+30; +#ifdef HAVE_SERVER + if (b->generic.islisten) + { + extern cvar_t maxclients; + char info[2048]; + int i; + client_t *cl; + int numclients = 0; + for (i=0 ; istate == cs_connected || cl->state == cs_spawned || cl->name[0]) && !cl->spectator) + numclients++; + } + + *info = 0; + Info_SetValueForKey(info, "protocol", com_protocolversion.string, sizeof(info)); + Info_SetValueForKey(info, "maxclients", maxclients.string, sizeof(info)); + Info_SetValueForKey(info, "clients", va("%i", numclients), sizeof(info)); + Info_SetValueForKey(info, "hostname", hostname.string, sizeof(info)); + Info_SetValueForKey(info, "modname", FS_GetGamedir(true), sizeof(info)); + Info_SetValueForKey(info, "mapname", InfoBuf_ValueForKey(&svs.info, "map"), sizeof(info)); + + FTENET_ICE_SplurgeCmd(b, ICEMSG_SERVERINFO, -1, info); + } +#endif +} +static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct icestate_s **ret) +{ //sends offer + char buf[8192]; + struct icestate_s *ice; + if (*ret) + iceapi.ICE_Close(*ret); + ice = *ret = iceapi.ICE_Create(b, NULL, "", ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT); + if (!*ret) + return; //some kind of error?!? + iceapi.ICE_Set(ice, "controller", b->generic.islisten?"0":"1"); + + Q_snprintfz(buf, sizeof(buf), "%i", BigShort(b->brokeradr.port)); + iceapi.ICE_Set(ice, "stunport", buf); + iceapi.ICE_Set(ice, "stunip", b->brokername); + + //okay, now send the sdp to our peer. + if (iceapi.ICE_Get(ice, "sdp", buf, sizeof(buf))) + FTENET_ICE_SplurgeCmd(b, ICEMSG_OFFER, cl, buf); +} +static void FTENET_ICE_Refresh(ftenet_ice_connection_t *b, int cl, struct icestate_s *ice) +{ //sends offer + char buf[8192]; + while (ice && iceapi.ICE_GetLCandidateSDP(ice, buf, sizeof(buf))) + FTENET_ICE_SplurgeCmd(b, ICEMSG_CANDIDATE, cl, buf); +} +static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon) +{ + ftenet_ice_connection_t *b = (void*)gcon; + int ctrl, len, cmd, cl, ofs; + char *data, n; + +#ifdef HAVE_CLIENT + //there's no point in hanging on to the ICE connection if we're not going to do anything with it now that we're connected. + //(shutting off the tcp connection will notify the server to shut down too) + if (!b->generic.islisten && !CL_TryingToConnect()) + b->error = true; + else +#endif + if (!b->broker) + { + const char *s; + if (b->timeout > realtime) + return false; + b->generic.thesocket = TCP_OpenStream(&b->brokeradr); //save this for select. + b->broker = FS_OpenTCPSocket(b->generic.thesocket, true, b->brokername); + +#ifdef HAVE_SSL + //convert to tls... + if (b->brokeradr.prot == NP_TLS || b->brokeradr.prot == NP_RTC_TLS) + b->broker = FS_OpenSSL(b->brokername, b->broker, false); +#endif + + if (!b->broker) + { + b->timeout = realtime + 30; + Con_Printf("rtc broker connection to %s failed (retry: 30 secs)\n", b->brokername); + return false; + } + + b->insize = b->outsize = 0; + + COM_Parse(com_protocolname.string); + + b->handshaking = true; + s = va("GET /%s/%s HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Protocol: %s\r\n" + "\r\n", com_token, b->gamename, b->brokername, b->generic.islisten?"rtc_host":"rtc_client"); + FTENET_ICE_SplurgeRaw(b, s, strlen(s)); + b->heartbeat = realtime; + b->nextping = realtime + 100; + b->timeout = realtime + 270; + } + if (b->error) + { +handleerror: + b->generic.thesocket = INVALID_SOCKET; + if (b->broker) + VFS_CLOSE(b->broker); + b->broker = NULL; + + for (cl = 0; cl < b->numclients; cl++) + { + if (b->clients[cl].ice) + iceapi.ICE_Close(b->clients[cl].ice); + b->clients[cl].ice = NULL; + } + if (b->ice) + iceapi.ICE_Close(b->ice); + b->ice = NULL; + if (b->error != 1 || !b->generic.islisten) + return false; //permanant error... + b->error = false; + b->insize = b->outsize = 0; + b->timeout = realtime + 30; + return false; + } + + //keep checking for new candidate info. + if (b->ice) + FTENET_ICE_Refresh(b, b->serverid, b->ice); + for (cl = 0; cl < b->numclients; cl++) + if (b->clients[cl].ice) + FTENET_ICE_Refresh(b, cl, b->clients[cl].ice); + if (realtime >= b->heartbeat) + FTENET_ICE_Heartbeat(b); + + len = VFS_READ(b->broker, b->in+b->insize, sizeof(b->in)-1-b->insize); + if (!len) + { + FTENET_ICE_Flush(b); + + if (realtime > b->nextping) + { //nothing happening... make sure the connection isn't dead... + FTENET_ICE_SplurgeWS(b, WS_PACKETTYPE_PING, NULL, 0, NULL, 0); + b->nextping = realtime + 100; + } + return false; //nothing new + } + if (len < 0) + { + if (!b->error) + Con_Printf("rtc broker connection to %s failed (retry: 30 secs)\n", b->brokername); + b->error = true; + goto handleerror; + } + b->insize += len; + b->in[b->insize] = 0; + ofs = 0; + + b->nextping = realtime + 100; + b->timeout = max(b->timeout, realtime + 270); + + if (b->handshaking) + { //we're still waiting for an http 101 code. websocket data starts straight after. + char *end = strstr(b->in, "\r\n\r\n"); + if (!end) + return false; //not available yet... + if (strncmp(b->in, "HTTP/1.1 101 ", 13)) + { + b->error = ~0; + return false; + } + end+=4; + b->handshaking = false; //done... + + ofs = (qbyte*)end-b->in; + } + + while (b->insize >= ofs+2) + { + ctrl = b->in[ofs+0]; + len = b->in[ofs+1]; + ofs+=2; + if (len > 126) + {//unsupported + b->error = 1; + break; + } + else if (len == 126) + { + if (b->insize <= 4) + break; + len = (b->in[ofs+0]<<8)|(b->in[ofs+1]); + ofs+=2; + } + if (b->insize < ofs+len) + break; + n = b->in[ofs+len]; + b->in[ofs+len] = 0; + + switch(ctrl & 0xf) + { + case WS_PACKETTYPE_PING: + FTENET_ICE_SplurgeWS(b, WS_PACKETTYPE_PONG, NULL, 0, b->in+ofs, len); + FTENET_ICE_Flush(b); + break; + case WS_PACKETTYPE_CLOSE: + b->error = true; + break; + default: + break; + case WS_PACKETTYPE_BINARYFRAME: + cmd = b->in[ofs]; + cl = (short)(b->in[ofs+1] | (b->in[ofs+2]<<8)); + data = b->in+ofs+3; + + switch(cmd) + { + case ICEMSG_PEERDROP: //connection closing... + if (cl == -1) + { + b->error = true; +// Con_Printf("Broker closed connection: %s\n", data); + } + else if (cl >= 0 && cl < b->numclients) + { + if (b->clients[cl].ice) + iceapi.ICE_Close(b->clients[cl].ice); + b->clients[cl].ice = NULL; +// Con_Printf("Broker closing connection: %s\n", data); + } + break; + case ICEMSG_GREETING: //reports the trailing url we're 'listening' on. anyone else using that url will connect to us. + data = strchr(data, '/'); + if (data++) + Q_strncpyz(b->gamename, data, sizeof(b->gamename)); + Con_Printf("Publicly listening on /%s\n", b->gamename); + break; + case ICEMSG_NEWPEER: //connection established with a new peer + //note that the server ought to wait for an offer from the client before replying with any ice state, but it doesn't really matter for our use-case. + if (b->generic.islisten) + { +// Con_DPrintf("Client connecting: %s\n", data); + if (cl < 1024 && cl >= b->numclients) + { //looks like a new one... but don't waste memory + Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0])); + } + if (cl >= 0 && cl < b->numclients) + FTENET_ICE_Establish(b, cl, &b->clients[cl].ice); + } + else + { +// Con_DPrintf("Server found: %s\n", data); + FTENET_ICE_Establish(b, cl, &b->ice); + b->serverid = cl; + } + break; + case ICEMSG_OFFER: //we received an offer from a client + if (b->generic.islisten) + { +// Con_Printf("Client offered: %s\n", data); + if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) + { + iceapi.ICE_Set(b->clients[cl].ice, "sdp", data); + iceapi.ICE_Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING)); + } + } + else + { +// Con_Printf("Server offered: %s\n", data); + if (b->ice) + { + iceapi.ICE_Set(b->ice, "sdp", data); + iceapi.ICE_Set(b->ice, "state", STRINGIFY(ICE_CONNECTING)); + } + } + break; + case ICEMSG_CANDIDATE: +// Con_Printf("Candidate update: %s\n", data); + if (b->generic.islisten) + { + if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) + iceapi.ICE_Set(b->clients[cl].ice, "sdp", data); + } + else + { + if (b->ice) + iceapi.ICE_Set(b->ice, "sdp", data); + } + break; + } + break; + } + + ofs+=len; + b->in[ofs] = n; + + } + + if (ofs) + { //and eat the newly parsed data... + b->insize -= ofs; + memmove(b->in, b->in+ofs, b->insize); + } + + FTENET_ICE_Flush(b); + return false; +} +static void FTENET_ICE_PrintStatus(ftenet_generic_connection_t *gcon) +{ + ftenet_ice_connection_t *b = (void*)gcon; + size_t c; + + if (b->ice) + ICE_Debug(b->ice); + if (b->numclients) + { + Con_Printf("%u clients\n", (unsigned)b->numclients); + for (c = 0; c < b->numclients; c++) + if (b->clients[c].ice) + ICE_Debug(b->clients[c].ice); + } +} +static int FTENET_ICE_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, const char **adrparms, int maxaddresses) +{ + ftenet_ice_connection_t *b = (void*)gcon; + if (maxaddresses < 1) + return 0; + *addresses = b->brokeradr; + *adrflags = 0; + *adrparms = b->gamename; + return 1; +} + +static qboolean FTENET_ICE_ChangeLocalAddress(struct ftenet_generic_connection_s *gcon, const char *address, netadr_t *newadr) +{ + ftenet_ice_connection_t *b = (void*)gcon; + netadr_t adr; + const char *path; + + if (!NET_StringToAdr2(address, PORT_ICEBROKER, &adr, 1, &path)) + return true; //err... something failed? don't break what works! + + if (!NET_CompareAdr(&adr, &b->brokeradr)) + return false; //the broker changed! zomg! just kill it all! + + if (path && *path++=='/') + { + if (*path && strcmp(path, b->gamename)) + return false; //it changed! and we care! break everything! + } + return true; +} + +ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) +{ + ftenet_ice_connection_t *newcon; + const char *path; + char *c; + + if (!NET_StringToAdr2(address, PORT_ICEBROKER, &adr, 1, &path)) + return NULL; +/* if (adr.prot == NP_ICES) + adr.prot = NP_TLS; + else if (adr.prot == NP_ICE) + adr.prot = NP_STREAM; +*/ + newcon = Z_Malloc(sizeof(*newcon)); + + if (address == path && *path=='/' && fs_manifest->rtcbroker) + { + if (!strncmp(fs_manifest->rtcbroker, "tls://", 6) || !strncmp(fs_manifest->rtcbroker, "tcp://", 6)) + Q_strncpyz(newcon->brokername, fs_manifest->rtcbroker+6, sizeof(newcon->brokername)); //name is for prints only. + else + Q_strncpyz(newcon->brokername, fs_manifest->rtcbroker, sizeof(newcon->brokername)); //name is for prints only. + Q_strncpyz(newcon->gamename, path+1, sizeof(newcon->gamename)); //so we know what to tell the broker. + } + else + { + if (!strncmp(address, "ice://", 6)||!strncmp(address, "rtc://", 6)) + address+=6; + else if (!strncmp(address, "ices://", 7)||!strncmp(address, "rtcs://", 7)) + address+=7; + Q_strncpyz(newcon->brokername, address, sizeof(newcon->brokername)); //name is for prints only. + if (path && *path == '/' && path-address < sizeof(newcon->brokername)) + { + newcon->brokername[path-address] = 0; + Q_strncpyz(newcon->gamename, path+1, sizeof(newcon->gamename)); //so we know what to tell the broker. + } + else + *newcon->gamename = 0; + } + c = strchr(newcon->brokername, ':'); + if (c) *c = 0; + + newcon->brokeradr = adr; + newcon->broker = NULL; + newcon->timeout = realtime; + newcon->heartbeat = realtime; + newcon->nextping = realtime; + newcon->generic.thesocket = INVALID_SOCKET; + + newcon->generic.addrtype[0] = NA_INVALID; + newcon->generic.addrtype[1] = NA_INVALID; + + newcon->generic.GetPacket = FTENET_ICE_GetPacket; + newcon->generic.SendPacket = FTENET_ICE_SendPacket; + newcon->generic.Close = FTENET_ICE_Close; + newcon->generic.PrintStatus = FTENET_ICE_PrintStatus; + newcon->generic.GetLocalAddresses = FTENET_ICE_GetLocalAddresses; + newcon->generic.ChangeLocalAddress = FTENET_ICE_ChangeLocalAddress; + + newcon->generic.islisten = isserver; + + return &newcon->generic; +} #endif \ No newline at end of file diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index 75e4ca51..ee2cf214 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -634,9 +634,14 @@ static int SSL_DoHandshake(gnutlsfile_t *file) //(e_again or e_intr) if (!qgnutls_error_is_fatal(err)) return 0; - - //certificate errors etc - qgnutls_perror (err); + if (developer.ival) + { + if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) + ; //peer doesn't like us. + else + //we didn't like the peer. + qgnutls_perror (err); + } SSL_Close(&file->funcs); // Con_Printf("%s: abort\n", file->certname); @@ -658,6 +663,9 @@ static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread) return read; } + if (!bytestoread) //gnutls doesn't like this. + return -1; + read = qgnutls_record_recv(file->session, buffer, bytestoread); if (read < 0) { @@ -675,7 +683,7 @@ static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread) return 0; //caller is expected to try again later, no real need to loop here, just in case it repeats (eg E_AGAIN) else { - Con_Printf("TLS Read Error %i (bufsize %i)\n", read, bytestoread); + Con_Printf("TLS Read Warning %i (bufsize %i)\n", read, bytestoread); return -1; } } @@ -702,7 +710,7 @@ static int QDECL SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestow return 0; else { - Con_Printf("TLS Send Error %i (%i bytes)\n", written, bytestowrite); + Con_Printf("TLS Send Warning %i (%i bytes)\n", written, bytestowrite); return -1; } } @@ -738,8 +746,8 @@ static ssize_t SSL_Push(gnutls_transport_ptr_t p, const void *data, size_t size) return -1; } qgnutls_transport_set_errno(file->session, done<0?errno:0); - if (done < 0) - return 0; +// if (done < 0) +// return 0; return done; } static ssize_t SSL_Pull(gnutls_transport_ptr_t p, void *data, size_t size) @@ -753,10 +761,10 @@ static ssize_t SSL_Pull(gnutls_transport_ptr_t p, void *data, size_t size) return -1; } qgnutls_transport_set_errno(file->session, done<0?errno:0); - if (done < 0) - { - return 0; - } +// if (done < 0) +// { +// return 0; +// } return done; } @@ -1058,6 +1066,15 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole if (!isserver) qgnutls_server_name_set(newf->session, GNUTLS_NAME_DNS, newf->certname, strlen(newf->certname)); + /*else + { + size_t size = sizeof(newf->certname); + unsigned int type = GNUTLS_NAME_DNS; + int err; + err=qgnutls_server_name_get(newf->session, newf->certname, &size, &type, 0); + if (err!=GNUTLS_E_SUCCESS) + *newf->certname = 0; + }*/ qgnutls_session_set_ptr(newf->session, newf); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index baa2db7a..9a49574c 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -128,18 +128,21 @@ cvar_t net_enable_qizmo = CVARD("net_enable_qizmo", "1", "Enables compatibili cvar_t net_enable_qtv = CVARD("net_enable_qtv", "1", "Listens for qtv proxies, or clients using the qtvplay command."); #endif #if defined(HAVE_SSL) -cvar_t net_enable_tls = CVARD("net_enable_tls", "1", "If enabled, binary data sent to a non-tls tcp port will be interpretted as a tls handshake (enabling https or wss over the same tcp port."); +cvar_t net_enable_tls = CVARD("net_enable_tls", "0", "If enabled, binary data sent to a non-tls tcp port will be interpretted as a tls handshake (enabling https or wss over the same tcp port."); #endif #ifdef HAVE_HTTPSV #ifdef SV_MASTER cvar_t net_enable_http = CVARD("net_enable_http", "1", "If enabled, tcp ports will accept inbound http clients, potentially serving large files which could distrupt gameplay (This does not affect outgoing http(s) requests)."); +cvar_t net_enable_rtcbroker = CVARD("net_enable_rtcbroker", "1", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); +cvar_t net_enable_websockets = CVARD("net_enable_websockets", "0", "If enabled, tcp ports will accept websocket game clients."); #else cvar_t net_enable_http = CVARD("net_enable_http", "0", "If enabled, tcp ports will accept inbound http clients, potentially serving large files which could distrupt gameplay (This does not affect outgoing http(s) requests)."); -#endif +cvar_t net_enable_rtcbroker = CVARD("net_enable_rtcbroker", "0", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); cvar_t net_enable_websockets = CVARD("net_enable_websockets", "1", "If enabled, tcp ports will accept websocket game clients."); -cvar_t net_enable_webrtcbroker = CVARD("net_enable_webrtcbroker", "0", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); #endif #endif +#endif +extern cvar_t net_ice_exchangeprivateips; #if defined(HAVE_DTLS) && defined(HAVE_SERVER) static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue) { @@ -372,6 +375,8 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) if (a->type != b->type) { int i; + if ((a->type == NA_INVALID || b->type == NA_INVALID) && (a->prot==NP_RTC_TCP||a->prot==NP_RTC_TLS)&&(b->prot==NP_RTC_TCP||b->prot==NP_RTC_TLS)) + return true; //broker stuff can be written as /foo which doesn't necessarily have all the info. if (a->port != b->port) return false; if (a->type == NA_IP && b->type == NA_IPV6) @@ -466,6 +471,9 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) } #endif + if (a->type == NA_INVALID && a->prot) + return true; //mneh... + Con_Printf("NET_CompareAdr: Bad address type\n"); return false; } @@ -646,13 +654,15 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) switch(a->prot) { case NP_INVALID:prot = "invalid://";break; - case NP_DGRAM: prot = ""; break; + case NP_DGRAM: prot = ""/*qw://*/; break; case NP_DTLS: prot = "dtls://"; break; case NP_STREAM: prot = "tcp://"; break; //not strictly true for ipx, but whatever. case NP_TLS: prot = "tls://"; break; case NP_WS: prot = "ws://"; break; case NP_WSS: prot = "wss://"; break; case NP_NATPMP: prot = "natpmp://"; break; + case NP_RTC_TCP:prot = "rtc://"; break; + case NP_RTC_TLS:prot = "rtcs://"; break; } switch(a->type) @@ -893,13 +903,15 @@ char *NET_BaseAdrToString (char *s, int len, netadr_t *a) switch(a->prot) { case NP_INVALID:prot = "invalid://";break; - case NP_DGRAM: prot = ""; break; + case NP_DGRAM: prot = ""/*qw://*/; break; case NP_DTLS: prot = "dtls://"; break; case NP_STREAM: prot = "tcp://"; break; //not strictly true for ipx, but whatever. case NP_TLS: prot = "tls://"; break; case NP_WS: prot = "ws://"; break; case NP_WSS: prot = "wss://"; break; case NP_NATPMP: prot = "natpmp://"; break; + case NP_RTC_TCP:prot = "rtc://"; break; + case NP_RTC_TLS:prot = "rtcs://"; break; } switch(a->type) @@ -1383,14 +1395,17 @@ size_t NET_StringToSockaddr2 (const char *s, int defaultport, netadrtype_t afhin /* accepts anything that NET_StringToSockaddr accepts plus certain url schemes including: tcp, irc + +FIXME: should move schemes out of here (so caller can handle paths+etc), using an address family hint for args. */ -size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t numaddresses) +size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t numaddresses, const char **pathstart) { size_t result = 0, i; struct sockaddr_qstorage sadr[8]; int asize[countof(sadr)]; netproto_t prot; netadrtype_t afhint; + char *path; struct { @@ -1422,6 +1437,13 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num {"tls://", NP_TLS_OR_INVALID, NA_INVALID}, {"dtls://", NP_DTLS_OR_INVALID, NA_INVALID}, +#ifdef SUPPORT_ICE + {"ice://", NP_RTC_TCP, NA_INVALID}, + {"rtc://", NP_RTC_TCP, NA_INVALID}, + {"ices://", NP_RTC_TLS, NA_INVALID}, + {"rtcs://", NP_RTC_TLS, NA_INVALID}, +#endif + {"irc://", NP_INVALID, NA_INVALID}, //should have been handled explicitly, if supported. #ifdef UNIXSOCKETS @@ -1432,6 +1454,8 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num memset(a, 0, sizeof(*a)*numaddresses); + if (pathstart) + *pathstart = NULL; if (!numaddresses) return false; @@ -1472,7 +1496,7 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "%s%s%s", prefix, fs_manifest->rtcbroker, s); return 1; } - else if (!strncmp (s, "rtc://", 6) || !strncmp (s, "rtcs://", 7)) + else if (!strncmp (s, "rtc://", 6) || !strncmp (s, "rtcs://", 7) || !strncmp (s, "ice://", 6) || !strncmp (s, "ices://", 7)) { //basically ICE using sdp-via-websockets to a named relay server. const char *prot, *host, *path; a->type = NA_WEBSOCKET; @@ -1527,9 +1551,9 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num } #endif +#ifdef IRCCONNECT if (!strncmp (s, "irc://", 6)) { -#ifdef IRCCONNECT char *at; char *slash; memset (a, 0, sizeof(*a)); @@ -1558,10 +1582,9 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num Q_strncpyz(a->address.irc.user, s, sizeof(a->address.irc.user)); } return 1; -#else - return 0; -#endif } +#endif + #ifdef HAVE_NATPMP if (!strncmp (s, "natpmp://", 9)) { //our natpmp thing omits the host part. FIXME: host should be the NAT that we're sending to @@ -1579,21 +1602,50 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num s += strlen(schemes[i].name); prot = schemes[i].prot; afhint = schemes[i].family; + + if (prot == NP_RTC_TCP || prot == NP_RTC_TLS) + defaultport = PORT_ICEBROKER; break; } } - result = NET_StringToSockaddr2 (s, defaultport, afhint, sadr, NULL, asize, min(numaddresses, countof(sadr))); + path = strchr(s, '/'); + if (path == s && fs_manifest->rtcbroker && *fs_manifest->rtcbroker) + { + s = fs_manifest->rtcbroker; + if (!strncmp(s, "tls://", 6)) + { + s += 6; + prot = NP_RTC_TLS; + } + else if (!strncmp(s, "tcp://", 6)) + { + s+=6; + prot = NP_RTC_TCP; + } + else + prot = NP_RTC_TLS; //best-practise by default. + if (pathstart) + *pathstart = path; + result = NET_StringToSockaddr2 (s, PORT_ICEBROKER, afhint, sadr, NULL, asize, min(numaddresses, countof(sadr))); + } + else if (path && (path-s)type = NA_LOOPBACK; addresses->port = con->thesocket+1; *adrflags = 0; + *adrparams = NULL; return 1; } return 0; @@ -2400,7 +2476,7 @@ typedef struct unsigned int refreshtime; } pmpcon_t; -int FTENET_NATPMP_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses); +int FTENET_NATPMP_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses); static qboolean NET_Was_NATPMP(ftenet_connections_t *collection) { pmpcon_t *pmp; @@ -2474,7 +2550,7 @@ static qboolean NET_Was_NATPMP(ftenet_connections_t *collection) return false; } - Con_DPrintf("NAT-PMP: Local port %u publically available on port %u\n", (unsigned short)BigShort(pmpreqrep->privport), (unsigned short)BigShort(pmpreqrep->pubport)); + Con_DPrintf("NAT-PMP: Local port %u publicly available on port %u\n", (unsigned short)BigShort(pmpreqrep->privport), (unsigned short)BigShort(pmpreqrep->pubport)); pmp->natadr.port = pmpreqrep->pubport; #ifdef SUPPORT_ICE @@ -2498,6 +2574,7 @@ static void FTENET_NATPMP_Refresh(pmpcon_t *pmp, short oldport, ftenet_connectio netadr_t addr[64]; struct ftenet_generic_connection_s *con[sizeof(addr)/sizeof(addr[0])]; int flags[sizeof(addr)/sizeof(addr[0])]; + const char *params[sizeof(addr)/sizeof(addr[0])]; struct { @@ -2516,7 +2593,7 @@ static void FTENET_NATPMP_Refresh(pmpcon_t *pmp, short oldport, ftenet_connectio if (!collection) return; - m = NET_EnumerateAddresses(collection, con, flags, addr, sizeof(addr)/sizeof(addr[0])); + m = NET_EnumerateAddresses(collection, con, flags, addr, params, sizeof(addr)/sizeof(addr[0])); for (i = 0; i < m; i++) { @@ -2579,7 +2656,7 @@ static void FTENET_NATPMP_Refresh(pmpcon_t *pmp, short oldport, ftenet_connectio } #define PMP_POLL_TIME (1000*30)//every 30 seconds qboolean Net_OpenUDPPort(char *privateip, int privateport, char *publicip, size_t publiciplen, int *publicport); -int FTENET_NATPMP_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_NATPMP_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { pmpcon_t *pmp = (pmpcon_t*)con; /* @@ -2596,6 +2673,7 @@ int FTENET_NATPMP_GetLocalAddresses(struct ftenet_generic_connection_s *con, uns if (maxaddresses) { *adrflags = ADDR_NATPMP; + *adrparams = NULL; *addresses = pmp->natadr; return (pmp->natadr.type != NA_INVALID) && (pmp->natadr.port != 0); } @@ -2898,7 +2976,7 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char if (adr && adr->type != NA_INVALID && islisten) if (col->conn[i]->ChangeLocalAddress) { - if (col->conn[i]->ChangeLocalAddress(col->conn[i], adr)) + if (col->conn[i]->ChangeLocalAddress(col->conn[i], address, adr)) return true; } @@ -2997,6 +3075,10 @@ qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, con if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else if (adr[i].prot == NP_TLS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else #endif +#ifdef SUPPORT_ICE + if (adr[i].prot == NP_RTC_TCP) establish[i] = FTENET_ICE_EstablishConnection; else + if (adr[i].prot == NP_RTC_TLS) establish[i] = FTENET_ICE_EstablishConnection; else +#endif #ifdef IRCCONNECT if (adr[i].prot == NP_TLS) establish[i] = FTENET_IRCConnect_EstablishConnection; else #endif @@ -3048,7 +3130,7 @@ void FTENET_Generic_Close(ftenet_generic_connection_t *con) } #if defined(_WIN32) && defined(HAVE_PACKET) -int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { //in win32, we can look up our own hostname to retrieve a list of local interface addresses. char adrs[MAX_ADR_SIZE]; @@ -3072,6 +3154,7 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, SockadrToNetadr((struct sockaddr_qstorage*)&from, sizeof(from), addresses); *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3090,6 +3173,7 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, memcpy(&from.sin6_addr, h->h_addr_list[b], sizeof(((struct sockaddr_in6*)&from)->sin6_addr)); SockadrToNetadr((struct sockaddr_qstorage*)&from, sizeof(from), addresses); *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3127,6 +3211,7 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, SockadrToNetadr((struct sockaddr_qstorage*)itr->ai_addr, sizeof(struct sockaddr_qstorage), addresses); addresses->port = port; *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3148,6 +3233,7 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, else addresses->type = NA_INVALID; *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3166,7 +3252,7 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, static struct ifaddrs *iflist; unsigned int iftime; //requery sometimes. -int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { struct ifaddrs *ifa; int fam; @@ -3210,19 +3296,20 @@ int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, SockadrToNetadr((struct sockaddr_qstorage*)ifa->ifa_addr, sizeof(struct sockaddr_qstorage), &addresses[idx]); addresses[idx].port = port; adrflags[idx] = 0; + adrparams[idx] = NULL; idx++; } } return idx; } #else -int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_GetLocalAddress(int port, qboolean ipx, qboolean ipv4, qboolean ipv6, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { return 0; } #endif -int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { #ifndef HAVE_PACKET return 0; @@ -3251,10 +3338,10 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un //win32 is buggy and treats binding to [::] as [::ffff:0.0.0.0] (even with pure ipv6 sockets) //explicitly binding to [::ffff:0.0.0.0] appears to fail in windows, thus any such socket will definitely support ipv6. qboolean canipv4 = (con->addrtype[0] == NA_IP) || (con->addrtype[1] == NA_IP); - found = FTENET_GetLocalAddress(adr.port, false, canipv4, true, adrflags, addresses, maxaddresses); + found = FTENET_GetLocalAddress(adr.port, false, canipv4, true, adrflags, addresses, adrparams, maxaddresses); #else //FIXME: we should validate that we support hybrid sockets? - found = FTENET_GetLocalAddress(adr.port, false, true, false, adrflags, addresses, maxaddresses); + found = FTENET_GetLocalAddress(adr.port, false, true, false, adrflags, addresses, adrparams, maxaddresses); #endif } else @@ -3277,7 +3364,7 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un ipv6 = true; } - found = FTENET_GetLocalAddress(adr.port, ipx, ipv4, ipv6, adrflags, addresses, maxaddresses); + found = FTENET_GetLocalAddress(adr.port, ipx, ipv4, ipv6, adrflags, addresses, adrparams, maxaddresses); } } #endif @@ -3295,6 +3382,7 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un addresses->type = NA_IP; *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3305,6 +3393,7 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un *addresses = adr; *adrflags++ = 0; + *adrparams++ = NULL; addresses++; maxaddresses--; found++; @@ -3472,8 +3561,10 @@ neterr_t FTENET_Datagram_SendPacket(ftenet_generic_connection_t *con, int length { char adr[256]; + if (ecode==NET_ENETUNREACH&&to->type==NA_IPV6) //ipv6 support STILL sucks too much. don't spam non-developers, its just annoying. + Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); #ifdef HAVE_CLIENT - if (ecode == NET_EADDRNOTAVAIL) + else if (ecode == NET_EADDRNOTAVAIL || (ecode==NET_ENETUNREACH&&to->type==NA_IPV6)) Con_DPrintf("NET_SendPacket(%s) Warning: %i\n", NET_AdrToString (adr, sizeof(adr), to), ecode); else #endif @@ -3516,7 +3607,7 @@ qboolean NET_PortToAdr (netadrtype_t adrfamily, netproto_t adrprot, const char * #ifdef HAVE_PACKET /*just here to prevent the client from spamming new sockets, which can be a problem with certain q2 servers*/ -static qboolean FTENET_Datagram_ChangeLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *adr) +static qboolean FTENET_Datagram_ChangeLocalAddress(struct ftenet_generic_connection_s *con, const char *addressstring, netadr_t *adr) { struct sockaddr_qstorage address; netadr_t current; @@ -3816,7 +3907,7 @@ typedef struct ftenet_tcpconnect_stream_s { #endif struct { - char resource[32]; + char resource[64]; int clientnum; #ifdef SUPPORT_RTC_ICE struct icestate_s *ice; @@ -3868,7 +3959,7 @@ void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen) *out = 0; } -neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, qbyte packettype, const qbyte *data, unsigned int length) +neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, enum websocketpackettype_e packettype, const qbyte *data, unsigned int length) { /*as a server, we don't need the mask stuff*/ unsigned short ctrl = 0x8000 | (packettype<<8); @@ -3877,10 +3968,10 @@ neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, qby int i; switch((ctrl>>8) & 0xf) { - case 1: + case WS_PACKETTYPE_TEXTFRAME: for (i = 0; i < length; i++) { - paylen += (((char*)data)[i] == 0 || ((unsigned char*)data)[i] >= 0x80)?2:1; + paylen += (data[i] == 0 || data[i] >= 0x80)?2:1; } break; default: @@ -3906,22 +3997,22 @@ neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, qby } switch((ctrl>>8) & 0xf) { - case 1:/*utf8ify the data*/ + case WS_PACKETTYPE_TEXTFRAME:/*utf8ify the data*/ for (i = 0; i < length; i++) { - if (!((unsigned char*)data)[i]) + if (!data[i]) { /*0 is encoded as 0x100 to avoid safety checks*/ st->outbuffer[payoffs++] = 0xc0 | (0x100>>6); st->outbuffer[payoffs++] = 0x80 | (0x100&0x3f); } - else if (((unsigned char*)data)[i] >= 0x80) + else if (data[i] >= 0x80) { /*larger bytes require markup*/ - st->outbuffer[payoffs++] = 0xc0 | (((unsigned char*)data)[i]>>6); - st->outbuffer[payoffs++] = 0x80 | (((unsigned char*)data)[i]&0x3f); + st->outbuffer[payoffs++] = 0xc0 | (data[i]>>6); + st->outbuffer[payoffs++] = 0x80 | (data[i]&0x3f); } else { /*lower 7 bits are as-is*/ - st->outbuffer[payoffs++] = ((char*)data)[i]; + st->outbuffer[payoffs++] = data[i]; } } break; @@ -3933,7 +4024,7 @@ neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, qby st->outlen = payoffs; - if (st->outlen) + if (st->outlen && st->clientstream) { /*try and flush the old data*/ int done; done = VFS_WRITE(st->clientstream, st->outbuffer, st->outlen); @@ -3992,9 +4083,13 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ { method = 404; resp = "HTTP/1.1 405 Method Not Allowed\r\n"; + st->httpstate.connection_close = true; body = NULL; } + if (!stricmp(arg[WCATTR_CONNECTION], "Close")) + st->httpstate.connection_close = true; //peer wants us to kill the connection on completion. + st->dlfile = NULL; if (!resp && *arg[WCATTR_URL] == '/') { //'can't use SV_LocateDownload, as that assumes an active client. @@ -4111,7 +4206,7 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ } #endif #if defined(SV_MASTER) && !defined(HAVE_SERVER) - else if ((st->dlfile=SVM_GenerateIndex(name))) + else if ((st->dlfile=SVM_GenerateIndex(arg[WCATTR_HOST], name))) ; #endif #ifdef HAVE_SERVER @@ -4285,6 +4380,7 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ Q_snprintfz(etag, sizeof(etag), "ETag: W/\"%0"PRIxQOFS"\"\r\n", (qofs_t)modificationtime); } + else *etag = 0; if (resp) { @@ -4342,6 +4438,16 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ memcpy(st->outbuffer+st->outlen, resp, i); st->outlen+= i; + if (st->httpstate.connection_close) + { + resp = "Connection: Close\r\n"; + i = strlen(resp); + if (st->outlen + i > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, resp, i); + st->outlen+= i; + } + if (st->dlfile || body) { qofs_t size; @@ -4385,7 +4491,7 @@ qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_ void FTENET_TCPConnect_WebRTCServerAssigned(ftenet_tcpconnect_stream_t *list, ftenet_tcpconnect_stream_t *client, ftenet_tcpconnect_stream_t *server) { - qbyte buffer[5]; + qbyte buffer[3]; int trynext = 0; ftenet_tcpconnect_stream_t *o; if (client->webrtc.clientnum < 0) @@ -4404,13 +4510,19 @@ void FTENET_TCPConnect_WebRTCServerAssigned(ftenet_tcpconnect_stream_t *list, ft if (server) { //and tell them both, if the server is actually up - buffer[0] = 2; + buffer[0] = ICEMSG_NEWPEER; buffer[1] = (client->webrtc.clientnum>>0)&0xff; buffer[2] = (client->webrtc.clientnum>>8)&0xff; - buffer[3] = (client->webrtc.clientnum>>16)&0xff; - buffer[4] = (client->webrtc.clientnum>>24)&0xff; - FTENET_TCPConnect_WebSocket_Splurge(server, 2, buffer, 5); - FTENET_TCPConnect_WebSocket_Splurge(client, 2, "\x02\xff\xff\xff\xff", 5); +// buffer[3] = (client->webrtc.clientnum>>16)&0xff; +// buffer[4] = (client->webrtc.clientnum>>24)&0xff; + FTENET_TCPConnect_WebSocket_Splurge(server, WS_PACKETTYPE_BINARYFRAME, buffer, 3); + + buffer[0] = ICEMSG_NEWPEER; + buffer[1] = 0xff; + buffer[2] = 0xff; +// buffer[3] = 0xff; +// buffer[4] = 0xff; + FTENET_TCPConnect_WebSocket_Splurge(client, WS_PACKETTYPE_BINARYFRAME, buffer, 3); } } @@ -4428,7 +4540,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet qboolean sendingweirdness = false; char arg[WCATTR_COUNT][64]; - if (!net_enable_http.ival && !net_enable_websockets.ival && !net_enable_webrtcbroker.ival) + if (!net_enable_http.ival && !net_enable_websockets.ival && !net_enable_rtcbroker.ival) { //we need to respond, firefox will create 10 different connections if we just close it resp = va( "HTTP/1.1 403 Forbidden\r\n" @@ -4649,11 +4761,11 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet //other fields will be ignored. if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && (!stricmp(arg[WCATTR_CONNECTION], "Upgrade") || !stricmp(arg[WCATTR_CONNECTION], "keep-alive, Upgrade"))) { - if (!net_enable_websockets.ival && !net_enable_webrtcbroker.ival) + if (!net_enable_websockets.ival && !net_enable_rtcbroker.ival) return false; if (websocketver != 13) { - Con_Printf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), websocketver); + Con_DPrintf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), websocketver); resp = va( "HTTP/1.1 426 Upgrade Required\r\n" "Sec-WebSocket-Version: 13\r\n" @@ -4664,7 +4776,7 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet } else { - qboolean fail = false; + const char *failreason = NULL; char acceptkey[20*2]; unsigned char sha1digest[20]; char *blurgh; @@ -4677,7 +4789,6 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet st->remoteaddr.prot = NP_WSS; else st->remoteaddr.prot = NP_WS; - Con_Printf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); @@ -4704,15 +4815,16 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: if (!net_enable_websockets.ival) - fail = true; + failreason = "blocked by net_enable_websockets"; break; case TCPC_WEBRTC_HOST: case TCPC_WEBRTC_CLIENT: - if (!net_enable_webrtcbroker.ival) - fail = true; + if (!net_enable_rtcbroker.ival) + failreason = "blocked by net_enable_rtcbroker"; break; default: - return false; + failreason = "unsupported protocol type"; + break; } if (*arg[WCATTR_URL] == '/') @@ -4722,11 +4834,15 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet st->webrtc.clientnum = -1; #ifndef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) - fail = true; + failreason = "client did not specify resource type"; #endif - if (fail) + if (failreason) + { + Con_DPrintf("Websocket(%s) request for %s from %s - %s\n", arg[WCATTR_WSPROTO], arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), failreason); return false; + } + Con_DPrintf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); resp = va( "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" "Upgrade: websocket\r\n" @@ -4740,29 +4856,42 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) { //client should be connected to us rather than any impostors. tell it to start its ICE handshake. - FTENET_TCPConnect_WebSocket_Splurge(st, 2, "\x02\xff\xff\xff\xff", 5); + net_message_buffer[0] = ICEMSG_NEWPEER; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, 3); } else if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) { ftenet_tcpconnect_stream_t *o; if (st->clienttype == TCPC_WEBRTC_HOST) { //if its a server, then let it know its final resource name - if (!*st->webrtc.resource) + char *idstart = strchr(st->webrtc.resource, '/'); + if (!idstart++) + { //MUST have a protocol name + return false; + } + if (!*idstart) { //webrtc servers need some unique resource address. lets use their ip+port for now. we should probably be randomising this - Q_snprintfz(st->webrtc.resource, sizeof(st->webrtc.resource), "%s", adr); + static unsigned int g; + int ofs = strlen(st->webrtc.resource); + Q_snprintfz(st->webrtc.resource+ofs, sizeof(st->webrtc.resource)-ofs, "%u", ++g); } for (o = con->tcpstreams; o; o = o->next) { if (o != st && o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) + { + *st->webrtc.resource = 0; //don't trigger shutdown broadcasts to valid clients. return false; //conflict! can't have two servers listening on the same url + } } - net_message_buffer[0] = 1; + net_message_buffer[0] = ICEMSG_GREETING; net_message_buffer[1] = 0xff; net_message_buffer[2] = 0xff; strcpy(net_message_buffer+3, st->webrtc.resource); - FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); //if we have (inactive) clients connected, assign them (and let them know that they need to start handshaking) for (o = con->tcpstreams; o; o = o->next) @@ -4770,6 +4899,10 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(st->webrtc.resource, o->webrtc.resource)) FTENET_TCPConnect_WebRTCServerAssigned(con->tcpstreams, o, st); } + +#ifdef SV_MASTER + SVM_AddBrokerGame(st->webrtc.resource, ""); +#endif } else { //find its server, if we can @@ -4874,6 +5007,7 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) st = con->tcpstreams; con->tcpstreams = con->tcpstreams->next; BZ_Free(st); + con->active--; } for (st = con->tcpstreams; st; st = st->next) @@ -4898,7 +5032,7 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) st->timeouttime = timeval + 30; st->pinging = true; - FTENET_TCPConnect_WebSocket_Splurge(st, 0x9, "ping", 4); + FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_PING, "ping", 4); } else #endif @@ -4908,6 +5042,17 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) } } + if (st->outlen) + { /*try and flush any old outgoing data*/ + int done; + done = VFS_WRITE(st->clientstream, st->outbuffer, st->outlen); + if (done > 0) + { + memmove(st->outbuffer, st->outbuffer + done, st->outlen - done); + st->outlen -= done; + } + } + ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); if (ret < 0) { @@ -4916,6 +5061,42 @@ closesvstream: if (st->clientstream) VFS_CLOSE(st->clientstream); st->clientstream = NULL; + net_message.cursize = 0; + + if (st->clienttype == TCPC_WEBRTC_CLIENT) + { //notify its server + ftenet_tcpconnect_stream_t *o; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + adr[0] = ICEMSG_PEERDROP; + adr[1] = (st->webrtc.clientnum>>0)&0xff; + adr[2] = (st->webrtc.clientnum>>8)&0xff; + + FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, adr, 3); + break; //should only be one. + } + } + } + else if (st->clienttype == TCPC_WEBRTC_HOST) + { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. + ftenet_tcpconnect_stream_t *o; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + adr[0] = ICEMSG_PEERDROP; + adr[1] = (st->webrtc.clientnum>>0)&0xff; + adr[2] = (st->webrtc.clientnum>>8)&0xff; + + FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, adr, 3); + } + } +#ifdef SV_MASTER + SVM_RemoveBrokerGame(st->webrtc.resource); +#endif + } continue; } st->inlen += ret; @@ -4926,35 +5107,48 @@ closesvstream: if (st->inlen < 6) continue; -#if defined(HAVE_SSL) && (defined(HAVE_SERVER) || defined(HAVE_HTTPSV)) //if its non-ascii, then try and upgrade the connection to tls - if (net_enable_tls.ival && con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) + //so TLS apparently uses a first byte that is always < 64. which is handy to know. + if (con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) { - //copy off our buffer so we can read it into the tls stream's buffer instead. - char tmpbuf[256]; - vfsfile_t *stream = st->clientstream; - int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); - realread = stream->ReadBytes; - stream->ReadBytes = TLSPromoteRead; - memcpy(net_message_buffer, st->inbuffer, st->inlen); - net_message.cursize = st->inlen; - //wrap the stream now - st->clientstream = FS_OpenSSL(NULL, st->clientstream, true); - st->remoteaddr.prot = NP_TLS; - if (st->clientstream) +#if defined(HAVE_SSL) && (defined(HAVE_SERVER) || defined(HAVE_HTTPSV)) //if its non-ascii, then try and upgrade the connection to tls + if (net_enable_tls.ival) { - //try and reclaim it all - st->inlen = VFS_READ(st->clientstream, st->inbuffer, sizeof(st->inbuffer)-1); - //make sure we actually read from the proper stream again - stream->ReadBytes = realread; + //copy off our buffer so we can read it into the tls stream's buffer instead. + char tmpbuf[256]; + vfsfile_t *stream = st->clientstream; + int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); + realread = stream->ReadBytes; + stream->ReadBytes = TLSPromoteRead; + memcpy(net_message_buffer, st->inbuffer, st->inlen); + net_message.cursize = st->inlen; + //wrap the stream now + st->clientstream = FS_OpenSSL(NULL, st->clientstream, true); + st->remoteaddr.prot = NP_TLS; + if (st->clientstream) + { + //try and reclaim it all + st->inlen = VFS_READ(st->clientstream, st->inbuffer, sizeof(st->inbuffer)-1); + if (st->inlen < 0) + { //okay, something failed... + st->inlen = 0; + goto closesvstream; + } + else + { + //make sure we actually read from the proper stream again + stream->ReadBytes = realread; + } + } + if (!st->clientstream || net_message.cursize) + goto closesvstream; //something cocked up. we didn't give the tls stream all the data. + if (developer.ival) + Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); + net_message.cursize = 0; + continue; } - if (!st->clientstream || net_message.cursize) - goto closesvstream; //something cocked up. we didn't give the tls stream all the data. - if (developer.ival) - Con_Printf("promoted peer to tls: %s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &st->remoteaddr)); - net_message.cursize = 0; - continue; - } #endif + goto closesvstream; //something cocked up. we didn't give the tls stream all the data. + } if (!strncmp(st->inbuffer, "qizmo\n", 6)) { @@ -5206,10 +5400,10 @@ closesvstream: switch((ctrl>>8) & 0xf) { - case 0x0: /*continuation*/ + case WS_PACKETTYPE_CONTINUATION: /*continuation*/ Con_Printf ("websocket continuation frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; - case 0x1: /*text frame*/ + goto closesvstream; //can't handle these. + case WS_PACKETTYPE_TEXTFRAME: /*text frame*/ // Con_Printf ("websocket text frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); { /*text frames are pure utf-8 chars, no dodgy encodings or anything, all pre-checked... @@ -5243,7 +5437,7 @@ closesvstream: net_message.cursize = out - net_message_buffer; } break; - case 0x2: /*binary frame*/ + case WS_PACKETTYPE_BINARYFRAME: /*binary frame*/ // Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); net_message.cursize = paylen; if (net_message.cursize+8 >= sizeof(net_message_buffer) ) @@ -5253,7 +5447,7 @@ closesvstream: } #ifdef SUPPORT_RTC_ICE if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) - { //this is a client that's corrected directly to us via webrtc. + { //this is a client that's connected directly to us via webrtc. //FIXME: we don't support dtls, so browers will bitch about our sdp. if (paylen+1 < sizeof(net_message_buffer)) { @@ -5265,16 +5459,25 @@ closesvstream: iceapi.ICE_Set(st->webrtc.ice, "sdp", net_message_buffer); if (iceapi.ICE_Get(st->webrtc.ice, "sdp", net_message_buffer, sizeof(net_message_buffer))) - FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); } net_message.cursize = 0; } else #endif - if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) + if (st->clienttype == TCPC_WEBRTC_HOST && st->inbuffer[payoffs+0] == ICEMSG_SERVERINFO) + { +#ifdef SV_MASTER + qbyte old = st->inbuffer[payoffs+paylen]; + st->inbuffer[payoffs+paylen] = 0; //make sure its null terminated... + SVM_AddBrokerGame(st->webrtc.resource, st->inbuffer+payoffs+3); + st->inbuffer[payoffs+paylen] = old; +#endif + } + else if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. ftenet_tcpconnect_stream_t *o; - int clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); + short clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; for (o = con->tcpstreams; o; o = o->next) { @@ -5282,10 +5485,12 @@ closesvstream: { st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; - FTENET_TCPConnect_WebSocket_Splurge(o, 2, st->inbuffer+payoffs, paylen); + FTENET_TCPConnect_WebSocket_Splurge(o, WS_PACKETTYPE_BINARYFRAME, st->inbuffer+payoffs, paylen); break; } } + if (!o) + Con_DPrintf("Unable to relay\n"); net_message.cursize = 0; } else @@ -5303,15 +5508,15 @@ closesvstream: #endif memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); break; - case 0x8: /*connection close*/ + case WS_PACKETTYPE_CLOSE: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; - case 0x9: /*ping*/ + case WS_PACKETTYPE_PING: /*ping*/ // Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - if (FTENET_TCPConnect_WebSocket_Splurge(st, 0xa, st->inbuffer+payoffs, paylen) != NETERR_SENT) + if (FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_PONG, st->inbuffer+payoffs, paylen) != NETERR_SENT) goto closesvstream; break; - case 0xa: /*pong*/ + case WS_PACKETTYPE_PONG: /*pong*/ st->timeouttime = Sys_DoubleTime() + 30; st->pinging = false; // Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); @@ -5431,7 +5636,7 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: { - neterr_t e = FTENET_TCPConnect_WebSocket_Splurge(st, (st->clienttype==TCPC_WEBSOCKETU)?1:2, data, length); + neterr_t e = FTENET_TCPConnect_WebSocket_Splurge(st, (st->clienttype==TCPC_WEBSOCKETU)?WS_PACKETTYPE_TEXTFRAME:WS_PACKETTYPE_BINARYFRAME, data, length); if (e != NETERR_SENT) return e; } @@ -5461,11 +5666,11 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len return NETERR_NOROUTE; } -static int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +static int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; netproto_t prot = con->tls?NP_TLS:NP_STREAM; - int i, r = FTENET_Generic_GetLocalAddresses(gcon, adrflags, addresses, maxaddresses); + int i, r = FTENET_Generic_GetLocalAddresses(gcon, adrflags, addresses, adrparams, maxaddresses); for (i = 0; i < r; i++) { addresses[i].prot = prot; @@ -5473,7 +5678,7 @@ static int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_ return r; } -static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *adr) +static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_connection_s *con, const char *addressstring, netadr_t *adr) { //if we're a server, we want to try switching listening tcp port without shutting down all other connections. //yes, this might mean we leave a connection active on the old port, but oh well. @@ -5529,6 +5734,12 @@ static qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_conne unsigned long _false = false; if ((newsocket = socket (AF_INET6, SOCK_STREAM, sysprot)) != INVALID_SOCKET) { +#ifdef __linux__ //note: windows blindly allows dupes, whereas linux prevents exact matches + setsockopt(newsocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&_true, sizeof(_true)); //try to avoid 'address in use' problems when killing+restarting. +#elif defined(_WIN32) + setsockopt(newsocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&_true, sizeof(_true)); //try to avoid 'address in use' problems when killing+restarting. +#endif + if (0 == setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_false, sizeof(_false))) { memset(&n, 0, sizeof(n)); @@ -5629,7 +5840,7 @@ int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readf { while(iceapi.ICE_GetLCandidateSDP(st->webrtc.ice, net_message_buffer, sizeof(net_message_buffer))) { - FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + FTENET_TCPConnect_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); } continue; } @@ -5681,7 +5892,7 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse if (isserver) { #ifdef HAVE_PACKET //unable to listen on tcp if we have no packet interface - if (!FTENET_TCPConnect_ChangeLocalAddress(&newcon->generic, &adr)) + if (!FTENET_TCPConnect_ChangeLocalAddress(&newcon->generic, address, &adr)) { Z_Free(newcon); return NULL; @@ -5740,12 +5951,30 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse #ifdef HAVE_SSL if (newcon->tls) //if we're meant to be using tls, wrap the stream in a tls connection - newcon->tcpstreams->clientstream = FS_OpenSSL(address, newcon->tcpstreams->clientstream, false); + { //remove any markup junk, get just the hostname out of it. + char hostonly[MAX_QPATH]; + const char *host = strstr(address, "://"); + const char *port; + host = host?host+3:address; + port = strchr(host, ':'); + if (!port) + port = host+strlen(host); + + if (port-host >= sizeof(hostonly)) + { + VFS_CLOSE(&newcon->generic); + return NULL; + } + memcpy(hostonly, host, port-host); + hostonly[port-host] = 0; + + newcon->tcpstreams->clientstream = FS_OpenSSL(hostonly, newcon->tcpstreams->clientstream, false); + } #endif - //send the qizmo greeting. + //send the qizmo greeting. any actual data is just otherwise consistent with qw/udp, including challenges. newcon->tcpstreams->clienttype = TCPC_UNKNOWN; - VFS_WRITE(newcon->tcpstreams->clientstream, "qizmo\n", 6); + memcpy(newcon->tcpstreams->outbuffer, "qizmo\n", newcon->tcpstreams->outlen = 6); newcon->tcpstreams->timeouttime = Sys_DoubleTime() + 30; } @@ -5765,9 +5994,6 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isse #endif - - - #ifdef IRCCONNECT typedef struct ftenet_ircconnect_stream_s { @@ -6414,7 +6640,7 @@ static neterr_t FTENET_WebSocket_SendPacket(ftenet_generic_connection_t *gcon, i } //called from the javascript when there was some ice event. just forwards over the broker connection. -static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int evtype, const char *data) +static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int/*enum icemsgtype_s*/ evtype, const char *data) { ftenet_websocket_connection_t *wcs = ctxp; size_t dl = strlen(data); @@ -6466,7 +6692,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) switch(cmd) { - case 0: //connection closing... + case ICEMSG_PEERDROP: //connection closing... if (cl == -1) { wsc->failed = true; @@ -6481,7 +6707,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); } break; - case 1: //reports the trailing url we're 'listening' on. anyone else using that url will connect to us. + case ICEMSG_GREETING: //reports the trailing url we're 'listening' on. anyone else using that url will connect to us. s = MSG_ReadString(); if (*s == '/') s++; @@ -6502,7 +6728,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) Q_strncatz(wsc->remoteadr.address.websocketurl, s, sizeof(wsc->remoteadr.address.websocketurl)); Con_Printf("Listening on %s\n", wsc->remoteadr.address.websocketurl); break; - case 2: //connection established with a new peer + case ICEMSG_NEWPEER: //connection established with a new peer if (wsc->generic.islisten) { if (cl < 1024 && cl >= wsc->numclients) @@ -6532,7 +6758,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) wsc->datasock = emscriptenfte_rtc_create(true, wsc, cl, FTENET_WebRTC_Callback); } break; - case 3: //we received an offer from a client + case ICEMSG_OFFER: //we received an offer from a client s = MSG_ReadString(); if (wsc->generic.islisten) { @@ -6545,7 +6771,7 @@ static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) emscriptenfte_rtc_offer(wsc->datasock, s, "answer"); } break; - case 4: + case ICEMSG_CANDIDATE: s = MSG_ReadString(); if (wsc->generic.islisten) { @@ -6599,7 +6825,7 @@ static neterr_t FTENET_WebRTC_SendPacket(ftenet_generic_connection_t *gcon, int return NETERR_NOROUTE; } -int FTENET_WebRTC_GetAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int FTENET_WebRTC_GetAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { ftenet_websocket_connection_t *wsc = (void*)con; if (maxaddresses) @@ -7085,6 +7311,7 @@ int NET_GetPacket (ftenet_connections_t *collection, int firstsock) int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote, netadr_t *local, int idx) { int adrflags; + const char *adrparams; if (!remote->connum) return 0; @@ -7094,7 +7321,7 @@ int NET_LocalAddressForRemote(ftenet_connections_t *collection, netadr_t *remote if (!collection->conn[remote->connum-1]->GetLocalAddresses) return 0; - return collection->conn[remote->connum-1]->GetLocalAddresses(collection->conn[remote->connum-1], &adrflags, local, 1); + return collection->conn[remote->connum-1]->GetLocalAddresses(collection->conn[remote->connum-1], &adrflags, local, &adrparams, 1); } static neterr_t NET_SendPacketCol (ftenet_connections_t *collection, int length, const void *data, netadr_t *to) @@ -7208,6 +7435,13 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char return false; Con_Printf("Establishing connection to %s\n", host); break; +#ifdef SUPPORT_ICE + case NP_RTC_TCP: + case NP_RTC_TLS: + if (!FTENET_AddToCollection(collection, routename, host, adr->type, adr->prot)) + return false; + break; +#endif default: //not recognised, or not needed break; @@ -7215,7 +7449,7 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char return true; } -int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses) { unsigned int found = 0, c, i, j; for (i = 0; i < MAX_CONNECTIONS; i++) @@ -7224,28 +7458,26 @@ int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_gener continue; if (collection->conn[i]->GetLocalAddresses) - c = collection->conn[i]->GetLocalAddresses(collection->conn[i], adrflags, addresses, maxaddresses); + c = collection->conn[i]->GetLocalAddresses(collection->conn[i], adrflags+found, addresses+found, adrparams+found, maxaddresses-found); else c = 0; - if (maxaddresses && !c) + if (maxaddresses>found && !c) { - *adrflags = 0; - addresses->type = NA_INVALID; + adrflags[found] = 0; + adrparams[found] = NULL; + addresses[found].type = NA_INVALID; + addresses[found].prot = NP_INVALID; c = 1; } //fill in connection info for (j = 0; j < c; j++) { - con[j] = collection->conn[i]; - addresses[j].connum = i+1; + con[found+j] = collection->conn[i]; + addresses[found+j].connum = i+1; } - con += c; - adrflags += c; - addresses += c; - maxaddresses -= c; found += c; } return found; @@ -7320,6 +7552,7 @@ void NET_PrintAddresses(ftenet_connections_t *collection) netadr_t addr[64]; struct ftenet_generic_connection_s *con[sizeof(addr)/sizeof(addr[0])]; int flags[sizeof(addr)/sizeof(addr[0])]; + const char *params[sizeof(addr)/sizeof(addr[0])]; qboolean warn = true; static const char *scopes[] = {"process", "local", "link", "lan", "net"}; char *desc; @@ -7327,7 +7560,7 @@ void NET_PrintAddresses(ftenet_connections_t *collection) if (!collection) return; - m = NET_EnumerateAddresses(collection, con, flags, addr, sizeof(addr)/sizeof(addr[0])); + m = NET_EnumerateAddresses(collection, con, flags, addr, params, sizeof(addr)/sizeof(addr[0])); for (i = 0; i < m; i++) { @@ -7337,7 +7570,14 @@ void NET_PrintAddresses(ftenet_connections_t *collection) if (developer.ival || scope >= ASCOPE_LAN) { warn = false; - if (desc) + if ((addr[i].prot == NP_RTC_TCP || addr[i].prot == NP_RTC_TLS) && params[i]) + { + if (addr[i].type == NA_INVALID) + Con_Printf("%s address (%s): /%s\n", scopes[scope], con[i]->name, params[i]); + else + Con_Printf("%s address (%s): %s/%s\n", scopes[scope], con[i]->name, NET_AdrToString(adrbuf, sizeof(adrbuf), &addr[i]), params[i]); + } + else if (desc) Con_Printf("%s address (%s): %s (%s)\n", scopes[scope], con[i]->name, NET_AdrToString(adrbuf, sizeof(adrbuf), &addr[i]), desc); else Con_Printf("%s address (%s): %s\n", scopes[scope], con[i]->name, NET_AdrToString(adrbuf, sizeof(adrbuf), &addr[i])); @@ -7466,85 +7706,6 @@ int TCP_OpenStream (netadr_t *remoteaddr) #endif } -/*int TCP_OpenListenSocket (const char *localip, int port) -{ -#ifndef HAVE_TCP - return INVALID_SOCKET; -#else - int newsocket; - struct sockaddr_qstorage address; - int pf; - unsigned long _true = true; - int i; -int maxport = port + 100; - - if (localip && *localip) - { - if (!NET_StringToSockaddr(localip, port, &address, &pf, &adrsize)) - return INVALID_SOCKET; - } - else - { - adrsize = sizeof(struct sockaddr_in); - pf = ((struct sockaddr_in*)&address)->sin_family = AF_INET; - ((struct sockaddr_in*)&address)->sin_port = htons(port); - - //ZOID -- check for interface binding option - if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) - { - ((struct sockaddr_in*)&address)->sin_addr.s_addr = inet_addr(com_argv[i+1]); - Con_TPrintf("Binding to IP Interface Address of %s\n", - inet_ntoa(address.sin_addr)); - } - else - ((struct sockaddr_in*)&address)->sin_addr.s_addr = INADDR_ANY; - } - - if ((newsocket = socket (pf, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) - return INVALID_SOCKET; - - if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) - Sys_Error ("TCP_OpenListenSocket: ioctl FIONBIO: %s", strerror(qerrno)); - - for(;;) - { - if (port == PORT_ANY) - address.sin_port = 0; - else - address.sin_port = htons((short)port); - - if( bind (newsocket, (void *)&address, sizeof(address)) == -1) - { - if (!port) - { - Con_Printf("Cannot bind tcp socket\n"); - closesocket(newsocket); - return INVALID_SOCKET; - } - port++; - if (port > maxport) - { - Con_Printf("Cannot bind tcp socket\n"); - closesocket(newsocket); - return INVALID_SOCKET; - } - } - else - break; - } - - if (listen(newsocket, 1) == INVALID_SOCKET) - { - Con_Printf("Cannot listen on tcp socket\n"); - closesocket(newsocket); - return INVALID_SOCKET; - } - - return newsocket; -#endif -} -*/ - #if defined(SV_MASTER) || defined(CL_MASTER) int UDP_OpenSocket (int port) { @@ -7922,7 +8083,7 @@ qboolean NET_WasSpecialPacket(ftenet_connections_t *collection) return true; #endif -#ifdef SUPPORT_ICE +#if defined(SUPPORT_ICE) || defined(MASTERONLY) if (ICE_WasStun(collection)) return true; #endif @@ -7984,7 +8145,7 @@ void NET_Init (void) #ifdef HAVE_HTTPSV Cvar_Register(&net_enable_http, "networking"); Cvar_Register(&net_enable_websockets, "networking"); - Cvar_Register(&net_enable_webrtcbroker, "networking"); + Cvar_Register(&net_enable_rtcbroker, "networking"); #endif #endif @@ -8011,6 +8172,10 @@ void NET_Init (void) SSL_Init(); #endif +#ifdef SUPPORT_ICE + Cvar_Register(&net_ice_exchangeprivateips, "networking"); +#endif + #if defined(HAVE_CLIENT)||defined(HAVE_SERVER) Net_Master_Init(); #endif @@ -8238,6 +8403,9 @@ void NET_InitServer(void) #endif #ifdef HAVE_DTLS Cvar_ForceCallback(&net_enable_dtls); +#endif +#ifdef SUPPORT_ICE + Cvar_ForceCallback(&sv_public); #endif } else diff --git a/engine/common/netinc.h b/engine/common/netinc.h index f743d025..9fd85aa3 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -226,7 +226,7 @@ struct icecandinfo_s { char candidateid[64]; char addr[64]; //v4/v6/fqdn. fqdn should prefer ipv6 - int port; + int port; //native endian... int transport; //0=udp. other values not supported int foundation; //to figure out... int component; //1-based. allows rtp+rtcp in a single ICE... we only support one. @@ -289,8 +289,8 @@ extern icefuncs_t iceapi; typedef struct ftenet_generic_connection_s { char name[MAX_QPATH]; - int (*GetLocalAddresses)(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses); - qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, netadr_t *newadr); + int (*GetLocalAddresses)(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses); + qboolean (*ChangeLocalAddress)(struct ftenet_generic_connection_s *con, const char *addressstring, netadr_t *newadr); qboolean (*GetPacket)(struct ftenet_generic_connection_s *con); neterr_t (*SendPacket)(struct ftenet_generic_connection_s *con, int length, const void *data, netadr_t *to); void (*Close)(struct ftenet_generic_connection_s *con); @@ -376,11 +376,33 @@ void ICE_Tick(void); qboolean ICE_WasStun(ftenet_connections_t *col); void QDECL ICE_AddLCandidateConn(ftenet_connections_t *col, netadr_t *addr, int type); void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrno, int type); +ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +enum icemsgtype_s +{ //shared by rtcpeers+broker + ICEMSG_PEERDROP=0, //other side dropped connection + ICEMSG_GREETING=1, //master telling us our unique game name + ICEMSG_NEWPEER=2, //relay established, send an offer now. + ICEMSG_OFFER=3, //peer's initial details + ICEMSG_CANDIDATE=4, //candidate updates. may arrive late as new ones are discovered. + ICEMSG_ACCEPT=5, //go go go (response from offer) + ICEMSG_SERVERINFO=6,//server->broker (for advertising the server properly) + ICEMSG_SERVERUPDATE=7,//broker->browser (for querying available server lists) +}; + +enum websocketpackettype_e +{ //websocket packet types, used by both our tcp/http/broker/etc server and our ice client. + WS_PACKETTYPE_CONTINUATION=0, + WS_PACKETTYPE_TEXTFRAME=1, + WS_PACKETTYPE_BINARYFRAME=2, + WS_PACKETTYPE_CLOSE=8, + WS_PACKETTYPE_PING=9, + WS_PACKETTYPE_PONG=10, +}; ftenet_connections_t *FTENET_CreateCollection(qboolean listen); void FTENET_CloseCollection(ftenet_connections_t *col); qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, netproto_t addrprot); -int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses); +int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, const char **adrparams, int maxaddresses); void *TLS_GetKnownCertificate(const char *certname, size_t *size); vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server); diff --git a/engine/common/plugin.c b/engine/common/plugin.c index f878a418..9cc13f81 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -1515,7 +1515,7 @@ void Plug_Close(plugin_t *plug) prev->next = plug->next; } - if (!com_workererror) + if (!com_workererror && plug->lib) Con_DPrintf("Closing plugin %s\n", plug->name); //ensure any active contexts provided by the plugin are closed (stuff with destroy callbacks) @@ -1893,10 +1893,12 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s Plug_GetTeamInfo, Plug_GetWeaponStats, Plug_GetTrackerOwnFrags, + Plug_GetPredInfo, #else NULL, NULL, NULL, + NULL, #endif }; if (structsize == sizeof(funcs)) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 816be4e5..ebc4d884 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -82,8 +82,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PEXT2_PREDINFO 0x00000020 //movevar stats, NQ input sequences+acks. #define PEXT2_NEWSIZEENCODING 0x00000040 //richer size encoding. #define PEXT2_INFOBLOBS 0x00000080 //serverinfo+userinfo lengths can be MUCH higher (protocol is unbounded, but expect low sanity limits on userinfo), and contain nulls etc. -//#define PEXT2_NEWINTENTS 0x00000100 //clc_move changes, more buttons etc -#define PEXT2_CLIENTSUPPORT (PEXT2_PRYDONCURSOR|PEXT2_VOICECHAT|PEXT2_SETANGLEDELTA|PEXT2_REPLACEMENTDELTAS|PEXT2_MAXPLAYERS|PEXT2_PREDINFO|PEXT2_NEWSIZEENCODING|PEXT2_INFOBLOBS) +#define PEXT2_STUNAWARE 0x00000100 //changes the netchan to biased-bigendian (so lead two bits are 1 and not stun's 0, so we don't get confused) +//#define PEXT2_NEWINTENTS 0x00000200 //clc_move changes, more buttons etc +#define PEXT2_CLIENTSUPPORT (PEXT2_PRYDONCURSOR|PEXT2_VOICECHAT|PEXT2_SETANGLEDELTA|PEXT2_REPLACEMENTDELTAS|PEXT2_MAXPLAYERS|PEXT2_PREDINFO|PEXT2_NEWSIZEENCODING|PEXT2_INFOBLOBS|PEXT2_STUNAWARE) //EzQuake/Mvdsv extensions. (use ezquake name, to avoid confusion about .mvd format and its protocol differences) #define EZPEXT1_FLOATENTCOORDS 0x00000001 //quirky - doesn't apply to broadcasts, just players+ents. this gives more precision, but will bug out if you try using it to increase map bounds in ways that may not be immediately apparent. iiuc this was added instead of fixing some inconsistent rounding... @@ -150,6 +151,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PORT_Q2SERVER 27910 #define PORT_Q3MASTER 27950 #define PORT_Q3SERVER 27960 +#define PORT_ICEBROKER PORT_DPMASTER #ifdef GAME_DEFAULTPORT #define PORT_DEFAULTSERVER GAME_DEFAULTPORT diff --git a/engine/common/q3common.c b/engine/common/q3common.c index 567c45bd..5cd4cf68 100644 --- a/engine/common/q3common.c +++ b/engine/common/q3common.c @@ -560,6 +560,7 @@ void MSG_WriteBits(sizebuf_t *msg, int value, int bits) #define MAX_PACKETLEN 1400 +#define STUNDEMULTIPLEX_MASK 0x40000000 //stun requires that we set this bit to avoid surprises, and ignore packets without it. #define FRAGMENT_MASK 0x80000000 #define FRAGMENTATION_TRESHOLD (MAX_PACKETLEN-100) qboolean Netchan_ProcessQ3 (netchan_t *chan) @@ -575,6 +576,15 @@ qboolean Netchan_ProcessQ3 (netchan_t *chan) MSG_BeginReading(msg_nullnetprim); sequence = MSG_ReadBits(32); + if (chan->pext_stunaware) + { + sequence = BigLong(sequence); + if (sequence & STUNDEMULTIPLEX_MASK) + sequence-= STUNDEMULTIPLEX_MASK; + else + return false; + } + // Read the qport if we are a server if (chan->sock == NS_SERVER) { @@ -710,13 +720,20 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) sizebuf_t send; qbyte send_buf[MAX_PACKETLEN]; int fragmentLength; + + int sequence = chan->outgoing_sequence | FRAGMENT_MASK; + if (chan->pext_stunaware) + { + sequence+= STUNDEMULTIPLEX_MASK; + sequence = BigLong(sequence); + } // Write the packet header memset(&send, 0, sizeof(send)); send.packing = SZ_RAWBYTES; send.maxsize = sizeof(send_buf); send.data = send_buf; - MSG_WriteLong( &send, chan->outgoing_sequence | FRAGMENT_MASK ); + MSG_WriteLong( &send, sequence ); #ifndef SERVERONLY // Send the qport if we are a client if( chan->sock == NS_CLIENT ) @@ -773,6 +790,7 @@ void Netchan_TransmitQ3( netchan_t *chan, int length, const qbyte *data ) sizebuf_t send; qbyte send_buf[MAX_OVERALLMSGLEN+6]; char adr[MAX_ADR_SIZE]; + int sequence; // Check for message overflow if( length > MAX_OVERALLMSGLEN ) @@ -810,6 +828,13 @@ void Netchan_TransmitQ3( netchan_t *chan, int length, const qbyte *data ) return; } + sequence = chan->outgoing_sequence; + if (chan->pext_stunaware) + { + sequence+= STUNDEMULTIPLEX_MASK; + sequence = BigLong(sequence); + } + // Write the packet header memset(&send, 0, sizeof(send)); send.packing = SZ_RAWBYTES; diff --git a/engine/common/zone.c b/engine/common/zone.c index 471119da..ae45006f 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -195,6 +195,7 @@ void *ZF_Malloc(size_t size) return ret; #elif defined(__linux__) void *ret = NULL; + if (!posix_memalign(&ret, max(sizeof(float)*4, sizeof(void*)), size)) memset(ret, 0, size); return ret; diff --git a/engine/gl/gl_alias.c b/engine/gl/gl_alias.c index c953744e..ef22ca21 100644 --- a/engine/gl/gl_alias.c +++ b/engine/gl/gl_alias.c @@ -759,9 +759,14 @@ static shader_t *GL_ChooseSkin(galiasinfo_t *inf, model_t *model, int surfnum, e { texnums_t *tex = shader->defaulttextures; //do this for the loading case too, in the hope that it'll avoid generating a per-player skin at all - if ((tex->loweroverlay && (tex->loweroverlay->status == TEX_LOADING || tex->loweroverlay->status == TEX_LOADED)) || + if ((tex->base && (tex->base->status == TEX_LOADING)) || + (tex->loweroverlay && (tex->loweroverlay->status == TEX_LOADING || tex->loweroverlay->status == TEX_LOADED)) || (tex->upperoverlay && (tex->upperoverlay->status == TEX_LOADING || tex->upperoverlay->status == TEX_LOADED))) return shader; + //if we've got a replacement texture (read: its size differs from the proper skin size) then don't use the base texels for colourmapping. + if (tex->base && skins && + (tex->base->width!=skins->skinwidth || tex->base->height!=skins->skinheight)) + return shader; } if ((shader->flags & SHADER_HASTOPBOTTOM) && !h2playertranslations) { //this shader will try to do top+bottom colours. this means we can generate only a black image, with separate top+bottom textures. diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index bfa19a29..af455b11 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -2211,7 +2211,7 @@ void Terr_FreeModel(model_t *mod) } #ifdef RUNTIMELIGHTING if (hm->relightcontext) - LightShutdown(hm->relightcontext, mod); + LightShutdown(hm->relightcontext); if (hm->lightthreadmem && !hm->inheritedlightthreadmem) BZ_Free(hm->lightthreadmem); #endif diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index 8b3bc8a2..88062ada 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -83,32 +83,7 @@ void Mod_SortShaders(model_t *mod); void Mod_LoadAliasShaders(model_t *mod); #ifdef RUNTIMELIGHTING -model_t *lightmodel; -static struct relight_ctx_s *lightcontext; -static int numlightdata; -static qboolean writelitfile; - -long relitsurface; -#ifndef MULTITHREAD -static void Mod_UpdateLightmap(int snum) -{ - msurface_t *s; - if (lightmodel) - { -// int i; -// for (s = lightmodel->surfaces,i=0; i < lightmodel->numsurfaces; i++,s++) -// s->cached_dlight = -1; - - if (snum < lightmodel->numsurfaces) - { - s = lightmodel->surfaces + snum; - s->cached_dlight = -1; - } - else - Con_Printf("lit non-existant surface\n"); - } -} -#endif +extern model_t *lightmodel; #endif static void Mod_MemList_f(void) @@ -304,167 +279,6 @@ static void Mod_BlockTextureColour_f (void) } #endif - -#ifdef RUNTIMELIGHTING -#if defined(MULTITHREAD) -#ifdef _WIN32 -#include -#elif defined(__linux__) -#include -#endif -static void *relightthread[8]; -static unsigned int relightthreads; -static volatile qboolean wantrelight; - -static int RelightThread(void *arg) -{ - int surf; - void *threadctx = malloc(lightthreadctxsize); - while (wantrelight) - { -#ifdef _WIN32 - surf = InterlockedIncrement(&relitsurface); -#elif defined(__GNUC__) - surf = __sync_add_and_fetch(&relitsurface, 1); -#else - surf = relitsurface++; -#endif - if (surf >= lightmodel->numsurfaces) - break; - LightFace(lightcontext, threadctx, surf); - lightmodel->surfaces[surf].cached_dlight = -1; - } - free(threadctx); - return 0; -} -#else -static void *lightmainthreadctx; -#endif -#endif - -void Mod_Think (void) -{ -#ifdef RUNTIMELIGHTING - if (lightmodel) - { -#ifdef MULTITHREAD - if (!relightthreads) - { - int i; -#if defined(_WIN32) && !defined(WINRT) - HANDLE me = GetCurrentProcess(); - DWORD_PTR proc, sys; - /*count cpus*/ - GetProcessAffinityMask(me, &proc, &sys); - relightthreads = 0; - for (i = 0; i < sizeof(proc)*8; i++) - if (proc & ((size_t)1u< sizeof(relightthread)/sizeof(relightthread[0])) - relightthreads = sizeof(relightthread)/sizeof(relightthread[0]); - wantrelight = true; - for (i = 0; i < relightthreads; i++) - relightthread[i] = Sys_CreateThread("relight", RelightThread, lightmodel, THREADP_NORMAL, 0); - } - if (relitsurface < lightmodel->numsurfaces) - { - return; - } -#else - if (!lightmainthreadctx) - lightmainthreadctx = malloc(lightthreadctxsize); - LightFace(lightcontext, lightmainthreadctx, relitsurface); - Mod_UpdateLightmap(relitsurface); - - relitsurface++; -#endif - if (relitsurface >= lightmodel->numsurfaces) - { - vfsfile_t *f; - char filename[MAX_QPATH]; - Con_Printf("Finished lighting %s\n", lightmodel->name); - -#ifdef MULTITHREAD - if (relightthreads) - { - int i; - wantrelight = false; - for (i = 0; i < relightthreads; i++) - { - Sys_WaitOnThread(relightthread[i]); - relightthread[i] = NULL; - } - relightthreads = 0; - } -#else - free(lightmainthreadctx); - lightmainthreadctx = NULL; -#endif - - LightShutdown(lightcontext, lightmodel); - lightcontext = NULL; - - if (lightmodel->deluxdata) - { - COM_StripExtension(lightmodel->name, filename, sizeof(filename)); - COM_DefaultExtension(filename, ".lux", sizeof(filename)); - f = FS_OpenVFS(filename, "wb", FS_GAME); - if (f) - { - VFS_WRITE(f, "QLIT\1\0\0\0", 8); - VFS_WRITE(f, lightmodel->deluxdata, numlightdata*3); - VFS_CLOSE(f); - } - else - Con_Printf("Unable to write \"%s\"\n", filename); - } - - if (writelitfile) //the user might already have a lit file (don't overwrite it). - { - COM_StripExtension(lightmodel->name, filename, sizeof(filename)); - COM_DefaultExtension(filename, ".lit", sizeof(filename)); - - f = FS_OpenVFS(filename, "wb", FS_GAME); - if (f) - { - if (lightmodel->lightmaps.fmt == LM_E5BGR9) - { - VFS_WRITE(f, "QLIT\x01\0\x01\0", 8); - VFS_WRITE(f, lightmodel->lightdata, numlightdata*4); - } - else - { - VFS_WRITE(f, "QLIT\1\0\0\0", 8); - VFS_WRITE(f, lightmodel->lightdata, numlightdata*3); - } - VFS_CLOSE(f); - } - else - Con_Printf("Unable to write \"%s\"\n", filename); - } - lightmodel = NULL; - } - } -#endif -} - void Mod_RebuildLightmaps (void) { int i, j; @@ -663,20 +477,7 @@ static int mod_datasequence; void Mod_ClearAll (void) { #ifdef RUNTIMELIGHTING -#ifdef MULTITHREAD - int i; - wantrelight = false; - for (i = 0; i < relightthreads; i++) - { - Sys_WaitOnThread(relightthread[i]); - relightthread[i] = NULL; - } - relightthreads = 0; -#else - free(lightmainthreadctx); - lightmainthreadctx = NULL; -#endif - lightmodel = NULL; + RelightTerminate(NULL); #endif mod_datasequence++; @@ -692,23 +493,7 @@ qboolean Mod_PurgeModel(model_t *mod, enum mod_purge_e ptype) } #ifdef RUNTIMELIGHTING - if (lightmodel == mod) - { -#ifdef MULTITHREAD - int i; - wantrelight = false; - for (i = 0; i < relightthreads; i++) - { - Sys_WaitOnThread(relightthread[i]); - relightthread[i] = NULL; - } - relightthreads = 0; -#else - free(lightmainthreadctx); - lightmainthreadctx = NULL; -#endif - lightmodel = NULL; - } + RelightTerminate(mod); #endif #ifdef TERRAIN @@ -1793,6 +1578,9 @@ void Mod_LoadLighting (model_t *loadmodel, bspx_header_t *bspx, qbyte *mod_base, qbyte *lumdata = NULL; //l8 qbyte *out; unsigned int samples; +#ifdef RUNTIMELIGHTING + qboolean relighting = false; +#endif extern cvar_t gl_overbright; loadmodel->lightmaps.fmt = LM_L8; @@ -2116,28 +1904,23 @@ void Mod_LoadLighting (model_t *loadmodel, bspx_header_t *bspx, qbyte *mod_base, #ifdef RUNTIMELIGHTING if ((loadmodel->type == mod_brush && loadmodel->fromgame == fg_quake) || loadmodel->type == mod_heightmap) { //we only support a couple of formats. :( - if (!lightmodel && r_loadlits.value >= 2 && ((!litdata&&!expdata) || (!luxdata && r_deluxemapping))) + if (r_loadlits.value >= 2 && ((!litdata&&!expdata) || (!luxdata && r_deluxemapping))) { - writelitfile = !litdata&&!expdata; - numlightdata = l->filelen; - lightmodel = loadmodel; - relitsurface = 0; + relighting = RelightSetup(loadmodel, l->filelen, !litdata&&!expdata); } - else if (!lightmodel && r_deluxemapping_cvar.value>1 && r_deluxemapping && !luxdata + else if (r_deluxemapping_cvar.value>1 && r_deluxemapping && !luxdata #ifdef RTLIGHTS && !(r_shadow_realtime_world.ival && r_shadow_realtime_world_lightmaps.value<=0) #endif ) { //if deluxemapping is on, generate missing lux files a little more often, but don't bother if we have rtlights on anyway. - writelitfile = false; - numlightdata = l->filelen; - lightmodel = loadmodel; - relitsurface = 0; + + relighting = RelightSetup(loadmodel, l->filelen, false); } } /*if we're relighting, make sure there's the proper lit data to be updated*/ - if (lightmodel == loadmodel && !litdata && !expdata) + if (relighting && !litdata && !expdata) { int i; unsigned int *ergb; @@ -2171,7 +1954,7 @@ void Mod_LoadLighting (model_t *loadmodel, bspx_header_t *bspx, qbyte *mod_base, } } /*if we're relighting, make sure there's the proper lux data to be updated*/ - if (lightmodel == loadmodel && r_deluxemapping && !luxdata) + if (relighting && r_deluxemapping && !luxdata) { int i; luxdata = ZG_Malloc(&loadmodel->memgroup, samples*3); @@ -5609,14 +5392,6 @@ TRACE(("LoadBrushModel %i\n", __LINE__)); } TRACE(("LoadBrushModel %i\n", __LINE__)); } -#ifdef RUNTIMELIGHTING - TRACE(("LoadBrushModel %i\n", __LINE__)); - if (lightmodel == mod) - { - lightcontext = LightStartup(NULL, lightmodel, true, !writelitfile); - LightReloadEntities(lightcontext, Mod_GetEntitiesString(lightmodel), false); - } -#endif TRACE(("LoadBrushModel %i\n", __LINE__)); if (!isDedicated) Mod_FixupMinsMaxs(mod); diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index 3ccee5d6..56d6ed61 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -5380,7 +5380,7 @@ done:; Shader_SetBlendmode (pass, i?pass-1:NULL); - if (pass->blendmode == PBM_ADD) + if (pass->blendmode == PBM_ADD && !s->defaulttextures->fullbright) s->defaulttextures->fullbright = pass->anim_frames[0]; } diff --git a/engine/gl/gl_shadow.c b/engine/gl/gl_shadow.c index 1d7342bc..4685f307 100644 --- a/engine/gl/gl_shadow.c +++ b/engine/gl/gl_shadow.c @@ -2328,6 +2328,9 @@ static void Sh_GenShadowFace(dlight_t *l, vec3_t axis[3], int lighttype, shadowm if (lighttype & LSHADER_ORTHO) r_refdef.frustum_numplanes = 4; //kill the near clip plane - we allow ANYTHING nearer through. + if (lighttype & LSHADER_FAKESHADOWS) + r_refdef.flipcull ^= SHADER_CULL_FLIP; + #ifdef SHADOWDBG_COLOURNOTDEPTH BE_SelectMode(BEM_STANDARD); #else @@ -2806,10 +2809,7 @@ void Sh_GenerateFakeShadows(void) //generates shadowmaps and selects the dlight, if (!BE_SelectDLight(l, vec3_origin, l->axis, LSHADER_SMAP|LSHADER_ORTHO)) return; - if (!Sh_GenShadowMap(l, LSHADER_SMAP|LSHADER_ORTHO|LSHADER_FAKESHADOWS, l->axis, NULL, smsize, texwidth)) - return; - - RQuantAdd(RQUANT_RTLIGHT_DRAWN, 1); + Sh_GenShadowMap(l, LSHADER_SMAP|LSHADER_ORTHO|LSHADER_FAKESHADOWS, l->axis, NULL, smsize, texwidth); } static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, vec3_t axis[3], qbyte *vvis) diff --git a/engine/gl/ltface.c b/engine/gl/ltface.c index ea7fd00c..fabea62f 100644 --- a/engine/gl/ltface.c +++ b/engine/gl/ltface.c @@ -19,7 +19,10 @@ typedef struct mentity_s { struct relight_ctx_s { unsigned int nummodels; - model_t *models[2048]; + model_t *models[2048]; //0 is the worldmodel and must be valid + + qboolean parsed; //ents have been parsed okay. + qboolean loaded; //needed models are all loaded. float minlight; qboolean skiplit; //lux only @@ -27,6 +30,8 @@ struct relight_ctx_s mentity_t *entities; unsigned int num_entities; unsigned int max_entities; + size_t lightmapsamples; + unsigned long nextface; }; @@ -120,19 +125,8 @@ static void ParseEpair (mentity_t *mapent, char *key, char *value) } } -void LightShutdown(struct relight_ctx_s *ctx, model_t *mod) +void LightShutdown(struct relight_ctx_s *ctx) { - qboolean stillheld = false; - unsigned int i; - for (i = 0; i < ctx->nummodels; i++) - { - if (ctx->models[i] == mod) - ctx->models[i] = NULL; - if (ctx->models[i]) - stillheld = true; - } - if (stillheld) - return; Z_Free(ctx->entities); Z_Free(ctx); } @@ -979,7 +973,7 @@ void LightPlane (struct relight_ctx_s *ctx, struct llightinfo_s *l, lightstylein } } } -void LightFace (struct relight_ctx_s *ctx, struct llightinfo_s *threadctx, int facenum) +static void LightFace (struct relight_ctx_s *ctx, struct llightinfo_s *threadctx, int facenum) { dface_t *f = ctx->models[0]->surfaces + facenum; vec4_t plane; @@ -1005,4 +999,243 @@ void LightFace (struct relight_ctx_s *ctx, struct llightinfo_s *threadctx, int f else LightPlane(ctx, threadctx, f->styles, NULL, f->samples, f->samples - ctx->models[0]->lightdata + ctx->models[0]->deluxdata, plane, f->texinfo->vecs, exactmins, exactmaxs, texmins, texsize, 1<lmshift); } + + + +#if defined(MULTITHREAD) +#ifdef _WIN32 +#include +#elif defined(__linux__) +#include +#endif +static void *relightthread[8]; +static unsigned int relightthreads; +static volatile qboolean wantrelight; +static struct relight_ctx_s *lightcontext; + +static int RelightThread(void *ctx) +{ + int surf; + struct relight_ctx_s *lightcontext = ctx; + model_t *lightmodel = lightcontext->models[0]; + void *threadctx = malloc(lightthreadctxsize); + while (wantrelight) + { +#ifdef _WIN32 + surf = InterlockedIncrement(&lightcontext->nextface); +#elif defined(__GNUC__) + surf = __sync_add_and_fetch(&lightcontext->nextface, 1); +#else + surf = relitsurface++; +#endif + if (surf >= lightmodel->numsurfaces) + break; + LightFace(lightcontext, threadctx, surf); + lightmodel->surfaces[surf].cached_dlight = -1; //invalidate it (slightly racey buy w/e + } + free(threadctx); + return 0; +} +#else +static void *lightmainthreadctx; +#endif + +void RelightTerminate(model_t *mod) +{ + model_t *lightmodel; + size_t u; + if (!lightcontext) + return; + + //if one of the models we're using is being purged then we have to abort the relight to avoid caching partial results (especially if its the model we're actually relighting) + if (mod) + { + for (u = 0; u < lightcontext->nummodels; u++) + if (lightcontext->models[u] == mod) + break; + } + else + u = 0; + + if (u < lightcontext->nummodels) + { + lightmodel = lightcontext->models[0]; + +#ifdef MULTITHREAD + wantrelight = false; + if (relightthreads) + { + int i; + wantrelight = false; + for (i = 0; i < relightthreads; i++) + { + Sys_WaitOnThread(relightthread[i]); + relightthread[i] = NULL; + } + relightthreads = 0; + } +#else + free(lightmainthreadctx); + lightmainthreadctx = NULL; +#endif + + if (lightcontext->nextface >= lightmodel->numsurfaces) + { + vfsfile_t *f; + char filename[MAX_QPATH]; + + if (lightmodel->deluxdata) + { + COM_StripExtension(lightmodel->name, filename, sizeof(filename)); + COM_DefaultExtension(filename, ".lux", sizeof(filename)); + f = FS_OpenVFS(filename, "wb", FS_GAME); + if (f) + { + VFS_WRITE(f, "QLIT\1\0\0\0", 8); + VFS_WRITE(f, lightmodel->deluxdata, lightcontext->lightmapsamples*3); + VFS_CLOSE(f); + } + else + Con_Printf("Unable to write \"%s\"\n", filename); + } + + if (!lightcontext->skiplit) //the user might already have a lit file (don't overwrite it). + { + COM_StripExtension(lightmodel->name, filename, sizeof(filename)); + COM_DefaultExtension(filename, ".lit", sizeof(filename)); + + f = FS_OpenVFS(filename, "wb", FS_GAME); + if (f) + { + if (lightmodel->lightmaps.fmt == LM_E5BGR9) + { + VFS_WRITE(f, "QLIT\x01\0\x01\0", 8); + VFS_WRITE(f, lightmodel->lightdata, lightcontext->lightmapsamples*4); + } + else + { + VFS_WRITE(f, "QLIT\1\0\0\0", 8); + VFS_WRITE(f, lightmodel->lightdata, lightcontext->lightmapsamples*3); + } + VFS_CLOSE(f); + } + else + Con_Printf("Unable to write \"%s\"\n", filename); + } + } + else + Con_Printf("Relighting aborted before completion\n"); + + LightShutdown(lightcontext); + lightcontext = NULL; + } +} + +qboolean RelightSetup (model_t *model, size_t lightsamples, qboolean generatelit) +{ + Sys_LockMutex(com_resourcemutex); //models can be loaded on different threads, so no race conditions please. + if (!lightcontext) + { + lightcontext = LightStartup(NULL, model, true, !generatelit); + lightcontext->lightmapsamples = lightsamples; + Sys_UnlockMutex(com_resourcemutex); + return true; + } + Sys_UnlockMutex(com_resourcemutex); + + return false; +} + +const char *RelightGetProgress(float *progress) +{ + char filename[MAX_QPATH]; + if (!lightcontext) + return NULL; + *progress = (lightcontext->nextface*100.0f) / lightcontext->models[0]->numsurfaces; + + COM_StripExtension(lightcontext->models[0]->name, filename, sizeof(filename)); + COM_DefaultExtension(filename, lightcontext->skiplit?".lux":".lit", sizeof(filename)); + return va("%s", filename); +} + +void RelightThink (void) +{ + if (lightcontext) + { + model_t *lightmodel = lightcontext->models[0]; + + if (!lightcontext->loaded) + { //make sure everything finished loading properly before we start poking things. + size_t u; + if (!lightcontext->parsed) + { + if (lightcontext->models[0]->loadstate != MLS_LOADED) + return; //not ready yet... + + LightReloadEntities(lightcontext, Mod_GetEntitiesString(lightmodel), false); + lightcontext->parsed = true; + } + + for (u = 0; u < lightcontext->nummodels; u++) + if (lightcontext->models[u]->loadstate != MLS_LOADED) + return; + lightcontext->loaded = true; + } + +#ifdef MULTITHREAD + if (!relightthreads) + { + int i; +#if defined(_WIN32) && !defined(WINRT) + HANDLE me = GetCurrentProcess(); + DWORD_PTR proc, sys; + /*count cpus*/ + GetProcessAffinityMask(me, &proc, &sys); + relightthreads = 0; + for (i = 0; i < sizeof(proc)*8; i++) + if (proc & ((size_t)1u< sizeof(relightthread)/sizeof(relightthread[0])) + relightthreads = sizeof(relightthread)/sizeof(relightthread[0]); + wantrelight = true; + for (i = 0; i < relightthreads; i++) + relightthread[i] = Sys_CreateThread("relight", RelightThread, lightcontext, THREADP_NORMAL, 0); + } + if (lightcontext->nextface < lightmodel->numsurfaces) + { + return; + } +#else + if (!lightmainthreadctx) + lightmainthreadctx = malloc(lightthreadctxsize); + LightFace(lightcontext, lightmainthreadctx, relitsurface); + lightmodel->surfaces[relitsurface].cached_dlight = -1; + + relitsurface++; +#endif + if (lightcontext->nextface >= lightmodel->numsurfaces) + { + Con_Printf("Finished lighting %s\n", lightmodel->name); + + RelightTerminate(lightmodel); + } + } +} #endif diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index 4360087b..b7ce9a8c 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -828,7 +828,7 @@ static qboolean HTTP_DL_Work(struct dl_download *dl) con->file = dl->file; con->state = HC_GETTING; dl->status = DL_ACTIVE; - //Fall through + goto firstread; //oh noes! an evil goto! this is the easiest way to avoid the 'return' here when no data follows a 0-byte download case HC_GETTING: if (con->bufferlen - con->bufferused < 1530) ExpandBuffer(con, 1530); @@ -851,6 +851,7 @@ static qboolean HTTP_DL_Work(struct dl_download *dl) con->bufferused+=ammount; +firstread: if (con->chunking) { //9\r\n @@ -1053,7 +1054,7 @@ void HTTPDL_Establish(struct dl_download *dl) netadr_t adr = {0}; //fixme: support more than one address possibility? //https uses a different default port - if (NET_StringToAdr2(con->server, https?443:80, &adr, 1)) + if (NET_StringToAdr2(con->server, https?443:80, &adr, 1, NULL)) con->sock = TCP_OpenStream(&adr); con->stream = FS_OpenTCPSocket(con->sock, true, con->server); } diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index fab95875..d3260a5e 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -179,7 +179,7 @@ Marks the edict as free FIXME: walk all entities and NULL out references to this entity ================= */ -void PDECL ED_Free (pubprogfuncs_t *ppf, struct edict_s *ed) +void PDECL ED_Free (pubprogfuncs_t *ppf, struct edict_s *ed, pbool instant) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e = (edictrun_t *)ed; @@ -201,7 +201,7 @@ void PDECL ED_Free (pubprogfuncs_t *ppf, struct edict_s *ed) return; e->ereftype = ER_FREE; - e->freetime = (float)*externs->gametime; + e->freetime = instant?0:(float)*externs->gametime; /* ed->v.model = 0; diff --git a/engine/qclib/progsint.h b/engine/qclib/progsint.h index 5b424c57..438a6e6e 100644 --- a/engine/qclib/progsint.h +++ b/engine/qclib/progsint.h @@ -403,7 +403,7 @@ void *PRHunkAlloc(progfuncs_t *progfuncs, int ammount, const char *name); void PR_Profile_f (void); struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *progfuncs, pbool object, size_t extrasize); -void PDECL ED_Free (pubprogfuncs_t *progfuncs, struct edict_s *ed); +void PDECL ED_Free (pubprogfuncs_t *progfuncs, struct edict_s *ed, pbool instant); pbool PR_RunGC (progfuncs_t *progfuncs); string_t PDECL PR_AllocTempString (pubprogfuncs_t *ppf, const char *str); diff --git a/engine/qclib/progslib.h b/engine/qclib/progslib.h index 774170cf..c0d778a6 100644 --- a/engine/qclib/progslib.h +++ b/engine/qclib/progslib.h @@ -105,7 +105,7 @@ struct pubprogfuncs_s void (PDECL *PrintEdict) (pubprogfuncs_t *prinst, struct edict_s *ed); //get a listing of all vars on an edict (sent back via 'print') struct edict_s *(PDECL *EntAlloc) (pubprogfuncs_t *prinst, pbool object, size_t extrasize); - void (PDECL *EntFree) (pubprogfuncs_t *prinst, struct edict_s *ed); + void (PDECL *EntFree) (pubprogfuncs_t *prinst, struct edict_s *ed, pbool instant); struct edict_s *(PDECL *EdictNum) (pubprogfuncs_t *prinst, unsigned int n); //get the nth edict unsigned int (PDECL *NumForEdict) (pubprogfuncs_t *prinst, struct edict_s *e); //so you can find out what that 'n' will be @@ -279,7 +279,7 @@ typedef union eval_s #define PR_RegisterFieldVar(pf,type,name,reqofs,qcofs) (*pf->RegisterFieldVar) (pf,type,name,reqofs,qcofs) #define ED_Alloc(pf,isobj,extsize) (*pf->EntAlloc) (pf, isobj, extsize) -#define ED_Free(pf, ed) (*pf->EntFree) (pf, ed) +#define ED_Free(pf, ed) (*pf->EntFree) (pf, ed, false) #define ED_Clear(pf, ed) (*pf->EntClear) (pf, ed) #define PR_LoadEnts(pf, s, ctx, entcb, extcb) (*pf->load_ents) (pf, s, ctx, entcb, extcb) diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index e4d1cc07..cc38423b 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -4288,9 +4288,27 @@ static void QCBUILTIN PF_Remove (pubprogfuncs_t *prinst, struct globalvars_s *pr return; //yeah, alright, so this is hacky. } - ED_Free (prinst, ed); + prinst->EntFree (prinst, ed, false); } +static void QCBUILTIN PF_RemoveInstant (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + edict_t *ed; + ed = G_EDICT(prinst, OFS_PARM0); + + if (ED_ISFREE(ed)) + { + ED_CanFree(ed); //fake it + if (developer.value) + { + Con_Printf("Tried removing free entity at:\n"); + PR_StackTrace(prinst, false); + } + return; //yeah, alright, so this is hacky. + } + + prinst->EntFree (prinst, ed, true); +} /* @@ -10511,7 +10529,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"vlen", PF_vlen, 12, 12, 12, 0, D("float(vector v)", "Returns the square root of the dotproduct of a vector with itself. Or in other words the length of a distance vector, in quake units.")}, {"vectoyaw", PF_vectoyaw, 13, 13, 13, 0, D("float(vector v, optional entity reference)", "Given a direction vector, returns the yaw angle in which that direction vector points. If an entity is passed, the yaw angle will be relative to that entity's gravity direction.")}, {"spawn", PF_Spawn, 14, 14, 14, 0, D("entity()", "Adds a brand new entity into the world! Hurrah, you're now a parent!")}, - {"remove", PF_Remove, 15, 15, 15, 0, D("void(entity e)", "Destroys the given entity and clears some limited fields (including model, modelindex, solid, classname). Any references to the entity following the call are an error. After two seconds, the entity will be reused, in the interim you can unfortunatly still read its fields to see if the reference is no longer valid.")}, + {"remove", PF_Remove, 15, 15, 15, 0, D("void(entity e)", "Destroys the given entity and clears some limited fields (including model, modelindex, solid, classname). Any references to the entity following the call are an error. After half a second the entity will be reused, in the interim you can unfortunatly still read its fields to see if the reference is no longer valid.")}, + {"removeinstant", PF_RemoveInstant, 0, 0, 0, 0, D("void(entity e)", "Same thing as the regular remove builtin, but bypasses the half-second rule. The entity slot may be reused instantly. Be CERTAIN that you have no lingering references, because if they're followed they will end up poking an entirely different type of entity! So only use this where you're sure its safe.")}, {"traceline", PF_svtraceline, 16, 16, 16, 0, D("void(vector v1, vector v2, float flags, entity ent)", "Traces a thin line through the world from v1 towards v2.\nWill not collide with ent, ent.owner, or any entity who's owner field refers to ent.\nThe passed entity will also be used to determine whether to use a capsule trace, the contents that the trace should impact, and a couple of other extra fields that define the trace.\nThere are no side effects beyond the trace_* globals being written.\nflags&MOVE_NOMONSTERS will not impact on non-bsp entities.\nflags&MOVE_MISSILE will impact with increased size.\nflags&MOVE_HITMODEL will impact upon model meshes, instead of their bounding boxes.\nflags&MOVE_TRIGGERS will also stop on triggers\nflags&MOVE_EVERYTHING will stop if it hits anything, even non-solid entities.\nflags&MOVE_LAGGED will backdate entity positions for the purposes of this builtin according to the indicated player ent's latency, to provide lag compensation.")}, {"checkclient", PF_checkclient, 17, 17, 17, 0, D("entity()", "Returns one of the player entities. The returned player will change periodically.")}, {"find", PF_FindString, 18, 18, 18, 0, D("entity(entity start, .string fld, string match)", "Scan for the next entity with a given field set to the given 'match' value. start should be either world, or the previous entity that was found. Returns world on failure/if there are no more.\nIf you have many many entities then you may find that hashtables will give more performance (but requires extra upkeep).")}, diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index 71b3829f..d2171043 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -460,12 +460,12 @@ static void QDECL Q1QVMPF_ClearEdict(pubprogfuncs_t *pf, edict_t *e) Q1QVMED_ClearEdict(e, true); } -static void QDECL Q1QVMPF_EntRemove(pubprogfuncs_t *pf, edict_t *e) +static void QDECL Q1QVMPF_EntRemove(pubprogfuncs_t *pf, edict_t *e, qboolean instant) { if (!ED_CanFree(e)) return; e->ereftype = ER_FREE; - e->freetime = sv.time; + e->freetime = instant?0:sv.time; } static edict_t *QDECL Q1QVMPF_EntAlloc(pubprogfuncs_t *pf, pbool object, size_t extrasize) @@ -811,7 +811,7 @@ static qintptr_t QVM_Remove_Ent (void *offset, quintptr_t mask, const qintptr_t { if (arg[0] >= sv.world.max_edicts) return false; - Q1QVMPF_EntRemove(svprogfuncs, q1qvmprogfuncs.edicttable[arg[0]]); + Q1QVMPF_EntRemove(svprogfuncs, q1qvmprogfuncs.edicttable[arg[0]], false); return true; } diff --git a/engine/server/server.h b/engine/server/server.h index 4d84e00f..202c4ade 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -1378,7 +1378,9 @@ void SV_ClientProtocolExtensionsChanged(client_t *client); //sv_master.c void SVM_Think(int port); -vfsfile_t *SVM_GenerateIndex(const char *fname); +vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname); +void SVM_AddBrokerGame(const char *brokerid, const char *info); +void SVM_RemoveBrokerGame(const char *brokerid); // @@ -1637,7 +1639,7 @@ typedef struct { qboolean hasauthed; qboolean isreverse; - char challenge[32]; + char challenge[64]; } qtvpendingstate_t; int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p); #endif diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index a83c19c3..65f0ad67 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1950,7 +1950,7 @@ static void SV_Status_f (void) extern cvar_t net_enable_tls; #endif #ifdef HAVE_HTTPSV - extern cvar_t net_enable_http, net_enable_webrtcbroker, net_enable_websockets; + extern cvar_t net_enable_http, net_enable_rtcbroker, net_enable_websockets; #endif extern cvar_t net_enable_qizmo, net_enable_qtv; #endif @@ -2013,6 +2013,7 @@ static void SV_Status_f (void) Con_Printf("client types :%s\n", sv_listen_qw.ival?" Q2":""); break; #endif + default: Con_Printf("client types :%s", sv_listen_qw.ival?" QW":""); #ifdef NQPROT @@ -2027,7 +2028,9 @@ static void SV_Status_f (void) else if (net_enable_dtls.ival) Con_Printf(" DTLS"); #endif + Con_Printf("\n"); #if defined(TCPCONNECT) && !defined(CLIENTONLY) + Con_Printf("tcp services :"); #if defined(HAVE_SSL) if (net_enable_tls.ival) Con_Printf(" TLS"); @@ -2035,8 +2038,8 @@ static void SV_Status_f (void) #ifdef HAVE_HTTPSV if (net_enable_http.ival) Con_Printf(" HTTP"); - if (net_enable_webrtcbroker.ival) - Con_Printf(" WebRTC"); + if (net_enable_rtcbroker.ival) + Con_Printf(" RTC"); if (net_enable_websockets.ival) Con_Printf(" WS"); #endif diff --git a/engine/server/sv_cluster.c b/engine/server/sv_cluster.c index dd9f1c6c..8e964c88 100644 --- a/engine/server/sv_cluster.c +++ b/engine/server/sv_cluster.c @@ -978,6 +978,7 @@ void SSV_UpdateAddresses(void) netadr_t addr[64]; struct ftenet_generic_connection_s *con[sizeof(addr)/sizeof(addr[0])]; unsigned int flags[sizeof(addr)/sizeof(addr[0])]; + const char *params[sizeof(addr)/sizeof(addr[0])]; int count; sizebuf_t send; qbyte send_buf[MAX_QWMSGLEN]; @@ -986,7 +987,7 @@ void SSV_UpdateAddresses(void) if (!SSV_IsSubServer() && !msv_loop_from_ss) return; - count = NET_EnumerateAddresses(svs.sockets, con, flags, addr, sizeof(addr)/sizeof(addr[0])); + count = NET_EnumerateAddresses(svs.sockets, con, flags, addr, params, sizeof(addr)/sizeof(addr[0])); if (*sv_serverip.string) { @@ -994,7 +995,7 @@ void SSV_UpdateAddresses(void) { if (addr[i].type == NA_IP) { - NET_StringToAdr2(sv_serverip.string, BigShort(addr[i].port), &addr[0], sizeof(addr)/sizeof(addr[0])); + NET_StringToAdr2(sv_serverip.string, BigShort(addr[i].port), &addr[0], sizeof(addr)/sizeof(addr[0]), NULL); count = 1; break; } diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index b46c5d77..8315079f 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -108,9 +108,21 @@ cvar_t allow_download_other = CVARD("allow_download_other", "0", "0 blocks down extern cvar_t sv_allow_splitscreen; +#ifdef SUPPORT_ICE +static void QDECL SV_Public_Callback(struct cvar_s *var, char *oldvalue) +{ + if (var->ival == 2) + FTENET_AddToCollection(svs.sockets, var->name, "/", NA_INVALID, NP_RTC_TLS); + else + FTENET_AddToCollection(svs.sockets, var->name, "", NA_INVALID, NP_INVALID); +} +cvar_t sv_public = CVARCD("sv_public", "0", SV_Public_Callback, "-1: Fully blocks all inbound connections.\n0: Disable subscribing to master servers (for private lan-only games).\n1: Subscribe to public master servers. Your IP address will be listed publicly. Make sure your Router/NAT+Firewall are set to allow inbound connections.\n2: Subscribe to a broker master, allowing firewall hole punching."); +#else +cvar_t sv_public = CVARD("sv_public", "0", "-1: Fully blocks all inbound connections.\n0: Disable subscribing to master servers (for private lan-only games).\n1: Subscribe to public master servers. Your IP address will be listed publicly. Make sure your Router/NAT+Firewall are set to allow inbound connections."); +#endif + cvar_t sv_guidhash = CVARD("sv_guidkey", "", "If set, clients will calculate their GUID values against this string instead of the server's IP address. This allows consistency between multiple servers (for stats tracking), but do NOT treat the client's GUID as something that is secure."); cvar_t sv_serverip = CVARD("sv_serverip", "", "Set this cvar to the server's public ip address if the server is behind a firewall and cannot detect its own public address. Providing a port is required if the firewall/nat remaps it, but is otherwise optional."); -cvar_t sv_public = CVARD("sv_public", "0", "Controls whether the server will publically advertise itself to master servers or not. Additionally, if set to -1, will block all new connection requests even on lan."); cvar_t sv_listen_qw = CVARAFD("sv_listen_qw", "1", "sv_listen", 0, "Specifies whether normal clients are allowed to connect."); cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server.\n0 = don't let them in.\n1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks).\n2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol)."); cvar_t sv_listen_dp = CVARD("sv_listen_dp", "0", "Allows the server to respond with the DP-specific handshake protocol.\nWarning: this can potentially get confused with quake2, and results in race conditions with both vanilla netquake and quakeworld protocols.\nOn the plus side, DP clients can usually be identified correctly, enabling a model+sound limit boost."); @@ -118,7 +130,7 @@ cvar_t sv_listen_dp = CVARD("sv_listen_dp", "0", "Allows the server to respond cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); #endif cvar_t sv_reconnectlimit = CVARD("sv_reconnectlimit", "0", "Blocks dupe connection within the specified length of time ."); -cvar_t sv_use_dns = CVARD("sv_use_dns", "", "Performs a reverse-dns lookup in order to report actual ip addresses of clients."); +cvar_t sv_use_dns = CVARD("sv_use_dns", "", "Performs a reverse-dns lookup in order to report more info about where clients are connecting from."); extern cvar_t net_enable_dtls; cvar_t sv_reportheartbeats = CVARD("sv_reportheartbeats", "2", "Print a notice each time a heartbeat is sent to a master server. When set to 2, the message will be displayed once."); cvar_t sv_heartbeat_interval = CVARD("sv_heartbeat_interval", "110", "Interval between heartbeats. Low values are abusive, high values may cause NAT/ghost issues."); @@ -2502,7 +2514,9 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) newcl->userid = ++nextuserid; newcl->fteprotocolextensions = info->ftepext1; newcl->fteprotocolextensions2 = info->ftepext2; + newcl->ezprotocolextensions1 = info->ezpext1; newcl->protocol = info->protocol; + newcl->pextknown = info->ftepext1||info->ftepext2; Q_strncpyz(newcl->guid, info->guid, sizeof(newcl->guid)); // Con_TPrintf("%s:%s:connect\n", sv.name, NET_AdrToString (adrbuf, sizeof(adrbuf), &adr)); @@ -2838,6 +2852,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) #endif newcl->netchan.compresstable = NULL; newcl->netchan.pext_fragmentation = info->mtu?true:false; + newcl->netchan.pext_stunaware = !!(info->ftepext2&PEXT2_STUNAWARE); //this is the upper bound of the mtu, if its too high we'll get EMSGSIZE and we'll reduce it. //however, if it drops below newcl->netchan.message.maxsize then we'll start to see undeliverable reliables, which means dropped clients. newcl->netchan.mtu = MAX_DATAGRAM; //vanilla qw clients are assumed to have an mtu of this size. @@ -4509,7 +4524,10 @@ qboolean SV_ReadPackets (float *delay) if (svs.gametype == GT_QUAKE3) { received++; - SVQ3_HandleClient(); + if (SVQ3_HandleClient()) + ; + else if (NET_WasSpecialPacket(svs.sockets)) + continue; continue; } #endif @@ -4997,7 +5015,7 @@ float SV_Frame (void) #endif #ifdef HAVE_CLIENT - isidle = !isDedicated && sv.allocated_client_slots == 1 && Key_Dest_Has(~kdm_game) && cls.state == ca_active; + isidle = !isDedicated && sv.allocated_client_slots == 1 && Key_Dest_Has(~kdm_game) && cls.state == ca_active && !cl.implicitpause; /*server is effectively paused in SP/coop if there are no clients/spectators*/ if (sv.spawned_client_slots == 0 && sv.spawned_observer_slots == 0 && !deathmatch.ival) isidle = true; @@ -5110,6 +5128,8 @@ float SV_Frame (void) // get packets isidle = !SV_ReadPackets (&delay); + if (isDedicated) + NET_Tick(); if (pr_imitatemvdsv.ival || dpcompat_nopreparse.ival) { @@ -5186,8 +5206,6 @@ float SV_Frame (void) if (isDedicated) #endif { - NET_Tick(); - if (sv.framenum != 1) { #ifndef SERVERONLY @@ -5372,6 +5390,7 @@ void SV_InitLocal (void) Cvar_Register (&sv_guidhash, cvargroup_servercontrol); Cvar_Register (&sv_serverip, cvargroup_servercontrol); Cvar_Register (&sv_public, cvargroup_servercontrol); + sv_public.restriction = RESTRICT_MAX; //no disabling this over rcon. Cvar_Register (&sv_listen_qw, cvargroup_servercontrol); Cvar_Register (&sv_listen_nq, cvargroup_servercontrol); Cvar_Register (&sv_listen_dp, cvargroup_servercontrol); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index a747e739..2d385c9a 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -35,6 +35,7 @@ enum gametypes_e }; typedef struct svm_server_s { netadr_t adr; + const char *brokerid; //from rtc broker, for ICE connections (persistent until killed). int protover; unsigned int clients; unsigned int maxclients; @@ -44,7 +45,7 @@ typedef struct svm_server_s { unsigned short gametype; float expiretime; - bucket_t bucket; + bucket_t bucket; //for faster address lookups. struct svm_game_s *game; struct svm_server_s *next; } svm_server_t; @@ -54,6 +55,7 @@ typedef struct svm_game_s { svm_server_t *firstserver; size_t numservers; + qboolean persistent; char name[1]; } svm_game_t; @@ -92,11 +94,16 @@ static void QDECL SVM_Port_Callback(struct cvar_s *var, char *oldvalue) FTENET_AddToCollection(svm_sockets, var->name, var->string, NA_IP, NP_DGRAM); } static cvar_t sv_heartbeattimeout = CVARD("sv_heartbeattimeout", "600", "How many seconds a server should remain listed after its latest heartbeat. Larger values can avoid issues from packetloss, but can also make dos attacks easier."); -static cvar_t sv_masterport = CVARC("sv_masterport", "27000 28000", SVM_Port_Callback); -static cvar_t sv_masterport_tcp = CVARC("sv_masterport_tcp", "27000 28000", SVM_Tcpport_Callback); +static cvar_t sv_masterport = CVARC("sv_masterport", STRINGIFY(PORT_QWMASTER)" "STRINGIFY(PORT_ICEBROKER), SVM_Port_Callback); +static cvar_t sv_masterport_tcp = CVARC("sv_masterport_tcp", STRINGIFY(PORT_ICEBROKER), SVM_Tcpport_Callback); static cvar_t sv_maxgames = CVARD("sv_maxgames", "100", "Limits the number of games that may be known. This is to reduce denial of service attacks."); static cvar_t sv_maxservers = CVARD("sv_maxservers", "1000", "Limits the number of servers (total from all games) that may be known. This is to reduce denial of service attacks."); +static unsigned int SVM_GenerateBrokerKey(const char *brokerid) +{ + return Hash_Key(brokerid, svm.serverhash.numbuckets); +} + //returns a hash key for a server's address. static unsigned int SVM_GenerateAddressKey(const netadr_t *adr) { @@ -127,16 +134,20 @@ static svm_server_t *SVM_GetServer(netadr_t *adr) unsigned int key = SVM_GenerateAddressKey(adr); server = Hash_GetKey(&svm.serverhash, key); - while (server && !NET_CompareAdr(&server->adr, adr)) + while (server) { + if (!server->brokerid) //don't report brokered servers by address. + if (NET_CompareAdr(&server->adr, adr)) + return server; server = Hash_GetNextKey(&svm.serverhash, key, server); } - return server; + return NULL; } -static svm_game_t *SVM_FindGame(const char *game, qboolean create) +static svm_game_t *SVM_FindGame(const char *game, int create) { - svm_game_t *g; + svm_game_t *g, **link; + const char *sanitise; for (g = svm.firstgame; g; g = g->next) { if (!Q_strcasecmp(game, g->name)) @@ -151,16 +162,28 @@ static svm_game_t *SVM_FindGame(const char *game, qboolean create) return NULL; } //block some chars that may cause issues/exploits. sorry. - if (strchr(game, '.') || strchr(game, '\"') || strchr(game, '/') || strchr(game, '?') || strchr(game, '&') || strchr(game, '+') || strchr(game, '\'') || strchr(game, '<') || strchr(game, '>')) + for (sanitise = game; *sanitise; sanitise++) + { + if ((*sanitise >= 'a' && *sanitise <= 'z') || //allow lowercase + (*sanitise >= 'A' && *sanitise <= 'Z') || //allow uppercase + (*sanitise >= '0' && *sanitise <= '9') || //allow numbers (but not leeding, see below) + (*sanitise == '-' || *sanitise == '_')) //allow a little punctuation, to make up for the lack of spaces. + continue; return NULL; + } if (!*game || (*game >= '0' && *game <= '9')) return NULL; //must not start with a number either. g = ZF_Malloc(sizeof(*g) + strlen(game)); if (g) { strcpy(g->name, game); - g->next = svm.firstgame; - svm.firstgame = g; + g->persistent = create==2; + g->next = NULL; + + //add it at the end. + for (link = &svm.firstgame; *link; link = &(*link)->next) + ; + *link = g; svm.numgames++; Con_DPrintf("Creating game \"%s\"\n", g->name); } @@ -176,7 +199,8 @@ static void SVM_RemoveOldServers(void) { for (serverlink = &g->firstserver; (s=*serverlink); ) { - if (s->expiretime < svm.time) + //brokered servers don't time out (they drop when their tcp connection dies) + if (!s->brokerid && s->expiretime < svm.time) { if (developer.ival) { @@ -196,7 +220,7 @@ static void SVM_RemoveOldServers(void) serverlink = &s->next; } - if (!g->firstserver) + if (!g->firstserver && !g->persistent) { Con_DPrintf("game \"%s\" has no active servers\n", g->name); *gamelink = g->next; @@ -208,7 +232,7 @@ static void SVM_RemoveOldServers(void) } } -int SVM_AddIPAddresses(sizebuf_t *sb, int first, const char *gamename, int v4, int v6, qboolean prefixes, int gametype) +int SVM_AddIPAddresses(sizebuf_t *sb, int first, int ver, const char *gamename, int v4, int v6, qboolean empty, qboolean full, qboolean prefixes, int gametype) { int number = 0; svm_server_t *server; @@ -227,9 +251,14 @@ int SVM_AddIPAddresses(sizebuf_t *sb, int first, const char *gamename, int v4, i for (; server; server = server->next) { -//FIXME -// if (gametype != -1 && server->gametype != gametype) -// continue; + if (server->protover != ver) + continue; + if (server->clients == 0 && !empty) + continue; + if (server->clients >= server->maxclients && !full) + continue; + if (gametype != -1 && server->gametype != gametype) + continue; switch(server->adr.type) { case NA_IP: @@ -260,7 +289,101 @@ int SVM_AddIPAddresses(sizebuf_t *sb, int first, const char *gamename, int v4, i return number; } -vfsfile_t *SVM_GenerateIndex(const char *fname) +static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake) +{ + char *ret = outhtml; + conchar_t chars[8192], *c=chars, *end; + unsigned int codeflags, codepoint, oldflags=CON_WHITEMASK; + unsigned int b; + + const char *htmlnames[16] = { + "#000000", //black + "#0000AA", //dark blue + "#00AA00", //dark green + "#00AAAA", //dark cyan + "#AA0000", //dark red + "#AA00AA", //dark magenta + "#AA5500", //brown + "#AAAAAA", //grey + "#555555", //dark grey + "#5555FF", //blue + "#55FF55", //green + "#55FFFF", //cyan + "#FF5555", //red + "#FF55FF", //magenta + "#FFFF55", //yellow + "#FFFFFF", //white + }; + + if (!outsize--) + return NULL; //no space for the null + + end = COM_ParseFunString(oldflags, quake, chars, sizeof(chars), false); + while (c < end) + { + c = Font_Decode(c, &codeflags, &codepoint); + + if (codeflags & CON_HIDDEN) + continue; //erk...? + + if (codeflags & CON_RICHFORECOLOUR) //fixme...? + codeflags = CON_WHITEMASK; + if ((codeflags&CON_FGMASK) != (oldflags&CON_FGMASK)) + { + if (oldflags != CON_WHITEMASK) + { + Q_strncpyz(outhtml, "", outsize); + b=strlen(outhtml); + outhtml += b; + outsize -= b; + } + if (codeflags != CON_WHITEMASK) + { + Q_snprintfz(outhtml, outsize, "", htmlnames[(codeflags&CON_FGMASK)>>CON_FGSHIFT]); + b=strlen(outhtml); + outhtml += b; + outsize -= b; + } + oldflags = codeflags; + } + if (codepoint == '<') + { + Q_strncpyz(outhtml, "<", outsize); + b=strlen(outhtml); + } + else if (codepoint == '>') + { + Q_strncpyz(outhtml, ">", outsize); + b=strlen(outhtml); + } + else if (codepoint == '&') + { + Q_strncpyz(outhtml, "&", outsize); + b=strlen(outhtml); + } + else if (codepoint == '\"') + { + Q_strncpyz(outhtml, """, outsize); + b=strlen(outhtml); + } + else if (codepoint == '\'') + { + Q_strncpyz(outhtml, "'", outsize); + b=strlen(outhtml); + } + else + b = utf8_encode(outhtml, codepoint, outsize); + if (b > 0) + { + outhtml += b; + outsize -= b; + } + } + *outhtml = 0; + return ret; +} + +vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname) { static const char *thecss = ""; char tmpbuf[256]; + char hostname[1024]; + const char *url; svm_game_t *game; svm_server_t *server; vfsfile_t *f = NULL; - unsigned clients = 0, maxclients=0; + unsigned clients = 0, maxclients=0, totalclients=0; if (!strcmp(fname, "index.html")) { f = VFSPIPE_Open(1, false); VFS_PRINTF(f, "%s", thecss); VFS_PRINTF(f, "

FTE-Master

\n"); VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "\n"); for (game = svm.firstgame; game; game = game->next) { - VFS_PRINTF(f, "\n", game->name, game->name, (unsigned)game->numservers); - clients += game->numservers; + for (clients=0, server = game->firstserver; server; server = server->next) + clients += server->clients; + if (game->numservers) //only show active servers + VFS_PRINTF(f, "\n", game->name, game->name, clients, (unsigned)game->numservers); + totalclients += clients; } VFS_PRINTF(f, "
Active GamesPlayersServer Count
%s
%s%u player(s)%u server(s)
\n"); - VFS_PRINTF(f, "%u game(s), %u server(s)\n", (unsigned)svm.numgames, clients); + VFS_PRINTF(f, "%u game(s), %u player(s), %u server(s)\n", (unsigned)svm.numgames, totalclients, (unsigned)svm.numservers); } else if (!strncmp(fname, "server/", 7)) { @@ -315,7 +444,8 @@ vfsfile_t *SVM_GenerateIndex(const char *fname) VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "\n"); - count = NET_StringToAdr2(fname+7, 0, adr, countof(adr)); + //FIXME: block dns lookups here? + count = NET_StringToAdr2(fname+7, 0, adr, countof(adr), NULL); while(count-->0) { server = SVM_GetServer(&adr[count]); @@ -328,12 +458,12 @@ vfsfile_t *SVM_GenerateIndex(const char *fname) } else if (!strncmp(fname, "game/", 5)) { - COM_StripExtension(fname+5, tmpbuf, sizeof(tmpbuf)); - game = SVM_FindGame(tmpbuf, false); + const char *gamename = fname+5; + game = SVM_FindGame(gamename, false); f = VFSPIPE_Open(1, false); VFS_PRINTF(f, "%s", thecss); - VFS_PRINTF(f, "

Servers for %s

\n", tmpbuf); + VFS_PRINTF(f, "

Servers for %s

\n", gamename); if(game) { @@ -341,7 +471,15 @@ vfsfile_t *SVM_GenerateIndex(const char *fname) VFS_PRINTF(f, "\n"); for (server = game->firstserver; server; server = server->next) { - VFS_PRINTF(f, "\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), server->hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + if (server->brokerid) + { + url = tmpbuf; + Q_snprintfz(tmpbuf, sizeof(tmpbuf), "rtc://%s/%s", requesthost, server->brokerid); + } + else + url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr); + QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname); + VFS_PRINTF(f, "\n", url, hostname, server->gamedir, server->mapname, server->clients, server->maxclients); clients += server->clients; maxclients += server->maxclients; } @@ -349,7 +487,7 @@ vfsfile_t *SVM_GenerateIndex(const char *fname) VFS_PRINTF(f, "%u server(s), %u/%u client(s)\n", (unsigned)game->numservers, clients, maxclients); } else - VFS_PRINTF(f, "No servers known for %s\n", tmpbuf); + VFS_PRINTF(f, "Protocol '%s' is not known\n", gamename); } else if (!strncmp(fname, "raw/", 4)) { //just spews all @@ -357,12 +495,116 @@ vfsfile_t *SVM_GenerateIndex(const char *fname) game = SVM_FindGame(tmpbuf, false); f = VFSPIPE_Open(1, false); + VFS_PRINTF(f, "#Server list for \"%s\"\n", tmpbuf); for (server = (game?game->firstserver:NULL); server; server = server->next) - VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); + { + if (server->brokerid) + VFS_PRINTF(f, "rtc:///%s \\maxclients\\%u\\clients\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\n", server->brokerid, server->maxclients, server->clients, server->hostname, server->gamedir, server->mapname); + else + VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr)); + } } return f; } +static svm_game_t *SVM_GameFromBrokerID(const char **brokerid) +{ + size_t l; + char name[128]; + const char *in = *brokerid; + if (*in == '/') + in++; + for (l = 0; *in && *in != '/' && *in != '?' && *in != '#'; in++) + if (l < countof(name)-1) + name[l++] = *in; + name[l] = 0; + if (*in == '/') + in++; + else + return NULL; //only one? no game specified? get lost. + *brokerid = in; + return SVM_FindGame(name, true); +} +static svm_server_t *SVM_FindBrokerHost(const char *brokerid) +{ + svm_server_t *server; + unsigned int key = SVM_GenerateBrokerKey(brokerid); + + server = Hash_GetKey(&svm.serverhash, key); + while (server) + { + if (server->brokerid) //don't report brokered servers by address. + if (server->brokerid == brokerid) + return server; + server = Hash_GetNextKey(&svm.serverhash, key, server); + } + return NULL; +} +void SVM_RemoveBrokerGame(const char *brokerid) +{ + svm_server_t *s, **link; + svm_game_t *game = SVM_GameFromBrokerID(&brokerid); + if (!game) + { + Con_Printf("SVM_RemoveBrokerGame: failed to find game for brokered server: %s\n", brokerid); + return; + } + + for (link = &game->firstserver; (s=*link); ) + { + if (s->brokerid == brokerid) + { + *link = s->next; + Z_Free(s); + game->numservers--; + svm.numservers--; + return; + } + else + link = &(*link)->next; + } + + Con_Printf("SVM_RemoveBrokerGame: failed to remove brokered server: %s\n", brokerid); +} +void SVM_AddBrokerGame(const char *brokerid, const char *info) +{ + svm_game_t *game = SVM_GameFromBrokerID(&brokerid); + svm_server_t *server = SVM_FindBrokerHost(brokerid); + if (!server) + { + if (!game) + return; + if (svm.numservers >= sv_maxservers.ival) + { + Con_DPrintf("server limit exceeded\n"); + return; + } + Con_DPrintf("heartbeat(new - %s): /%s\n", game->name, brokerid); + + server = Z_Malloc(sizeof(svm_server_t)); + server->game = game; + server->brokerid = brokerid; + + server->next = game->firstserver; + game->firstserver = server; + game->numservers++; + svm.numservers++; + + svm.total.adds++; + + Hash_AddKey(&svm.serverhash, SVM_GenerateBrokerKey(brokerid), server, &server->bucket); + } + else + Con_DPrintf("heartbeat(update - %s): /%s\n", game->name, brokerid); + + server->protover = atoi(Info_ValueForKey(info, "protocol")); + server->maxclients = atoi(Info_ValueForKey(info, "maxclients")); + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostname, Info_ValueForKey(info, "hostname"), sizeof(server->hostname)); + Q_strncpyz(server->gamedir, Info_ValueForKey(info, "modname"), sizeof(server->gamedir)); + Q_strncpyz(server->mapname, Info_ValueForKey(info, "mapname"), sizeof(server->mapname)); +} + static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numclients, float validuntil) { svm_server_t *server = SVM_GetServer(adr); @@ -416,6 +658,25 @@ static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numc return server; } +#ifdef TCPCONNECT +void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen); +#endif +void SVM_GenChallenge(char *out, size_t outsize, netadr_t *foradr) +{ //this function needs to return some sort of unguessable string so that you can't spoof the server with fake responses + char adr[64]; + const unsigned char *strings[] = {"somethingrandom", (const unsigned char *)NET_AdrToString(adr, sizeof(adr), foradr)}; + size_t lengths[] = {strlen(strings[0]), strlen(strings[1])}; + char digest[4*5]; + int digestsize = SHA1_m(digest, sizeof(digest), countof(lengths), strings, lengths); + +#ifdef TCPCONNECT + tobase64(out, outsize, digest, min(16, digestsize)); //truncate it, so its not excessive +#else + Q_snprintfz(out, outsize, "%08x", *(int*)digest); + (void)digestsize; +#endif +} + void SVM_Think(int port) { char *s; @@ -426,6 +687,23 @@ void SVM_Think(int port) { net_message.data[net_message.cursize] = '\0'; //null term all strings. + //well that's annoying. why is our networking code not doing this? LAME! + if (net_from.type == NA_IPV6 && + !*(int*)&net_from.address.ip6[0] && + !*(int*)&net_from.address.ip6[4] && + !*(short*)&net_from.address.ip6[8] && + *(short*)&net_from.address.ip6[10]==(short)0xffff) + { //convert this ipv4-mapped-ipv6 address back to actual ipv4, so we don't get confused about stuff. + net_from.type = NA_IP; + *(int*)&net_from.address.ip[0] = *(int*)&net_from.address.ip6[12]; + //and null it out, just in case. + *(int*)&net_from.address.ip6[8]=0; + *(int*)&net_from.address.ip6[12]=0; + } + + if (NET_WasSpecialPacket(svm_sockets)) + continue; + svm.time = Sys_DoubleTime(); MSG_BeginReading(msg_nullnetprim); @@ -442,6 +720,7 @@ void SVM_Think(int port) char *eos; char game[64]; qboolean ext = !strcmp(com_token, "getserversExt"); + const char *resp=ext?"getserversExtResponse":"getserversResponse"; qboolean empty = false; qboolean full = false; qboolean ipv4 = !ext; @@ -450,11 +729,11 @@ void SVM_Think(int port) s = COM_ParseOut(s, game, sizeof(game)); ver = strtol(game, &eos, 0); if (*eos) - { + { //not a number, must have been a game name. s = COM_Parse(s); ver = strtol(com_token, NULL, 0); } - else + else //first arg was a number. that means its vanilla quake3. Q_strncpyz(game, "Quake3", sizeof(game)); for(;s&&*s;) { @@ -485,30 +764,22 @@ void SVM_Think(int port) Con_DPrintf("Unknown request filter: %s\n", COM_QuotedString(com_token, buf, sizeof(buf), false)); } } + if (!ipv4 && !ipv6) + ipv4 = ipv6 = true; //neither specified? use both + + svm.total.queries++; memset(&sb, 0, sizeof(sb)); sb.maxsize = sizeof(net_message_buffer)-2; sb.data = net_message_buffer; MSG_WriteLong(&sb, -1); - - if (!ipv4 && !ipv6) - ipv4 = ipv6 = true; //neither specified? use both - (void)ver, (void)full, (void)empty; - if (ext) - { //ipv6 and ipv4 addresses - MSG_WriteString(&sb, "getserversExtResponse"); - SVM_AddIPAddresses(&sb, 0, game, ipv4, ipv6, true, gametype); - } - else - { //ipv4 only - MSG_WriteString(&sb, "getserversResponse"); - SVM_AddIPAddresses(&sb, 0, game, ipv4, ipv6, true, gametype); - } + SZ_Write(&sb, resp, strlen(resp)); //WriteString, but without the null. + SVM_AddIPAddresses(&sb, 0, ver, game, ipv4, ipv6, empty, full, true, gametype); sb.maxsize+=2; MSG_WriteByte(&sb, '\\'); //otherwise the last may be considered invalid and ignored. -// MSG_WriteByte(&sb, 'E'); -// MSG_WriteByte(&sb, 'O'); -// MSG_WriteByte(&sb, 'T'); + MSG_WriteByte(&sb, 'E'); + MSG_WriteByte(&sb, 'O'); + MSG_WriteByte(&sb, 'T'); NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); } else if (!strcmp(com_token, "heartbeat")) @@ -521,25 +792,29 @@ void SVM_Think(int port) else { //dp/q3/etc are annoying, but we can query from an emphemerial socket to check NAT rules. sizebuf_t sb; + char ourchallenge[256]; + SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); svm.total.queries++; memset(&sb, 0, sizeof(sb)); sb.maxsize = sizeof(net_message_buffer); sb.data = net_message_buffer; MSG_WriteLong(&sb, -1); - MSG_WriteString(&sb, "getinfo CAKE\n"); + MSG_WriteString(&sb, va("getinfo %s\n", ourchallenge)); sb.cursize--; NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); } } else if (!strcmp(com_token, "infoResponse")) { + char ourchallenge[256]; int clients; const char *game, *chal; svm_server_t *srv; s = MSG_ReadStringLine(); svm.total.heartbeats++; chal = Info_ValueForKey(s, "challenge"); - if (!strcmp(chal, "CAKE")) + SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from); + if (!strcmp(chal, ourchallenge)) { clients = atoi(Info_ValueForKey(s, "clients")); game = Info_ValueForKey(s, "gamename"); @@ -566,7 +841,7 @@ void SVM_Think(int port) MSG_WriteLong(&sb, -1); MSG_WriteString(&sb, "servers\n"); sb.cursize--; - SVM_AddIPAddresses(&sb, 0, "Quake2", true, false, false, -1); + SVM_AddIPAddresses(&sb, 0, 0, "Quake2", true, false, true, true, false, -1); NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); } else if (*com_token == S2M_HEARTBEAT) //sequence, players @@ -589,7 +864,7 @@ void SVM_Think(int port) MSG_WriteLong(&sb, -1); MSG_WriteByte(&sb, M2C_MASTER_REPLY); MSG_WriteByte(&sb, '\n'); - SVM_AddIPAddresses(&sb, 0, "QuakeWorld", true, false, false, -1); + SVM_AddIPAddresses(&sb, 0, 0, "QuakeWorld", true, false, true, true, false, -1); NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from); } else if (*com_token == A2A_PING) @@ -656,6 +931,7 @@ static void SVM_Status_f(void) void SV_Init (struct quakeparms_s *parms) { int manarg; + char *g; COM_InitArgv (parms->argc, parms->argv); @@ -711,6 +987,13 @@ void SV_Init (struct quakeparms_s *parms) Cvar_ForceCallback(&sv_masterport); Cvar_ForceCallback(&sv_masterport_tcp); + if (fs_manifest->protocolname) + for (g = fs_manifest->protocolname; *g; ) + { + g = COM_Parse(g); + SVM_FindGame(com_token, 2); + } + Con_Printf ("Exe: %s\n", version_string()); Con_TPrintf ("======== %s Initialized ========\n", "FTEMaster"); @@ -742,4 +1025,4 @@ float SV_Frame (void) return 4; } -#endif \ No newline at end of file +#endif diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 4f286bd8..422a8c25 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -61,6 +61,9 @@ cvar_t sv_demotxt = CVAR("sv_demotxt", "1"); void SV_WriteMVDMessage (sizebuf_t *msg, int type, int to, float time); void SV_WriteRecordMVDMessage (sizebuf_t *msg); +#ifdef TCPCONNECT +void tobase64(unsigned char *out, int outlen, unsigned char *in, int inlen); +#endif static struct @@ -83,6 +86,15 @@ int demomsgto; static char demomsgbuf[MAX_OVERALLMSGLEN]; static mvddest_t *singledest; //used when a stream is starting up so redundant data doesn't get dumped into other streams +static struct reversedest_s +{ + struct reversedest_s *next; + qtvpendingstate_t info; + vfsfile_t *stream; + char inbuffer[2048]; + int inbuffersize; + double timeout; +} *reversedest; //used when a reverse stream is starting up static mvddest_t *SV_MVD_InitStream(vfsfile_t *stream, const char *info); qboolean SV_MVD_Record (mvddest_t *dest); @@ -253,12 +265,17 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade char password[256] = ""; char userinfo[1024]; enum { + //MUST BE ORDERED HIGHEST-PRIORITY-LAST QTVAM_NONE, QTVAM_PLAIN, +#ifdef TCPCONNECT // QTVAM_CCITT, //16bit = ddos it QTVAM_MD4, //fucked -// QTVAM_MD5, //no hash implemented - QTVAM_SHA1, +// QTVAM_MD5, //fucked, no hash implemented + QTVAM_SHA1, //fucked too nowadays +// QTVAM_SHA2_256, //no hash implemented +// QTVAM_SHA2_512, //no hash implemented +#endif } authmethod = QTVAM_NONE; start = headerstart; @@ -329,9 +346,10 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade int thisauth; start = COM_ParseToken(start, NULL); if (!strcmp(com_token, "NONE")) - thisauth = QTVAM_PLAIN; + thisauth = QTVAM_NONE; else if (!strcmp(com_token, "PLAIN")) thisauth = QTVAM_PLAIN; +#ifdef TCPCONNECT // else if (!strcmp(com_token, "CCIT")) // thisauth = QTVAM_CCITT; else if (!strcmp(com_token, "MD4")) @@ -340,6 +358,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade // thisauth = QTVAM_MD5; else if (!strcmp(com_token, "SHA1")) thisauth = QTVAM_SHA1; +#endif else { thisauth = QTVAM_NONE; @@ -391,7 +410,10 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade p->hasauthed = true; //no password, no need to auth. else if (*password) { - switch (authmethod) + if (!*p->challenge && authmethod>QTVAM_PLAIN) + e = ("QTVSV 1\n" + "PERROR: Challenge wasn't given...\n\n"); + else switch (authmethod) { case QTVAM_NONE: e = ("QTVSV 1\n" @@ -400,6 +422,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade case QTVAM_PLAIN: p->hasauthed = !strcmp(qtv_password.string, password); break; +#ifdef TCPCONNECT /*case QTVAM_CCITT: { unsigned short ushort_result; @@ -414,9 +437,9 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade char hash[512]; int md4sum[4]; - snprintf(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); + Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); - sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); + Q_snprintfz(hash, sizeof(hash), "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); p->hasauthed = !strcmp(password, hash); } break; @@ -425,16 +448,17 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade char hash[512]; int digest[5]; - snprintf(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); + Q_snprintfz(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); SHA1((char*)digest, sizeof(digest), hash, strlen(hash)); - sprintf(hash, "%08X%08X%08X%08X%08X", digest[0], digest[1], digest[2], digest[3], digest[4]); + Q_snprintfz(hash, sizeof(hash), "%08X%08X%08X%08X%08X", digest[0], digest[1], digest[2], digest[3], digest[4]); p->hasauthed = !strcmp(password, hash); } break; // case QTVAM_MD5: +#endif default: e = ("QTVSV 1\n" - "PERROR: FTEQWSV bug detected.\n\n"); + "PERROR: server bug detected.\n\n"); break; } if (!p->hasauthed && !e) @@ -461,34 +485,32 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade case QTVAM_PLAIN: p->hasauthed = !strcmp(qtv_password.string, password); break; - - if (0) - { +#ifdef TCPCONNECT /*case QTVAM_CCITT: - e = ("QTVSV 1\n" - "AUTH: CCITT\n" - "CHALLENGE: "); - } - else if (0) - {*/ + e = ("QTVSV 1\n" + "AUTH: CCITT\n" + "CHALLENGE: "); + goto hashedpassword;*/ case QTVAM_MD4: - e = ("QTVSV 1\n" - "AUTH: MD4\n" - "CHALLENGE: "); - } - else - { + e = ("QTVSV 1\n" + "AUTH: MD4\n" + "CHALLENGE: "); + goto hashedpassword; /*case QTVAM_MD5: - e = ("QTVSV 1\n" - "AUTH: MD5\n" - "CHALLENGE: "); - } - else - {*/ + e = ("QTVSV 1\n" + "AUTH: MD5\n" + "CHALLENGE: "); + goto hashedpassword;*/ case QTVAM_SHA1: - e = ("QTVSV 1\n" - "AUTH: SHA1\n" - "CHALLENGE: "); + e = ("QTVSV 1\n" + "AUTH: SHA1\n" + "CHALLENGE: "); + goto hashedpassword; +hashedpassword: + { + char tmp[32]; + Sys_RandomBytes(tmp, sizeof(tmp)); + tobase64(p->challenge, sizeof(p->challenge), tmp, sizeof(tmp)); } VFS_WRITE(clientstream, e, strlen(e)); @@ -496,15 +518,15 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade e = "\n\n"; VFS_WRITE(clientstream, e, strlen(e)); return QTV_RETRY; - +#endif default: e = ("QTVSV 1\n" - "PERROR: FTEQWSV bug detected.\n\n"); + "PERROR: server bug detected.\n\n"); break; } } - if (*qtv_maxstreams.string) + if (*qtv_maxstreams.string && !p->isreverse) { int count = 0; mvddest_t *dest; @@ -2128,10 +2150,80 @@ void SV_MVD_AutoRecord (void) } } +void SV_MVD_CheckReverse(void) +{ + struct reversedest_s *rd, **link; + enum qtvstatus_e s; + int len; + for (link = &reversedest; *link; link = &rd->next) + { + rd = *link; + if (realtime > rd->timeout) + len = -1; + else + len = VFS_READ(rd->stream, rd->inbuffer+rd->inbuffersize, sizeof(rd->inbuffer)-1-rd->inbuffersize); + if (len < 0) + s = QTV_ERROR; + else if (!len) + continue; //keep waiting... + else + { + rd->inbuffersize += len; + rd->inbuffer[rd->inbuffersize] = 0; + if (rd->inbuffersize >= 3 && strncmp(rd->inbuffer, "QTV", 3)) + s = QTV_ERROR; //not qtv server... + else + { + char *e = strstr(rd->inbuffer, "\n\n"); + if (e) + s = SV_MVD_GotQTVRequest(rd->stream, rd->inbuffer, rd->inbuffer+rd->inbuffersize, &rd->info); + else + continue; //not nuff data yet + } + } + switch(s) + { + case QTV_RETRY: //need to parse new stuff. + continue; + case QTV_ACCEPT: + rd->stream = NULL; + //fallthrough + case QTV_ERROR: + if (rd->stream) + VFS_CLOSE(rd->stream); + *link = rd->next; + Z_Free(rd); + return; + } + } +} + void SV_MVD_QTVReverse_f (void) { #if 1//ndef HAVE_TCP - Con_Printf ("%s is not supported in this build\n", Cmd_Argv(0)); +// Con_Printf ("%s is not supported in this build\n", Cmd_Argv(0)); + + const char *ip = Cmd_Argv(1); + vfsfile_t *f; + const char *msg = "QTV\n" + "VERSION: 1\n" + "REVERSE\n" + "\n"; + struct reversedest_s *rd; + if (sv.statestream = f; + rd->info.isreverse = true; + rd->timeout = realtime + 10; + reversedest = rd; #else char *ip; if (sv.state != ss_active) diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index ab8482ac..3e907627 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -3674,6 +3674,7 @@ void SV_SendClientMessages (void) #ifdef MVD_RECORDING void SV_WriteMVDMessage (sizebuf_t *msg, int type, int to, float time); +void SV_MVD_CheckReverse(void); void DemoWriteQTVTimePad(int msecs); #define Max(a, b) ((a>b)?a:b) @@ -3692,6 +3693,8 @@ void SV_SendMVDMessage(void) // extern cvar_t sv_demoMaxSize; sizebuf_t *dmsg; + SV_MVD_CheckReverse(); + if (!sv.mvdrecording) return; diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index d9ac8a95..99f5d586 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -1069,7 +1069,7 @@ static int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char * if (!func(file, st.st_size, st.st_mtime, parm, spath)) { - Con_DPrintf("giving up on search after finding %s\n", file); +// Con_DPrintf("giving up on search after finding %s\n", file); closedir(dir); return false; } diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 69883e8d..e86c0155 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -239,7 +239,7 @@ void SV_New_f (void) int playernum; int splitnum; client_t *split; - unsigned int fteext1; //reported to client + unsigned int fteext1, fteext2; //reported to client host_client->prespawn_stage = PRESPAWN_INVALID; host_client->prespawn_idx = 0; @@ -298,6 +298,7 @@ void SV_New_f (void) } fteext1 = host_client->fteprotocolextensions; + fteext2 = host_client->fteprotocolextensions2; switch(svs.netprim.coordtype) { case COORDTYPE_FLOAT_32: @@ -319,6 +320,7 @@ void SV_New_f (void) host_client->drop = true; return; } + fteext2 &= ~PEXT2_STUNAWARE; //don't complicate demos. ClientReliableCheckBlock(host_client, 800); //okay, so it might be longer, but I'm too lazy to work out the real size. @@ -329,10 +331,10 @@ void SV_New_f (void) ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE1); ClientReliableWrite_Long (host_client, fteext1); } - if (host_client->fteprotocolextensions2)//let the client know + if (fteext2)//let the client know { ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE2); - ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions2); + ClientReliableWrite_Long (host_client, fteext2); } ClientReliableWrite_Long (host_client, ISQ2CLIENT(host_client)?PROTOCOL_VERSION_Q2:PROTOCOL_VERSION_QW); ClientReliableWrite_Long (host_client, svs.spawncount); @@ -340,7 +342,7 @@ void SV_New_f (void) ClientReliableWrite_Byte (host_client, 0); ClientReliableWrite_String (host_client, gamedir); - if (host_client->fteprotocolextensions2 & PEXT2_MAXPLAYERS) + if (fteext2 & PEXT2_MAXPLAYERS) { /*is this a sane way to do it? or should we split the spectator thing off entirely?*/ ClientReliableWrite_Byte (host_client, sv.allocated_client_slots); @@ -546,6 +548,7 @@ void SVNQ_New_f (void) protext1 |= PEXT_FLOATCOORDS; else protext1 &= ~PEXT_FLOATCOORDS; + protext2 &= ~PEXT2_STUNAWARE; //always clear this, don't confuse demos. op = host_client->protocol; if (host_client->supportedprotocols) @@ -3892,19 +3895,13 @@ void SV_Say (qboolean team) if (strlen(text)+strlen(p)+2 >= sizeof(text)-10) { - SV_ClientTPrintf(host_client, PRINT_CHAT, "buffer overflow protection: failiure\n"); + SV_ClientTPrintf(host_client, PRINT_CHAT, "buffer overflow protection: failure\n"); return; } if (svprogfuncs) if (PR_QCChat(p, team)) //true if handled. return; - if (strstr(p, "password")) - { - Z_Free(host_client->centerprintstring); - host_client->centerprintstring = Z_StrDup("big brother is watching you"); - } - Q_strcat(text, p); //filter out '\n' and '\r' @@ -3984,6 +3981,9 @@ void SV_Say (qboolean team) #ifdef MVD_RECORDING sv.mvdrecording = mvdrecording; + if (strstr(p, "password")) //just a friendly reminder. + SV_ClientPrintf(host_client, PRINT_HIGH, "DON'T SHARE PASSWORDS HERE, YOU MUPPET!\r"); + if (!sv.mvdrecording || !cls) return; diff --git a/fteqtv/control.c b/fteqtv/control.c index 99b085c5..49d995b9 100644 --- a/fteqtv/control.c +++ b/fteqtv/control.c @@ -313,7 +313,7 @@ void Cluster_Run(cluster_t *cluster, qboolean dowait) { char buffer[8192]; char *result; - cluster->inputlength = read (0, cluster->commandinput, sizeof(cluster->commandinput)); + cluster->inputlength = read (STDIN, cluster->commandinput, sizeof(cluster->commandinput)); if (cluster->inputlength >= 1) { cluster->commandinput[cluster->inputlength-1] = 0; // rip off the /n and terminate diff --git a/fteqtv/forward.c b/fteqtv/forward.c index c93ec3f8..676cc47d 100644 --- a/fteqtv/forward.c +++ b/fteqtv/forward.c @@ -862,7 +862,8 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) printf("pending drop\n"); if (pend->srcfile) fclose(pend->srcfile); - closesocket(pend->sock); + if (pend->sock != INVALID_SOCKET) + closesocket(pend->sock); free(pend); cluster->numproxies--; return true; @@ -1088,24 +1089,30 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) { //this is actually a server trying to connect to us //start up a new stream - //FIXME: does this work? -#if 0 //left disabled until properly tested - qtv = QTV_NewServerConnection(cluster, "reverse"/*server*/, "", true, AD_REVERSECONNECT, false, 0); + if (cluster->reverseallowed) + { + qtv = QTV_NewServerConnection(cluster, 0, "reverse"/*server*/, "", true, AD_REVERSECONNECT, false, 0); - Net_ProxySendString(cluster, pend, QTVSVHEADER); - Net_ProxySendString(cluster, pend, "REVERSED\n"); - Net_ProxySendString(cluster, pend, "VERSION: 1\n"); - Net_ProxySendString(cluster, pend, "\n"); + Net_ProxySendString(cluster, pend, QTVSVHEADER); + Net_ProxySendString(cluster, pend, "VERSION: 1\n"); + Net_ProxySendString(cluster, pend, "REVERSED\n"); + Net_ProxySendString(cluster, pend, "\n"); - //switch over the socket to the actual source connection rather than the pending - Net_TryFlushProxyBuffer(cluster, pend); //flush anything... this isn't ideal, but should be small enough - qtv->sourcesock = pend->sock; - pend->sock = 0; + //switch over the socket to the actual source connection rather than the pending + Net_TryFlushProxyBuffer(cluster, pend); //flush anything... this isn't ideal, but should be small enough + qtv->sourcesock = pend->sock; + pend->sock = INVALID_SOCKET; - memcpy(qtv->buffer, pend->inbuffer + headersize, pend->inbuffersize - headersize); - qtv->parsingqtvheader = true; - return false; -#endif + memcpy(qtv->buffer, pend->inbuffer + headersize, pend->inbuffersize - headersize); + qtv->parsingqtvheader = true; + return false; + } + else + { + Net_ProxySendString(cluster, pend, QTVSVHEADER + "PERROR: Reverse connections are disabled on this proxy\n"); + pend->flushing = true; + } } else if (!ustrcmp(s, "RECEIVE")) { //a client connection request without a source diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index 6f6444d3..ccfdc88b 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -726,7 +726,7 @@ struct sv_s { //details about a server connection (also known as stream) int nailcount; char gamedir[MAX_QPATH]; - char mapname[256]; + char mapname[256]; //world.message movevars_t movevars; int cdtrack; entity_t entity[MAX_ENTITIES]; @@ -809,6 +809,7 @@ struct cluster_s { qboolean nobsp; qboolean allownqclients; //nq clients require no challenge qboolean nouserconnects; //prohibit users from connecting to new streams. + qboolean reverseallowed; //demos can be submitted from servers via 'qtvreverse' without needing to keep idle connections live. int anticheattime; //intial connection buffer delay (set high to block specing enemies) int tooslowdelay; //if stream ran out of data, stop parsing for this long diff --git a/fteqtv/qw.c b/fteqtv/qw.c index cc06df6e..56419b09 100644 --- a/fteqtv/qw.c +++ b/fteqtv/qw.c @@ -1184,7 +1184,7 @@ void QTV_StatusResponse(cluster_t *cluster, char *msg, netadr_t *from) strlcpy(sv->map.serverinfo, msg, sizeof(sv->map.serverinfo)); QTV_UpdatedServerInfo(sv); - Info_ValueForKey(sv->map.serverinfo, "map", sv->map.mapname, sizeof(sv->map.mapname)); +// Info_ValueForKey(sv->map.serverinfo, "map", sv->map.mapname, sizeof(sv->map.mapname)); Info_ValueForKey(sv->map.serverinfo, "*gamedir", sv->map.gamedir, sizeof(sv->map.gamedir)); if (!*sv->map.gamedir) strlcpy(sv->map.gamedir, "qw", sizeof(sv->map.gamedir)); diff --git a/fteqtv/rcon.c b/fteqtv/rcon.c index 7b9e226f..5d899713 100644 --- a/fteqtv/rcon.c +++ b/fteqtv/rcon.c @@ -712,6 +712,13 @@ void Cmd_Late(cmdctxt_t *ctx) ctx->cluster->lateforward = !!atoi(Cmd_Argv(ctx, 1)); Cmd_Printf(ctx, "late forwarding set\n"); } +void Cmd_ReverseAllowed(cmdctxt_t *ctx) +{ + if (Cmd_Argc(ctx) >= 2) + ctx->cluster->reverseallowed = !!atoi(Cmd_Argv(ctx, 1)); + Cmd_Printf(ctx, "reverse connections are %s\n", ctx->cluster->reverseallowed?"enabled":"disabled"); +} + void Cmd_Talking(cmdctxt_t *ctx) { if (Cmd_Argc(ctx) < 2) diff --git a/imgtool.c b/imgtool.c index 67209377..5dc869fb 100644 --- a/imgtool.c +++ b/imgtool.c @@ -3,6 +3,8 @@ #undef stderr #define stderr stdout +#define LittleLong(s) s + #include #include #ifdef _WIN32 @@ -703,7 +705,7 @@ static struct pendingtextureinfo *ImgTool_Read(struct opts_s *args, const char * printf("%s: unable to read\n", inname); else { - in = Image_LoadMipsFromMemory(args->flags, inname, inname, indata, fsize); + in = Image_LoadMipsFromMemory(args->flags|IF_NOMIPMAP, inname, inname, indata, fsize); if (!in) { printf("%s: unsupported format\n", inname); @@ -948,8 +950,11 @@ static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, if (in) { - if (!(args->flags & IF_NOMIPMAP) && in->mipcount == 1) - Image_GenerateMips(in, args->flags); + if (!strcmp(outext, ".ktx") || !strcmp(outext, ".dds") || args->mipnum >= in->mipcount) + { + if (!(args->flags & IF_NOMIPMAP) && in->mipcount == 1) + Image_GenerateMips(in, args->flags); + } if (args->mipnum >= in->mipcount) { @@ -965,18 +970,13 @@ static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, in->mipcount -= k; memmove(in->mip, &in->mip[k], sizeof(in->mip[0])*in->mipcount); + printf("%s(%s)->", inname, Image_FormatName(in->encoding)); + if (args->newpixelformat != PTI_INVALID && (args->newpixelformat < PTI_BC1_RGB || allowcompressed) && ImgTool_ConvertPixelFormat(args, inname, in)) - printf("\t(Converted to %s)\n", Image_FormatName(in->encoding)); + printf("(%s)->\n", Image_FormatName(in->encoding)); if (!in->mipcount) - { - ImgTool_FreeMips(in); printf("%s: unable to convert any mips\n", inname); - return; - } - - if (0) - ; #ifdef IMAGEFMT_KTX else if (!strcmp(outext, ".ktx")) { @@ -1014,10 +1014,7 @@ static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, (k == PTI_BGR8) || (k == PTI_BGR8) || 0; if (!outformats[in->encoding]) - { Image_ChangeFormat(in, outformats, PTI_INVALID, outname); - printf("\t(Exporting as %s)\n", Image_FormatName(in->encoding)); - } Image_BlockSizeForEncoding(in->encoding, &bb, &bw,&bh); if (!Image_WritePNG(outname, FS_SYSTEM, 0, &in->mip[0].data, 1, in->mip[0].width*bb, in->mip[0].width, in->mip[0].height, in->encoding, false)) #endif @@ -1039,10 +1036,7 @@ static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, (k == PTI_BGR8) || (k == PTI_BGR8) || 0; if (!outformats[in->encoding]) - { Image_ChangeFormat(in, outformats, PTI_INVALID, outname); - printf("\t(Exporting as %s)\n", Image_FormatName(in->encoding)); - } Image_BlockSizeForEncoding(in->encoding, &bb, &bw,&bh); if (!WriteTGA(outname, FS_SYSTEM, in->mip[0].data, in->mip[0].width*bb, in->mip[0].width, in->mip[0].height, in->encoding)) Con_Printf("%s(%s): Write failed\n", outname, Image_FormatName(in->encoding)); @@ -1052,9 +1046,14 @@ static void ImgTool_Convert(struct opts_s *args, struct pendingtextureinfo *in, Con_Printf("%s: Unknown output file format\n", outname); } - printf("%s: %s %s, %i*%i, %i mips\n", outname, imagetypename[in->type], Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height, in->mipcount); - for (k = 0; k < in->mipcount; k++) - printf("\t%u: %i*%i*%i, %u\n", (unsigned)k, in->mip[k].width, in->mip[k].height, in->mip[k].depth, (unsigned)in->mip[k].datasize); + if (in->mipcount > 1) + { + printf("%s(%s): %s %i*%i*%i, %i mips\n", outname, Image_FormatName(in->encoding), imagetypename[in->type], in->mip[0].width, in->mip[0].height, in->mip[0].depth, in->mipcount); + for (k = 0; k < in->mipcount; k++) + printf("\t%u: %i*%i*%i, %u\n", (unsigned)k, in->mip[k].width, in->mip[k].height, in->mip[k].depth, (unsigned)in->mip[k].datasize); + } + else + printf("%s(%s): %s %i*%i*%i, %u bytes\n", outname, Image_FormatName(in->encoding), imagetypename[in->type], in->mip[0].width, in->mip[0].height, in->mip[0].depth, (unsigned)in->mip[0].datasize); ImgTool_FreeMips(in); } @@ -1100,12 +1099,16 @@ static void ImgTool_Info(struct opts_s *args, const char *inname) } else { - in = Image_LoadMipsFromMemory(args->flags, inname, inname, indata, fsize); + in = Image_LoadMipsFromMemory(args->flags|IF_NOMIPMAP, inname, inname, indata, fsize); if (!in) - printf("%s: unsupported format\n", inname); + printf("%-20s: unsupported format\n", inname); + else if (in->mipcount == 1 && in->type == PTI_2D && in->mip[0].depth == 1) + printf("%-20s(%s): %4i*%-4i\n", inname, Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height); + else if (in->mipcount == 1) + printf("%-20s(%s): %s, %i*%i*%i, %u bytes\n", inname, Image_FormatName(in->encoding), imagetypename[in->type], in->mip[0].width, in->mip[0].height, in->mip[0].depth, (unsigned)in->mip[0].datasize); else { - printf("%s: %s %s, %i*%i, %i mips\n", inname, imagetypename[in->type], Image_FormatName(in->encoding), in->mip[0].width, in->mip[0].height, in->mipcount); + printf("%-20s(%s): %s, %i*%i*%i, %i mips\n", inname, Image_FormatName(in->encoding), imagetypename[in->type], in->mip[0].width, in->mip[0].height, in->mip[0].depth, in->mipcount); for (m = 0; m < in->mipcount; m++) printf("\t%u: %i*%i*%i, %u\n", (unsigned)m, in->mip[m].width, in->mip[m].height, in->mip[m].depth, (unsigned)in->mip[m].datasize); @@ -1314,6 +1317,8 @@ static void ImgTool_WadExtract(struct opts_s *args, const char *wadname) { const wad2_t *w = (const wad2_t *)indata; const wad2entry_t *e = (const wad2entry_t *)(indata+w->offset); + int i; + char clean[sizeof(e->name)+1]; for (m = 0; m < w->num; m++, e++) { @@ -1325,6 +1330,20 @@ static void ImgTool_WadExtract(struct opts_s *args, const char *wadname) miptex_t *mip = (miptex_t *)(indata+e->offset); struct pendingtextureinfo *out = Z_Malloc(sizeof(*out)); + if (!strcmp(e->name, "CONCHARS") && e->size==128*128) + { //special hack for conchars, which is listed as a miptex for some reason, with no qpic header (it not being a qpic lump) + out->encoding = TF_H2_TRANS8_0; + out->type = PTI_2D; + out->mip[0].width = 128; + out->mip[0].height = 128; + out->mip[0].depth = 1; + out->mip[0].datasize = out->mip[0].width*out->mip[0].height*out->mip[0].depth; + out->mip[0].data = (char*)mip; + out->mipcount = 1; + ImgTool_Convert(args, out, "conchars", NULL); + break; + } + out->encoding = PTI_P8; out->type = PTI_2D; for (out->mipcount = 0; out->mipcount < 4 && mip->offsets[out->mipcount]; out->mipcount++) @@ -1340,6 +1359,44 @@ static void ImgTool_WadExtract(struct opts_s *args, const char *wadname) ImgTool_Convert(args, out, mip->name, NULL); } break; + case TYP_QPIC: + { + int *qpic = (int *)(indata+e->offset); + struct pendingtextureinfo *out = Z_Malloc(sizeof(*out)); + size_t sz; + qbyte *p; + + if (e->size < 8 || 8+qpic[0]*qpic[1] != e->size) + { + printf("invalid size/header for qpic lump: %s\n", e->name); + break; + } + + out->type = PTI_2D; + out->mip[0].width = LittleLong(qpic[0]); + out->mip[0].height = LittleLong(qpic[1]); + out->mip[0].depth = 1; + out->mip[0].datasize = out->mip[0].width*out->mip[0].height*out->mip[0].depth; + out->mip[0].data = (char*)(qpic+2); + out->mipcount = 1; + + for (sz = 0, p = out->mip[0].data; sz < out->mip[0].datasize; sz++) + if (p[sz] == 255) + break; + out->encoding = szmip[0].datasize?TF_TRANS8:PTI_P8; + + for (i = 0; i < sizeof(e->name); i++) + { //lowercase it. + if (e->name[i] >= 'A' && e->name[i] <= 'Z') + clean[i] = (e->name[i]-'A')+'a'; + else + clean[i] = e->name[i]; + } + clean[sizeof(e->name)] = 0; + + ImgTool_Convert(args, out, clean, NULL); + } + break; case TYP_PALETTE: default: printf("skipping %s\n", e->name); @@ -1391,7 +1448,7 @@ static void ImgTool_WadConvert(struct opts_s *args, const char *destpath, const sh_config.texfmt[u] = (u==PTI_RGBA8)||(u==PTI_RGBX8)||(u==PTI_P8); if (wadtype == 2) - { //WAD2 files generally have a palette lump. + { //WAD2 texture files generally have a palette lump. if (wad2.num == maxentries) { maxentries += 64; @@ -1468,10 +1525,6 @@ static void ImgTool_WadConvert(struct opts_s *args, const char *destpath, const in->mipcount = 4; memmove(&in->mip[0], &in->mip[args->mipnum], sizeof(in->mip[0])*in->mipcount); memset(&in->mip[in->mipcount], 0, sizeof(in->mip[0])*((args->mipnum+4)-in->mipcount)); //null it out, just in case. - if (!in->mip[0].width || (in->mip[0].width & 15)) - Con_Printf("%s(%i): WARNING: miptex width is not a multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height); - if (!in->mip[0].height || (in->mip[0].height & 15)) - Con_Printf("%s(%i): WARNING: miptex height is not a not multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height); if (in->encoding != PTI_P8) Image_ChangeFormat(in, wadpixelformats, (*inname=='{')?TF_TRANS8:PTI_INVALID, inname); @@ -1503,25 +1556,48 @@ static void ImgTool_WadConvert(struct opts_s *args, const char *destpath, const entry->dummy = 0; entry->offset = VFS_TELL(f); - memcpy(mip.name, entry->name, sizeof(mip.name)); - mip.width = in->mip[0].width; - mip.height = in->mip[0].height; - mip.offsets[0] = in->mip[0].datasize?sizeof(mip):0; - mip.offsets[1] = in->mip[1].datasize?mip.offsets[0]+in->mip[0].datasize:0; - mip.offsets[2] = in->mip[2].datasize?mip.offsets[1]+in->mip[1].datasize:0; - mip.offsets[3] = in->mip[3].datasize?mip.offsets[2]+in->mip[2].datasize:0; + if (!in->mip[0].width || (in->mip[0].width & 15)) + Con_Printf("%s(%i): WARNING: miptex width is not a multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height); + if (!in->mip[0].height || (in->mip[0].height & 15)) + Con_Printf("%s(%i): WARNING: miptex height is not a not multiple of 16 - %i*%i\n", inname, args->mipnum, in->mip[0].width, in->mip[0].height); - Con_Printf("%s: %ix%i\n", mip.name, mip.width, mip.height); - - VFS_WRITE(f, &mip, sizeof(mip)); - VFS_WRITE(f, in->mip[0].data, in->mip[0].datasize); - VFS_WRITE(f, in->mip[1].data, in->mip[1].datasize); - VFS_WRITE(f, in->mip[2].data, in->mip[2].datasize); - VFS_WRITE(f, in->mip[3].data, in->mip[3].datasize); - if (wad2.magic[3] == '3') + if (0) { - VFS_WRITE(f, "\x00\x01", 2); - VFS_WRITE(f, host_basepal, 256*3); + if (!strcasecmp(entry->name, "CONCHARS") && in->mip[0].width==128&&in->mip[0].height==128) + entry->type = TYP_MIPTEX; //yes, weird. match vanilla quake. explicitly avoid qpic to avoid corruption in the first 8 bytes (due to the engine's early endian swapping) + //FIXME: encoding should be pti_trans8_0... + else + { + entry->type = TYP_QPIC; + //qpics need a header + VFS_WRITE(f, &in->mip[0].width, sizeof(int)); + VFS_WRITE(f, &in->mip[0].height, sizeof(int)); + } + //and now the 8bit pixel data itself + VFS_WRITE(f, in->mip[0].data, in->mip[0].datasize); + } + else + { + memcpy(mip.name, entry->name, sizeof(mip.name)); + mip.width = in->mip[0].width; + mip.height = in->mip[0].height; + mip.offsets[0] = in->mip[0].datasize?sizeof(mip):0; + mip.offsets[1] = in->mip[1].datasize?mip.offsets[0]+in->mip[0].datasize:0; + mip.offsets[2] = in->mip[2].datasize?mip.offsets[1]+in->mip[1].datasize:0; + mip.offsets[3] = in->mip[3].datasize?mip.offsets[2]+in->mip[2].datasize:0; + + Con_Printf("%s: %ix%i\n", mip.name, mip.width, mip.height); + + VFS_WRITE(f, &mip, sizeof(mip)); + VFS_WRITE(f, in->mip[0].data, in->mip[0].datasize); + VFS_WRITE(f, in->mip[1].data, in->mip[1].datasize); + VFS_WRITE(f, in->mip[2].data, in->mip[2].datasize); + VFS_WRITE(f, in->mip[3].data, in->mip[3].datasize); + if (wad2.magic[3] == '3') + { + VFS_WRITE(f, "\x00\x01", 2); + VFS_WRITE(f, host_basepal, 256*3); + } } entry->size = entry->dsize = VFS_TELL(f)-entry->offset; diff --git a/plugins/ezhud/ezquakeisms.c b/plugins/ezhud/ezquakeisms.c index 03c2747c..9d0afecb 100644 --- a/plugins/ezhud/ezquakeisms.c +++ b/plugins/ezhud/ezquakeisms.c @@ -669,9 +669,10 @@ int EZHud_Draw(int seat, float viewx, float viewy, float viewwidth, float viewhe sb_lines = 24 + 16 + 8; } + clientfuncs->GetPredInfo(seat, cl.simvel); + //cl.faceanimtime - //cl.simvel //cl.item_gettime //cls.state; @@ -679,8 +680,6 @@ int EZHud_Draw(int seat, float viewx, float viewy, float viewwidth, float viewhe //cls.fps; ////cls.realtime; ////cls.trueframetime; - //cls.mvdplayback; - //cls.demoplayback; host_screenupdatecount++; HUD_Draw(); diff --git a/plugins/ezhud/hud_common.c b/plugins/ezhud/hud_common.c index 72abb316..589f987f 100644 --- a/plugins/ezhud/hud_common.c +++ b/plugins/ezhud/hud_common.c @@ -1104,7 +1104,291 @@ static void SCR_HUD_DrawNetStats(hud_t *hud) // // speed-o-meter // -#ifdef HAXX + +#define SPEED_TAG_LENGTH 2 +#define SPEED_OUTLINE_SPACING SPEED_TAG_LENGTH +#define SPEED_FILL_SPACING SPEED_OUTLINE_SPACING + 1 +#define SPEED_WHITE 10 +#define SPEED_TEXT_ONLY 1 + +#define SPEED_TEXT_ALIGN_NONE 0 +#define SPEED_TEXT_ALIGN_CLOSE 1 +#define SPEED_TEXT_ALIGN_CENTER 2 +#define SPEED_TEXT_ALIGN_FAR 3 + +// FIXME: hud-only now, can/should be moved +void SCR_DrawHUDSpeed ( + int x, int y, int width, int height, + int type, + float tick_spacing, + float opacity, + int vertical, + int vertical_text, + int text_align, + byte color_stopped, + byte color_normal, + byte color_fast, + byte color_fastest, + byte color_insane, + int style, + float scale +) +{ + byte color_offset; + byte color1, color2; + int player_speed; + vec_t *velocity; + + if (scr_con_current == vid.height) { + return; // console is full screen + } + + // Get the velocity. +// if (cl.players[cl.playernum].spectator && Cam_TrackNum() >= 0) { +// velocity = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].playerstate[Cam_TrackNum()].velocity; +// } +// else { + velocity = cl.simvel; +// } + + // Calculate the speed + if (!type) + { + // Based on XY. + player_speed = sqrt(velocity[0]*velocity[0] + + velocity[1]*velocity[1]); + } + else + { + // Based on XYZ. + player_speed = sqrt(velocity[0]*velocity[0] + + velocity[1]*velocity[1] + + velocity[2]*velocity[2]); + } + + // Calculate the color offset for the "background color". + if (vertical) { + color_offset = height * (player_speed % 500) / 500; + } + else { + color_offset = width * (player_speed % 500) / 500; + } + + // Set the color based on the speed. + switch (player_speed / 500) + { + case 0: + color1 = color_stopped; + color2 = color_normal; + break; + case 1: + color1 = color_normal; + color2 = color_fast; + break; + case 2: + color1 = color_fast; + color2 = color_fastest; + break; + default: + color1 = color_fastest; + color2 = color_insane; + break; + } + + // Draw tag marks. + if (tick_spacing > 0.0 && style != SPEED_TEXT_ONLY) + { + float f; + + for(f = tick_spacing; f < 1.0; f += tick_spacing) + { + if(vertical) + { + // Left. + Draw_AlphaFill(x, // x + y + (int)(f * height), // y + SPEED_TAG_LENGTH, // Width + 1, // Height + SPEED_WHITE, // Color + opacity); // Opacity + + // Right. + Draw_AlphaFill(x + width - SPEED_TAG_LENGTH + 1, + y + (int)(f * height), + SPEED_TAG_LENGTH, + 1, + SPEED_WHITE, + opacity); + } + else + { + // Above. + Draw_AlphaFill(x + (int)(f * width), + y, + 1, + SPEED_TAG_LENGTH, + SPEED_WHITE, + opacity); + + // Below. + Draw_AlphaFill(x + (int)(f * width), + y + height - SPEED_TAG_LENGTH + 1, + 1, + SPEED_TAG_LENGTH, + SPEED_WHITE, + opacity); + } + } + } + + // + // Draw outline. + // + if (style != SPEED_TEXT_ONLY) + { + if(vertical) + { + // Left. + Draw_AlphaFill(x + SPEED_OUTLINE_SPACING, + y, + 1, + height, + SPEED_WHITE, + opacity); + + // Right. + Draw_AlphaFill(x + width - SPEED_OUTLINE_SPACING, + y, + 1, + height, + SPEED_WHITE, + opacity); + } + else + { + // Above. + Draw_AlphaFill(x, + y + SPEED_OUTLINE_SPACING, + width, + 1, + SPEED_WHITE, + opacity); + + // Below. + Draw_AlphaFill(x, + y + height - SPEED_OUTLINE_SPACING, + width, + 1, + SPEED_WHITE, + opacity); + } + } + + // + // Draw fill. + // + if (style != SPEED_TEXT_ONLY) + { + if(vertical) + { + // Draw the right color (slower). + Draw_AlphaFill (x + SPEED_FILL_SPACING, + y, + width - (2 * SPEED_FILL_SPACING), + height - color_offset, + color1, + opacity); + + // Draw the left color (faster). + Draw_AlphaFill (x + SPEED_FILL_SPACING, + y + height - color_offset, + width - (2 * SPEED_FILL_SPACING), + color_offset, + color2, + opacity); + } + else + { + // Draw the right color (slower). + Draw_AlphaFill (x + color_offset, + y + SPEED_FILL_SPACING, + width - color_offset, + height - (2 * SPEED_FILL_SPACING), + color1, + opacity); + + // Draw the left color (faster). + Draw_AlphaFill (x, + y + SPEED_FILL_SPACING, + color_offset, + height - (2 * SPEED_FILL_SPACING), + color2, + opacity); + } + } + + // Draw the speed text. + if(vertical && vertical_text) + { + int i = 1; + int len = 0; + + // Align the text accordingly. + switch(text_align) + { + case SPEED_TEXT_ALIGN_NONE: return; + case SPEED_TEXT_ALIGN_FAR: y = y + height - 4*8; break; + case SPEED_TEXT_ALIGN_CENTER: y = Q_rint(y + height/2.0 - 16); break; + case SPEED_TEXT_ALIGN_CLOSE: + default: break; + } + + len = strlen(va("%d", player_speed)); + + // 10^len + while(len > 0) + { + i *= 10; + len--; + } + + // Write one number per row. + for(; i > 1; i /= 10) + { + int next; + next = (i/10); + + // Really make sure we don't try division by zero :) + if(next <= 0) + { + break; + } + + Draw_SString(Q_rint(x + width/2.0 - 4 * scale), y, va("%1d", (player_speed % i) / next), scale); + y += 8; + } + } + else + { + // Align the text accordingly. + switch(text_align) + { + case SPEED_TEXT_ALIGN_FAR: + x = x + width - 4 * 8 * scale; + break; + case SPEED_TEXT_ALIGN_CENTER: + x = Q_rint(x + width / 2.0 - 2 * 8 * scale); + break; + case SPEED_TEXT_ALIGN_CLOSE: + case SPEED_TEXT_ALIGN_NONE: + default: + break; + } + + Draw_SString(x, Q_rint(y + height/2.0 - 4 * scale), va("%4d", player_speed), scale); + } +} + static void SCR_HUD_DrawSpeed(hud_t *hud) { int width, height; @@ -1123,7 +1407,8 @@ static void SCR_HUD_DrawSpeed(hud_t *hud) *hud_speed_vertical, *hud_speed_vertical_text, *hud_speed_text_align, - *hud_speed_style; + *hud_speed_style, + *hud_speed_scale; if (hud_speed_xyz == NULL) // first time { @@ -1141,6 +1426,7 @@ static void SCR_HUD_DrawSpeed(hud_t *hud) hud_speed_vertical_text = HUD_FindVar(hud, "vertical_text"); hud_speed_text_align = HUD_FindVar(hud, "text_align"); hud_speed_style = HUD_FindVar(hud, "style"); + hud_speed_scale = HUD_FindVar(hud, "scale"); } width = max(0, hud_speed_width->value); @@ -1160,10 +1446,10 @@ static void SCR_HUD_DrawSpeed(hud_t *hud) hud_speed_color_fast->value, hud_speed_color_fastest->value, hud_speed_color_insane->value, - hud_speed_style->ival); + hud_speed_style->ival, + hud_speed_scale->value); } } -#endif #define HUD_SPEED2_ORIENTATION_UP 0 #define HUD_SPEED2_ORIENTATION_DOWN 1 @@ -7688,7 +7974,6 @@ static void SCR_HUD_DrawNotImplemented(hud_t *hud) Draw_SString(x, y, line1, 1); } -#define SCR_HUD_DrawSpeed SCR_HUD_DrawNotImplemented #define SCR_HUD_DrawTeamHoldBar SCR_HUD_DrawNotImplemented #define SCR_HUD_DrawTeamHoldInfo SCR_HUD_DrawNotImplemented #define SCR_HUD_DrawItemsClock SCR_HUD_DrawNotImplemented @@ -7848,6 +8133,7 @@ void CommonDraw_Init(void) "vertical_text", "1", "text_align", "1", "style", "0", + "scale", "1", NULL); // Init speed2 (half circle thingie). diff --git a/plugins/irc/ircclient.c b/plugins/irc/ircclient.c index f708f4a8..1824ee3a 100644 --- a/plugins/irc/ircclient.c +++ b/plugins/irc/ircclient.c @@ -1400,6 +1400,11 @@ static struct ircice_s *IRC_ICE_Create(ircclient_t *irc, const char *sender, enu } static void IRC_ICE_Update(ircclient_t *irc, struct ircice_s *ice, char updatetype) { + //'+' propose ('hey, can I call you please?') + //'=' offer ('these are my details') + //'*' finalise ('this is what I'm going to use') + //'-' reject ('get lost, I don't want to talk to you any more') + //'%' candiate ('try this address') //I was originally using colons to separate terms, but switched to slashes to avoid smilies for irc clients that print unknown CTCP messages. char message[1024]; struct icecandinfo_s *c; diff --git a/plugins/models/gltf.c b/plugins/models/gltf.c index 9e99ec8c..52f57374 100644 --- a/plugins/models/gltf.c +++ b/plugins/models/gltf.c @@ -1,4 +1,4 @@ -#ifndef GLQUAKE +#if !defined(GLQUAKE) && !defined(FTEENGINE) #define GLQUAKE //this is shit, but ensures index sizes come out the right size #endif #include "quakedef.h" diff --git a/plugins/plugin.h b/plugins/plugin.h index 17c3e346..461a10dd 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -262,6 +262,7 @@ typedef struct //q1 client/network info F(size_t, GetTeamInfo, (teamplayerinfo_t *clients, size_t maxclients, qboolean showenemies, int seat)); F(int, GetWeaponStats, (int player, struct wstats_s *result, size_t maxresults)); F(float, GetTrackerOwnFrags, (int seat, char *text, size_t textsize)); + F(void, GetPredInfo, (int seat, vec3_t outvel)); #define plugclientfuncs_name "Client" } plugclientfuncs_t; diff --git a/plugins/serverb/cl_master.h b/plugins/serverb/cl_master.h index 27b7f3bd..cc90499c 100644 --- a/plugins/serverb/cl_master.h +++ b/plugins/serverb/cl_master.h @@ -131,7 +131,6 @@ extern player_t *mplayers; void CL_QueryServers(void); int NET_CheckPollSockets(void); -void MasterInfo_Request(master_t *mast); serverinfo_t *Master_InfoForServer (struct sockaddr_in addr); serverinfo_t *Master_InfoForNum (int num); int Master_TotalCount(void); diff --git a/quakec/menusys/menu/newgame.qc b/quakec/menusys/menu/newgame.qc index 639ce7da..b06c07a3 100644 --- a/quakec/menusys/menu/newgame.qc +++ b/quakec/menusys/menu/newgame.qc @@ -91,7 +91,12 @@ nonstatic void(mitem_desktop desktop) M_NewGame = m.addm(banner, [(160-banner.item_size_x)*0.5, pos-32], [(160+banner.item_size_x)*0.5, pos-8]); m.addm(menuitemeditt_spawn(_("Hostname"), "hostname", '280 8'), [-160, pos], [160, pos+8]); pos += 8; - m.addm(menuitemcheck_spawn(_("Public"), "sv_public", '280 8'), [-160, pos], [160, pos+8]); pos += 8; + m.addm(menuitemcombo_spawn(_("Public"), "sv_public", '280 8', _( + "-1 \"Reject All (Splitscreen)\" " + "0 \"Private (Manual IP Sharing)\" " + "1 \"Public (Manual Config)\" " + "2 \"Public (Holepunch)\" " + )), [-160, pos], [160, pos+8]); pos += 8; m.addm(menuitemcombo_spawn(_("Max Clients"), "maxclients", '280 8', _( "2 \"Two\" " "3 \"Three\" " diff --git a/specs/fte_manifests.txt b/specs/fte_manifests.txt index f32f2de2..8f61a507 100644 --- a/specs/fte_manifests.txt +++ b/specs/fte_manifests.txt @@ -15,6 +15,7 @@ FTEMANIFEST 1 GAME quake NAME "In The Shadows" PROTOCOLNAME ITShadows +RTCBROKER "tls://master.frag-net.com:27950" DEFAULTEXEC "" //UPDATEURL "http://example.com/mods/its.fmf" BASEGAME id1 @@ -25,7 +26,8 @@ GAMEDIR shadows //this is a major pain for systems where there is no official installer (read: fte's webgl port, or android... or win64). //its pak0 can be downloaded from sock's public server ARCHIVEDPACKAGE shadows/pak0.pak "0xb1768147" "shadows/pak0.pak" "http://simonoc.com/files/maps/sp/its_demo_v1_1.zip" - +-seta cl_forwardspeed 400 ++bind w +forward @@ -46,11 +48,24 @@ This specifies what the game should be referred to by various console prints etc PROTOCOLNAME "foo" This is how the game/mod should be identified to master servers. A unique value here helps prevent servers from other games being listed, as well as preventing other games from seeing this game/mod. -FIXME: engine support for this should be improved. +This should be reasonably human readable - master servers might show it publically. This actually gets written into the com_protocolname cvar. You should use the com_protocolversion cvar in your default.cfg if you want to exclude older versions, avoiding annoying numbers etc. + +RTCBROKER "url" +This should be a tls:// or tcp:// address that tells the engine where it can find a running instance of an 'ftemaster' program. +The broker program is used to allow client and target server to exchange address and port information in order to punch holes through NATs and Firewalls, allowing users to play without having to configure any of those monstrosities. DEFAULTEXEC "" If specified, this is inserted before the game's normal default.cfg file, and can be used to override the engine's default cvar settings for mods where the default.cfg was originally written for a different engine (and thus omits certain settings). This should only be used for games where the default.cfg itself is not replacable. Standalone mods should not need to specify this, and should instead just put everything in the mod's default.cfg file. For recognised games, the engine will automatically assume usable defaults, but its possible that a mod depends upon a setting that I overlooked. +This is obsolete, other than to disable the built-in overrides for known games. See the following - and + prefixes for easier usage. + +-set foo bar +The text following a leading minus are commands that are included before the mod's default.cfg, allowing to override engine behaviour without breaking mods that require a specific value. This mechanism is quite handy for mimicing other engines. +Any commands may be used, but its best to limit yourself to set/seta/setfl, alias, and bind. + ++set foo bar +The text following a leading plus are commands that are included AFTER the mod's default.cfg, allowing you to stomp all over defaults that were stupid and flawed (or just outdated). This is useful for making quake's default settings and binds usable, but its generally better to just provide a new default.cfg instead. +Any commands may be used, but its best to limit yourself to set/seta/setfl, alias, and bind. BASEGAME "id1" Multiple basegame lines can be specified. These are the core subdirectories that should always be loaded, even if the gamedir command was used to load a different mod. @@ -71,6 +86,12 @@ UPDATEURL "http://example.com/foo.fmf" This command gives the url from which to update the manifest file from. The updated manifest *MUST* contain the same url for the update to be accepted. Because of this, you should ensure that it is has a url that will be valid for as long as your mod will be relevent. This replaces just the manifest, but because the manifest file specifies a list of packages that should be used, a new set of packages can be downloaded automatically, and this is the true power of the update url. +DOWNLOADSURL "https://updates.triptohell.info/downloadables.php?game=Q1" +This command tells the engine where it can obtain a list of available packages. The user is able to enable/disable as they desire. + +INSTALL "package1;package2" +This command tells the engine which packages should be 'strongly encouraged' onto the user (ie: the user will be prompted to install them if they're missing). Naturally this should not be abused, however the user is better able to see what is happening (they must still accept installation). + PACKAGE "id1/pak0.pak" 0x4f069cac "http://mirror1.example.com/qsw106_pak0.pak" "http://mirror2.example.com/qsw106_pak0.pak" WARNING: be sure to respect third-party copyrights. This command, of which multiple can be specified, lists the packages which should automatically be downloaded.
GameAddressHostnameMod dirMapnamePlayers
AddressHostnameGamedirMapnamePlayers
%s%s%s%s%u/%u
%s%s%s%s%u/%u