From 80fcfa7938a1f68b49bc97d594c2c8a9dd4ec2d8 Mon Sep 17 00:00:00 2001 From: Spoike Date: Sat, 8 Jan 2022 10:01:15 +0000 Subject: [PATCH] Hack in some support for qex's clc differences. Add some extra qex-specific svc differences to try to work around its prediction issues. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6157 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/common/common.c | 47 ++++++++++++++++++ engine/common/common.h | 4 ++ engine/common/net.h | 1 + engine/common/protocol.h | 9 ++++ engine/server/net_preparse.c | 8 ++-- engine/server/server.h | 2 + engine/server/sv_ccmds.c | 2 +- engine/server/sv_main.c | 34 +++++++++---- engine/server/sv_send.c | 33 +++++++++++++ engine/server/sv_user.c | 93 +++++++++++++++++++++++++++++++++--- 10 files changed, 213 insertions(+), 20 deletions(-) diff --git a/engine/common/common.c b/engine/common/common.c index d82fc8840..c3d0c59c0 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -918,6 +918,39 @@ void MSG_WriteLong (sizebuf_t *sb, int c) buf[2] = (c>>16)&0xff; buf[3] = (c>>24)&0xff; } +void MSG_WriteULEB128 (sizebuf_t *sb, quint64_t c) +{ + qbyte b; + for(;;) + { + b = c&0x7f; + c>>=7; + if (!c) + break; + MSG_WriteByte(sb, b|0x80); + } + MSG_WriteByte(sb, b); +} +/*void MSG_WriteSLEB128 (sizebuf_t *sb, qint64_t c) +{ + qbyte b; + for(;;) + { + b = c&0x7f; + c>>=7; + if ((c==0 && (b&64)==0) || (c==-1 && (b&64)!=0)) + break; + MSG_WriteByte(sb, b|0x80); + } + MSG_WriteByte(sb, b); +}*/ +void MSG_WriteSignedQEX (sizebuf_t *sb, qint64_t c) +{ + if (c < 0) + MSG_WriteULEB128(sb, ((quint64_t)(-1-c)<<1)|1); + else + MSG_WriteULEB128(sb, c<<1); +} void MSG_WriteUInt64 (sizebuf_t *sb, quint64_t c) { //0* 10*,*, 110*,*,* etc, up to 0xff followed by 8 continuation bytes qbyte *buf; @@ -1985,6 +2018,20 @@ int MSG_ReadLong (void) return c; } +quint64_t MSG_ReadULEB128 (void) +{ + quint64_t r = 0; + qbyte b, o=0; + while (!msg_badread) + { + b = MSG_ReadByte(); + r |= (b&0x7f)< [float] density [byte] red [byte] green [byte] blue +//QuakeEx(aka: rerelease) svcs. +// 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_servervars 50 // [leb128] changedvalues, [???] value... +#define svcqex_seq 51 // [leb128] input sequence ack +#define svcqex_achievement 52 // [string] codename + //DP extended svcs #define svcdp_downloaddata 50 #define svcdp_updatestatbyte 51 diff --git a/engine/server/net_preparse.c b/engine/server/net_preparse.c index 9cf6e44a5..9497c9214 100644 --- a/engine/server/net_preparse.c +++ b/engine/server/net_preparse.c @@ -601,8 +601,6 @@ static int te_515sevilhackworkaround; #define svcdp_showlmp 35 // [string] slotname [string] lmpfilename [short] x [short] y #define svcdp_hidelmp 36 // [string] slotname -#define svcrm_acheesement 52 // [string] codename - //#define TE_RAILTRAIL_NEH 15 // [vector] origin [coord] red [coord] green [coord] blue (fixme: ignored) #define TE_EXPLOSION3_NEH 16 // [vector] origin [coord] red [coord] green [coord] blue (fixme: ignored) #define TE_LIGHTNING4_NEH 17 // [string] model [entity] entity [vector] start [vector] end @@ -1140,7 +1138,7 @@ void NPP_NQWriteByte(int dest, qbyte data) //replacement write func (nq to qw) data = svcqw_updatestatlong; //ho hum... let it through (should check size later.) protocollen = 6; break; - case svcrm_acheesement: + case svcqex_achievement: ignoreprotocol = true; nullterms = 1; break; @@ -1401,7 +1399,7 @@ void NPP_NQWriteByte(int dest, qbyte data) //replacement write func (nq to qw) break; } break; - case svcrm_acheesement: + case svcqex_achievement: case svc_updatename: case svc_stufftext: case svc_centerprint: @@ -1419,7 +1417,7 @@ void NPP_NQWriteByte(int dest, qbyte data) //replacement write func (nq to qw) case svc_updatename: if (bufferlen < 2) break; //don't truncate the name if the mod is sending the slot number - case svcrm_acheesement: + case svcqex_achievement: case svc_stufftext: case svc_centerprint: case svc_cutscene: diff --git a/engine/server/server.h b/engine/server/server.h index fa0914df2..326d9d812 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -698,6 +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. 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 @@ -1183,6 +1184,7 @@ typedef struct enum serverprotocols_e protocol; //protocol used to talk to this client. #ifdef NQPROT qboolean proquakeanglehack; //specifies that the client will expect proquake angles if we give a proquake CCREP_ACCEPT response. + qboolean isqex; //yay quirks... unsigned int expectedreliablesequence; //required for nq connection cookies (like tcp's syn cookies). unsigned int supportedprotocols; //1<fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"fteq":"qw"; break; case SCP_QUAKE2: p = "q2"; break; case SCP_QUAKE3: p = "q3"; break; - case SCP_NETQUAKE: p = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ften":"nq"; 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_DARKPLACES6: p = "dpp6"; break; diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index b579f0bba..d420750f0 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -129,7 +129,7 @@ cvar_t sv_public = CVARD("sv_public", "0", "-1: Fully blocks all inbound conne 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_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_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).\nYou may also need to set net_enable_dtls if you wish for the rerelease's client to connect."); 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."); #ifdef QWOVERQ3 cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0"); @@ -2111,6 +2111,13 @@ void SV_ClientProtocolExtensionsChanged(client_t *client) client->datagram.maxsize = sizeof(host_client->datagram_buf); } + else if (client->qex_input_hack) + { + client->max_net_clients = NQMAX_CLIENTS; + client->datagram.maxsize = sizeof(host_client->datagram_buf); + client->max_net_ents = bound(512, pr_maxedicts.ival, 32768); + client->max_net_staticents = 4096; + } else { client->max_net_clients = NQMAX_CLIENTS; @@ -2564,6 +2571,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; #endif newcl->userid = ++nextuserid; @@ -3202,6 +3210,7 @@ void SVC_DirectConnect(int expectedreliablesequence) #ifdef NQPROT extern cvar_t sv_protocol_nq; info.proquakeanglehack = false; + info.isqex = false; info.supportedprotocols = 0; info.expectedreliablesequence = expectedreliablesequence; #endif @@ -3376,7 +3385,7 @@ void SVC_DirectConnect(int expectedreliablesequence) if (version >= 31 && version <= 34) info.protocol = SCP_QUAKE2; #ifdef NQPROT - else if (version == 3) + else if (version == NQ_NETCHAN_VERSION) { info.protocol = SCP_NETQUAKE; //because we can switch(atoi(Info_ValueForKey(Cmd_Argv(4), "mod"))) @@ -3394,6 +3403,11 @@ void SVC_DirectConnect(int expectedreliablesequence) break; } } + else if (version == NQ_NETCHAN_VERSION_QEX) + { //rerelease... + info.protocol = SCP_NETQUAKE; + info.isqex = true; + } #endif else if (version == PROTOCOL_VERSION_QW) info.protocol = SCP_QUAKEWORLD; @@ -4193,7 +4207,7 @@ qboolean SVNQ_ConnectionlessPacket(void) sizebuf_t sb; int header; int length; - int active, i; + int active, i, protver; int mod, modver, flags; unsigned int passwd; char *str; @@ -4302,12 +4316,15 @@ qboolean SVNQ_ConnectionlessPacket(void) switch(MSG_ReadByte()) { case CCREQ_CONNECT: + str = MSG_ReadString(); + protver = MSG_ReadByte(); + if (sv_showconnectionlessmessages.ival) - Con_Printf("%s: CCREQ_CONNECT\n", NET_AdrToString (com_token, sizeof(com_token), &net_from)); + Con_Printf("%s: CCREQ_CONNECT (\"%s\" %i)\n", NET_AdrToString (com_token, sizeof(com_token), &net_from), str, protver); sb.maxsize = sizeof(buffer); sb.data = buffer; - if (strcmp(MSG_ReadString(), NQ_NETCHAN_GAMENAME)) + if (strcmp(str, NQ_NETCHAN_GAMENAME)) { SZ_Clear(&sb); MSG_WriteLong(&sb, 0); @@ -4317,7 +4334,7 @@ qboolean SVNQ_ConnectionlessPacket(void) NET_SendPacket(svs.sockets, sb.cursize, sb.data, &net_from); return false; //not our game. } - if (MSG_ReadByte() != NQ_NETCHAN_VERSION) + if (protver != NQ_NETCHAN_VERSION && protver != NQ_NETCHAN_VERSION_QEX) { SZ_Clear(&sb); MSG_WriteLong(&sb, 0); @@ -4340,6 +4357,7 @@ qboolean SVNQ_ConnectionlessPacket(void) return false; //not our version... } + //proquake's extensions mod = MSG_ReadByte(); modver = MSG_ReadByte(); flags = MSG_ReadByte(); @@ -4364,7 +4382,7 @@ qboolean SVNQ_ConnectionlessPacket(void) NET_SendPacket(svs.sockets, sb.cursize, sb.data, &net_from); return false; //not our version... } - if (sv_listen_nq.ival == 2) + if (sv_listen_nq.ival == 2 && net_from.prot == NP_DGRAM) { SZ_Clear(&sb); MSG_WriteLong(&sb, 0); @@ -4390,7 +4408,7 @@ qboolean SVNQ_ConnectionlessPacket(void) } else { - str = va("connect %i %i %i \"\\name\\unconnected\\mod\\%i\\modver\\%i\\flags\\%i\\password\\%i\"", NQ_NETCHAN_VERSION, 0, SV_NewChallenge(), mod, modver, flags, passwd); + str = va("connect %i %i %i \"\\name\\unconnected\\mod\\%i\\modver\\%i\\flags\\%i\\password\\%i\"", protver, 0, SV_NewChallenge(), mod, modver, flags, passwd); Cmd_TokenizeString (str, false, false); SVC_DirectConnect(0); diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 51fc69cc0..3aaaf8893 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -1691,6 +1691,11 @@ 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) + { + MSG_WriteByte(msg, svcqex_seq); + MSG_WriteULEB128(msg, client->last_sequence); + } // Con_Printf("%f\n", sv.world.physicstime); } @@ -2887,6 +2892,17 @@ void SV_UpdateToReliableMessages (void) float curspeed; int curfrags; + static double pingtimer, lasttime; + double t = Sys_DoubleTime(); + qboolean sendpings = false; + pingtimer -= (t-lasttime); + lasttime = t; + if (pingtimer < 0) + { //update about once every 5 secs. + sendpings = true; + pingtimer = 5; + } + // check for changes to be sent over the reliable streams to all clients for (i=0, host_client = svs.clients ; isendinfo = false; SV_FullClientUpdate (host_client, NULL); } + + if (host_client->qex_input_hack && sendpings) + { + sizebuf_t *m; + for (j=0, client = svs.clients ; jmax_net_clients; j++, client++) + { + if (client->state != cs_spawned) + continue; + + m = ClientReliable_StartWrite(host_client, 64); + MSG_WriteByte(m, svcqex_updateping); + MSG_WriteByte(m, j); + MSG_WriteSignedQEX(m, SV_CalcPing(client, false)); + ClientReliable_FinishWrite(host_client); + } + } + if (host_client->old_frags != curfrags) { for (j=0, client = svs.clients ; jnetchan.message, 0); } + if (host_client->qex_input_hack) + { + 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, + + 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) + MSG_WriteByte (&host_client->netchan.message, deathmatch.ival); + if (bits & QEX_GV_IDEALPITCHSCALE) + MSG_WriteFloat (&host_client->netchan.message, 0); + if (bits & QEX_GV_FRICTION) + MSG_WriteFloat (&host_client->netchan.message, sv_friction.value); + if (bits & QEX_GV_EDGEFRICTION) + MSG_WriteFloat (&host_client->netchan.message, *pm_edgefriction.string?pm_edgefriction.value:2); + if (bits & QEX_GV_STOPSPEED) + MSG_WriteFloat (&host_client->netchan.message, sv_stopspeed.value); + if (bits & QEX_GV_MAXVELOCITY) + MSG_WriteFloat (&host_client->netchan.message, sv_maxvelocity.value); + if (bits & QEX_GV_GRAVITY) + MSG_WriteFloat (&host_client->netchan.message, sv_gravity.value); + if (bits & QEX_GV_NOSTEP) + MSG_WriteByte (&host_client->netchan.message, false); + if (bits & QEX_GV_MAXSPEED) + MSG_WriteFloat (&host_client->netchan.message, sv_maxspeed.value); + if (bits & QEX_GV_ACCELERATE) + MSG_WriteFloat (&host_client->netchan.message, sv_accelerate.value); + if (bits & QEX_GV_CONTROLLERONLY) + MSG_WriteByte (&host_client->netchan.message, 0); + if (bits & QEX_GV_TIMELIMIT) + MSG_WriteFloat (&host_client->netchan.message, timelimit.value); + if (bits & QEX_GV_FRAGLIMIT) + MSG_WriteFloat (&host_client->netchan.message, fraglimit.value); + } + // set view MSG_WriteByte (&host_client->netchan.message, svc_setview); MSG_WriteEntity (&host_client->netchan.message, (host_client - svs.clients)+1);//NUM_FOR_EDICT(svprogfuncs, host_client->edict)); @@ -1875,7 +1928,7 @@ void SVQW_PreSpawn_f (void) { char *msg; SV_ClientTPrintf (host_client, PRINT_HIGH, - "Map model file does not match (%s), %i != %i/%i.\nYou may need a new version of the map, or the proper install files.\n", + "Map model file does not match (%s), %#X != %#X/%#X.\nYou may need a new version of the map, or the proper install files.\n", sv.modelname, check, sv.world.worldmodel->checksum, sv.world.worldmodel->checksum2); @@ -8156,6 +8209,12 @@ void SV_ExecuteClientMessage (client_t *cl) SV_DropClient (cl); return; } + if (cl->state < cs_connected) + { //something went badly... just give up instead of crashing. + host_client = NULL; + sv_player = NULL; + return; + } c = MSG_ReadByte (); if (c == -1) @@ -8351,7 +8410,6 @@ void SV_ExecuteClientMessage (client_t *cl) #ifdef NETPREPARSE NPP_Flush(); //flush it just in case there was an error and we stopped preparsing. This is only really needed while debugging. #endif - host_client = cl; sv_player = cl->edict; break; @@ -8622,7 +8680,7 @@ void SVQ2_ExecuteClientMessage (client_t *cl) } #endif #ifdef NQPROT -void SVNQ_ReadClientMove (qboolean forceangle16) +void SVNQ_ReadClientMove (qboolean forceangle16, qboolean quakeex) { int i; client_frame_t *frame; @@ -8633,7 +8691,9 @@ void SVNQ_ReadClientMove (qboolean forceangle16) frame = &host_client->frameunion.frames[host_client->netchan.incoming_acknowledged & UPDATE_MASK]; - if (host_client->protocol == SCP_DARKPLACES7) + if (quakeex) + ; + else if (host_client->protocol == SCP_DARKPLACES7) host_client->last_sequence = MSG_ReadLong (); else if (host_client->fteprotocolextensions2 & PEXT2_PREDINFO) { @@ -8659,6 +8719,13 @@ void SVNQ_ReadClientMove (qboolean forceangle16) cmd.fservertime = sv.time - 2; cmd.servertime = cmd.fservertime*1000; + if (quakeex) + { //I'm guessing this has something to do with splitscreen. + if (MSG_ReadByte() != 1) + msg_badread = true; + } + + //read angles for (i=0 ; i<3 ; i++) { @@ -8806,7 +8873,8 @@ void SVNQ_ReadClientMove (qboolean forceangle16) } else { - host_client->last_sequence = 0; //let the client know that prediction is fucked, by not acking any input frames. + if (!host_client->qex_input_hack) + 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; host_client->isindependant = false; @@ -8821,6 +8889,7 @@ 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; @@ -8951,7 +9020,7 @@ void SVNQ_ExecuteClientMessage (client_t *cl) break; } - SVNQ_ReadClientMove (forceangle16); + SVNQ_ReadClientMove (forceangle16, qex); // cmd = host_client->lastcmd; // SV_ClientThink(); break; @@ -9004,6 +9073,18 @@ void SVNQ_ExecuteClientMessage (client_t *cl) break; #endif + case clc_delta://clcqex_sequence: + host_client->last_sequence = MSG_ReadULEB128(); + qex = true; + break; + + case clc_tmove://clcqex_auth + //This allows for the client's positions to be slightly wrong, with the client being authoritive instead of the server (within tolerances anyway). + host_client->last_sequence = MSG_ReadULEB128(); + /*host_client->edict->v->origin[0] =*/ MSG_ReadFloat(); + /*host_client->edict->v->origin[1] =*/ MSG_ReadFloat(); + /*host_client->edict->v->origin[2] =*/ MSG_ReadFloat(); + break; safedefault: Con_Printf ("SVNQ_ReadClientMessage: unknown command char %i\n", c); SV_DropClient (cl);