Index: README =================================================================== --- README (revision 2060) +++ README (working copy) @@ -216,9 +216,15 @@ ipv6 servers on the local network net_mcastiface - outgoing interface to use for scan - protocol - Allow changing protocol version + com_protocol - when encountering a server/client that + only supports the version configured in + this cvar, ioquake3 will use the old and + less secure protocol from quake3 1.32c. (startup only) + com_legacyprotocol - Allow changing protocol version that is + sent to the server (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 @@ -279,8 +285,9 @@ 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 parameters: +set sv_pure 0 +set vm_cgame 0 +set vm_game 0 +set vm_ui 0 @@ -307,6 +314,109 @@ In this case you can always revert back to the old behaviour by setting the cvar com_busyWait to 1. +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 + the sv_dlURL cvar into your SERVERINFO string and ensure sv_allowDownloads + is set to 1 + + sv_dlURL is the base of the URL that contains your custom .pk3 files + the client will append both fs_game and the filename to the end of + this value. For example, if you have sv_dlURL set to + "http://ioquake3.org", fs_game is "baseq3", and the client is + missing "test.pk3", it will attempt to download from the URL + "http://ioquake3.org/baseq3/test.pk3" + + sv_allowDownload's value is now a bitmask made up of the following + flags: + 1 - ENABLE + 4 - do not use UDP downloads + 8 - do not ask the client to disconnect when using HTTP/FTP + + Server operators who are concerned about potential "leeching" from their + HTTP servers from other ioquake3 servers can make use of the HTTP_REFERER + that ioquake3 sets which is "ioQ3://{SERVER_IP}:{SERVER_PORT}". For, + example, Apache's mod_rewrite can restrict access based on HTTP_REFERER. + +Using HTTP/FTP Download Support (Client) + Simply setting cl_allowDownload to 1 will enable HTTP/FTP downloads + assuming ioquake3 was compiled with USE_CURL=1 (the default). + like sv_allowDownload, cl_allowDownload also uses a bitmask value + supporting the following flags: + 1 - ENABLE + 2 - do not use HTTP/FTP downloads + 4 - do not use UDP downloads + + When ioquake3 is built with USE_CURL_DLOPEN=1 (default on some platforms), + it will use the value of the cvar cl_cURLLib as the filename of the cURL + library to dynamically load. + +Multiuser Support on Windows systems + On Windows, all user specific files such as autogenerated configuration, + demos, videos, screenshots, and autodownloaded pk3s are now saved in a + directory specific to the user who is running ioquake3. + + On NT-based such as Windows XP, this is usually a directory named: + "C:\Documents and Settings\%USERNAME%\Application Data\Quake3\" + + Windows 95, Windows 98, and Windows ME will use a directory like: + "C:\Windows\Application Data\Quake3" + in single-user mode, or: + "C:\Windows\Profiles\%USERNAME%\Application Data\Quake3" + if multiple logins have been enabled. + + In order to access this directory more easily, the installer may create a + Shortcut which has its target set to: + "%APPDATA%\Quake3\" + This Shortcut would work for all users on the system regardless of the + locale settings. Unfortunately, this environment variable is only + present on Windows NT based systems. + + You can revert to the old single-user behaviour by setting the fs_homepath + cvar to the directory where ioquake3 is installed. For example: + ioquake3.exe +set fs_homepath "c:\ioquake3" + Note that this cvar MUST be set as a command line parameter. + +SDL Keyboard Differences + ioquake3 clients have different keyboard behaviour compared to the original + Quake3 clients. + + * "Caps Lock" and "Num Lock" can not be used as normal binds since they + do not send a KEYUP event until the key is pressed again. + + * SDL > 1.2.9 does not support disabling dead key recognition. In order to + send dead key characters (e.g. ~, ', `, and ^), you must key a Space (or + sometimes the same character again) after the character to send it on + many international keyboard layouts. + + * The SDL client supports many more keys than the original Quake3 client. + For example the keys: "Windows", "SysReq", "ScrollLock", and "Break". + For non-US keyboards, all of the so called "World" keys are now supported + as well as F13, F14, F15, and the country-specific mode/meta keys. + + On many international layouts the default console toggle keys are also dead + keys, meaning that dropping the console potentially results in + unintentionally initiating the keying of a dead key. Futhermore SDL 1.2's + dead key support is broken by design and Q3 doesn't support non-ASCII text + entry, so the chances are you won't get the correct character anyway. + + If you use such a keyboard layout, you can set the cvar cl_consoleKeys. This + is a space delimited list of key names that will toggle the console. The key + names are the usual Q3 names e.g. "~", "`", "c", "BACKSPACE", "PAUSE", + "WINDOWS" etc. It's also possible to use ASCII characters, by hexadecimal + number. Some example values for cl_consoleKeys: + + "~ ` 0x7e 0x60" Toggle on ~ or ` (the default) + "WINDOWS" Toggle on the Windows key + "c" Toggle on the c key + "0x43" Toggle on the C character (Shift-c) + "PAUSE F1 PGUP" Toggle on the Pause, F1 or Page Up keys + + Note that when you elect a set of console keys or characters, they cannot + then be used for binding, nor will they generate characters when entering + 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 @@ -352,6 +462,9 @@ 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 @@ -408,7 +521,7 @@ +set com_homepath - to the command line. Then you can control which kind of messages to send to + to the command line. You can also control which kind of messages to send to the master server: +set sv_heartbeat +set sv_flatline @@ -458,6 +571,34 @@ 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 protocol 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. You can enable it in q_shared.h if desired 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 @@ -476,109 +617,6 @@ 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 - the sv_dlURL cvar into your SERVERINFO string and ensure sv_allowDownloads - is set to 1 - - sv_dlURL is the base of the URL that contains your custom .pk3 files - the client will append both fs_game and the filename to the end of - this value. For example, if you have sv_dlURL set to - "http://ioquake3.org", fs_game is "baseq3", and the client is - missing "test.pk3", it will attempt to download from the URL - "http://ioquake3.org/baseq3/test.pk3" - - sv_allowDownload's value is now a bitmask made up of the following - flags: - 1 - ENABLE - 4 - do not use UDP downloads - 8 - do not ask the client to disconnect when using HTTP/FTP - - Server operators who are concerned about potential "leeching" from their - HTTP servers from other ioquake3 servers can make use of the HTTP_REFERER - that ioquake3 sets which is "ioQ3://{SERVER_IP}:{SERVER_PORT}". For, - example, Apache's mod_rewrite can restrict access based on HTTP_REFERER. - -Using HTTP/FTP Download Support (Client) - Simply setting cl_allowDownload to 1 will enable HTTP/FTP downloads - assuming ioquake3 was compiled with USE_CURL=1 (the default). - like sv_allowDownload, cl_allowDownload also uses a bitmask value - supporting the following flags: - 1 - ENABLE - 2 - do not use HTTP/FTP downloads - 4 - do not use UDP downloads - - When ioquake3 is built with USE_CURL_DLOPEN=1 (default on some platforms), - it will use the value of the cvar cl_cURLLib as the filename of the cURL - library to dynamically load. - -Multiuser Support on Windows systems - On Windows, all user specific files such as autogenerated configuration, - demos, videos, screenshots, and autodownloaded pk3s are now saved in a - directory specific to the user who is running ioquake3. - - On NT-based such as Windows XP, this is usually a directory named: - "C:\Documents and Settings\%USERNAME%\Application Data\Quake3\" - - Windows 95, Windows 98, and Windows ME will use a directory like: - "C:\Windows\Application Data\Quake3" - in single-user mode, or: - "C:\Windows\Profiles\%USERNAME%\Application Data\Quake3" - if multiple logins have been enabled. - - In order to access this directory more easily, the installer may create a - Shortcut which has its target set to: - "%APPDATA%\Quake3\" - This Shortcut would work for all users on the system regardless of the - locale settings. Unfortunately, this environment variable is only - present on Windows NT based systems. - - You can revert to the old single-user behaviour by setting the fs_homepath - cvar to the directory where ioquake3 is installed. For example: - ioquake3.exe +set fs_homepath "c:\ioquake3" - Note that this cvar MUST be set as a command line parameter. - -SDL Keyboard Differences - ioquake3 clients have different keyboard behaviour compared to the original - Quake3 clients. - - * "Caps Lock" and "Num Lock" can not be used as normal binds since they - do not send a KEYUP event until the key is pressed again. - - * SDL > 1.2.9 does not support disabling dead key recognition. In order to - send dead key characters (e.g. ~, ', `, and ^), you must key a Space (or - sometimes the same character again) after the character to send it on - many international keyboard layouts. - - * The SDL client supports many more keys than the original Quake3 client. - For example the keys: "Windows", "SysReq", "ScrollLock", and "Break". - For non-US keyboards, all of the so called "World" keys are now supported - as well as F13, F14, F15, and the country-specific mode/meta keys. - - On many international layouts the default console toggle keys are also dead - keys, meaning that dropping the console potentially results in - unintentionally initiating the keying of a dead key. Futhermore SDL 1.2's - dead key support is broken by design and Q3 doesn't support non-ASCII text - entry, so the chances are you won't get the correct character anyway. - - If you use such a keyboard layout, you can set the cvar cl_consoleKeys. This - is a space delimited list of key names that will toggle the console. The key - names are the usual Q3 names e.g. "~", "`", "c", "BACKSPACE", "PAUSE", - "WINDOWS" etc. It's also possible to use ASCII characters, by hexadecimal - number. Some example values for cl_consoleKeys: - - "~ ` 0x7e 0x60" Toggle on ~ or ` (the default) - "WINDOWS" Toggle on the Windows key - "c" Toggle on the c key - "0x43" Toggle on the C character (Shift-c) - "PAUSE F1 PGUP" Toggle on the Pause, F1 or Page Up keys - - Note that when you elect a set of console keys or characters, they cannot - then be used for binding, nor will they generate characters when entering - text. Also, in addition to the nominated console keys, Shift-ESC is hard - coded to always toggle the console. - 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 Index: code/q3_ui/ui_demo2.c =================================================================== --- code/q3_ui/ui_demo2.c (revision 2060) +++ code/q3_ui/ui_demo2.c (working copy) @@ -42,8 +42,8 @@ #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 @@ int numDemos; char names[NAMEBUFSIZE]; + int numLegacyDemos; + char namesLegacy[NAMEBUFSIZE]; + char *demolist[MAX_DEMOS]; } demos_t; @@ -133,6 +136,7 @@ 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 @@ 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,16 +266,22 @@ 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; } Index: code/server/sv_client.c =================================================================== --- code/server/sv_client.c (revision 2065) +++ code/server/sv_client.c (working copy) @@ -59,12 +59,25 @@ 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 @@ intptr_t denied; int count; char *ip; +#ifdef LEGACY_PROTOCOL + qboolean compat = qfalse; +#endif Com_DPrintf ("SVC_DirectConnect ()\n"); @@ -314,11 +330,21 @@ 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 @@ 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 @@ 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 ); Index: code/server/server.h =================================================================== --- code/server/server.h (revision 2065) +++ code/server/server.h (working copy) @@ -188,7 +188,11 @@ #endif int oldServerTime; - qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + +#ifdef LEGACY_PROTOCOL + qboolean compat; +#endif } client_t; //============================================================================= Index: code/server/sv_main.c =================================================================== --- code/server/sv_main.c (revision 2060) +++ code/server/sv_main.c (working copy) @@ -643,7 +643,13 @@ // 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 @@ } 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" ); } Index: code/qcommon/net_chan.c =================================================================== --- code/qcommon/net_chan.c (revision 2060) +++ code/qcommon/net_chan.c (working copy) @@ -83,7 +83,8 @@ 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 @@ 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 @@ 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,13 +281,18 @@ 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 ); // send the datagram @@ -327,6 +345,17 @@ 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 ); Index: code/qcommon/q_shared.h =================================================================== --- code/qcommon/q_shared.h (revision 2067) +++ code/qcommon/q_shared.h (working copy) @@ -37,6 +37,7 @@ #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 @@ #define HOMEPATH_NAME_UNIX ".q3a" #define HOMEPATH_NAME_WIN "Quake3" #define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN + #define LEGACY_PROTOCOL #endif #define BASETA "missionpack" Index: code/qcommon/files.c =================================================================== --- code/qcommon/files.c (revision 2060) +++ code/qcommon/files.c (working copy) @@ -1056,6 +1056,11 @@ 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) Index: code/qcommon/qcommon.h =================================================================== --- code/qcommon/qcommon.h (revision 2060) +++ code/qcommon/qcommon.h (working copy) @@ -194,8 +194,9 @@ #define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames #define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks - +#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge))) + /* Netchan handles packet fragmentation and out of order / duplicate suppression */ @@ -223,10 +224,16 @@ 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 ); @@ -242,7 +249,8 @@ ============================================================== */ -#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 @@ -862,6 +870,9 @@ 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; Index: code/qcommon/common.c =================================================================== --- code/qcommon/common.c (revision 2060) +++ code/qcommon/common.c (working copy) @@ -32,7 +32,7 @@ #endif int demo_protocols[] = -{ 66, 67, 68, 0 }; +{ 67, 66, 0 }; #define MAX_NUM_ARGVS 50 @@ -86,6 +86,9 @@ 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; @@ -2794,8 +2797,17 @@ 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(); if( Sys_WritePIDFile( ) ) { Index: code/client/client.h =================================================================== --- code/client/client.h (revision 2060) +++ code/client/client.h (working copy) @@ -263,6 +263,10 @@ 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; Index: code/client/cl_main.c =================================================================== --- code/client/cl_main.c (revision 2060) +++ code/client/cl_main.c (working copy) @@ -533,7 +533,6 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ #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 @@ 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 @@ 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 @@ // 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(ver != com_protocol->integer) + { +#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("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("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 || atoi(c) != clc.challenge) + if(!*c || challenge != clc.challenge) + { + Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + return; + } + } + } + else +#endif + { + if(!*c || challenge != clc.challenge) { - Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + Com_Printf("Bad challenge for challengeResponse. Ignored.\n"); return; } } @@ -2494,7 +2598,36 @@ 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 @@ 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 @@ } // 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 @@ // 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; } Index: code/client/cl_net_chan.c =================================================================== --- code/client/cl_net_chan.c (revision 2060) +++ code/client/cl_net_chan.c (working copy) @@ -147,9 +147,6 @@ Netchan_Transmit( chan, msg->cursize, msg->data ); } -extern int oldsize; -int newsize = 0; - /* ================= CL_Netchan_Process @@ -161,7 +158,8 @@ ret = Netchan_Process( chan, msg ); if (!ret) return qfalse; + CL_Netchan_Decode( msg ); - newsize += msg->cursize; + return qtrue; }