- Implement dual protocol support (#4962)

- Fix several UDP spoofing security issues
This commit is contained in:
Thilo Schulz 2011-07-12 11:59:48 +00:00
parent 309c322b80
commit e06c117e9e
13 changed files with 550 additions and 247 deletions

384
README
View file

@ -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 <filename/path> - 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 <stdint.h>
#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 <yournewbase>
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 <homedirname>
to the command line. Then you can control which kind of messages to send to
the master server:
+set sv_heartbeat <heartbeat> +set sv_flatline <flatline>
+set cl_gamename <gamename>
The <heartbeat> and <flatline> 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 <stdint.h>
#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 <yournewbase>
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 <homedirname>
to the command line. You can also control which kind of messages to send to
the master server:
+set sv_heartbeat <heartbeat> +set sv_flatline <flatline>
+set cl_gamename <gamename>
The <heartbeat> and <flatline> 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

View file

@ -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,6 +635,11 @@ void CL_Record_f( void ) {
if ( Cmd_Argc() == 2 ) {
s = Cmd_Argv(1);
Q_strncpyz( demoName, s, sizeof( demoName ) );
#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;
@ -643,6 +647,11 @@ void CL_Record_f( void ) {
// scan for a free demo name
for ( number = 0 ; number <= 9999 ; number++ ) {
CL_DemoFilename( number, demoName );
#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))
@ -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);
#ifdef LEGACY_PROTOCOL
if(com_legacyprotocol->integer > 0)
{
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;
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,6 +2237,15 @@ void CL_CheckForResend( void ) {
port = Cvar_VariableValue ("net_qport");
Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
#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,26 +2503,73 @@ 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;
}
c = Cmd_Argv(2);
if(*c)
challenge = atoi(c);
strver = Cmd_Argv(3);
if(*strver)
{
ver = atoi(strver);
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(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.
c = Cmd_Argv(2);
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_Printf("Bad challenge for challengeResponse. Ignored.\n");
return;
}
}
// start sending challenge response instead of challenge request packets
clc.challenge = atoi(Cmd_Argv(1));
@ -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) );
@ -2540,8 +2666,10 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
// echo request from server
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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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';
// 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;
}

View file

@ -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();

View file

@ -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)

View file

@ -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 ) {
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 );

View file

@ -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"

View file

@ -196,6 +196,7 @@ void NET_Sleep(int msec);
// 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;

View file

@ -189,6 +189,10 @@ typedef struct client_s {
int oldServerTime;
qboolean csUpdated[MAX_CONFIGSTRINGS+1];
#ifdef LEGACY_PROTOCOL
qboolean compat;
#endif
} client_t;
//=============================================================================

View file

@ -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");
@ -315,11 +331,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 );
#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" ) );
qport = atoi( Info_ValueForKey( userinfo, "qport" ) );
@ -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 );

View file

@ -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) );
#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" );
}