From 6c112c33683c80a933cc248c83ab0d6f0723401f Mon Sep 17 00:00:00 2001 From: Spoike Date: Sun, 16 Jan 2022 18:41:44 +0000 Subject: [PATCH] Attempt to support more of QuakeEx's network protocol changes, including a 'connectqe host' command to connect to QEx servers with the appropriate handshakes (requires manual PSK setup). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6162 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_ents.c | 39 +++++- engine/client/cl_input.c | 9 ++ engine/client/cl_main.c | 235 ++++++++++++++++++++++----------- engine/client/cl_parse.c | 274 +++++++++++++++++++++++++++++++-------- engine/client/client.h | 4 +- engine/client/p_script.c | 2 +- engine/client/sbar.c | 4 +- engine/client/zqtp.c | 2 +- engine/common/common.c | 8 ++ engine/common/common.h | 9 +- engine/common/fs_zip.c | 46 +++++++ engine/common/net.h | 1 + engine/common/net_chan.c | 37 +++++- engine/common/protocol.h | 53 +++++++- engine/server/server.h | 2 +- engine/server/sv_ccmds.c | 10 +- engine/server/sv_ents.c | 49 ++++++- engine/server/sv_main.c | 73 +++++++---- engine/server/sv_send.c | 46 ++++++- engine/server/sv_user.c | 66 ++++------ 20 files changed, 733 insertions(+), 236 deletions(-) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 81ede7a7..d0cd0af0 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -1774,6 +1774,7 @@ void CLNQ_ParseEntity(unsigned int bits) packet_entities_t *pack; qboolean isnehahra = CPNQ_IS_BJP||(cls.protocol_nq == CPNQ_NEHAHRA); + qboolean floatcoords; if (cls.signon == 4 - 1) { // first update is the final signon stage @@ -1837,6 +1838,8 @@ void CLNQ_ParseEntity(unsigned int bits) state->dpflags = 0; + floatcoords = cls.qex && (bits & QE_U_FLOATCOORDS); + if (bits & NQU_MODEL) { if (CPNQ_IS_BJP) @@ -1855,20 +1858,34 @@ void CLNQ_ParseEntity(unsigned int bits) state->skinnum = MSG_ReadByte(); if (bits & NQU_EFFECTS) - state->effects = MSG_ReadByte(); + { + i = MSG_ReadByte(); + if (cls.qex) + { + unsigned fixed = i & ~(REEF_QUADLIGHT|REEF_PENTLIGHT|REEF_CANDLELIGHT); + if (i & REEF_QUADLIGHT) + fixed |= EF_BLUE; + if (i & REEF_PENTLIGHT) + fixed |= EF_RED; + if (i & REEF_CANDLELIGHT) + fixed |= 0; //tiny light + i = fixed; + } + state->effects = i; + } if (bits & NQU_ORIGIN1) - state->origin[0] = MSG_ReadCoord (); + state->origin[0] = floatcoords?MSG_ReadFloat():MSG_ReadCoord (); if (bits & NQU_ANGLE1) state->angles[0] = MSG_ReadAngle(); if (bits & NQU_ORIGIN2) - state->origin[1] = MSG_ReadCoord (); + state->origin[1] = floatcoords?MSG_ReadFloat():MSG_ReadCoord (); if (bits & NQU_ANGLE2) state->angles[1] = MSG_ReadAngle(); if (bits & NQU_ORIGIN3) - state->origin[2] = MSG_ReadCoord (); + state->origin[2] = floatcoords?MSG_ReadFloat():MSG_ReadCoord (); if (bits & NQU_ANGLE3) state->angles[2] = MSG_ReadAngle(); @@ -1907,6 +1924,20 @@ void CLNQ_ParseEntity(unsigned int bits) if (bits & FITZU_LERPFINISH) MSG_ReadByte(); + + if (cls.qex) + { + if (bits & QE_U_SOLIDTYPE) /*state->solidsize =*/ MSG_ReadByte(); //needed for correct prediction + if (bits & QE_U_ENTFLAGS) /*state->entflags = */ MSG_ReadULEB128(); //for onground/etc state + if (bits & QE_U_HEALTH) /*state->health =*/ MSG_ReadSignedQEX(); //health... not really sure why, I suppose it changes player physics (they should have sent movetype instead though). + if (bits & QE_U_UNKNOWN26) /*unknown =*/MSG_ReadByte(); + if (bits & QE_U_UNUSED27) Con_Printf(CON_WARNING"QE_U_UNUSED27: %u\n", MSG_ReadByte()); + + if (bits & QE_U_UNUSED28) Con_Printf(CON_WARNING"QE_U_UNUSED28: %u\n", MSG_ReadByte()); + if (bits & QE_U_UNUSED29) Con_Printf(CON_WARNING"QE_U_UNUSED29: %u\n", MSG_ReadByte()); + if (bits & QE_U_UNUSED30) Con_Printf(CON_WARNING"QE_U_UNUSED30: %u\n", MSG_ReadByte()); + if (bits & QE_U_UNUSED31) Con_Printf(CON_WARNING"QE_U_UNUSED31: %u\n", MSG_ReadByte()); + } } else { //dp tends to leak stuff, so parse as quakedp if the normal protocol doesn't define it as something better. diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 25d05990..63707758 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1362,6 +1362,12 @@ void CLNQ_SendMove (usercmd_t *cmd, int pnum, sizebuf_t *buf) return; } + if (cls.qex) + { + MSG_WriteByte (buf, clc_delta); + MSG_WriteULEB128(buf, cl.movesequence); + } + MSG_WriteByte (buf, clc_move); if (cls.protocol_nq >= CPNQ_DP7) @@ -1376,6 +1382,9 @@ void CLNQ_SendMove (usercmd_t *cmd, int pnum, sizebuf_t *buf) MSG_WriteFloat (buf, cmd->fservertime); // use latest time. because ping reports! + if (cls.qex) + MSG_WriteByte(buf, 4); + for (i=0 ; i<3 ; i++) { if (cls.protocol_nq == CPNQ_FITZ666 || (cls.proquake_angles_hack && buf->prim.anglesize <= 1)) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index e8d9aff4..8df10182 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -185,6 +185,9 @@ cvar_t cl_gunanglex = CVAR("cl_gunanglex", "0"); cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunanglez = CVAR("cl_gunanglez", "0"); +#ifdef HAVE_DTLS +extern cvar_t net_enable_dtls; +#endif cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); cvar_t cl_sendguid = CVARD("cl_sendguid", "", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); cvar_t cl_downloads = CVARAFD("cl_downloads", "1", /*q3*/"cl_allowDownload", CVAR_NOTFROMSERVER, "Allows you to block all automatic downloads."); @@ -286,11 +289,11 @@ static struct netadr_t adr[8]; //addresses that we're trying to transfer to, one entry per dns result, eg both ::1 AND 127.0.0.1 #ifdef HAVE_DTLS enum - { + { //not relevant when given a direct dtls address. DTLS_DISABLE, DTLS_TRY, DTLS_REQUIRE, - DTLS_ACTIVE + DTLS_ACTIVE, } dtlsupgrade; #endif int mtu; @@ -304,6 +307,12 @@ static struct int challenge; //tracked as part of guesswork based upon what replies we get. double time; //for connection retransmits qboolean clogged; //ignore time... + enum coninfomode_e + { + CIM_DEFAULT, //sends both a qw getchallenge and nq connect (also with postfixed getchallenge so modified servers can force getchallenge) + CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq server). should not be used for dpp7 servers. + CIM_QEONLY, //forces dtls and uses a different nq netchan version + } mode; int defaultport; int tries; //increased each try, every fourth trys nq connect packets. unsigned char guid[64]; @@ -799,13 +808,14 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b) #ifdef HAVE_DTLS for (i = 0; i < ctx->found; i++) { - if (connectinfo.dtlsupgrade == DTLS_ACTIVE) + if (connectinfo.dtlsupgrade == DTLS_ACTIVE || connectinfo.mode==CIM_QEONLY) { //if we've already established a dtls connection, stick with it if (ctx->adr[i].prot == NP_DGRAM) ctx->adr[i].prot = NP_DTLS; } else if (connectinfo.adr[i].prot == NP_DTLS) { //dtls connections start out with regular udp, and upgrade to dtls once its established that the server supports it. + //FIXME: remove this block once our new netcode is better established. connectinfo.dtlsupgrade = DTLS_REQUIRE; ctx->adr[i].prot = NP_DGRAM; } @@ -966,6 +976,12 @@ void CL_CheckForResend (void) connectinfo.protocol = CP_NETQUAKE; connectinfo.subprotocol = CPNQ_FITZ666; } + else if (!strcmp(lbp, "qe")||!strcmp(lbp, "qex")||!strcmp(lbp, "kex")) + { //quake-ex has special quirks that cannot be defined by protocol numbers alone. + connectinfo.protocol = CP_NETQUAKE; + connectinfo.subprotocol = CPNQ_FITZ666; + connectinfo.mode = CIM_QEONLY; + } else if (!strcmp(lbp, "bjp1") || !strcmp(lbp, "bjp2") || //placeholders only !strcmp(lbp, "bjp3") || !strcmp(lbp, "bjp")) { @@ -1094,7 +1110,14 @@ void CL_CheckForResend (void) net_message.cursize = 0; MSG_BeginReading(net_message.prim); - if (connectinfo.subprotocol == CPNQ_ID && !proquakeangles) + if (connectinfo.mode == CIM_QEONLY) + { + net_from = connectinfo.adr[connectinfo.nextadr]; + Cmd_TokenizeString (va("connect %i %i %i \"\\name\\unconnected\"", NQ_NETCHAN_VERSION_QEX, 0, SV_NewChallenge()), false, false); + + SVC_DirectConnect(0); + } + else if (connectinfo.subprotocol == CPNQ_ID && !proquakeangles) { net_from = connectinfo.adr[connectinfo.nextadr]; Cmd_TokenizeString (va("connect %i %i %i \"\\name\\unconnected\"", NQ_NETCHAN_VERSION, 0, SV_NewChallenge()), false, false); @@ -1239,11 +1262,16 @@ void CL_CheckForResend (void) if (to->prot == NP_DGRAM) connectinfo.nextadr++; //cycle hosts with each ping (if we got multiple). - contype |= 1; /*always try qw type connections*/ + if (connectinfo.mode==CIM_QEONLY || connectinfo.mode==CIM_NQONLY) + contype |= 2; + else + { + contype |= 1; /*always try qw type connections*/ #ifdef VM_UI - if (!UI_IsRunning()) //don't try to connect to nq servers when running a q3ui. I was getting annoying error messages from q3 servers due to this. + if (!UI_IsRunning()) //don't try to connect to nq servers when running a q3ui. I was getting annoying error messages from q3 servers due to this. #endif - contype |= 2; /*try nq connections periodically (or if its the default nq port)*/ + contype |= 2; /*try nq connections periodically (or if its the default nq port)*/ + } /*DP, QW, Q2, Q3*/ /*NOTE: ioq3 has args. yes, a challenge to get a challenge.*/ @@ -1273,22 +1301,27 @@ void CL_CheckForResend (void) MSG_WriteLong(&sb, LongSwap(NETFLAG_CTL | (strlen(NQ_NETCHAN_GAMENAME)+7))); MSG_WriteByte(&sb, CCREQ_CONNECT); MSG_WriteString(&sb, NQ_NETCHAN_GAMENAME); - MSG_WriteByte(&sb, NQ_NETCHAN_VERSION); + if (connectinfo.mode==CIM_QEONLY) + MSG_WriteByte(&sb, NQ_NETCHAN_VERSION_QEX); + else + { + MSG_WriteByte(&sb, NQ_NETCHAN_VERSION); - /*NQ engines have a few extra bits on the end*/ - /*proquake servers wait for us to send them a packet before anything happens, - which means it corrects for our public port if our nat uses different public ports for different remote ports - thus all nq engines claim to be proquake - */ + /*NQ engines have a few extra bits on the end*/ + /*proquake servers wait for us to send them a packet before anything happens, + which means it corrects for our public port if our nat uses different public ports for different remote ports + thus all nq engines claim to be proquake + */ - MSG_WriteByte(&sb, 1); /*'mod'*/ - MSG_WriteByte(&sb, 34); /*'mod' version*/ - MSG_WriteByte(&sb, 0); /*flags*/ - MSG_WriteLong(&sb, strtoul(password.string, NULL, 0)); /*password*/ + MSG_WriteByte(&sb, 1); /*'mod'*/ + MSG_WriteByte(&sb, 34); /*'mod' version*/ + MSG_WriteByte(&sb, 0); /*flags*/ + MSG_WriteLong(&sb, strtoul(password.string, NULL, 0)); /*password*/ - /*FTE servers will detect this string and treat it as a qw challenge instead (if it allows qw clients), so protocol choice is deterministic*/ - if (contype & 1) - MSG_WriteString(&sb, "getchallenge"); + /*FTE servers will detect this string and treat it as a qw challenge instead (if it allows qw clients), so protocol choice is deterministic*/ + if (contype & 1) + MSG_WriteString(&sb, "getchallenge"); + } *(int*)sb.data = LongSwap(NETFLAG_CTL | sb.cursize); switch(NET_SendPacket (cls.sockets, sb.cursize, sb.data, to)) @@ -1308,7 +1341,10 @@ void CL_CheckForResend (void) if (!keeptrying) { if (to->prot != NP_DGRAM && connectinfo.nextadr+1 < connectinfo.numadr) - connectinfo.nextadr++; //cycle hosts with each ping (if we got multiple). + { + connectinfo.nextadr++; //cycle hosts with each connection failure (if we got multiple addresses). + connectinfo.tries = 0; + } else { Cvar_Set(&cl_disconnectreason, va("No route to \"%s\", giving up\n", cls.servername)); @@ -1319,7 +1355,7 @@ void CL_CheckForResend (void) } } -void CL_BeginServerConnect(const char *host, int port, qboolean noproxy) +static void CL_BeginServerConnect(const char *host, int port, qboolean noproxy, enum coninfomode_e mode) { if (!strncmp(host, "localhost", 9)) noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses. @@ -1341,6 +1377,16 @@ void CL_BeginServerConnect(const char *host, int port, qboolean noproxy) connectinfo.trying = true; connectinfo.defaultport = port; connectinfo.protocol = CP_UNKNOWN; + connectinfo.mode = mode; + +#ifdef HAVE_DTLS + if (net_enable_dtls.ival >= 3) + connectinfo.dtlsupgrade = DTLS_REQUIRE; + else if (net_enable_dtls.ival >= 2) + connectinfo.dtlsupgrade = DTLS_TRY; + else + connectinfo.dtlsupgrade = DTLS_DISABLE; +#endif SCR_SetLoadingStage(LS_CONNECTION); CL_CheckForResend(); @@ -1436,7 +1482,7 @@ void CL_Connect_f (void) #endif CL_Disconnect_f (); - CL_BeginServerConnect(server, 0, false); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); } #if defined(CL_MASTER) && defined(HAVE_PACKET) static void CL_ConnectBestRoute_f (void) @@ -1471,7 +1517,7 @@ static void CL_ConnectBestRoute_f (void) else #endif CL_Disconnect_f (); - CL_BeginServerConnect(server, 0, true); + CL_BeginServerConnect(server, 0, true, CIM_DEFAULT); } #endif @@ -1500,7 +1546,7 @@ static void CL_Join_f (void) Cvar_Set(&spectator, "0"); - CL_BeginServerConnect(server, 0, false); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); } void CL_Observe_f (void) @@ -1528,13 +1574,14 @@ void CL_Observe_f (void) Cvar_Set(&spectator, "1"); - CL_BeginServerConnect(server, 0, false); + CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); } #ifdef NQPROT void CLNQ_Connect_f (void) { char *server; + enum coninfomode_e mode; if (Cmd_Argc() != 2) { @@ -1542,12 +1589,17 @@ void CLNQ_Connect_f (void) return; } + if (!strcmp(Cmd_Argv(0), "connectqe")) + mode = CIM_QEONLY; + else + mode = CIM_NQONLY; + server = Cmd_Argv (1); server = strcpy(alloca(strlen(server)+1), server); CL_Disconnect_f (); - CL_BeginServerConnect(server, 26000, true); + CL_BeginServerConnect(server, 26000, true, mode); } #endif @@ -3218,7 +3270,7 @@ void CL_ConnectionlessPacket (void) } if (cls.demoplayback == DPB_NONE && net_from.type != NA_LOOPBACK) - Con_Printf ("%s: ", NET_AdrToString (adr, sizeof(adr), &net_from)); + Con_Printf (S_COLOR_GRAY"%s: ", NET_AdrToString (adr, sizeof(adr), &net_from)); // Con_DPrintf ("%s", net_message.data + 4); if (c == 'f') //using 'f' as a prefix so that I don't need lots of hacks @@ -3228,13 +3280,13 @@ void CL_ConnectionlessPacket (void) { netadr_t adr; char *data = MSG_ReadStringLine(); - Con_TPrintf ("redirect to %s\n", data); + Con_TPrintf (S_COLOR_GRAY"redirect to %s\n", data); if (NET_StringToAdr(data, PORT_DEFAULTSERVER, &adr)) { if (CL_IsPendingServerAddress(&net_from)) { if (!NET_EnsureRoute(cls.sockets, "redir", cls.servername, &adr)) - Con_Printf ("Unable to redirect to %s\n", data); + Con_Printf (CON_ERROR"Unable to redirect to %s\n", data); else { connectinfo.istransfer = true; @@ -3310,12 +3362,12 @@ void CL_ConnectionlessPacket (void) } #endif - Con_TPrintf ("challenge\n"); + Con_TPrintf (S_COLOR_GRAY"challenge\n"); if (!CL_IsPendingServerAddress(&net_from)) { if (net_from.prot != NP_RTC_TCP && net_from.prot != NP_RTC_TLS) - Con_Printf("Challenge from wrong server, ignoring\n"); + Con_Printf(CON_WARNING"Challenge from wrong server, ignoring\n"); return; } connectinfo.numadr = 1; @@ -3557,7 +3609,7 @@ void CL_ConnectionlessPacket (void) if (!strcmp(s, "print")) { - Con_TPrintf ("print\n"); + Con_TPrintf (S_COLOR_GRAY"print\n"); s = MSG_ReadString (); if (connectinfo.trying && CL_IsPendingServerBaseAddress(&net_from) == false) @@ -3602,7 +3654,7 @@ void CL_ConnectionlessPacket (void) if (!strcmp(com_token, "ccept")) { /*this is a DP server... but we don't know which version nor nq protocol*/ - Con_Printf ("accept\n"); + Con_Printf (S_COLOR_GRAY"accept\n"); if (cls.state == ca_connected) return; //we're already connected. don't do it again! @@ -3638,7 +3690,7 @@ void CL_ConnectionlessPacket (void) { if (!strncmp(net_message.data+4, "infoResponse\n", 13)) { - Con_TPrintf ("infoResponse\n"); + Con_TPrintf (S_COLOR_GRAY"infoResponse\n"); Info_Print(net_message.data+17, ""); return; } @@ -3648,7 +3700,7 @@ void CL_ConnectionlessPacket (void) if (!strncmp(net_message.data+4, "getserversResponse", 18)) { qbyte *b = net_message.data+4+18; - Con_TPrintf ("getserversResponse\n"); + Con_TPrintf (S_COLOR_GRAY"getserversResponse\n"); while (b+7 <= net_message.data+net_message.cursize) { if (*b == '\\') @@ -3686,7 +3738,7 @@ void CL_ConnectionlessPacket (void) else if (!strcmp(com_token, "tlsopened")) { //server is letting us know that its now listening for a dtls handshake. #ifdef HAVE_DTLS - Con_Printf ("dtlsopened\n"); + Con_Printf (S_COLOR_GRAY"dtlsopened\n"); if (!CL_IsPendingServerAddress(&net_from)) return; @@ -3715,7 +3767,7 @@ void CL_ConnectionlessPacket (void) } else { - Con_Printf ("d\n"); + Con_Printf ("disconnect\n"); if (cls.demoplayback != DPB_NONE) { Con_Printf("Disconnect\n"); @@ -3743,7 +3795,7 @@ client_connect: //fixme: make function } if (net_from.type != NA_LOOPBACK) { - Con_TPrintf ("connection\n"); + Con_TPrintf (S_COLOR_GRAY"connection\n"); #ifdef HAVE_SERVER if (sv.state && sv.state != ss_clustermode) @@ -3883,7 +3935,7 @@ client_connect: //fixme: make function { if (!strncmp(net_message.data+4, "print\n", 6)) { //quake2+quake3 send rejects this way - Con_TPrintf ("print\n"); + Con_TPrintf (S_COLOR_GRAY"print\n"); Con_Printf ("%s", net_message.data+10); if (connectinfo.trying && CL_IsPendingServerBaseAddress(&net_from) == false) @@ -3893,7 +3945,7 @@ client_connect: //fixme: make function } if (c == A2C_PRINT) { //closest quakeworld has to a reject message - Con_TPrintf ("print\n"); + Con_TPrintf (S_COLOR_GRAY"print\n"); s = MSG_ReadString (); Con_Printf ("%s", s); @@ -3929,6 +3981,9 @@ void CLNQ_ConnectionlessPacket(void) int length; unsigned short port; + if (net_message.cursize < 5) + return; //not enough size to be meaningful (qe does not include a port number) + MSG_BeginReading (msg_nullnetprim); length = LongSwap(MSG_ReadLong ()); if (!(length & NETFLAG_CTL)) @@ -3947,30 +4002,40 @@ void CLNQ_ConnectionlessPacket(void) Con_TPrintf ("Dup connect received. Ignored.\n"); return; } - port = htons((unsigned short)MSG_ReadLong()); - //this is the port that we're meant to respond to. - if (port) + if (length == 5 && net_from.prot == NP_DTLS) { - char buf[256]; - net_from.port = port; - Con_DPrintf("redirecting to port %s\n", NET_AdrToString(buf, sizeof(buf), &net_from)); + cls.proquake_angles_hack = false; + cls.protocol_nq = CPNQ_ID; + Con_DPrintf("QuakeEx server...\n"); } - - cls.proquake_angles_hack = false; - cls.protocol_nq = CPNQ_ID; - if (MSG_ReadByte() == 1) //a proquake server adds a little extra info + else { - int ver = MSG_ReadByte(); - Con_DPrintf("ProQuake server %i.%i\n", ver/10, ver%10); + port = htons((unsigned short)MSG_ReadLong()); + //this is the port that we're meant to respond to. -// if (ver >= 34) - cls.proquake_angles_hack = true; - if (MSG_ReadByte() == 1) + if (port) { - //its a 'pure' server. - Con_Printf("pure ProQuake server\n"); - return; + char buf[256]; + net_from.port = port; + Con_DPrintf("redirecting to port %s\n", NET_AdrToString(buf, sizeof(buf), &net_from)); + } + + cls.proquake_angles_hack = false; + cls.protocol_nq = CPNQ_ID; + if (MSG_ReadByte() == 1) //a proquake server adds a little extra info + { + int ver = MSG_ReadByte(); + Con_DPrintf("ProQuake server %i.%i\n", ver/10, ver%10); + +// if (ver >= 34) + cls.proquake_angles_hack = true; + if (MSG_ReadByte() == 1) + { + //its a 'pure' server. + Con_Printf("pure ProQuake server\n"); + return; + } } } @@ -3985,7 +4050,11 @@ void CLNQ_ConnectionlessPacket(void) cls.netchan.compresstable = NULL; cls.protocol = CP_NETQUAKE; cls.state = ca_connected; - Con_TPrintf ("Connected.\n"); + + if (cls.netchan.remote_address.prot == NP_DTLS || cls.netchan.remote_address.prot == NP_TLS || cls.netchan.remote_address.prot == NP_WSS) + Con_TPrintf ("Connected (^[^2encrypted\\tip\\Any passwords will be sent securely, but may still be logged^]).\n"); + else + Con_TPrintf ("Connected (^[^1plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]).\n"); total_loading_size = 100; current_loading_size = 0; @@ -3995,9 +4064,14 @@ void CLNQ_ConnectionlessPacket(void) allowremotecmd = false; // localid required now for remote cmds #endif - //send a dummy packet. - //this makes our local nat think we initialised the conversation, so that we can receive the. - Netchan_Transmit(&cls.netchan, 1, "\x01", 2500); + if (length == 5) + cls.qex = (connectinfo.mode==CIM_QEONLY); + else + { + //send a dummy packet. + //this makes our local nat think we initialised the conversation, so that we can receive the. + Netchan_Transmit(&cls.netchan, 1, "\x01", 2500); + } return; case CCREP_REJECT: @@ -4056,16 +4130,6 @@ void CL_ReadPacket(void) return; } - if (net_message.cursize < 6 && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) //MVDs don't have the whole sequence header thing going on - { - char adr[MAX_ADR_SIZE]; - if (net_message.cursize == 1 && net_message.data[0] == A2A_ACK) - Con_TPrintf ("%s: Ack (Pong)\n", NET_AdrToString(adr, sizeof(adr), &net_from)); - else - Con_TPrintf ("%s: Runt packet\n", NET_AdrToString(adr, sizeof(adr), &net_from)); - return; - } - if (cls.state == ca_disconnected) { //connect to nq servers, but don't get confused with sequenced packets. if (NET_WasSpecialPacket(cls.sockets)) @@ -4076,6 +4140,16 @@ void CL_ReadPacket(void) return; //ignore it. We arn't connected. } + if (net_message.cursize < 6 && (cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV)) //MVDs don't have the whole sequence header thing going on + { + char adr[MAX_ADR_SIZE]; + if (net_message.cursize == 1 && net_message.data[0] == A2A_ACK) + Con_TPrintf ("%s: Ack (Pong)\n", NET_AdrToString(adr, sizeof(adr), &net_from)); + else + Con_TPrintf ("%s: Runt packet\n", NET_AdrToString(adr, sizeof(adr), &net_from)); + return; + } + // // packet from server // @@ -4098,11 +4172,17 @@ void CL_ReadPacket(void) { case CP_NETQUAKE: #ifdef NQPROT - if(NQNetChan_Process(&cls.netchan)) + switch(NQNetChan_Process(&cls.netchan)) { + case NQNC_IGNORED: + break; + case NQNC_ACK: + case NQNC_RELIABLE: + case NQNC_UNRELIABLE: MSG_ChangePrimitives(cls.netchan.netprim); CL_WriteDemoMessage (&net_message, msg_readcount); CLNQ_ParseServerMessage (); + break; } #endif break; @@ -5148,13 +5228,14 @@ void CL_Init (void) ); Cmd_AddCommandD ("cl_transfer", CL_Transfer_f, "Connect to a different server, disconnecting from the current server only when the new server replies."); #ifdef TCPCONNECT - Cmd_AddCommandAD ("tcpconnect", CL_TCPConnect_f, CL_Connect_c, "Connect to a server using the tcp:// prefix"); + Cmd_AddCommandAD ("connecttcp", CL_TCPConnect_f, CL_Connect_c, "Connect to a server using the tcp:// prefix"); #endif #ifdef IRCCONNECT - Cmd_AddCommand ("ircconnect", CL_IRCConnect_f); + Cmd_AddCommand ("connectirc", CL_IRCConnect_f); #endif #ifdef NQPROT - Cmd_AddCommandD ("nqconnect", CLNQ_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_NQSERVER)". Otherwise identical to the connect command."); + Cmd_AddCommandD ("connectnq", CLNQ_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_NQSERVER)". Also disables QW/Q2/Q3/DP handshakes preventing them from being favoured, so should only be used when you actually want NQ protocols specifically."); + Cmd_AddCommandD ("connectqe", CLNQ_Connect_f, "Connects to the specified server, defaulting to port "STRINGIFY(PORT_NQSERVER)". Also forces the use of DTLS and QE-specific handshakes. You will also need to ensure the dtls_psk_* cvars are set properly or the server will refuse the connection."); #endif Cmd_AddCommand ("reconnect", CL_Reconnect_f); Cmd_AddCommandAD ("join", CL_Join_f, CL_Connect_c, "Switches away from spectator mode, optionally connecting to a different server."); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 9b2e73c5..b67347c0 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -201,13 +201,13 @@ static const char *svc_nqstrings[] = "nqsvc_time", // [float] server time "nqsvc_print", // [string] null terminated string "nqsvc_stufftext", // [string] stuffed into client's console buffer - // the string should be \n terminated + // the string should be \n terminated "nqsvc_setangle", // [vec3] set the view angle to this absolute value "nqsvc_serverinfo", // [long] version - // [string] signon string - // [string]..[0]model cache [string]...[0]sounds cache - // [string]..[0]item cache + // [string] signon string + // [string]..[0]model cache [string]...[0]sounds cache + // [string]..[0]item cache "nqsvc_lightstyle", // [qbyte] [string] "nqsvc_updatename", // [qbyte] [string] "nqsvc_updatefrags", // [qbyte] [short] @@ -218,10 +218,10 @@ static const char *svc_nqstrings[] = "nqsvc_damage", // [qbyte] impact [qbyte] blood [vec3] from "nqsvc_spawnstatic", - "nqsvcfte_spawnstatic2(21)", + "ftenq_spawnstatic2(21)", "nqsvc_spawnbaseline", - "nqsvc_temp_entity", // + "nqsvc_temp_entity", // "nqsvc_setpause", "nqsvc_signonnum", "nqsvc_centerprint", @@ -230,42 +230,42 @@ static const char *svc_nqstrings[] = "nqsvc_spawnstaticsound", "nqsvc_intermission", "nqsvc_finale", // [string] music [string] text - "nqsvc_cdtrack", // [qbyte] track [qbyte] looptrack + "nqsvc_cdtrack", // [qbyte] track [qbyte] looptrack "nqsvc_sellscreen", "nqsvc_cutscene", //34 "NEW PROTOCOL", //35 "NEW PROTOCOL", //36 - "fitzsvc_skybox", //37 + "fitz_skybox", //37 "NEW PROTOCOL", //38 "NEW PROTOCOL", //39 - "fitzsvc_bf", //40 - "fitzsvc_fog", //41 - "fitzsvc_spawnbaseline2", //42 - "fitzsvc_spawnstatic2", //43 - "fitzsvc_spawnstaticsound2", //44 + "fitz_bf", //40 + "fitz_fog", //41 + "fitz_spawnbaseline2", //42 + "fitz_spawnstatic2", //43 + "fitz_spawnstaticsound2", //44 "NEW PROTOCOL", //45 - "NEW PROTOCOL", //46 - "NEW PROTOCOL", //47 - "NEW PROTOCOL", //48 - "NEW PROTOCOL", //49 - "dpsvc_downloaddata", //50 - "dpsvc_updatestatubyte", //51 - "dpsvc_effect", //52 - "dpsvc_effect2", //53 - "dp6svc_precache/dp5svc_sound2", //54 - "dpsvc_spawnbaseline2", //55 - "dpsvc_spawnstatic2", //56 obsolete - "dpsvc_entities", //57 - "dpsvc_csqcentities", //58 - "dpsvc_spawnstaticsound2", //59 - "dpsvc_trailparticles", //60 - "dpsvc_pointparticles", //61 - "dpsvc_pointparticles1", //62 + "qex_updateping", //46 + "qex_updatesocial", //47 + "qex_updateplinfo", //48 + "qex_print", //49 + "dp_downloaddata / neh_skyboxsize / qex_servervars", //50 + "dp_updatestatubyte / neh_fog / qex_seq", //51 + "dp_effect / qex_achievement", //52 + "dp_effect2", //53 + "dp6_precache / dp5_sound2", //54 + "dp_spawnbaseline2", //55 + "dp_spawnstatic2", //56 obsolete + "dp_entities", //57 + "dp_csqcentities", //58 + "dp_spawnstaticsound2", //59 + "dp_trailparticles", //60 + "dp_pointparticles", //61 + "dp_pointparticles1", //62 "NEW PROTOCOL(63)", //63 "NEW PROTOCOL(64)", //64 "NEW PROTOCOL(65)", //65 - "ftenqsvc_spawnbaseline2", //66 + "ftenq_spawnbaseline2", //66 "NEW PROTOCOL(67)", //67 "NEW PROTOCOL(68)", //68 "NEW PROTOCOL(69)", //69 @@ -277,21 +277,21 @@ static const char *svc_nqstrings[] = "NEW PROTOCOL(75)", //75 "NEW PROTOCOL(76)", //76 "NEW PROTOCOL(77)", //77 - "nqsvcfte_updatestatstring", //78 - "nqsvcfte_updatestatfloat", //79 + "ftenq_updatestatstring", //78 + "ftenq_updatestatfloat", //79 "NEW PROTOCOL(80)", //80 "NEW PROTOCOL(81)", //81 "NEW PROTOCOL(82)", //82 - "nqsvcfte_cgamepacket", //83 - "nqsvcfte_voicechat", //84 - "nqsvcfte_setangledelta", //85 - "nqsvcfte_updateentities", //86 + "ftenq_cgamepacket", //83 + "ftenq_voicechat", //84 + "ftenq_setangledelta", //85 + "ftenq_updateentities", //86 "NEW PROTOCOL(87)", //87 "NEW PROTOCOL(88)", //88 - "svcfte_setinfoblob", //89 - "svcfte_cgamepacket_sized", //90 - "svcfte_temp_entity_sized", //91 - "svcfte_csqcentities_sized", //92 + "ftenq_setinfoblob", //89 + "ftenq_cgamepacket_sized", //90 + "ftenq_temp_entity_sized", //91 + "ftenq_csqcentities_sized", //92 }; #endif @@ -3712,6 +3712,7 @@ static void CLQ2_ParseServerData (void) void CL_ParseEstablished(void) { #ifdef NQPROT + cls.qex = false; Z_Free(cl_dp_packagenames); cl_dp_packagenames = NULL; cl_dp_serverextension_download = false; @@ -3880,7 +3881,7 @@ void CL_KeepaliveMessage(void){} static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caution. { int nummodels, numsounds; - char *str; + char *str = NULL; int gametype; Con_DPrintf ("Serverdata packet %s.\n", cls.demoplayback?"read":"received"); SCR_SetLoadingStage(LS_CLIENT); @@ -3892,9 +3893,19 @@ static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caut CLNQ_ParseProtoVersion(); - if (cls.fteprotocolextensions2 & PEXT2_PREDINFO) + if (cls.qex) { + cl.allocated_client_slots = MSG_ReadByte(); str = MSG_ReadString(); + } + else + { + if (cls.fteprotocolextensions2 & PEXT2_PREDINFO) + str = MSG_ReadString(); + cl.allocated_client_slots = MSG_ReadByte(); + } + if (str) + { #ifndef CLIENTONLY if (!sv.state) #endif @@ -3906,8 +3917,6 @@ static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caut #endif } } - - cl.allocated_client_slots = MSG_ReadByte(); if (cl.allocated_client_slots > MAX_CLIENTS) { cl.allocated_client_slots = MAX_CLIENTS; @@ -3916,6 +3925,7 @@ static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caut cl.splitclients = 1; + gametype = MSG_ReadByte (); str = MSG_ReadString (); @@ -4063,6 +4073,55 @@ static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caut CSQC_Shutdown(); #endif } +static void CLQEX_ParseServerVars(void) +{ + unsigned int bits = MSG_ReadULEB128(); + + if (bits & QEX_GV_DEATHMATCH) + cl.deathmatch = MSG_ReadByte (); + if (bits & QEX_GV_IDEALPITCHSCALE) + MSG_ReadFloat (); + if (bits & QEX_GV_FRICTION) + movevars.friction = MSG_ReadFloat (); + if (bits & QEX_GV_EDGEFRICTION) + movevars.edgefriction = MSG_ReadFloat (); + if (bits & QEX_GV_STOPSPEED) + movevars.stopspeed = MSG_ReadFloat (); + if (bits & QEX_GV_MAXVELOCITY) + /*movevars.maxvelocity =*/ MSG_ReadFloat (); + if (bits & QEX_GV_GRAVITY) + movevars.gravity = MSG_ReadFloat (); + if (bits & QEX_GV_NOSTEP) + /*movevars.nostep =*/ MSG_ReadByte (); + if (bits & QEX_GV_MAXSPEED) + movevars.maxspeed = MSG_ReadFloat (); + if (bits & QEX_GV_ACCELERATE) + movevars.accelerate = MSG_ReadFloat (); + if (bits & QEX_GV_CONTROLLERONLY) + /*cl.blockmouse =*/ MSG_ReadByte (); + if (bits & QEX_GV_TIMELIMIT) + InfoBuf_SetStarKey(&cl.serverinfo, "timelimit", va("%g", MSG_ReadFloat ())); + if (bits & QEX_GV_FRAGLIMIT) + InfoBuf_SetStarKey(&cl.serverinfo, "fraglimit", va("%g", MSG_ReadFloat ())); +} +static char *CLQEX_ReadStrings(void) +{ + unsigned short count = MSG_ReadShort(), a; + const char *arg[8]; + static char formatted[8192]; + if (count > countof(arg)) + Host_EndGame ("CLQEX_ReadStrings: too many strings (%u>%u)", count, (unsigned)countof(arg)); + for (a = 0; a < count; a++) + { + char *s = alloca(8192); + arg[a] = s; + MSG_ReadStringBuffer(s, 8192); + } + + TL_Reformat(formatted, sizeof(formatted), count, arg); + return formatted; +} + static void CLNQ_SendInitialUserInfo(void *ctx, const char *key, const char *value) { InfoSync_Add(&cls.userinfosync, ctx, key); @@ -4153,7 +4212,7 @@ static void CLNQ_ParseClientdata (void) if (bits & (SU_VELOCITY1<velocity[i] = MSG_ReadFloat(); else pl->velocity[i] = MSG_ReadChar()*16; @@ -4233,6 +4292,11 @@ static void CLNQ_ParseClientdata (void) weaponframe |= MSG_ReadByte() << 8; if (bits & FITZSU_WEAPONALPHA) MSG_ReadByte(); + + if (cls.qex) + { + if (bits & QEX_SU_ENTFLAGS) /*entflags =*/ MSG_ReadULEB128(); + } } CL_SetStatInt(0, STAT_WEAPONFRAME, weaponframe); @@ -4705,7 +4769,21 @@ static void CL_ParseBaseline (entity_state_t *es, int baselinetype2) } es->trans = (bits & FITZ_B_ALPHA) ? MSG_ReadByte() : 255; - es->scale = (bits & RMQFITZ_B_SCALE) ? MSG_ReadByte() : 16; + if (cls.qex) + { + if (bits & QEX_B_SOLID) + /*es->solidtype =*/ MSG_ReadByte(); + if (bits & QEX_B_UNKNOWN4) + Con_Printf(CON_WARNING"QEX_B_UNKNOWN4: %x\n", MSG_ReadByte()); + if (bits & QEX_B_UNKNOWN5) + Con_Printf(CON_WARNING"QEX_B_UNKNOWN5: %x\n", MSG_ReadByte()); + if (bits & QEX_B_UNKNOWN6) + Con_DPrintf(CON_WARNING"QEX_B_UNKNOWN6: %x\n", MSG_ReadByte()); + if (bits & QEX_B_UNKNOWN7) + Con_Printf(CON_WARNING"QEX_B_UNKNOWN7: %x\n", MSG_ReadByte()); + } + else + es->scale = (bits & RMQFITZ_B_SCALE) ? MSG_ReadByte() : 16; } static void CL_ParseBaselineDelta (void) { @@ -7972,7 +8050,7 @@ static qboolean CLNQ_ParseNQPrints(char *s) { int i; char *start = s; - if (!strcmp(s, "Client ping times:\n")) + if (!strcmp(s, "Client ping times:\n") && !cls.qex) { cl.nqparseprint = CLNQPP_PINGS; return true; @@ -8020,7 +8098,7 @@ static qboolean CLNQ_ParseNQPrints(char *s) s = start; } - if (!strncmp(s, "host: ", 9)) + if (!strncmp(s, "host: ", 9) && !cls.qex) { cl.nqparseprint = CLNQPP_STATUS; return cls.nqexpectingstatusresponse; @@ -8134,6 +8212,7 @@ void CLNQ_ParseServerMessage (void) switch (cmd) { default: + badsvc: CL_DumpPacket(); Host_EndGame ("CLNQ_ParseServerMessage: Illegible server message (%i@%i)", cmd, msg_readcount-1); return; @@ -8143,7 +8222,13 @@ void CLNQ_ParseServerMessage (void) break; case svc_print: - s = MSG_ReadString (); + if(cls.qex && cls.protocol_nq != CPNQ_ID) + s = CLQEX_ReadStrings(); + else + { + svcprint: + s = MSG_ReadString (); + } if (*s == 1 || *s == 2) { @@ -8161,7 +8246,10 @@ void CLNQ_ParseServerMessage (void) return; case svc_centerprint: - s = MSG_ReadString (); + if (cls.qex && cls.protocol_nq != CPNQ_ID) + s = CLQEX_ReadStrings(); + else + s = MSG_ReadString (); #ifdef PLUGINS if (Plug_CenterPrintMessage(s, destsplit)) @@ -8188,6 +8276,14 @@ void CLNQ_ParseServerMessage (void) break; case svcdp_precache: + //also svcqex_levelcompleted + if (cls.qex) + { //svcqex_levelcompleted + //not really sure why this even exists. + MSG_ReadSkip(10); + MSG_ReadString(); + break; + } CL_ParsePrecache(); break; @@ -8431,6 +8527,14 @@ void CLNQ_ParseServerMessage (void) break; case svcdp_updatestatbyte: //case svcneh_fog: + //also svcqex_seq + if (cls.qex) + { //svcqex_seq + unsigned seq = MSG_ReadULEB128(); + if (!cls.demoplayback && 0) + CL_AckedInputFrame(cls.netchan.incoming_sequence, seq, true); + break; + } if (CPNQ_IS_BJP || cls.protocol_nq == CPNQ_NEHAHRA) { CL_ResetFog(FOGTYPE_AIR); @@ -8599,9 +8703,28 @@ void CLNQ_ParseServerMessage (void) case svcnq_effect: + //also svcqex_achievement + if (cls.qex) + { //svcqex_achievement + MSG_ReadString(); + break; + } CL_ParseEffect(false); break; case svcnq_effect2: + //also svcqex_chat + if (cls.qex) + { //svcqex_chat + //in qex this text is in some small special chat box. which disappears quickly rendering its contents kinda unreadable. and its messagemode stuff seems broken too, so whatever. and it seems to have newline issues. + //FIXME: figure out the player index so we can kickban/mute/etc them. + qbyte plcolour = MSG_ReadByte(); + qbyte chatcolour = MSG_ReadByte(); + char *pcols[] = {S_COLOR_WHITE,S_COLOR_RED,S_COLOR_YELLOW, S_COLOR_CYAN}; + char *ccols[] = {S_COLOR_WHITE,S_COLOR_CYAN}; + Con_Printf("^[%s%s"/*"\\player\\%i"*/"^]: ", pcols[plcolour%countof(pcols)], MSG_ReadString()); + Con_Printf("%s%s\n", ccols[chatcolour%countof(ccols)], MSG_ReadString()); + break; + } CL_ParseEffect(true); break; @@ -8638,7 +8761,11 @@ void CLNQ_ParseServerMessage (void) #endif case svcdp_downloaddata: - CLDP_ParseDownloadData(); + //also svcqex_servervars: + if (cls.qex) + CLQEX_ParseServerVars(); + else + CLDP_ParseDownloadData(); break; case svcdp_trailparticles: @@ -8650,6 +8777,49 @@ void CLNQ_ParseServerMessage (void) case svcdp_pointparticles1: CL_ParsePointParticles(true); break; + + case svcqex_updateping: + if (cls.qex) + { //svcqex_updateping + int ping; + i = MSG_ReadByte(); + ping = MSG_ReadSignedQEX(); + if (i < MAX_CLIENTS) + cl.players[i].ping = ping; + break; + } + goto badsvc; + case svcqex_updatesocial: + if (cls.qex) + { //svcqex_updatesocial + //both ints are -1 for lan/direct clients, and 0 for the host. I guess this is for chatting to people via steam. + /*slot =*/ MSG_ReadByte(); + /*??? =*/ MSG_ReadLong(); + /*??? =*/ MSG_ReadLong(); + break; + } + goto badsvc; + + case svcqex_updateplinfo: + if (cls.qex) + { //svcqex_updateplinfo + unsigned int slot = MSG_ReadByte(); + int health = MSG_ReadSignedQEX(); + int armour = MSG_ReadSignedQEX(); + if (slot < MAX_CLIENTS) + { + InfoBuf_SetValueForKey(&cl.players[slot].userinfo, "health", va("%i", health)); + InfoBuf_SetValueForKey(&cl.players[slot].userinfo, "health", va("%i", armour)); + } + break; + } + goto badsvc; + case svcqex_print: + if (cls.qex) + { //svcqex_'raw'print + goto svcprint; + } + goto badsvc; } } diff --git a/engine/client/client.h b/engine/client/client.h index 22d2e50c..17235787 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -474,6 +474,9 @@ typedef struct #define CPNQ_IS_DP (cls.protocol_nq >= CPNQ_DP5) #define CPNQ_IS_BJP (cls.protocol_nq >= CPNQ_BJP1 && cls.protocol_nq <= CPNQ_BJP3) qboolean proquake_angles_hack; //angles are always 16bit +#ifdef NQPROT + qboolean qex; //we're connected to a QuakeEx server, which means lots of special workarounds that are not controlled via the actual protocol version. +#endif int protocol_q2; @@ -1144,7 +1147,6 @@ void CL_SaveInfo(vfsfile_t *f); void CL_SetInfo (int pnum, const char *key, const char *value); void CL_SetInfoBlob (int pnum, const char *key, const char *value, size_t valuesize); -void CL_BeginServerConnect(const char *host, int port, qboolean noproxy); char *CL_TryingToConnect(void); void CL_ExecInitialConfigs(char *defaultexec); diff --git a/engine/client/p_script.c b/engine/client/p_script.c index c1af8104..a1994af6 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -6090,7 +6090,7 @@ static int PScript_ParticleTrail (vec3_t startpos, vec3_t end, int type, float t // issues with entities in action during particle reconfiguration // but that shouldn't be happening too often trailstate_t* ts = P_FetchTrailstate(tk); - return fallback->ParticleTrail(startpos, end, type - FALLBACKBIAS, timeinterval, dlkey, axis, &(ts->fallbackkey)); + return fallback->ParticleTrail(startpos, end, type - FALLBACKBIAS, timeinterval, dlkey, axis, ts?&(ts->fallbackkey):NULL); } if (type < 0 || type >= numparticletypes) diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 558f6718..f0c87c09 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -3374,13 +3374,13 @@ void Sbar_DeathmatchOverlay (playerview_t *pv, int start) cl.last_ping_request = realtime; CL_SendClientCommand(true, "pings"); } - else if (cls.protocol == CP_NETQUAKE) + else if (cls.protocol == CP_NETQUAKE && !cls.qex) { cl.last_ping_request = realtime; CL_SendClientCommand(true, "ping"); } } - if (cls.protocol == CP_NETQUAKE) + if (cls.protocol == CP_NETQUAKE && !cls.qex) { if (cl.nqplayernamechanged && cl.nqplayernamechanged < realtime) { diff --git a/engine/client/zqtp.c b/engine/client/zqtp.c index 97568f5a..a3895175 100644 --- a/engine/client/zqtp.c +++ b/engine/client/zqtp.c @@ -3882,7 +3882,7 @@ void CL_Say (qboolean team, char *extra) //messagemode always adds quotes. the console command never did. //the server is expected to use Cmd_Args and to strip first+last chars if the first is a quote. this is annoying and clumsy for mods to parse. #ifdef HAVE_LEGACY - if (!dpcompat_console.ival) + if (!dpcompat_console.ival && !cls.qex) CL_SendSeatClientCommand(true, split, "%s \"%s%s\"", team ? "say_team" : "say", extra?extra:"", sendtext); else #endif diff --git a/engine/common/common.c b/engine/common/common.c index 504fcbaf..259fc788 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -2032,6 +2032,14 @@ quint64_t MSG_ReadULEB128 (void) } return r; } +qint64_t MSG_ReadSignedQEX (void) +{ //this is not signed leb128 (which would normally just sign-extend) + quint64_t c = MSG_ReadULEB128(); + if (c&1) + return -1-(qint64_t)(c>>1); + else + return (qint64_t)(c>>1); +} quint64_t MSG_ReadUInt64 (void) { //0* 10*,*, 110*,*,* etc, up to 0xff followed by 8 continuation bytes qbyte l=0x80, v, b = 0; diff --git a/engine/common/common.h b/engine/common/common.h index 7639494c..0e777ed3 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -356,6 +356,7 @@ qint64_t MSG_ReadInt64 (void); quint64_t MSG_ReadUInt64 (void); qint64_t MSG_ReadSLEB128 (void); quint64_t MSG_ReadULEB128 (void); +qint64_t MSG_ReadSignedQEX (void); struct client_s; unsigned int MSGSV_ReadEntity (struct client_s *fromclient); unsigned int MSGCL_ReadEntity (void); @@ -1002,11 +1003,11 @@ qboolean IPLog_Merge_File(const char *fname); #endif enum certlog_problem_e { - CERTLOG_WRONGHOST, - CERTLOG_EXPIRED, - CERTLOG_MISSINGCA, + CERTLOG_WRONGHOST =1<<0, + CERTLOG_EXPIRED =1<<1, + CERTLOG_MISSINGCA =1<<2, - CERTLOG_UNKNOWN, + CERTLOG_UNKNOWN =1<<3, }; qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems); diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index f24da142..8fcd5171 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -293,6 +293,52 @@ vfsfile_t *FS_DecompressGZip(vfsfile_t *infile, vfsfile_t *outfile) +size_t ZLib_DecompressBuffer(qbyte *in, size_t insize, qbyte *out, size_t maxoutsize) +{ + int ret; + + z_stream strm = { + in, + insize, + 0, + + out, + maxoutsize, + 0, + + NULL, + NULL, + + NULL, + NULL, + NULL, + + Z_UNKNOWN, + 0, + 0 + }; + + qinflateInit2(&strm, MAX_WBITS); + + while ((ret=qinflate(&strm, Z_SYNC_FLUSH)) != Z_STREAM_END) + { + if (strm.avail_in == 0 || strm.avail_out == 0) + { + if (strm.avail_in == 0) + break; //reached the end of the input + //output not big enough, but max size is fixed. + break; + } + + //doh, it terminated for no reason + if (ret != Z_STREAM_END) + break; + } + qinflateEnd(&strm); + + return strm.total_out; +} + diff --git a/engine/common/net.h b/engine/common/net.h index 05c6fcb0..75e75003 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -328,6 +328,7 @@ void Huff_EmitByte(int ch, qbyte *buffer, int *count); #define NETFLAG_NAK 0x00040000 #define NETFLAG_EOM 0x00080000 #define NETFLAG_UNRELIABLE 0x00100000 +#define NETFLAG_ZLIB 0x00200000 //QEx - payload contains the real (full) packet #define NETFLAG_CTL 0x80000000 #define NQ_NETCHAN_GAMENAME "QUAKE" diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 8bfd781d..75c7ac11 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -443,6 +443,7 @@ qboolean ServerPaused(void); #endif #ifdef NQPROT +size_t ZLib_DecompressBuffer(qbyte *in, size_t insize, qbyte *out, size_t maxoutsize); enum nqnc_packettype_e NQNetChan_Process(netchan_t *chan) { int header; @@ -453,12 +454,44 @@ enum nqnc_packettype_e NQNetChan_Process(netchan_t *chan) MSG_BeginReading (chan->netprim); header = LongSwap(MSG_ReadLong()); - if (net_message.cursize != (header & NETFLAG_LENGTH_MASK)) - return NQNC_IGNORED; //size was wrong, couldn't have been ours. if (header & NETFLAG_CTL) return NQNC_IGNORED; //huh? +#ifdef HAVE_CLIENT + if (header & NETFLAG_ZLIB) + { //note: qex gets the size header wrong here. + qbyte *tmp; + if (net_message.cursize <= PACKET_HEADER || net_message.cursize != PACKET_HEADER+(header & NETFLAG_LENGTH_MASK)) + return NQNC_IGNORED; //huh? + /*redundantsequence =*/ MSG_ReadLong(); //wasting 4 bytes... +#ifdef AVAIL_ZLIB + tmp = alloca(0xffff); + //note: its zlib rather than raw deflate (wasting a further 6 bytes...). + net_message.cursize = ZLib_DecompressBuffer(net_message.data+8, net_message.cursize-8, tmp, 0xffff); + if (net_message.cursize < PACKET_HEADER) +#endif + { + if (chan->sock == NS_CLIENT) + { //clients can just throw an error. the server will appear dead if we try to just ignore it. + Host_EndGame("QuakeEx netchan decompression error"); + return NQNC_IGNORED; + } + else + { //inject a disconnect request. clients shouldn't be sending this anyway. + net_message.data[8] = clc_disconnect; + net_message.cursize = 9; + return NQNC_RELIABLE; + } + } + memcpy(net_message.data, tmp, net_message.cursize); + + MSG_BeginReading (chan->netprim); + header = LongSwap(MSG_ReadLong()); //re-read the now-decompressed copy of the header for the real flags + } +#endif + if (net_message.cursize != (header & NETFLAG_LENGTH_MASK)) + return NQNC_IGNORED; //size was wrong, couldn't have been ours. sequence = LongSwap(MSG_ReadLong()); if (header & NETFLAG_ACK) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index aaf9847d..d2b4a88f 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -348,7 +348,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // these are not really documented anywhere. we're trying to stick with protocol 15 (because that's the only documented protocol it supports properly thanks to demos) // however we still need some special case svcs // (there's also some problematic c2s differences too) -#define svcqex_updateping 46 +#define svcqex_updateping 46 // [byte] slot, [signed_qe] ping +#define svcqex_updatesocial 47 // [byte] slot, 8 bytes of unknown +#define svcqex_updateplinfo 48 // [byte] slot, [leb128] health, [leb128] armour +#define svcqex_print 49 // identical to svc_print, except not broken by qex. #define svcqex_servervars 50 // [leb128] changedvalues, [???] value... #define svcqex_seq 51 // [leb128] input sequence ack #define svcqex_achievement 52 // [string] codename @@ -791,6 +794,10 @@ enum { #define FITZSU_UNUSED30 (1<<30) #define SU_EXTEND3 (1<<31) // another byte to follow, future expansion +//builds on top of fitz +#define QEX_SU_FLOATCOORDS (1<<8) +#define QEX_SU_ENTFLAGS (1<<26) // ULEB128 copy of the player's .flags field + // first extend byte #define DPSU_PUNCHVEC1 (1<<16) #define DPSU_PUNCHVEC2 (1<<17) @@ -852,11 +859,23 @@ enum { #define DPU_UNUSED30 (1<<30) // future expansion #define DPU_EXTEND3 (1<<31) // another byte to follow, future expansion -#define FITZU_ALPHA (1<<16) -#define FITZU_FRAME2 (1<<17) -#define FITZU_MODEL2 (1<<18) -#define FITZU_LERPFINISH (1<<19) -#define RMQU_SCALE (1<<20) +#define FITZU_ALPHA (1<<16) +#define FITZU_FRAME2 (1<<17) +#define FITZU_MODEL2 (1<<18) +#define FITZU_LERPFINISH (1<<19) +#define RMQU_SCALE (1<<20) + +#define QE_U_FLOATCOORDS (1<<21) //set on the local player entity, to boost precision for prediction. +#define QE_U_SOLIDTYPE (1<<22) //for prediction I suppose. +//#define QE_U_EXTEND (1<<23) +#define QE_U_ENTFLAGS (1<<24) //not sure why this needs to be networked, oh well. redundant with clientdata +#define QE_U_HEALTH (1<<25) //not sure why this needs to be networked, oh well. +#define QE_U_UNKNOWN26 (1<<26) //seems to be some sort of nodraw flag (presumably for solid bmodels that need a modelindex for collisions). +#define QE_U_UNUSED27 (1<<27) +#define QE_U_UNUSED28 (1<<28) +#define QE_U_UNUSED29 (1<<29) +#define QE_U_UNUSED30 (1<<30) +#define QE_U_UNUSED31 (1u<<31) #endif @@ -903,6 +922,22 @@ enum { #define Q2UX_UNUSED (Q2UX_UNUSED1|Q2UX_UNUSED2|Q2UX_UNUSED3|Q2UX_UNUSED4) +//QuakeEx-specific stuff +//gamevar info +#define QEX_GV_DEATHMATCH (1<<0) +#define QEX_GV_IDEALPITCHSCALE (1<<1) +#define QEX_GV_FRICTION (1<<2) +#define QEX_GV_EDGEFRICTION (1<<3) +#define QEX_GV_STOPSPEED (1<<4) +#define QEX_GV_MAXVELOCITY (1<<5) +#define QEX_GV_GRAVITY (1<<6) +#define QEX_GV_NOSTEP (1<<7) +#define QEX_GV_MAXSPEED (1<<8) +#define QEX_GV_ACCELERATE (1<<9) +#define QEX_GV_CONTROLLERONLY (1<<10) +#define QEX_GV_TIMELIMIT (1<<11) +#define QEX_GV_FRAGLIMIT (1<<12) +#define QEX_GV_ALL ((1<<13)-1) //============================================== //obsolete demo players info @@ -955,6 +990,12 @@ enum { #define FITZ_B_ALPHA (1<<2) #define RMQFITZ_B_SCALE (1<<3) +#define QEX_B_SOLID (1<<3) +#define QEX_B_UNKNOWN4 (1<<4) +#define QEX_B_UNKNOWN5 (1<<5) +#define QEX_B_UNKNOWN6 (1<<6) +#define QEX_B_UNKNOWN7 (1<<7) + #define DEFAULT_VIEWHEIGHT 22 diff --git a/engine/server/server.h b/engine/server/server.h index 326d9d81..2cdec089 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -698,7 +698,7 @@ typedef struct client_s enum serverprotocols_e protocol; unsigned int supportedprotocols; qboolean proquake_angles_hack; //expect 16bit client->server angles . - qboolean qex_input_hack; //qex sends strange clc inputs and needs workarounds for its prediction. + qboolean qex; //qex sends strange clc inputs and needs workarounds for its prediction. it also favours fitzquake's protocol but violates parts of it. unsigned int lastruncmd; //for non-qw physics. timestamp they were last run, so switching between physics modes isn't a (significant) cheat //speed cheat testing diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 8876e2c1..6578a87a 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -2246,7 +2246,7 @@ static void SV_Status_f (void) #define C_DROP COLUMN(6, "drop", Con_Printf("%4.1f ", 100.0*cl->netchan.drop_count / cl->netchan.incoming_sequence)) #define C_DLP COLUMN(7, "dl ", if (!cl->download)Con_Printf(" ");else Con_Printf("%3g ", (cl->downloadcount*100.0)/cl->downloadsize)) #define C_DLS COLUMN(8, "dls", if (!cl->download)Con_Printf(" ");else Con_Printf("%3u ", (unsigned int)(cl->downloadsize/1024))) -#define C_PROT COLUMN(9, "prot", Con_Printf("%-5.5s", p)) +#define C_PROT COLUMN(9, "prot ", Con_Printf("%-6.6s", p)) #define C_ADDRESS2 COLUMN(10, "address ", Con_Printf("%s", s)) int columns = (1<<6)-1; @@ -2305,12 +2305,12 @@ static void SV_Status_f (void) case SCP_BAD: p = ""; break; - case SCP_QUAKEWORLD: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"fteq":"qw"; break; + case SCP_QUAKEWORLD: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"fteqw":"qw"; break; case SCP_QUAKE2: p = "q2"; break; case SCP_QUAKE3: p = "q3"; break; - case SCP_NETQUAKE: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ften":(cl->qex_input_hack?"qe15":"nq"); break; - case SCP_BJP3: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ften":"bjp3"; break; - case SCP_FITZ666: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ften":"fitz"; break; + case SCP_NETQUAKE: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":(cl->qex?"qe15":"nq"); break; + case SCP_BJP3: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"bjp3"; break; + case SCP_FITZ666: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":(cl->qex?"qe666":"fitz"); break; case SCP_DARKPLACES6: p = "dpp6"; break; case SCP_DARKPLACES7: p = "dpp7"; break; } diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 19999722..300db8f5 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -3042,6 +3042,23 @@ int glowsize=0, glowcolour=0, colourmod=0; if (baseline->dpflags & RENDER_STEP) bits |= FITZU_LERPFINISH; + + + if (host_client->qex) + { + if (host_client->edict == ed) + { //only send some bloated things to yourself. + bits |= QE_U_FLOATCOORDS; //when predicting, you'll need more precision to avoid errors + + if (ed->v->flags) + bits |= QE_U_ENTFLAGS; + } + if (ed->v->solid) + bits |= QE_U_SOLIDTYPE; + if (ed->v->health) + bits |= QE_U_HEALTH; + //bits |= QE_U_UNKNOWN26; + } } else if (host_client->protocol == SCP_BJP3) { @@ -3126,12 +3143,24 @@ int glowsize=0, glowcolour=0, colourmod=0; if (bits & NQU_COLORMAP) MSG_WriteByte (msg, ent->colormap & 0xff); if (bits & NQU_SKIN) MSG_WriteByte (msg, ent->skinnum & 0xff); if (bits & NQU_EFFECTS) MSG_WriteByte (msg, eff & 0x00ff); - if (bits & NQU_ORIGIN1) MSG_WriteCoord (msg, ent->origin[0]); - if (bits & NQU_ANGLE1) MSG_WriteAngle(msg, ent->angles[0]); - if (bits & NQU_ORIGIN2) MSG_WriteCoord (msg, ent->origin[1]); - if (bits & NQU_ANGLE2) MSG_WriteAngle(msg, ent->angles[1]); - if (bits & NQU_ORIGIN3) MSG_WriteCoord (msg, ent->origin[2]); - if (bits & NQU_ANGLE3) MSG_WriteAngle(msg, ent->angles[2]); + if (host_client->qex && (bits & QE_U_FLOATCOORDS)) + { + if (bits & NQU_ORIGIN1) MSG_WriteFloat (msg, ent->origin[0]); + if (bits & NQU_ANGLE1) MSG_WriteAngle (msg, ent->angles[0]); + if (bits & NQU_ORIGIN2) MSG_WriteFloat (msg, ent->origin[1]); + if (bits & NQU_ANGLE2) MSG_WriteAngle (msg, ent->angles[1]); + if (bits & NQU_ORIGIN3) MSG_WriteFloat (msg, ent->origin[2]); + if (bits & NQU_ANGLE3) MSG_WriteAngle (msg, ent->angles[2]); + } + else + { + if (bits & NQU_ORIGIN1) MSG_WriteCoord (msg, ent->origin[0]); + if (bits & NQU_ANGLE1) MSG_WriteAngle (msg, ent->angles[0]); + if (bits & NQU_ORIGIN2) MSG_WriteCoord (msg, ent->origin[1]); + if (bits & NQU_ANGLE2) MSG_WriteAngle (msg, ent->angles[1]); + if (bits & NQU_ORIGIN3) MSG_WriteCoord (msg, ent->origin[2]); + if (bits & NQU_ANGLE3) MSG_WriteAngle (msg, ent->angles[2]); + } if (host_client->protocol == SCP_FITZ666) { @@ -3140,6 +3169,14 @@ int glowsize=0, glowcolour=0, colourmod=0; if (bits & FITZU_FRAME2) MSG_WriteByte(msg, ent->frame>>8); if (bits & FITZU_MODEL2) MSG_WriteByte(msg, ent->modelindex>>8); if (bits & FITZU_LERPFINISH)MSG_WriteByte(msg, bound(0, (int)((ed->v->nextthink - sv.world.physicstime) * 255), 255)); + + if (host_client->qex) + { + if (bits & QE_U_SOLIDTYPE) MSG_WriteByte(msg, ed->v->solid); + if (bits & QE_U_ENTFLAGS) MSG_WriteULEB128(msg, ed->v->flags); + if (bits & QE_U_HEALTH) MSG_WriteSignedQEX(msg, ed->v->health); + if (bits & QE_U_UNKNOWN26) MSG_WriteByte(msg, 0); + } } else if (host_client->protocol == SCP_BJP3) { diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index c4f8d51c..b73de90c 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -446,17 +446,35 @@ void SV_FinalMessage (char *message) buf.data = bufdata; buf.maxsize = sizeof(bufdata); - SZ_Clear (&buf); - MSG_WriteByte (&buf, svc_print); - MSG_WriteByte (&buf, PRINT_HIGH); - MSG_WriteString (&buf, message); - MSG_WriteByte (&buf, svc_disconnect); - for (i=0, cl = svs.clients ; istate >= cs_spawned && !cl->controlled) - if (ISNQCLIENT(cl) || ISQWCLIENT(cl)) - Netchan_Transmit (&cl->netchan, buf.cursize - , buf.data, 10000); + { + if (ISQWCLIENT(cl)) + { + SZ_Clear (&buf); + MSG_WriteByte (&buf, svc_print); + MSG_WriteByte (&buf, PRINT_HIGH); + MSG_WriteString (&buf, message); + MSG_WriteByte (&buf, svc_disconnect); + } +#ifdef NQPROT + else if (ISNQCLIENT(cl)) + { + SZ_Clear (&buf); + if (cl->qex && cl->protocol != SCP_NETQUAKE) + MSG_WriteByte (&buf, svcqex_print); //urgh, ffs. + else + MSG_WriteByte (&buf, svc_print); + MSG_WriteString (&buf, message); + MSG_WriteByte (&buf, svc_disconnect); + } +#endif + else + continue; + + Netchan_Transmit (&cl->netchan, buf.cursize + , buf.data, 10000); + } } @@ -1883,13 +1901,18 @@ void SV_AcceptMessage(client_t *newcl) SZ_Clear(&sb); MSG_WriteLong(&sb, 0); MSG_WriteByte(&sb, CCREP_ACCEPT); - NET_LocalAddressForRemote(svs.sockets, &net_from, &localaddr, 0); - MSG_WriteLong(&sb, ShortSwap(localaddr.port)); - if (newcl->proquake_angles_hack) + if (newcl->qex) + ; //skip any port info (as well as any proquake ident stuff. + else { - MSG_WriteByte(&sb, MOD_PROQUAKE); - MSG_WriteByte(&sb, MOD_PROQUAKE_VERSION); - MSG_WriteByte(&sb, 0/*flags*/); + NET_LocalAddressForRemote(svs.sockets, &net_from, &localaddr, 0); + MSG_WriteLong(&sb, ShortSwap(localaddr.port)); + if (newcl->proquake_angles_hack) + { + MSG_WriteByte(&sb, MOD_PROQUAKE); + MSG_WriteByte(&sb, MOD_PROQUAKE_VERSION); + MSG_WriteByte(&sb, 0/*flags*/); + } } *(int*)sb.data = BigLong(NETFLAG_CTL|sb.cursize); NET_SendPacket(svs.sockets, sb.cursize, sb.data, &net_from); @@ -2110,7 +2133,7 @@ void SV_ClientProtocolExtensionsChanged(client_t *client) client->datagram.maxsize = sizeof(host_client->datagram_buf); } - else if (client->qex_input_hack) + else if (client->qex) { client->max_net_clients = NQMAX_CLIENTS; client->datagram.maxsize = sizeof(host_client->datagram_buf); @@ -2570,7 +2593,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) } newcl->supportedprotocols = info->supportedprotocols; newcl->proquake_angles_hack = info->proquakeanglehack; - newcl->qex_input_hack = info->isqex; + newcl->qex = info->isqex; #endif newcl->userid = ++nextuserid; @@ -3404,7 +3427,7 @@ void SVC_DirectConnect(int expectedreliablesequence) } else if (version == NQ_NETCHAN_VERSION_QEX) { //rerelease... - info.protocol = SCP_NETQUAKE; + info.protocol = SCP_FITZ666;//NETQUAKE; info.isqex = true; } #endif @@ -4031,7 +4054,7 @@ qboolean SV_ConnectionlessPacket (void) c = Cmd_Argv(0); if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: %s\n", NET_AdrToString (adr, sizeof(adr), &net_from), s); + Con_Printf(S_COLOR_GRAY"%s: %s\n", NET_AdrToString (adr, sizeof(adr), &net_from), s); if (!strcmp(c, "ping") || ( c[0] == A2A_PING && (c[1] == 0 || c[1] == '\n')) ) { //only continue respond to these if we're actually public. qwfwd likes spamming us endlessly even if we stop heartbeating (which leaves us discoverable to others, too). @@ -4253,7 +4276,7 @@ qboolean SVNQ_ConnectionlessPacket(void) if (msg_readcount+17 <= net_message.cursize && !strncmp("challengeconnect ", &net_message.data[msg_readcount], 17)) { if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_CONNECT_COOKIE\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); + Con_Printf(S_COLOR_GRAY"%s: CCREQ_CONNECT_COOKIE\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); Cmd_TokenizeString(MSG_ReadStringLine(), false, false); /*okay, so this is a reliable packet from a client, containing a 'cmd challengeconnect $challenge' response*/ str = va("connect %i %i %s \"\\name\\unconnected\\mod\\%s\\modver\\%s\\flags\\%s\\password\\%s\"", NQ_NETCHAN_VERSION, 0, Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3), Cmd_Argv(4), Cmd_Argv(5)); @@ -4323,7 +4346,7 @@ qboolean SVNQ_ConnectionlessPacket(void) protver = MSG_ReadByte(); if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_CONNECT (\"%s\" %i)\n", NET_AdrToString (com_token, sizeof(com_token), &net_from), str, protver); + Con_Printf(S_COLOR_GRAY"%s: CCREQ_CONNECT (\"%s\" %i)\n", NET_AdrToString (com_token, sizeof(com_token), &net_from), str, protver); sb.maxsize = sizeof(buffer); sb.data = buffer; @@ -4420,7 +4443,7 @@ qboolean SVNQ_ConnectionlessPacket(void) return true; case CCREQ_SERVER_INFO: if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_SERVER_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); + Con_Printf(S_COLOR_GRAY"%s: CCREQ_SERVER_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); if (sv_public.ival < 0) return false; if (SV_BannedReason (&net_from)) @@ -4452,7 +4475,7 @@ qboolean SVNQ_ConnectionlessPacket(void) return true; case CCREQ_PLAYER_INFO: if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_PLAYER_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); + Con_Printf(S_COLOR_GRAY"%s: CCREQ_PLAYER_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); if (sv_public.ival < 0) return false; if (SV_BannedReason (&net_from)) @@ -4486,7 +4509,7 @@ qboolean SVNQ_ConnectionlessPacket(void) return true; case CCREQ_RULE_INFO: if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_RULE_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); + Con_Printf(S_COLOR_GRAY"%s: CCREQ_RULE_INFO\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); if (sv_public.ival < 0) return false; if (SV_BannedReason (&net_from)) @@ -4781,7 +4804,7 @@ dominping: // packet is not from a known client if (sv_showconnectionlessmessages.ival) - Con_Printf ("%s:sequenced packet without connection\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); //hack: com_token cos we need some random temp buffer. + Con_Printf (S_COLOR_GRAY "%s:sequenced packet without connection\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); //hack: com_token cos we need some random temp buffer. } /* diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 3aaaf889..81f85108 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -184,7 +184,10 @@ void SV_PrintToClient(client_t *cl, int level, const char *string) case SCP_BJP3: case SCP_FITZ666: #ifdef NQPROT - ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3); + if (cl->qex && cl->protocol != SCP_NETQUAKE) + ClientReliableWrite_Begin (cl, svcqex_print, strlen(string)+3); //urgh! + else + ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3); if (level == PRINT_CHAT) ClientReliableWrite_Byte (cl, 1); ClientReliableWrite_String (cl, string); @@ -1594,7 +1597,13 @@ void SV_WriteCenterPrint(client_t *cl, char *s) } else { - ClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s)); + if (cl->qex && cl->protocol != SCP_NETQUAKE) + { + ClientReliableWrite_Begin (cl, svc_centerprint, 4 + strlen(s)); + ClientReliableWrite_Short (cl, 1); + } + else + ClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s)); } ClientReliableWrite_String (cl, s); @@ -1691,7 +1700,7 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) if (client->fteprotocolextensions2 & PEXT2_PREDINFO) MSG_WriteShort(msg, client->last_sequence); - else if (client->qex_input_hack && client->last_sequence) + else if (client->qex && client->last_sequence) { MSG_WriteByte(msg, svcqex_seq); MSG_WriteULEB128(msg, client->last_sequence); @@ -1779,6 +1788,14 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) bits |= FITZSU_WEAPONFRAME2; if (ent->xv->alpha && ent->xv->alpha < 1) bits |= FITZSU_WEAPONALPHA; + + if (client->qex) + { + if (ent->v->flags) + bits |= QEX_SU_ENTFLAGS; + if (bits & (SU_VELOCITY1|SU_VELOCITY2|SU_VELOCITY3)) + bits |= QEX_SU_FLOATCOORDS; + } } } @@ -1818,8 +1835,10 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) // Msg_WriteCoord(msg, ent->xv->punchvector); if (bits & (SU_VELOCITY1<protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) - MSG_WriteCoord(msg, ent->v->velocity[i]); + if (client->qex && (bits & QEX_SU_ENTFLAGS)) + MSG_WriteFloat(msg, ent->v->velocity[i]); + else if (client->protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) + MSG_WriteFloat(msg, ent->v->velocity[i]); else MSG_WriteChar (msg, bound(-128, ent->v->velocity[i]/16, 127)); } @@ -1898,6 +1917,11 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) if (bits & FITZSU_CELLS2) MSG_WriteByte (msg, (int)ent->v->ammo_cells >> 8); if (bits & FITZSU_WEAPONFRAME2) MSG_WriteByte (msg, (int)ent->v->weaponframe >> 8); if (bits & FITZSU_WEAPONALPHA) MSG_WriteByte (msg, ent->xv->alpha*255); + + if (client->qex) + { + if (bits & QEX_SU_ENTFLAGS) MSG_WriteULEB128 (msg, ent->v->flags); + } } #endif } @@ -3049,7 +3073,7 @@ void SV_UpdateToReliableMessages (void) SV_FullClientUpdate (host_client, NULL); } - if (host_client->qex_input_hack && sendpings) + if (host_client->qex && sendpings) { sizebuf_t *m; for (j=0, client = svs.clients ; jmax_net_clients; j++, client++) @@ -3062,6 +3086,16 @@ void SV_UpdateToReliableMessages (void) MSG_WriteByte(m, j); MSG_WriteSignedQEX(m, SV_CalcPing(client, false)); ClientReliable_FinishWrite(host_client); + + if (coop.ival) + { + m = ClientReliable_StartWrite(host_client, 64); + MSG_WriteByte(m, svcqex_updateplinfo); + MSG_WriteByte(m, j); + MSG_WriteSignedQEX(m, client->edict->v->health); + MSG_WriteSignedQEX(m, client->edict->v->armorvalue); + ClientReliable_FinishWrite(host_client); + } } } diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 2d238fda..86e6634a 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -173,9 +173,7 @@ qboolean SV_CheckRealIP(client_t *client, qboolean force) return true; //we know that the ip is authentic if (client->realip_status == 2) { - ClientReliableWrite_Begin(client, svc_print, 256); - ClientReliableWrite_Byte(client, PRINT_HIGH); - ClientReliableWrite_String(client, "Couldn't verify your real ip\n"); + SV_PrintToClient(client, PRINT_HIGH, "Couldn't verify your real ip\n"); return true; //client doesn't support certainty. } if (client->realip_status == -1) @@ -183,12 +181,10 @@ qboolean SV_CheckRealIP(client_t *client, qboolean force) if (realtime - client->connection_started > sv_realip_timeout.value) { - ClientReliableWrite_Begin(client, svc_print, 256); - ClientReliableWrite_Byte(client, PRINT_HIGH); if (client->realip_status > 0) - ClientReliableWrite_String(client, "Couldn't verify your real ip\n"); + SV_PrintToClient(client, PRINT_HIGH, "Couldn't verify your real ip\n"); else - ClientReliableWrite_String(client, "Couldn't determine your real ip\n"); + SV_PrintToClient(client, PRINT_HIGH, "Couldn't determine your real ip\n"); if (sv_realip_kick.value > host_client->realip_status) { client->drop = true; @@ -529,7 +525,7 @@ void SVNQ_New_f (void) if (host_client->drop) return; - if (!host_client->pextknown && sv_listen_nq.ival != 1) //1 acts as a legacy mode, used for clients that can't cope with cmd before serverdata (either because they crash out or because they refuse to send reliables until after they got the first serverdata) + if (!host_client->pextknown && sv_listen_nq.ival != 1 && !host_client->qex) //1 acts as a legacy mode, used for clients that can't cope with cmd before serverdata (either because they crash out or because they refuse to send reliables until after they got the first serverdata) { if (!host_client->supportedprotocols && host_client->netchan.remote_address.type != NA_LOOPBACK) { //don't override cl_loopbackprotocol's choice @@ -544,6 +540,8 @@ void SVNQ_New_f (void) } return; } + else + host_client->pextknown = true; //just in case. if (dpcompat_nopreparse.ival && progstype == PROG_QW) { @@ -629,12 +627,12 @@ void SVNQ_New_f (void) { protext1 &= ~PEXT_FLOATCOORDS; //never report floatcoords when using rmq protocol, as the base protocol allows us to be more specific anyway. protmain = PROTOCOL_VERSION_RMQ; - protoname = "RMQ"; + protoname = host_client->qex?"QE999":"RMQ"; } else { protmain = PROTOCOL_VERSION_FITZ; - protoname = "666"; + protoname = host_client->qex?"QE666":"666"; } } else if (host_client->protocol == SCP_BJP3) @@ -658,14 +656,13 @@ void SVNQ_New_f (void) "!!! EXPECT MISSING MODELS, SOUNDS, OR ENTITIES\n"); //if you're reading this message to try to avoid your client being described as shitty, implement support for 'cmd protocol' and maybe 'cmd pext' stuffcmds. //simply put, I can't use 666 if I don't know that its safe to do so. - MSG_WriteByte (&host_client->netchan.message, svc_print); - MSG_WriteString (&host_client->netchan.message,message); + SV_PrintToClient(host_client, PRINT_HIGH, message); } } host_client->protocol = SCP_NETQUAKE; //identical other than the client->server angles protmain = PROTOCOL_VERSION_NQ; - protoname = "NQ"; + protoname = host_client->qex?"QE15":"NQ"; } break; case SCP_DARKPLACES6: @@ -728,8 +725,7 @@ void SVNQ_New_f (void) protoname,(protext1||(protext2&~PEXT2_VOICECHAT))?"+":"",(protext2&PEXT2_VOICECHAT)?"Voip":"", build,gamedir, mapname); } - MSG_WriteByte (&host_client->netchan.message, svc_print); - MSG_WriteString (&host_client->netchan.message,message); + SV_PrintToClient(host_client, PRINT_HIGH, message); } if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7) @@ -797,8 +793,9 @@ void SVNQ_New_f (void) if (protext2 & PEXT2_PREDINFO) MSG_WriteString(&host_client->netchan.message, gamedir); - MSG_WriteByte (&host_client->netchan.message, (sv.allocated_client_slots>host_client->max_net_clients)?host_client->max_net_clients:sv.allocated_client_slots); + if (host_client->qex) + MSG_WriteString(&host_client->netchan.message, gamedir); if (!coop.value && deathmatch.value) MSG_WriteByte (&host_client->netchan.message, GAME_DEATHMATCH); @@ -828,29 +825,11 @@ void SVNQ_New_f (void) MSG_WriteByte (&host_client->netchan.message, 0); } - if (host_client->qex_input_hack) - { + if (host_client->qex) + { //FIXME: these may change mid-map. extern cvar_t sv_friction, sv_stopspeed, sv_maxvelocity, sv_accelerate, sv_gravity; - enum - { - QEX_GV_DEATHMATCH = 1<<0, - QEX_GV_IDEALPITCHSCALE = 1<<1, - QEX_GV_FRICTION = 1<<2, - QEX_GV_EDGEFRICTION = 1<<3, - QEX_GV_STOPSPEED = 1<<4, - QEX_GV_MAXVELOCITY = 1<<5, - QEX_GV_GRAVITY = 1<<6, - QEX_GV_NOSTEP = 1<<7, - QEX_GV_MAXSPEED = 1<<8, - QEX_GV_ACCELERATE = 1<<9, - QEX_GV_CONTROLLERONLY = 1<<10, - QEX_GV_TIMELIMIT = 1<<11, - QEX_GV_FRAGLIMIT = 1<<12, + unsigned int bits = QEX_GV_ALL; - QEX_GV_ALL =(1<<13)-1 - } bits = QEX_GV_ALL; - - bits = QEX_GV_ALL; MSG_WriteByte (&host_client->netchan.message, svcqex_servervars); MSG_WriteULEB128 (&host_client->netchan.message, bits); if (bits & QEX_GV_DEATHMATCH) @@ -6026,13 +6005,13 @@ static void SVNQ_PreSpawn_f (void) default: break; case SCP_NETQUAKE: - prot = " (nq)"; + prot = host_client->qex?" (qe15)":" (nq)"; break; case SCP_BJP3: prot = " (bjp3)"; break; case SCP_FITZ666: - prot = " (fitz)"; + prot = host_client->qex?" (qe666)":" (fitz)"; break; case SCP_DARKPLACES6: prot = " (dpp6)"; @@ -8722,7 +8701,10 @@ void SVNQ_ReadClientMove (qboolean forceangle16, qboolean quakeex) if (quakeex) { //I'm guessing this has something to do with splitscreen. if (MSG_ReadByte() != 1) + { + Con_Printf("Unknown byte wasn't 1\n"); msg_badread = true; + } } @@ -8873,7 +8855,7 @@ void SVNQ_ReadClientMove (qboolean forceangle16, qboolean quakeex) } else { - if (!host_client->qex_input_hack) + if (!host_client->qex) host_client->last_sequence = 0; //let the client know that prediction is fucked, by not acking any input frames. if (cmd.impulse) host_client->edict->v->impulse = cmd.impulse; @@ -8889,7 +8871,6 @@ void SVNQ_ExecuteClientMessage (client_t *cl) char *s; // client_frame_t *frame; qboolean forceangle16; - qboolean qex = false; cl->netchan.outgoing_sequence++; cl->netchan.incoming_acknowledged = cl->netchan.outgoing_sequence-1; @@ -9020,7 +9001,7 @@ void SVNQ_ExecuteClientMessage (client_t *cl) break; } - SVNQ_ReadClientMove (forceangle16, qex); + SVNQ_ReadClientMove (forceangle16, cl->qex); // cmd = host_client->lastcmd; // SV_ClientThink(); break; @@ -9075,7 +9056,6 @@ void SVNQ_ExecuteClientMessage (client_t *cl) case clc_delta://clcqex_sequence: host_client->last_sequence = MSG_ReadULEB128(); - qex = true; break; case clc_tmove://clcqex_auth