From 579672760092292b7a9752899010ffa4ac6faf6f Mon Sep 17 00:00:00 2001
From: Tim Angus <tim@ngus.net>
Date: Sat, 17 Jun 2006 23:13:57 +0000
Subject: * Merged ioq3-r810

---
 src/client/cl_main.c       |   2 +-
 src/client/cl_parse.c      |  28 ++++++---
 src/client/snd_codec.c     |  15 +++--
 src/client/snd_codec_ogg.c |   9 ++-
 src/client/snd_openal.c    |   1 +
 src/qcommon/common.c       |   5 ++
 src/qcommon/files.c        |  52 +++++++++++++----
 src/qcommon/md4.c          |   4 +-
 src/qcommon/msg.c          |   9 +++
 src/qcommon/net_chan.c     |  66 ++++++++++++++++++++-
 src/qcommon/qcommon.h      |   5 +-
 src/qcommon/vm_ppc_new.c   |   8 +--
 src/server/server.h        |   1 +
 src/server/sv_client.c     |   6 ++
 src/server/sv_init.c       |   1 +
 src/server/sv_main.c       |   1 +
 src/server/sv_snapshot.c   |   7 +++
 src/unix/sdl_glimp.c       | 141 ++++++++++++++++++++++++++++++++++++++-------
 18 files changed, 306 insertions(+), 55 deletions(-)

(limited to 'src')

diff --git a/src/client/cl_main.c b/src/client/cl_main.c
index 6e0cdaff..3390ead5 100644
--- a/src/client/cl_main.c
+++ b/src/client/cl_main.c
@@ -1403,7 +1403,7 @@ void CL_NextDownload(void) {
 			*s++ = 0;
 		else
 			s = localName + strlen(localName); // point at the nul byte
-
+		
 		CL_BeginDownload( localName, remoteName );
 
 		clc.downloadRestart = qtrue;
diff --git a/src/client/cl_parse.c b/src/client/cl_parse.c
index 7f219b32..dc14cd66 100644
--- a/src/client/cl_parse.c
+++ b/src/client/cl_parse.c
@@ -256,6 +256,13 @@ void CL_ParseSnapshot( msg_t *msg ) {
 
 	// read areamask
 	len = MSG_ReadByte( msg );
+	
+	if(len > sizeof(newSnap.areamask))
+	{
+		Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask.", len);
+		return;
+	}
+	
 	MSG_ReadData( msg, &newSnap.areamask, len);
 
 	// read playerinfo
@@ -476,6 +483,12 @@ void CL_ParseDownload ( msg_t *msg ) {
 	unsigned char data[MAX_MSGLEN];
 	int block;
 
+	if (!*clc.downloadTempName) {
+		Com_Printf("Server sending download, but no download was requested\n");
+		CL_AddReliableCommand( "stopdl" );
+		return;
+	}
+
 	// read the data
 	block = MSG_ReadShort ( msg );
 
@@ -494,8 +507,13 @@ void CL_ParseDownload ( msg_t *msg ) {
 	}
 
 	size = MSG_ReadShort ( msg );
-	if (size > 0)
-		MSG_ReadData( msg, data, size );
+	if (size < 0 || size > sizeof(data))
+	{
+		Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size);
+		return;
+	}
+	
+	MSG_ReadData(msg, data, size);
 
 	if (clc.downloadBlock != block) {
 		Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block);
@@ -505,12 +523,6 @@ void CL_ParseDownload ( msg_t *msg ) {
 	// open the file if not opened yet
 	if (!clc.download)
 	{
-		if (!*clc.downloadTempName) {
-			Com_Printf("Server sending download, but no download was requested\n");
-			CL_AddReliableCommand( "stopdl" );
-			return;
-		}
-
 		clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName );
 
 		if (!clc.download) {
diff --git a/src/client/snd_codec.c b/src/client/snd_codec.c
index 98fae65f..088236a4 100644
--- a/src/client/snd_codec.c
+++ b/src/client/snd_codec.c
@@ -34,13 +34,16 @@ S_FileExtension
 */
 static char *S_FileExtension(const char *fni)
 {
-	char *fn = (char *)fni;
+	// we should search from the ending to the last '/'
+
+	char *fn = (char *) fni + strlen(fni) - 1;
 	char *eptr = NULL;
-	while(*fn)
+
+	while(*fn != '/' && fn != fni)
 	{
 		if(*fn == '.')
 			eptr = fn;
-		fn++;
+		fn--;
 	}
 
 	return eptr;
@@ -64,8 +67,10 @@ static snd_codec_t *S_FindCodecForFile(const char *filename)
 		while(codec)
 		{
 			char fn[MAX_QPATH];
-			Q_strncpyz(fn, filename, sizeof(fn) - 4);
-			COM_DefaultExtension(fn, sizeof(fn), codec->ext);
+			
+			// there is no extension so we do not need to subtract 4 chars
+			Q_strncpyz(fn, filename, MAX_QPATH);
+			COM_DefaultExtension(fn, MAX_QPATH, codec->ext);
 
 			// Check it exists
 			if(FS_ReadFile(fn, NULL) != -1)
diff --git a/src/client/snd_codec_ogg.c b/src/client/snd_codec_ogg.c
index 9c17c0b9..72ece944 100644
--- a/src/client/snd_codec_ogg.c
+++ b/src/client/snd_codec_ogg.c
@@ -360,6 +360,13 @@ int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
 	// Bitstream for the decoder
 	int BS = 0;
 
+	// big endian machines want their samples in big endian order
+	int IsBigEndian = 0;
+
+#	ifdef Q3_BIG_ENDIAN
+	IsBigEndian = 1;
+#	endif // Q3_BIG_ENDIAN
+
 	// check if input is valid
 	if(!(stream && buffer))
 	{
@@ -379,7 +386,7 @@ int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
 	while(-1)
 	{
 		// read some bytes from the OGG codec
-		c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, 0, OGG_SAMPLEWIDTH, 1, &BS);
+		c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS);
 		
 		// no more bytes are left
 		if(c <= 0)
diff --git a/src/client/snd_openal.c b/src/client/snd_openal.c
index c44f1e0a..c71a58ee 100644
--- a/src/client/snd_openal.c
+++ b/src/client/snd_openal.c
@@ -1539,6 +1539,7 @@ void S_AL_StopAllSounds( void )
 {
 	S_AL_SrcShutup();
 	S_AL_StopBackgroundTrack();
+	S_AL_StreamDie();
 }
 
 /*
diff --git a/src/qcommon/common.c b/src/qcommon/common.c
index 44174ead..a33eb6fd 100644
--- a/src/qcommon/common.c
+++ b/src/qcommon/common.c
@@ -77,6 +77,8 @@ cvar_t	*com_blood;
 cvar_t	*com_buildScript;	// for automated data building scripts
 cvar_t	*cl_paused;
 cvar_t	*sv_paused;
+cvar_t  *cl_packetdelay;
+cvar_t  *sv_packetdelay;
 cvar_t	*com_cameraMode;
 #if defined(_WIN32) && defined(_DEBUG)
 cvar_t	*com_noErrorInterrupt;
@@ -2085,6 +2087,7 @@ int Com_EventLoop( void ) {
 	MSG_Init( &buf, bufData, sizeof( bufData ) );
 
 	while ( 1 ) {
+		NET_FlushPacketQueue();
 		ev = Com_GetEvent();
 
 		// if no more events are available
@@ -2358,6 +2361,8 @@ void Com_Init( char *commandLine ) {
 
 	cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM);
 	sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM);
+	cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT);
+	sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT);
 	com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM);
 	com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM);
 	com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
diff --git a/src/qcommon/files.c b/src/qcommon/files.c
index c5b46607..9e9d21d0 100644
--- a/src/qcommon/files.c
+++ b/src/qcommon/files.c
@@ -2591,15 +2591,16 @@ we are not interested in a download string format, we want something human-reada
 qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
 	searchpath_t	*sp;
 	qboolean havepak, badchecksum;
+	char *origpos = neededpaks;
 	int i;
 
-	if ( !fs_numServerReferencedPaks ) {
+	if (!fs_numServerReferencedPaks)
 		return qfalse; // Server didn't send any pack information along
-	}
 
 	*neededpaks = 0;
 
-	for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) {
+	for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ )
+	{
 		// Ok, see if we have this pak file
 		badchecksum = qfalse;
 		havepak = qfalse;
@@ -2609,6 +2610,13 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
 			continue;
 		}
 
+		// Make sure the server cannot make us write to non-quake3 directories.
+		if(strstr(fs_serverReferencedPakNames[i], "../") || strstr(fs_serverReferencedPakNames[i], "..\\"))
+                {
+			Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]);
+                        continue;
+                }
+
 		for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
 			if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
 				havepak = qtrue; // This is it!
@@ -2621,6 +2629,12 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
 
       if (dlstring)
       {
+	// We need this to make sure we won't hit the end of the buffer or the server could
+	// overwrite non-pk3 files on clients by writing so much crap into neededpaks that
+	// Q_strcat cuts off the .pk3 extension.
+	
+	origpos += strlen(origpos);
+	
         // Remote name
         Q_strcat( neededpaks, len, "@");
         Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
@@ -2641,6 +2655,14 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
           Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
           Q_strcat( neededpaks, len, ".pk3" );
         }
+        
+        // Find out whether it might have overflowed the buffer and don't add this file to the
+        // list if that is the case.
+        if(strlen(origpos) + (origpos - neededpaks) >= len - 1)
+	{
+		*origpos = '\0';
+		break;
+	}
       }
       else
       {
@@ -3146,7 +3168,7 @@ checksums to see if any pk3 files need to be auto-downloaded.
 =====================
 */
 void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
-	int		i, c, d;
+	int		i, c, d = 0;
 
 	Cmd_TokenizeString( pakSums );
 
@@ -3155,30 +3177,36 @@ void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames )
 		c = MAX_SEARCH_PATHS;
 	}
 
-	fs_numServerReferencedPaks = c;
-
 	for ( i = 0 ; i < c ; i++ ) {
 		fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
 	}
 
-	for ( i = 0 ; i < c ; i++ ) {
-		if (fs_serverReferencedPakNames[i]) {
+	for (i = 0 ; i < sizeof(fs_serverReferencedPakNames) / sizeof(*fs_serverReferencedPakNames); i++)
+	{
+		if(fs_serverReferencedPakNames[i])
 			Z_Free(fs_serverReferencedPakNames[i]);
-		}
+
 		fs_serverReferencedPakNames[i] = NULL;
 	}
+
 	if ( pakNames && *pakNames ) {
 		Cmd_TokenizeString( pakNames );
 
 		d = Cmd_Argc();
-		if ( d > MAX_SEARCH_PATHS ) {
-			d = MAX_SEARCH_PATHS;
-		}
+
+		if(d > c)
+			d = c;
 
 		for ( i = 0 ; i < d ; i++ ) {
 			fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
 		}
 	}
+	
+	// ensure that there are as many checksums as there are pak names.
+	if(d < c)
+		c = d;
+	
+	fs_numServerReferencedPaks = c;	
 }
 
 /*
diff --git a/src/qcommon/md4.c b/src/qcommon/md4.c
index 2501b2b6..838b062e 100644
--- a/src/qcommon/md4.c
+++ b/src/qcommon/md4.c
@@ -159,10 +159,10 @@ static void mdfour_update(struct mdfour *md, byte *in, int n)
 {
 	uint32_t M[16];
 
-	if (n == 0) mdfour_tail(in, n);
-
 	m = md;
 
+	if (n == 0) mdfour_tail(in, n);
+
 	while (n >= 64) {
 		copy64(M, in);
 		mdfour64(M);
diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c
index da96dab9..e4db4d8e 100644
--- a/src/qcommon/msg.c
+++ b/src/qcommon/msg.c
@@ -469,6 +469,10 @@ char *MSG_ReadBigString( msg_t *msg ) {
 		if ( c == '%' ) {
 			c = '.';
 		}
+		// don't allow higher ascii values
+		if ( c > 127 ) {
+			c = '.';
+		}
 
 		string[l] = c;
 		l++;
@@ -493,6 +497,11 @@ char *MSG_ReadStringLine( msg_t *msg ) {
 		if ( c == '%' ) {
 			c = '.';
 		}
+		// don't allow higher ascii values
+		if ( c > 127 ) {
+			c = '.';
+		}
+
 		string[l] = c;
 		l++;
 	} while (l < sizeof(string)-1);
diff --git a/src/qcommon/net_chan.c b/src/qcommon/net_chan.c
index 4df95c46..9dd41589 100644
--- a/src/qcommon/net_chan.c
+++ b/src/qcommon/net_chan.c
@@ -614,6 +614,62 @@ void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t t
 
 //=============================================================================
 
+typedef struct packetQueue_s {
+        struct packetQueue_s *next;
+        int length;
+        byte *data;
+        netadr_t to;
+        int release;
+} packetQueue_t;
+
+packetQueue_t *packetQueue = NULL;
+
+static void NET_QueuePacket( int length, const void *data, netadr_t to,
+	int offset )
+{
+	packetQueue_t *new, *next = packetQueue;
+
+	if(offset > 999)
+		offset = 999;
+
+	new = S_Malloc(sizeof(packetQueue_t));
+	new->data = S_Malloc(length);
+	Com_Memcpy(new->data, data, length);
+	new->length = length;
+	new->to = to;
+	new->release = Sys_Milliseconds() + offset;	
+	new->next = NULL;
+
+	if(!packetQueue) {
+		packetQueue = new;
+		return;
+	}
+	while(next) {
+		if(!next->next) {
+			next->next = new;
+			return;
+		}
+		next = next->next;
+	}
+}
+
+void NET_FlushPacketQueue(void)
+{
+	packetQueue_t *last;
+	int now;
+
+	while(packetQueue) {
+		now = Sys_Milliseconds();
+		if(packetQueue->release >= now)
+			break;
+		Sys_SendPacket(packetQueue->length, packetQueue->data,
+			packetQueue->to);
+		last = packetQueue;
+		packetQueue = packetQueue->next;
+		Z_Free(last->data);
+		Z_Free(last);
+	}
+}
 
 void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) {
 
@@ -630,7 +686,15 @@ void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to )
 		return;
 	}
 
-	Sys_SendPacket( length, data, to );
+	if ( sock == NS_CLIENT && cl_packetdelay->integer > 0 ) {
+		NET_QueuePacket( length, data, to, cl_packetdelay->integer );
+	}
+	else if ( sock == NS_SERVER && sv_packetdelay->integer > 0 ) {
+		NET_QueuePacket( length, data, to, sv_packetdelay->integer );
+	}
+	else {
+		Sys_SendPacket( length, data, to );
+	}
 }
 
 /*
diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h
index b21b5958..9cb03b6f 100644
--- a/src/qcommon/qcommon.h
+++ b/src/qcommon/qcommon.h
@@ -157,7 +157,7 @@ void		NET_Init( void );
 void		NET_Shutdown( void );
 void		NET_Restart( void );
 void		NET_Config( qboolean enableNetworking );
-
+void		NET_FlushPacketQueue(void);
 void		NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to);
 void		QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...);
 void		QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len );
@@ -750,6 +750,9 @@ extern	cvar_t	*com_altivec;
 extern	cvar_t	*cl_paused;
 extern	cvar_t	*sv_paused;
 
+extern	cvar_t	*cl_packetdelay;
+extern	cvar_t	*sv_packetdelay;
+
 // com_speeds times
 extern	int		time_game;
 extern	int		time_frontend;
diff --git a/src/qcommon/vm_ppc_new.c b/src/qcommon/vm_ppc_new.c
index 42a03a50..a731d653 100644
--- a/src/qcommon/vm_ppc_new.c
+++ b/src/qcommon/vm_ppc_new.c
@@ -1107,7 +1107,7 @@ void VM_Compile( vm_t *vm, vmHeader_t *header ) {
 		#endif
 		assertInteger(opStackDepth-1);
 		assertInteger(opStackDepth-2);
-                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
 		opStackRegType[opStackDepth-1] = 0;
 		opStackRegType[opStackDepth-2] = 0;
 		opStackLoadInstructionAddr[opStackDepth-1] = 0;
@@ -1131,7 +1131,7 @@ void VM_Compile( vm_t *vm, vmHeader_t *header ) {
 		#endif
 		assertInteger(opStackDepth-1);
 		assertInteger(opStackDepth-2);
-                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
 		opStackRegType[opStackDepth-1] = 0;
 		opStackRegType[opStackDepth-2] = 0;
 		opStackLoadInstructionAddr[opStackDepth-1] = 0;
@@ -1155,7 +1155,7 @@ void VM_Compile( vm_t *vm, vmHeader_t *header ) {
 		#endif
 		assertInteger(opStackDepth-1);
 		assertInteger(opStackDepth-2);
-                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
 		opStackRegType[opStackDepth-1] = 0;
 		opStackRegType[opStackDepth-2] = 0;
 		opStackLoadInstructionAddr[opStackDepth-1] = 0;
@@ -1179,7 +1179,7 @@ void VM_Compile( vm_t *vm, vmHeader_t *header ) {
 		#endif
 		assertInteger(opStackDepth-1);
 		assertInteger(opStackDepth-2);
-                Inst( "cmp", PPC_CMP, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
+                Inst( "cmpl", PPC_CMPL, 0, opStackIntRegisters[opStackDepth-2], opStackIntRegisters[opStackDepth-1] );
 		opStackRegType[opStackDepth-1] = 0;
 		opStackRegType[opStackDepth-2] = 0;
 		opStackLoadInstructionAddr[opStackDepth-1] = 0;
diff --git a/src/server/server.h b/src/server/server.h
index 091d7e7a..fd49da7b 100644
--- a/src/server/server.h
+++ b/src/server/server.h
@@ -239,6 +239,7 @@ extern	cvar_t	*sv_killserver;
 extern	cvar_t	*sv_mapname;
 extern	cvar_t	*sv_mapChecksum;
 extern	cvar_t	*sv_serverid;
+extern	cvar_t	*sv_minRate;
 extern	cvar_t	*sv_maxRate;
 extern	cvar_t	*sv_minPing;
 extern	cvar_t	*sv_maxPing;
diff --git a/src/server/sv_client.c b/src/server/sv_client.c
index 6cc9eecd..794f32a8 100644
--- a/src/server/sv_client.c
+++ b/src/server/sv_client.c
@@ -743,6 +743,12 @@ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
 			rate = sv_maxRate->integer;
 		}
 	}
+	if ( sv_minRate->integer ) {
+		if ( sv_minRate->integer < 1000 )
+			Cvar_Set( "sv_minRate", "1000" );
+		if ( sv_minRate->integer > rate )
+			rate = sv_minRate->integer;
+	}
 
 	if (!rate) {
 		blockspersnap = 1;
diff --git a/src/server/sv_init.c b/src/server/sv_init.c
index 22f45729..0099e63f 100644
--- a/src/server/sv_init.c
+++ b/src/server/sv_init.c
@@ -539,6 +539,7 @@ void SV_Init (void) {
 	sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE );
 	sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
 
+	sv_minRate = Cvar_Get ("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 	sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 	sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 	sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
diff --git a/src/server/sv_main.c b/src/server/sv_main.c
index f4d960d9..c84e5ac3 100644
--- a/src/server/sv_main.c
+++ b/src/server/sv_main.c
@@ -45,6 +45,7 @@ cvar_t	*sv_killserver;			// menu system can set to 1 to shut server down
 cvar_t	*sv_mapname;
 cvar_t	*sv_mapChecksum;
 cvar_t	*sv_serverid;
+cvar_t	*sv_minRate;
 cvar_t	*sv_maxRate;
 cvar_t	*sv_minPing;
 cvar_t	*sv_maxPing;
diff --git a/src/server/sv_snapshot.c b/src/server/sv_snapshot.c
index cd295edf..dd483f17 100644
--- a/src/server/sv_snapshot.c
+++ b/src/server/sv_snapshot.c
@@ -552,6 +552,13 @@ static int SV_RateMsec( client_t *client, int messageSize ) {
 			rate = sv_maxRate->integer;
 		}
 	}
+	if ( sv_minRate->integer ) {
+		if ( sv_minRate->integer < 1000 )
+			Cvar_Set( "sv_minRate", "1000" );
+		if ( sv_minRate->integer > rate )
+			rate = sv_minRate->integer;
+	}
+
 	rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate;
 
 	return rateMsec;
diff --git a/src/unix/sdl_glimp.c b/src/unix/sdl_glimp.c
index 4469f9e0..fcab7125 100644
--- a/src/unix/sdl_glimp.c
+++ b/src/unix/sdl_glimp.c
@@ -1357,17 +1357,32 @@ static int joy_keys[16] = {
      K_JOY26, K_JOY27
 };
 
+// translate hat events into keypresses
+// the 4 highest buttons are used for the first hat ...
+static int hat_keys[16] = {
+     K_JOY29, K_JOY30,
+     K_JOY31, K_JOY32,
+     K_JOY25, K_JOY26,
+     K_JOY27, K_JOY28,
+     K_JOY21, K_JOY22,
+     K_JOY23, K_JOY24,
+     K_JOY17, K_JOY18,
+     K_JOY19, K_JOY20
+};
+
 
 // bk001130 - from linux_glimp.c
 extern cvar_t *  in_joystick;
 extern cvar_t *  in_joystickDebug;
 extern cvar_t *  joy_threshold;
+cvar_t *in_joystickNo;
 
 #define ARRAYLEN(x) (sizeof (x) / sizeof (x[0]))
 struct
 {
     qboolean buttons[16];  // !!! FIXME: these might be too many.
     unsigned int oldaxes;
+    unsigned int oldhats;
 } stick_state;
 
 
@@ -1407,36 +1422,35 @@ void IN_StartupJoystick( void )
   for (i = 0; i < total; i++)
     Com_Printf("[%d] %s\n", i, SDL_JoystickName(i));
 
-  // !!! FIXME: someone should add a way to select a specific stick.
-  for( i = 0; i < total; i++ ) {
-    stick = SDL_JoystickOpen(i);
-    if (stick == NULL)
-        continue;
-
-    Com_Printf( "Joystick %d opened\n", i );
-    Com_Printf( "Name:    %s\n", SDL_JoystickName(i) );
-    Com_Printf( "Axes:    %d\n", SDL_JoystickNumAxes(stick) );
-    Com_Printf( "Hats:    %d\n", SDL_JoystickNumHats(stick) );
-    Com_Printf( "Buttons: %d\n", SDL_JoystickNumButtons(stick) );
-    Com_Printf( "Balls: %d\n", SDL_JoystickNumBalls(stick) );
-
-    SDL_JoystickEventState(SDL_QUERY);
+  in_joystickNo = Cvar_Get( "in_joystickNo", "0", CVAR_ARCHIVE );
+  if( in_joystickNo->integer < 0 || in_joystickNo->integer >= total )
+    Cvar_Set( "in_joystickNo", "0" );
 
-    /* Our work here is done. */
-    return;
-  }
+  stick = SDL_JoystickOpen( in_joystickNo->integer );
 
-  /* No soup for you. */
-  if( stick == NULL ) {
+  if (stick == NULL) {
     Com_Printf( "No joystick opened.\n" );
     return;
   }
+
+  Com_Printf( "Joystick %d opened\n", in_joystickNo->integer );
+  Com_Printf( "Name:    %s\n", SDL_JoystickName(in_joystickNo->integer) );
+  Com_Printf( "Axes:    %d\n", SDL_JoystickNumAxes(stick) );
+  Com_Printf( "Hats:    %d\n", SDL_JoystickNumHats(stick) );
+  Com_Printf( "Buttons: %d\n", SDL_JoystickNumButtons(stick) );
+  Com_Printf( "Balls: %d\n", SDL_JoystickNumBalls(stick) );
+
+  SDL_JoystickEventState(SDL_QUERY);
+
+  /* Our work here is done. */
+  return;
 }
 
 void IN_JoyMove( void )
 {
     qboolean joy_pressed[ARRAYLEN(joy_keys)];
     unsigned int axes = 0;
+    unsigned int hats = 0;
     int total = 0;
     int i = 0;
 
@@ -1490,7 +1504,94 @@ void IN_JoyMove( void )
         }
     }
 
-    // !!! FIXME: look at the hats...
+    // look at the hats...
+    total = SDL_JoystickNumHats(stick);
+    if (total > 0)
+    {
+        if (total > 4) total = 4;
+        for (i = 0; i < total; i++)
+        {
+	    ((Uint8 *)&hats)[i] = SDL_JoystickGetHat(stick, i);
+        }
+    }
+
+    // update hat state
+    if (hats != stick_state.oldhats)
+    {
+        for( i = 0; i < 4; i++ ) {
+            if( ((Uint8 *)&hats)[i] != ((Uint8 *)&stick_state.oldhats)[i] ) {
+	        // release event
+	        switch( ((Uint8 *)&stick_state.oldhats)[i] ) {
+		case SDL_HAT_UP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHT:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_DOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_LEFT:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHTUP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHTDOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_LEFTUP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL );
+		  break;
+		case SDL_HAT_LEFTDOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL );
+		  break;
+		default:
+		  break;
+		}
+		// press event
+	        switch( ((Uint8 *)&hats)[i] ) {
+		case SDL_HAT_UP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHT:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_DOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_LEFT:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHTUP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_RIGHTDOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_LEFTUP:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL );
+		  break;
+		case SDL_HAT_LEFTDOWN:
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL );
+                  Sys_QueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL );
+		  break;
+		default:
+		  break;
+		}
+            }
+        }
+    }
+
+    // save hat state
+    stick_state.oldhats = hats;
 
     // finally, look at the axes...
     total = SDL_JoystickNumAxes(stick);
-- 
cgit