From e06c117e9e4361d5c0e4124682a23fd9872bd41f Mon Sep 17 00:00:00 2001 From: Thilo Schulz Date: Tue, 12 Jul 2011 11:59:48 +0000 Subject: [PATCH] - Implement dual protocol support (#4962) - Fix several UDP spoofing security issues --- README | 384 +++++++++++++++++++++----------------- code/client/cl_main.c | 206 ++++++++++++++++---- code/client/cl_net_chan.c | 6 +- code/client/client.h | 4 + code/q3_ui/ui_demo2.c | 53 +++++- code/qcommon/common.c | 16 +- code/qcommon/files.c | 5 + code/qcommon/net_chan.c | 41 +++- code/qcommon/q_shared.h | 2 + code/qcommon/qcommon.h | 17 +- code/server/server.h | 6 +- code/server/sv_client.c | 45 ++++- code/server/sv_main.c | 12 +- 13 files changed, 550 insertions(+), 247 deletions(-) diff --git a/README b/README index 1247e651..63d467df 100644 --- a/README +++ b/README @@ -185,6 +185,10 @@ New cvars standalone mode com_homepath - Specify name that is to be appended to the home path + com_legacyprotocol - Specify protocol version number for + legacy Quake3 1.32c protocol, see + "Network protocols" section below + (startup only) com_maxfpsUnfocused - Maximum frames per second when unfocused com_maxfpsMinimized - Maximum frames per second when minimized com_busyWait - Will use a busy loop to wait for rendering @@ -193,6 +197,10 @@ New cvars through which other processes can control the server while it is running. Nonfunctional on Windows. + com_protocol - Specify protocol version number for + current ioquake3 protocol, see + "Network protocols" section below + (startup only) in_joystickNo - select which joystick to use in_availableJoysticks - list of available Joysticks @@ -218,9 +226,6 @@ New cvars ipv6 servers on the local network net_mcastiface - outgoing interface to use for scan - protocol - Allow changing protocol version - (startup only) - r_allowResize - make window resizable (SDL only) r_ext_texture_filter_anisotropic - anisotropic texture filtering r_zProj - distance of observer camera to projection @@ -281,7 +286,8 @@ New commands which - print out the path on disk to a loaded item ------------------------------------------------------------- Miscellaneous ----- + +--------------------------------------------------------- README for Users ----- Using shared libraries instead of qvm To force Q3 to use shared libraries instead of qvms run it with the following @@ -309,175 +315,6 @@ Help! Ioquake3 won't give me an fps of X anymore when setting com_maxfps! In this case you can always revert back to the old behaviour by setting the cvar com_busyWait to 1. -QuakeLive mouse acceleration (patch and this text written by TTimo from id) - I've been using an experimental mouse acceleration code for a while, and - decided to make it available to everyone. Don't be too worried if you don't - understand the explanations below, this is mostly intended for advanced - players: - To enable it, set cl_mouseAccelStyle 1 (0 is the default/legacy behavior) - - New style is controlled with 3 cvars: - - sensitivity - cl_mouseAccel - cl_mouseAccelOffset - - The old code (cl_mouseAccelStyle 0) can be difficult to calibrate because if - you have a base sensitivity setup, as soon as you set a non zero acceleration - your base sensitivity at low speeds will change as well. The other problem - with style 0 is that you are stuck on a square (power of two) acceleration - curve. - - The new code tries to solve both problems: - - Once you setup your sensitivity to feel comfortable and accurate enough for - low mouse deltas with no acceleration (cl_mouseAccel 0), you can start - increasing cl_mouseAccel and tweaking cl_mouseAccelOffset to get the - amplification you want for high deltas with little effect on low mouse deltas. - - cl_mouseAccel is a power value. Should be >= 1, 2 will be the same power curve - as style 0. The higher the value, the faster the amplification grows with the - mouse delta. - - cl_mouseAccelOffset sets how much base mouse delta will be doubled by - acceleration. The closer to zero you bring it, the more acceleration will - happen at low speeds. This is also very useful if you are changing to a new - mouse with higher dpi, if you go from 500 to 1000 dpi, you can divide your - cl_mouseAccelOffset by two to keep the same overall 'feel' (you will likely - gain in precision when you do that, but that is not related to mouse - acceleration). - - Mouse acceleration is tricky to configure, and when you do you'll have to - re-learn your aiming. But you will find that it's very much forth it in the - long run. - - If you try the new acceleration code and start using it, I'd be very - interested by your feedback. - -64bit mods - If you wish to compile external mods as shared libraries on a 64bit platform, - and the mod source is derived from the id Q3 SDK, you will need to modify the - interface code a little. Open the files ending in _syscalls.c and change - every instance of int to intptr_t in the declaration of the syscall function - pointer and the dllEntry function. Also find the vmMain function for each - module (usually in cg_main.c g_main.c etc.) and similarly replace the return - value in the prototype with intptr_t (arg0, arg1, ...stay int). - - Add the following code snippet to q_shared.h: - - #ifdef Q3_VM - typedef int intptr_t; - #else - #include - #endif - - Note if you simply wish to run mods on a 64bit platform you do not need to - recompile anything since by default Q3 uses a virtual machine system. - -Creating mods compatible with Q3 1.32b - If you're using this package to create mods for the last official release of - Q3, it is necessary to pass the commandline option '-vq3' to your invocation - of q3asm. This is because by default q3asm outputs an updated qvm format that - is necessary to fix a bug involving the optimizing pass of the x86 vm JIT - compiler. - -Creating standalone games - Have you finished the daunting task of removing all dependencies on the Q3 - game data? You probably now want to give your users the opportunity to play - the game without owning a copy of Q3, which consequently means removing cd-key - and authentication server checks. In addition to being a straightforward Q3 - client, ioquake3 also purports to be a reliable and stable code base on which - to base your game project. - - However, before you start compiling your own version of ioquake3, you have to - ask yourself: Have we changed or will we need to change anything of importance - in the engine? - - If your answer to this question is "no", it probably makes no sense to build - your own binaries. Instead, you can just use the pre-built binaries on the - website. Just make sure the game is called with: - - +set com_basegame - - in any links/scripts you install for your users to start the game. The - binary must not detect any original quake3 game pak files. If this - condition is met, the game will set com_standalone to 1 and is then running - in stand alone mode. - - If you want the engine to use a different directory in your homepath than - e.g. "Quake3" on Windows or ".q3a" on Linux, then set a new name at startup - by adding - - +set com_homepath - - to the command line. Then you can control which kind of messages to send to - the master server: - - +set sv_heartbeat +set sv_flatline - +set cl_gamename - - The and message can be specific to your game. The - flatline message is sent to signal the master server that the game server is - quitting. Vanilla quake3 uses "QuakeArena-1" both for the heartbeat and - flatline messages. - The cl_gamename message is for dpmaster to specify which game the client - wants a server list for. It is only used in the new ipv6 based getServersExt - query. - - Example line: - - +set com_basegame basefoo +set com_homepath .foo - +set sv_heartbeat fooalive +set sv_flatline foodead - +set cl_gamename foo - - - If you really changed parts that would make vanilla ioquake3 incompatible with - your mod, we have included another way to conveniently build a stand-alone - binary. Just run make with the option BUILD_STANDALONE=1. Don't forget to edit - the PRODUCT_NAME and subsequent #defines in qcommon/q_shared.h with - information appropriate for your project. - - While a lot of work has been put into ioquake3 that you can benefit from free - of charge, it does not mean that you have no obligations to fulfil. Please be - aware that as soon as you start distributing your game with an engine based on - our sources we expect you to fully comply with the requirements as stated in - the GPL. That includes making sources and modifications you made to the - ioquake3 engine as well as the game-code used to compile the .qvm files for - the game logic freely available to everyone. Furthermore, note that the "QIIIA - Game Source License" prohibits distribution of mods that are intended to - operate on a version of Q3 not sanctioned by id software: - - "with this Agreement, ID grants to you the non-exclusive and limited right - to distribute copies of the Software ... for operation only with the full - version of the software game QUAKE III ARENA" - - This means that if you're creating a standalone game, you cannot use said - license on any portion of the product. As the only other license this code has - been released under is the GPL, this is the only option. - - This does NOT mean that you cannot market this game commercially. The GPL does - not prohibit commercial exploitation and all assets (e.g. textures, sounds, - maps) created by yourself are your property and can be sold like every other - game you find in stores. - -cl_guid Support - cl_guid is a cvar which is part of the client's USERINFO string. Its value - is a 32 character string made up of [a-f] and [0-9] characters. This - value is pseudo-unique for every player. Id's Quake 3 Arena client also - sets cl_guid, but only if Punkbuster is enabled on the client. - - If cl_guidServerUniq is non-zero (the default), then this value is also - pseudo-unique for each server a client connects to (based on IP:PORT of - the server). - - The purpose of cl_guid is to add an identifier for each player on - a server. This value can be reset by the client at any time so it's not - useful for blocking access. However, it can have at least two uses in - your mod's game code: - 1) improve logging to allow statistical tools to index players by more - than just name - 2) granting some weak admin rights to players without requiring passwords - Using HTTP/FTP Download Support (Server) You can enable redirected downloads on your server even if it's not an ioquake3 server. You simply need to use the 'sets' command to put @@ -586,6 +423,207 @@ SDL Keyboard Differences text. Also, in addition to the nominated console keys, Shift-ESC is hard coded to always toggle the console. +QuakeLive mouse acceleration (patch and this text written by TTimo from id) + I've been using an experimental mouse acceleration code for a while, and + decided to make it available to everyone. Don't be too worried if you don't + understand the explanations below, this is mostly intended for advanced + players: + To enable it, set cl_mouseAccelStyle 1 (0 is the default/legacy behavior) + + New style is controlled with 3 cvars: + + sensitivity + cl_mouseAccel + cl_mouseAccelOffset + + The old code (cl_mouseAccelStyle 0) can be difficult to calibrate because if + you have a base sensitivity setup, as soon as you set a non zero acceleration + your base sensitivity at low speeds will change as well. The other problem + with style 0 is that you are stuck on a square (power of two) acceleration + curve. + + The new code tries to solve both problems: + + Once you setup your sensitivity to feel comfortable and accurate enough for + low mouse deltas with no acceleration (cl_mouseAccel 0), you can start + increasing cl_mouseAccel and tweaking cl_mouseAccelOffset to get the + amplification you want for high deltas with little effect on low mouse deltas. + + cl_mouseAccel is a power value. Should be >= 1, 2 will be the same power curve + as style 0. The higher the value, the faster the amplification grows with the + mouse delta. + + cl_mouseAccelOffset sets how much base mouse delta will be doubled by + acceleration. The closer to zero you bring it, the more acceleration will + happen at low speeds. This is also very useful if you are changing to a new + mouse with higher dpi, if you go from 500 to 1000 dpi, you can divide your + cl_mouseAccelOffset by two to keep the same overall 'feel' (you will likely + gain in precision when you do that, but that is not related to mouse + acceleration). + + Mouse acceleration is tricky to configure, and when you do you'll have to + re-learn your aiming. But you will find that it's very much forth it in the + long run. + + If you try the new acceleration code and start using it, I'd be very + interested by your feedback. + + +---------------------------------------------------- README for Developers ----- + +64bit mods + If you wish to compile external mods as shared libraries on a 64bit platform, + and the mod source is derived from the id Q3 SDK, you will need to modify the + interface code a little. Open the files ending in _syscalls.c and change + every instance of int to intptr_t in the declaration of the syscall function + pointer and the dllEntry function. Also find the vmMain function for each + module (usually in cg_main.c g_main.c etc.) and similarly replace the return + value in the prototype with intptr_t (arg0, arg1, ...stay int). + + Add the following code snippet to q_shared.h: + + #ifdef Q3_VM + typedef int intptr_t; + #else + #include + #endif + + Note if you simply wish to run mods on a 64bit platform you do not need to + recompile anything since by default Q3 uses a virtual machine system. + +Creating mods compatible with Q3 1.32b + If you're using this package to create mods for the last official release of + Q3, it is necessary to pass the commandline option '-vq3' to your invocation + of q3asm. This is because by default q3asm outputs an updated qvm format that + is necessary to fix a bug involving the optimizing pass of the x86 vm JIT + compiler. + +Creating standalone games + Have you finished the daunting task of removing all dependencies on the Q3 + game data? You probably now want to give your users the opportunity to play + the game without owning a copy of Q3, which consequently means removing cd-key + and authentication server checks. In addition to being a straightforward Q3 + client, ioquake3 also purports to be a reliable and stable code base on which + to base your game project. + + However, before you start compiling your own version of ioquake3, you have to + ask yourself: Have we changed or will we need to change anything of importance + in the engine? + + If your answer to this question is "no", it probably makes no sense to build + your own binaries. Instead, you can just use the pre-built binaries on the + website. Just make sure the game is called with: + + +set com_basegame + + in any links/scripts you install for your users to start the game. The + binary must not detect any original quake3 game pak files. If this + condition is met, the game will set com_standalone to 1 and is then running + in stand alone mode. + + If you want the engine to use a different directory in your homepath than + e.g. "Quake3" on Windows or ".q3a" on Linux, then set a new name at startup + by adding + + +set com_homepath + + to the command line. You can also control which kind of messages to send to + the master server: + + +set sv_heartbeat +set sv_flatline + +set cl_gamename + + The and message can be specific to your game. The + flatline message is sent to signal the master server that the game server is + quitting. Vanilla quake3 uses "QuakeArena-1" both for the heartbeat and + flatline messages. + The cl_gamename message is for dpmaster to specify which game the client + wants a server list for. It is only used in the new ipv6 based getServersExt + query. + + Example line: + + +set com_basegame basefoo +set com_homepath .foo + +set sv_heartbeat fooalive +set sv_flatline foodead + +set cl_gamename foo + + + If you really changed parts that would make vanilla ioquake3 incompatible with + your mod, we have included another way to conveniently build a stand-alone + binary. Just run make with the option BUILD_STANDALONE=1. Don't forget to edit + the PRODUCT_NAME and subsequent #defines in qcommon/q_shared.h with + information appropriate for your project. + + While a lot of work has been put into ioquake3 that you can benefit from free + of charge, it does not mean that you have no obligations to fulfil. Please be + aware that as soon as you start distributing your game with an engine based on + our sources we expect you to fully comply with the requirements as stated in + the GPL. That includes making sources and modifications you made to the + ioquake3 engine as well as the game-code used to compile the .qvm files for + the game logic freely available to everyone. Furthermore, note that the "QIIIA + Game Source License" prohibits distribution of mods that are intended to + operate on a version of Q3 not sanctioned by id software: + + "with this Agreement, ID grants to you the non-exclusive and limited right + to distribute copies of the Software ... for operation only with the full + version of the software game QUAKE III ARENA" + + This means that if you're creating a standalone game, you cannot use said + license on any portion of the product. As the only other license this code has + been released under is the GPL, this is the only option. + + This does NOT mean that you cannot market this game commercially. The GPL does + not prohibit commercial exploitation and all assets (e.g. textures, sounds, + maps) created by yourself are your property and can be sold like every other + game you find in stores. + +Network protocols + There are now two cvars that give you some degree of freedom over the reported + protocol versions between clients and servers: "com_protocol" and + "com_legacyprotocol". + The reason for this is that some standalone games increased the protocol + number even though nothing really changed in their protocol and the ioquake3 + engine is still fully compatible. + + In order to harden the network protocol against UDP spoofing attacks a new + network protocol was introduced that defends against such attacks. + Unfortunately, this protocol will be incompatible to the original quake3 1.32c + which is the latest official release from id. + Luckily, ioquake3 has backwards compatibility, on the client as well as on the + server. This means ioquake3 players can play on old servers just as ioquake3 + servers are able to service old clients. + + The cvar "com_protocol" denotes the protocol version for the new hardened + protocol, whereas the "com_legacyprotocol" cvar denotes the protocol version + for the legacy protocol. + If the value for "com_protocol" and "com_legacyprotocol" is identical, then + the legacy protocol is always used. If "com_legacyprotocol" is set to 0, then + support for the legacy protocol is disabled. + + Mods that use a standalone engine obviously do not require dual protocol + support, and it is turned off if the engine is compiled with STANDALONE per + default. If you desire backwards compatibility to older versions of your + game you can still enable it in q_shared.h by defining + LEGACY_PROTOCOL. + +cl_guid Support + cl_guid is a cvar which is part of the client's USERINFO string. Its value + is a 32 character string made up of [a-f] and [0-9] characters. This + value is pseudo-unique for every player. Id's Quake 3 Arena client also + sets cl_guid, but only if Punkbuster is enabled on the client. + + If cl_guidServerUniq is non-zero (the default), then this value is also + pseudo-unique for each server a client connects to (based on IP:PORT of + the server). + + The purpose of cl_guid is to add an identifier for each player on + a server. This value can be reset by the client at any time so it's not + useful for blocking access. However, it can have at least two uses in + your mod's game code: + 1) improve logging to allow statistical tools to index players by more + than just name + 2) granting some weak admin rights to players without requiring passwords + PNG support ioquake3 supports the use of PNG (Portable Network Graphic) images as textures. It should be noted that the use of such images in a map will diff --git a/code/client/cl_main.c b/code/client/cl_main.c index ea2e46cc..8070354f 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -533,7 +533,6 @@ void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { len = clc.serverMessageSequence; swlen = LittleLong( len ); FS_Write (&swlen, 4, clc.demofile); - // skip the packet sequencing information len = msg->cursize - headerBytes; swlen = LittleLong(len); @@ -636,14 +635,24 @@ void CL_Record_f( void ) { if ( Cmd_Argc() == 2 ) { s = Cmd_Argv(1); Q_strncpyz( demoName, s, sizeof( demoName ) ); - Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer ); +#ifdef LEGACY_PROTOCOL + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer); + else +#endif + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); } else { int number; // scan for a free demo name for ( number = 0 ; number <= 9999 ; number++ ) { CL_DemoFilename( number, demoName ); - Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer ); +#ifdef LEGACY_PROTOCOL + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer); + else +#endif + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); if (!FS_FileExists(name)) break; // file doesn't exist @@ -665,7 +674,6 @@ void CL_Record_f( void ) { clc.spDemoRecording = qfalse; } - Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); // don't start saving messages until a non-delta compressed message is received @@ -889,36 +897,62 @@ void CL_ReadDemoMessage( void ) { CL_WalkDemoExt ==================== */ -static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +static int CL_WalkDemoExt(char *arg, char *name, int *demofile) { int i = 0; *demofile = 0; - Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer); - - FS_FOpenFileRead( name, demofile, qtrue ); - - if (*demofile) +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer > 0) { - Com_Printf("Demo file: %s\n", name); - return; + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_legacyprotocol->integer); + FS_FOpenFileRead(name, demofile, qtrue); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return com_legacyprotocol->integer; + } + } + + if(com_protocol->integer != com_legacyprotocol->integer) +#endif + { + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer); + FS_FOpenFileRead(name, demofile, qtrue); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return com_protocol->integer; + } } Com_Printf("Not found: %s\n", name); while(demo_protocols[i]) { +#ifdef LEGACY_PROTOCOL + if(demo_protocols[i] == com_legacyprotocol->integer) + continue; +#endif + if(demo_protocols[i] == com_protocol->integer) + continue; + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); FS_FOpenFileRead( name, demofile, qtrue ); if (*demofile) { Com_Printf("Demo file: %s\n", name); - break; + + return demo_protocols[i]; } else Com_Printf("Not found: %s\n", name); i++; } + + return -1; } /* @@ -978,7 +1012,11 @@ void CL_PlayDemo_f( void ) { break; } - if(demo_protocols[i] || protocol == com_protocol->integer) + if(demo_protocols[i] || protocol == com_protocol->integer +#ifdef LEGACY_PROTOCOL + || protocol == com_legacyprotocol->integer +#endif + ) { Com_sprintf(name, sizeof(name), "demos/%s", arg); FS_FOpenFileRead(name, &clc.demofile, qtrue); @@ -995,11 +1033,11 @@ void CL_PlayDemo_f( void ) { Q_strncpyz(retry, arg, len + 1); retry[len] = '\0'; - CL_WalkDemoExt(retry, name, &clc.demofile); + protocol = CL_WalkDemoExt(retry, name, &clc.demofile); } } else - CL_WalkDemoExt(arg, name, &clc.demofile); + protocol = CL_WalkDemoExt(arg, name, &clc.demofile); if (!clc.demofile) { Com_Error( ERR_DROP, "couldn't open %s", name); @@ -1013,6 +1051,13 @@ void CL_PlayDemo_f( void ) { clc.demoplaying = qtrue; Q_strncpyz( clc.servername, Cmd_Argv(1), sizeof( clc.servername ) ); +#ifdef LEGACY_PROTOCOL + if(protocol <= com_legacyprotocol->integer) + clc.compat = qtrue; + else + clc.compat = qfalse; +#endif + // read demo messages until connected while ( clc.state >= CA_CONNECTED && clc.state < CA_PRIMED ) { CL_ReadDemoMessage(); @@ -2180,7 +2225,9 @@ void CL_CheckForResend( void ) { #endif // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection. - Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge); + // Add the heartbeat gamename so the server knows we're running the correct game and can reject the client + // with a meaningful message + Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, Cvar_VariableString("sv_heartbeat")); NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); break; @@ -2190,7 +2237,16 @@ void CL_CheckForResend( void ) { port = Cvar_VariableValue ("net_qport"); Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); - Info_SetValueForKey( info, "protocol", va("%i", com_protocol->integer ) ); + +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer == com_protocol->integer) + clc.compat = qtrue; + + if(clc.compat) + Info_SetValueForKey(info, "protocol", va("%i", com_legacyprotocol->integer)); + else +#endif + Info_SetValueForKey(info, "protocol", va("%i", com_protocol->integer)); Info_SetValueForKey( info, "qport", va("%i", port ) ); Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); @@ -2431,6 +2487,7 @@ Responses to broadcasts, etc void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { char *s; char *c; + int challenge; MSG_BeginReadingOOB( msg ); MSG_ReadLong( msg ); // skip the -1 @@ -2446,23 +2503,70 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { // challenge from the server we are connecting to if (!Q_stricmp(c, "challengeResponse")) { + char *strver; + int ver; + if (clc.state != CA_CONNECTING) { Com_DPrintf("Unwanted challenge response received. Ignored.\n"); return; } - if(!NET_CompareAdr(from, clc.serverAddress)) + c = Cmd_Argv(2); + if(*c) + challenge = atoi(c); + + strver = Cmd_Argv(3); + if(*strver) { - // This challenge response is not coming from the expected address. - // Check whether we have a matching client challenge to prevent - // connection hi-jacking. + ver = atoi(strver); - c = Cmd_Argv(2); - - if(!*c || atoi(c) != clc.challenge) + if(ver != com_protocol->integer) { - Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer > 0) + { + // Server is ioq3 but has a different protocol than we do. + // Fall back to idq3 protocol. + clc.compat = qtrue; + + Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, " + "we have %d. Trying legacy protocol %d.\n", + ver, com_protocol->integer, com_legacyprotocol->integer); + } + else +#endif + { + Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, we have %d. " + "Trying anyways.\n", ver, com_protocol->integer); + } + } + } +#ifdef LEGACY_PROTOCOL + else + clc.compat = qtrue; + + if(clc.compat) + { + if(!NET_CompareAdr(from, clc.serverAddress)) + { + // This challenge response is not coming from the expected address. + // Check whether we have a matching client challenge to prevent + // connection hi-jacking. + + if(!*c || challenge != clc.challenge) + { + Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + return; + } + } + } + else +#endif + { + if(!*c || challenge != clc.challenge) + { + Com_Printf("Bad challenge for challengeResponse. Ignored.\n"); return; } } @@ -2494,7 +2598,36 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { Com_Printf( "connectResponse from wrong address. Ignored.\n" ); return; } - Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + +#ifdef LEGACY_PROTOCOL + if(!clc.compat) +#endif + { + c = Cmd_Argv(1); + + if(*c) + challenge = atoi(c); + else + { + Com_Printf("Bad connectResponse received. Ignored.\n"); + return; + } + + if(challenge != clc.challenge) + { + Com_Printf("ConnectResponse with bad challenge received. Ignored.\n"); + return; + } + } + +#ifdef LEGACY_PROTOCOL + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge, clc.compat); +#else + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge, qfalse); +#endif + clc.state = CA_CONNECTED; clc.lastPacketSentTime = -9999; // send first packet immediately return; @@ -2512,13 +2645,6 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { return; } - // a disconnect message from the server, which will happen if the server - // dropped the connection but it is still getting packets from us - if (!Q_stricmp(c, "disconnect")) { - CL_DisconnectPacket( from ); - return; - } - // echo request from server if ( !Q_stricmp(c, "echo") ) { NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); @@ -2538,10 +2664,12 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { } // echo request from server - if ( !Q_stricmp(c, "print") ) { + if(!Q_stricmp(c, "print")){ s = MSG_ReadString( msg ); + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); Com_Printf( "%s", s ); + return; } @@ -3492,7 +3620,13 @@ void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { // if this isn't the correct protocol version, ignore it prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); - if ( prot != com_protocol->integer ) { + + if(prot != com_protocol->integer +#ifdef LEGACY_PROTOCOL + && prot != com_legacyprotocol->integer +#endif + ) + { Com_DPrintf( "Different protocol info packet: %s\n", infoString ); return; } diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c index 1433c434..abea06d1 100644 --- a/code/client/cl_net_chan.c +++ b/code/client/cl_net_chan.c @@ -147,9 +147,6 @@ void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { Netchan_Transmit( chan, msg->cursize, msg->data ); } -extern int oldsize; -int newsize = 0; - /* ================= CL_Netchan_Process @@ -161,7 +158,8 @@ qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { ret = Netchan_Process( chan, msg ); if (!ret) return qfalse; + CL_Netchan_Decode( msg ); - newsize += msg->cursize; + return qtrue; } diff --git a/code/client/client.h b/code/client/client.h index 533696fd..9c7274d0 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -263,6 +263,10 @@ typedef struct { float voipPower; #endif +#ifdef LEGACY_PROTOCOL + qboolean compat; +#endif + // big stuff at end of structure so most offsets are 15 bits or less netchan_t netchan; } clientConnection_t; diff --git a/code/q3_ui/ui_demo2.c b/code/q3_ui/ui_demo2.c index 6e522d8f..f63bcdf7 100644 --- a/code/q3_ui/ui_demo2.c +++ b/code/q3_ui/ui_demo2.c @@ -42,8 +42,8 @@ DEMOS MENU #define ART_ARROWLEFT "menu/art/arrows_horz_left" #define ART_ARROWRIGHT "menu/art/arrows_horz_right" -#define MAX_DEMOS 128 -#define NAMEBUFSIZE ( MAX_DEMOS * 16 ) +#define MAX_DEMOS 1024 +#define NAMEBUFSIZE (MAX_DEMOS * 32) #define ID_BACK 10 #define ID_GO 11 @@ -72,6 +72,9 @@ typedef struct { int numDemos; char names[NAMEBUFSIZE]; + int numLegacyDemos; + char namesLegacy[NAMEBUFSIZE]; + char *demolist[MAX_DEMOS]; } demos_t; @@ -133,6 +136,7 @@ static void Demos_MenuInit( void ) { int i; int len; char *demoname, extension[32]; + int protocol, protocolLegacy; memset( &s_demos, 0 ,sizeof(demos_t) ); s_demos.menu.key = UI_DemosMenu_Key; @@ -223,11 +227,34 @@ static void Demos_MenuInit( void ) { s_demos.list.generic.y = 130; s_demos.list.width = 16; s_demos.list.height = 14; - Com_sprintf(extension, sizeof(extension), ".%s%d", DEMOEXT, (int) trap_Cvar_VariableValue("protocol")); - s_demos.list.numitems = trap_FS_GetFileList( "demos", extension, s_demos.names, NAMEBUFSIZE ); s_demos.list.itemnames = (const char **)s_demos.demolist; s_demos.list.columns = 3; + protocolLegacy = trap_Cvar_VariableValue("com_legacyprotocol"); + protocol = trap_Cvar_VariableValue("com_protocol"); + + if(!protocol) + protocol = trap_Cvar_VariableValue("protocol"); + if(protocolLegacy == protocol) + protocolLegacy = 0; + + Com_sprintf(extension, sizeof(extension), ".%s%d", DEMOEXT, protocol); + s_demos.numDemos = trap_FS_GetFileList("demos", extension, s_demos.names, NAMEBUFSIZE); + + if(s_demos.numDemos > MAX_DEMOS) + s_demos.numDemos = MAX_DEMOS; + + if(protocolLegacy > 0) + { + Com_sprintf(extension, sizeof(extension), ".%s%d", DEMOEXT, protocolLegacy); + s_demos.numLegacyDemos = trap_FS_GetFileList("demos", extension, s_demos.namesLegacy, NAMEBUFSIZE); + } + else + s_demos.numLegacyDemos = 0; + + s_demos.list.numitems = s_demos.numDemos + s_demos.numLegacyDemos; + + if (!s_demos.list.numitems) { strcpy( s_demos.names, "No Demos Found." ); s_demos.list.numitems = 1; @@ -239,15 +266,21 @@ static void Demos_MenuInit( void ) { s_demos.list.numitems = MAX_DEMOS; demoname = s_demos.names; - for ( i = 0; i < s_demos.list.numitems; i++ ) { + for(i = 0; i < s_demos.numDemos; i++) + { s_demos.list.itemnames[i] = demoname; - // strip extension - len = strlen( demoname ); - if (!Q_stricmp(demoname + len - 4,".dm3")) - demoname[len-4] = '\0'; + len = strlen(demoname); -// Q_strupr(demoname); + demoname += len + 1; + } + + demoname = s_demos.namesLegacy; + for(; i < s_demos.list.numitems; i++) + { + s_demos.list.itemnames[i] = demoname; + + len = strlen(demoname); demoname += len + 1; } diff --git a/code/qcommon/common.c b/code/qcommon/common.c index b47c2619..a865f726 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -32,7 +32,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif int demo_protocols[] = -{ 66, 67, 68, 0 }; +{ 67, 66, 0 }; #define MAX_NUM_ARGVS 50 @@ -87,6 +87,9 @@ cvar_t *com_maxfpsMinimized; cvar_t *com_abnormalExit; cvar_t *com_standalone; cvar_t *com_protocol; +#ifdef LEGACY_PROTOCOL +cvar_t *com_legacyprotocol; +#endif cvar_t *com_basegame; cvar_t *com_homepath; cvar_t *com_busyWait; @@ -2796,7 +2799,16 @@ void Com_Init( char *commandLine ) { s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ ); com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); - com_protocol = Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_INIT); + com_protocol = Cvar_Get("com_protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_INIT); +#ifdef LEGACY_PROTOCOL + com_legacyprotocol = Cvar_Get("com_legacyprotocol", va("%i", PROTOCOL_LEGACY_VERSION), CVAR_INIT); + + // Keep for compatibility with old mods / mods that haven't updated yet. + if(com_legacyprotocol->integer > 0) + Cvar_Get("protocol", com_legacyprotocol->string, CVAR_ROM); + else +#endif + Cvar_Get("protocol", com_protocol->string, CVAR_ROM); Sys_Init(); diff --git a/code/qcommon/files.c b/code/qcommon/files.c index 59ede021..7a7d80bb 100644 --- a/code/qcommon/files.c +++ b/code/qcommon/files.c @@ -1056,6 +1056,11 @@ qboolean FS_IsDemoExt(const char *filename, int namelen) if(protocol == com_protocol->integer) return qtrue; +#ifdef LEGACY_PROTOCOL + if(protocol == com_legacyprotocol->integer) + return qtrue; +#endif + for(index = 0; demo_protocols[index]; index++) { if(demo_protocols[index] == protocol) diff --git a/code/qcommon/net_chan.c b/code/qcommon/net_chan.c index 5b359f92..7fb11e1c 100644 --- a/code/qcommon/net_chan.c +++ b/code/qcommon/net_chan.c @@ -83,7 +83,8 @@ Netchan_Setup called to open a channel to a remote system ============== */ -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge, qboolean compat) +{ Com_Memset (chan, 0, sizeof(*chan)); chan->sock = sock; @@ -91,6 +92,11 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { chan->qport = qport; chan->incomingSequence = 0; chan->outgoingSequence = 1; + chan->challenge = challenge; + +#ifdef LEGACY_PROTOCOL + chan->compat = compat; +#endif } // TTimo: unused, commenting out to make gcc happy @@ -190,17 +196,24 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) { msg_t send; byte send_buf[MAX_PACKETLEN]; int fragmentLength; + int outgoingSequence; // write the packet header MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here - MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + outgoingSequence = chan->outgoingSequence | FRAGMENT_BIT; + MSG_WriteLong(&send, outgoingSequence); // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } +#ifdef LEGACY_PROTOCOL + if(!chan->compat) +#endif + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + // copy the reliable message to the packet first fragmentLength = FRAGMENT_SIZE; if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { @@ -268,12 +281,17 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { MSG_InitOOB (&send, send_buf, sizeof(send_buf)); MSG_WriteLong( &send, chan->outgoingSequence ); - chan->outgoingSequence++; // send the qport if we are a client - if ( chan->sock == NS_CLIENT ) { - MSG_WriteShort( &send, qport->integer ); - } + if(chan->sock == NS_CLIENT) + MSG_WriteShort(&send, qport->integer); + +#ifdef LEGACY_PROTOCOL + if(!chan->compat) +#endif + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + + chan->outgoingSequence++; MSG_WriteData( &send, data, length ); @@ -327,6 +345,17 @@ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { qport = MSG_ReadShort( msg ); } +#ifdef LEGACY_PROTOCOL + if(!chan->compat) +#endif + { + int checksum = MSG_ReadLong(msg); + + // UDP spoofing protection + if(NETCHAN_GENCHECKSUM(chan->challenge, sequence) != checksum) + return qfalse; + } + // read the fragment information if ( fragmented ) { fragmentStart = MSG_ReadShort( msg ); diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index 30054a4c..89e89c05 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -37,6 +37,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HOMEPATH_NAME_UNIX ".foo" #define HOMEPATH_NAME_WIN "FooBar" #define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN +// #define LEGACY_PROTOCOL // You probably don't need this for your standalone game #else #define PRODUCT_NAME "ioq3" #define BASEGAME "baseq3" @@ -48,6 +49,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HOMEPATH_NAME_UNIX ".q3a" #define HOMEPATH_NAME_WIN "Quake3" #define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN + #define LEGACY_PROTOCOL #endif #define BASETA "missionpack" diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 99ad3b05..836a1d6b 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -195,7 +195,8 @@ void NET_Sleep(int msec); #define MAX_DOWNLOAD_WINDOW 48 // ACK window of 48 download chunks. Cannot set this higher, or clients // will overflow the reliable commands buffer #define MAX_DOWNLOAD_BLKSIZE 1024 // 896 byte block chunks - + +#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge))) /* Netchan handles packet fragmentation and out of order / duplicate suppression @@ -224,10 +225,16 @@ typedef struct { int unsentFragmentStart; int unsentLength; byte unsentBuffer[MAX_MSGLEN]; + + int challenge; + +#ifdef LEGACY_PROTOCOL + qboolean compat; +#endif } netchan_t; void Netchan_Init( int qport ); -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge, qboolean compat); void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); void Netchan_TransmitNextFragment( netchan_t *chan ); @@ -243,7 +250,8 @@ PROTOCOL ============================================================== */ -#define PROTOCOL_VERSION 68 +#define PROTOCOL_VERSION 69 +#define PROTOCOL_LEGACY_VERSION 68 // 1.31 - 67 // maintain a list of compatible protocols for demo playing @@ -863,6 +871,9 @@ extern cvar_t *cl_packetdelay; extern cvar_t *sv_packetdelay; extern cvar_t *com_protocol; +#ifdef LEGACY_PROTOCOL +extern cvar_t *com_legacyprotocol; +#endif // com_speeds times extern int time_game; diff --git a/code/server/server.h b/code/server/server.h index 40e8b9b0..67e71470 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -188,7 +188,11 @@ typedef struct client_s { #endif int oldServerTime; - qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + +#ifdef LEGACY_PROTOCOL + qboolean compat; +#endif } client_t; //============================================================================= diff --git a/code/server/sv_client.c b/code/server/sv_client.c index 345f273d..f90aeaba 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -59,12 +59,25 @@ void SV_GetChallenge(netadr_t from) int clientChallenge; challenge_t *challenge; qboolean wasfound = qfalse; + char *gameName; // ignore if we are in single player if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) { return; } + gameName = Cmd_Argv(2); + if(gameName && *gameName) + { + // reject client if the heartbeat string sent by the client doesn't match ours + if(strcmp(gameName, sv_heartbeat->string)) + { + NET_OutOfBandPrint(NS_SERVER, from, "print\nGame mismatch: This is a %s server\n", + sv_heartbeat->string); + return; + } + } + oldest = 0; oldestClientTime = oldestTime = 0x7fffffff; @@ -302,6 +315,9 @@ void SV_DirectConnect( netadr_t from ) { intptr_t denied; int count; char *ip; +#ifdef LEGACY_PROTOCOL + qboolean compat = qfalse; +#endif Com_DPrintf ("SVC_DirectConnect ()\n"); @@ -314,11 +330,21 @@ void SV_DirectConnect( netadr_t from ) { Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); - version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); - if ( version != com_protocol->integer ) { - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).\n", com_protocol->integer, version ); - Com_DPrintf (" rejected connect from version %i\n", version); - return; + version = atoi(Info_ValueForKey(userinfo, "protocol")); + +#ifdef LEGACY_PROTOCOL + if(version > 0 && com_legacyprotocol->integer == version) + compat = qtrue; + else +#endif + { + if(version != com_protocol->integer) + { + NET_OutOfBandPrint(NS_SERVER, from, "print\nServer uses protocol version %i " + "(yours is %i).\n", com_protocol->integer, version); + Com_DPrintf(" rejected connect from version %i\n", version); + return; + } } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); @@ -500,7 +526,12 @@ gotnewcl: newcl->challenge = challenge; // save the address - Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); +#ifdef LEGACY_PROTOCOL + newcl->compat = compat; + Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge, compat); +#else + Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge, qfalse); +#endif // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; @@ -521,7 +552,7 @@ gotnewcl: SV_UserinfoChanged( newcl ); // send the connect packet to the client - NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 1fd85fc0..c31965c4 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -643,7 +643,13 @@ void SVC_Info( netadr_t from ) { // to prevent timed spoofed reply packets that add ghost servers Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); - Info_SetValueForKey( infostring, "protocol", va("%i", com_protocol->integer) ); +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer > 0) + Info_SetValueForKey(infostring, "protocol", va("%i", com_legacyprotocol->integer)); + else +#endif + Info_SetValueForKey(infostring, "protocol", va("%i", com_protocol->integer)); + Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); Info_SetValueForKey( infostring, "clients", va("%i", count) ); @@ -868,10 +874,6 @@ void SV_PacketEvent( netadr_t from, msg_t *msg ) { } return; } - - // if we received a sequenced packet from an address we don't recognize, - // send an out of band disconnect packet to it - NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); }