From 01beb9919b95479d8be040bec74abc5cc67a5e43 Mon Sep 17 00:00:00 2001
From: Mikko Tiusanen <ams@daug.net>
Date: Sun, 4 May 2014 01:18:52 +0300
Subject: Initial import.

---
 src/client/cl_avi.c        |  676 +++++++
 src/client/cl_cgame.c      | 1155 ++++++++++++
 src/client/cl_cin.c        | 1697 +++++++++++++++++
 src/client/cl_console.c    |  624 +++++++
 src/client/cl_curl.c       |  339 ++++
 src/client/cl_curl.h       |  103 ++
 src/client/cl_input.c      | 1010 ++++++++++
 src/client/cl_keys.c       | 1490 +++++++++++++++
 src/client/cl_main.c       | 4418 ++++++++++++++++++++++++++++++++++++++++++++
 src/client/cl_net_chan.c   |  168 ++
 src/client/cl_parse.c      |  910 +++++++++
 src/client/cl_scrn.c       |  571 ++++++
 src/client/cl_ui.c         | 1140 ++++++++++++
 src/client/client.h        |  645 +++++++
 src/client/keycodes.h      |  281 +++
 src/client/keys.h          |   58 +
 src/client/libmumblelink.c |  189 ++
 src/client/libmumblelink.h |   36 +
 src/client/qal.c           |  352 ++++
 src/client/qal.h           |  253 +++
 src/client/snd_adpcm.c     |  330 ++++
 src/client/snd_codec.c     |  238 +++
 src/client/snd_codec.h     |   99 +
 src/client/snd_codec_ogg.c |  479 +++++
 src/client/snd_codec_wav.c |  295 +++
 src/client/snd_dma.c       | 1558 ++++++++++++++++
 src/client/snd_local.h     |  254 +++
 src/client/snd_main.c      |  562 ++++++
 src/client/snd_mem.c       |  270 +++
 src/client/snd_mix.c       |  742 ++++++++
 src/client/snd_openal.c    | 2601 ++++++++++++++++++++++++++
 src/client/snd_public.h    |   85 +
 src/client/snd_wavelet.c   |  254 +++
 33 files changed, 23882 insertions(+)
 create mode 100644 src/client/cl_avi.c
 create mode 100644 src/client/cl_cgame.c
 create mode 100644 src/client/cl_cin.c
 create mode 100644 src/client/cl_console.c
 create mode 100644 src/client/cl_curl.c
 create mode 100644 src/client/cl_curl.h
 create mode 100644 src/client/cl_input.c
 create mode 100644 src/client/cl_keys.c
 create mode 100644 src/client/cl_main.c
 create mode 100644 src/client/cl_net_chan.c
 create mode 100644 src/client/cl_parse.c
 create mode 100644 src/client/cl_scrn.c
 create mode 100644 src/client/cl_ui.c
 create mode 100644 src/client/client.h
 create mode 100644 src/client/keycodes.h
 create mode 100644 src/client/keys.h
 create mode 100644 src/client/libmumblelink.c
 create mode 100644 src/client/libmumblelink.h
 create mode 100644 src/client/qal.c
 create mode 100644 src/client/qal.h
 create mode 100644 src/client/snd_adpcm.c
 create mode 100644 src/client/snd_codec.c
 create mode 100644 src/client/snd_codec.h
 create mode 100644 src/client/snd_codec_ogg.c
 create mode 100644 src/client/snd_codec_wav.c
 create mode 100644 src/client/snd_dma.c
 create mode 100644 src/client/snd_local.h
 create mode 100644 src/client/snd_main.c
 create mode 100644 src/client/snd_mem.c
 create mode 100644 src/client/snd_mix.c
 create mode 100644 src/client/snd_openal.c
 create mode 100644 src/client/snd_public.h
 create mode 100644 src/client/snd_wavelet.c

(limited to 'src/client')

diff --git a/src/client/cl_avi.c b/src/client/cl_avi.c
new file mode 100644
index 0000000..7f673fa
--- /dev/null
+++ b/src/client/cl_avi.c
@@ -0,0 +1,676 @@
+/*
+===========================================================================
+Copyright (C) 2005-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_local.h"
+
+#define INDEX_FILE_EXTENSION ".index.dat"
+
+#define MAX_RIFF_CHUNKS 16
+
+typedef struct audioFormat_s
+{
+  int rate;
+  int format;
+  int channels;
+  int bits;
+
+  int sampleSize;
+  int totalBytes;
+} audioFormat_t;
+
+typedef struct aviFileData_s
+{
+  qboolean      fileOpen;
+  fileHandle_t  f;
+  char          fileName[ MAX_QPATH ];
+  int           fileSize;
+  int           moviOffset;
+  int           moviSize;
+
+  fileHandle_t  idxF;
+  int           numIndices;
+
+  int           frameRate;
+  int           framePeriod;
+  int           width, height;
+  int           numVideoFrames;
+  int           maxRecordSize;
+  qboolean      motionJpeg;
+
+  qboolean      audio;
+  audioFormat_t a;
+  int           numAudioFrames;
+
+  int           chunkStack[ MAX_RIFF_CHUNKS ];
+  int           chunkStackTop;
+
+  byte          *cBuffer, *eBuffer;
+} aviFileData_t;
+
+static aviFileData_t afd;
+
+#define MAX_AVI_BUFFER 2048
+
+static byte buffer[ MAX_AVI_BUFFER ];
+static int  bufIndex;
+
+/*
+===============
+SafeFS_Write
+===============
+*/
+static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f )
+{
+  if( FS_Write( buffer, len, f ) < len )
+    Com_Error( ERR_DROP, "Failed to write avi file\n" );
+}
+
+/*
+===============
+WRITE_STRING
+===============
+*/
+static ID_INLINE void WRITE_STRING( const char *s )
+{
+  Com_Memcpy( &buffer[ bufIndex ], s, strlen( s ) );
+  bufIndex += strlen( s );
+}
+
+/*
+===============
+WRITE_4BYTES
+===============
+*/
+static ID_INLINE void WRITE_4BYTES( int x )
+{
+  buffer[ bufIndex + 0 ] = (byte)( ( x >>  0 ) & 0xFF );
+  buffer[ bufIndex + 1 ] = (byte)( ( x >>  8 ) & 0xFF );
+  buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF );
+  buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF );
+  bufIndex += 4;
+}
+
+/*
+===============
+WRITE_2BYTES
+===============
+*/
+static ID_INLINE void WRITE_2BYTES( int x )
+{
+  buffer[ bufIndex + 0 ] = (byte)( ( x >>  0 ) & 0xFF );
+  buffer[ bufIndex + 1 ] = (byte)( ( x >>  8 ) & 0xFF );
+  bufIndex += 2;
+}
+
+/*
+===============
+WRITE_1BYTES
+===============
+*/
+static ID_INLINE void WRITE_1BYTES( int x )
+{
+  buffer[ bufIndex ] = x;
+  bufIndex += 1;
+}
+
+/*
+===============
+START_CHUNK
+===============
+*/
+static ID_INLINE void START_CHUNK( const char *s )
+{
+  if( afd.chunkStackTop == MAX_RIFF_CHUNKS )
+  {
+    Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached\n" );
+  }
+
+  afd.chunkStack[ afd.chunkStackTop ] = bufIndex;
+  afd.chunkStackTop++;
+  WRITE_STRING( s );
+  WRITE_4BYTES( 0 );
+}
+
+/*
+===============
+END_CHUNK
+===============
+*/
+static ID_INLINE void END_CHUNK( void )
+{
+  int endIndex = bufIndex;
+
+  if( afd.chunkStackTop <= 0 )
+  {
+    Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached\n" );
+  }
+
+  afd.chunkStackTop--;
+  bufIndex = afd.chunkStack[ afd.chunkStackTop ];
+  bufIndex += 4;
+  WRITE_4BYTES( endIndex - bufIndex - 4 );
+  bufIndex = endIndex;
+  bufIndex = PAD( bufIndex, 2 );
+}
+
+/*
+===============
+CL_WriteAVIHeader
+===============
+*/
+void CL_WriteAVIHeader( void )
+{
+  bufIndex = 0;
+  afd.chunkStackTop = 0;
+
+  START_CHUNK( "RIFF" );
+  {
+    WRITE_STRING( "AVI " );
+    {
+      START_CHUNK( "LIST" );
+      {
+        WRITE_STRING( "hdrl" );
+        WRITE_STRING( "avih" );
+        WRITE_4BYTES( 56 );                     //"avih" "chunk" size
+        WRITE_4BYTES( afd.framePeriod );        //dwMicroSecPerFrame
+        WRITE_4BYTES( afd.maxRecordSize *
+            afd.frameRate );                    //dwMaxBytesPerSec
+        WRITE_4BYTES( 0 );                      //dwReserved1
+        WRITE_4BYTES( 0x110 );                  //dwFlags bits HAS_INDEX and IS_INTERLEAVED
+        WRITE_4BYTES( afd.numVideoFrames );     //dwTotalFrames
+        WRITE_4BYTES( 0 );                      //dwInitialFrame
+
+        if( afd.audio )                         //dwStreams
+          WRITE_4BYTES( 2 );
+        else
+          WRITE_4BYTES( 1 );
+
+        WRITE_4BYTES( afd.maxRecordSize );      //dwSuggestedBufferSize
+        WRITE_4BYTES( afd.width );              //dwWidth
+        WRITE_4BYTES( afd.height );             //dwHeight
+        WRITE_4BYTES( 0 );                      //dwReserved[ 0 ]
+        WRITE_4BYTES( 0 );                      //dwReserved[ 1 ]
+        WRITE_4BYTES( 0 );                      //dwReserved[ 2 ]
+        WRITE_4BYTES( 0 );                      //dwReserved[ 3 ]
+
+        START_CHUNK( "LIST" );
+        {
+          WRITE_STRING( "strl" );
+          WRITE_STRING( "strh" );
+          WRITE_4BYTES( 56 );                   //"strh" "chunk" size
+          WRITE_STRING( "vids" );
+
+          if( afd.motionJpeg )
+            WRITE_STRING( "MJPG" );
+          else
+            WRITE_4BYTES( 0 );                  // BI_RGB
+
+          WRITE_4BYTES( 0 );                    //dwFlags
+          WRITE_4BYTES( 0 );                    //dwPriority
+          WRITE_4BYTES( 0 );                    //dwInitialFrame
+
+          WRITE_4BYTES( 1 );                    //dwTimescale
+          WRITE_4BYTES( afd.frameRate );        //dwDataRate
+          WRITE_4BYTES( 0 );                    //dwStartTime
+          WRITE_4BYTES( afd.numVideoFrames );   //dwDataLength
+
+          WRITE_4BYTES( afd.maxRecordSize );    //dwSuggestedBufferSize
+          WRITE_4BYTES( -1 );                   //dwQuality
+          WRITE_4BYTES( 0 );                    //dwSampleSize
+          WRITE_2BYTES( 0 );                    //rcFrame
+          WRITE_2BYTES( 0 );                    //rcFrame
+          WRITE_2BYTES( afd.width );            //rcFrame
+          WRITE_2BYTES( afd.height );           //rcFrame
+
+          WRITE_STRING( "strf" );
+          WRITE_4BYTES( 40 );                   //"strf" "chunk" size
+          WRITE_4BYTES( 40 );                   //biSize
+          WRITE_4BYTES( afd.width );            //biWidth
+          WRITE_4BYTES( afd.height );           //biHeight
+          WRITE_2BYTES( 1 );                    //biPlanes
+          WRITE_2BYTES( 24 );                   //biBitCount
+
+          if( afd.motionJpeg )                  //biCompression
+          {
+            WRITE_STRING( "MJPG" );
+            WRITE_4BYTES( afd.width *
+                afd.height );                   //biSizeImage
+          }
+          else
+          {
+            WRITE_4BYTES( 0 );                  // BI_RGB
+            WRITE_4BYTES( afd.width *
+                afd.height * 3 );               //biSizeImage
+          }
+
+          WRITE_4BYTES( 0 );                    //biXPelsPetMeter
+          WRITE_4BYTES( 0 );                    //biYPelsPetMeter
+          WRITE_4BYTES( 0 );                    //biClrUsed
+          WRITE_4BYTES( 0 );                    //biClrImportant
+        }
+        END_CHUNK( );
+
+        if( afd.audio )
+        {
+          START_CHUNK( "LIST" );
+          {
+            WRITE_STRING( "strl" );
+            WRITE_STRING( "strh" );
+            WRITE_4BYTES( 56 );                 //"strh" "chunk" size
+            WRITE_STRING( "auds" );
+            WRITE_4BYTES( 0 );                  //FCC
+            WRITE_4BYTES( 0 );                  //dwFlags
+            WRITE_4BYTES( 0 );                  //dwPriority
+            WRITE_4BYTES( 0 );                  //dwInitialFrame
+
+            WRITE_4BYTES( afd.a.sampleSize );   //dwTimescale
+            WRITE_4BYTES( afd.a.sampleSize *
+                afd.a.rate );                   //dwDataRate
+            WRITE_4BYTES( 0 );                  //dwStartTime
+            WRITE_4BYTES( afd.a.totalBytes /
+                afd.a.sampleSize );             //dwDataLength
+
+            WRITE_4BYTES( 0 );                  //dwSuggestedBufferSize
+            WRITE_4BYTES( -1 );                 //dwQuality
+            WRITE_4BYTES( afd.a.sampleSize );   //dwSampleSize
+            WRITE_2BYTES( 0 );                  //rcFrame
+            WRITE_2BYTES( 0 );                  //rcFrame
+            WRITE_2BYTES( 0 );                  //rcFrame
+            WRITE_2BYTES( 0 );                  //rcFrame
+
+            WRITE_STRING( "strf" );
+            WRITE_4BYTES( 18 );                 //"strf" "chunk" size
+            WRITE_2BYTES( afd.a.format );       //wFormatTag
+            WRITE_2BYTES( afd.a.channels );     //nChannels
+            WRITE_4BYTES( afd.a.rate );         //nSamplesPerSec
+            WRITE_4BYTES( afd.a.sampleSize *
+                afd.a.rate );                   //nAvgBytesPerSec
+            WRITE_2BYTES( afd.a.sampleSize );   //nBlockAlign
+            WRITE_2BYTES( afd.a.bits );         //wBitsPerSample
+            WRITE_2BYTES( 0 );                  //cbSize
+          }
+          END_CHUNK( );
+        }
+      }
+      END_CHUNK( );
+
+      afd.moviOffset = bufIndex;
+
+      START_CHUNK( "LIST" );
+      {
+        WRITE_STRING( "movi" );
+      }
+    }
+  }
+}
+
+/*
+===============
+CL_OpenAVIForWriting
+
+Creates an AVI file and gets it into a state where
+writing the actual data can begin
+===============
+*/
+qboolean CL_OpenAVIForWriting( const char *fileName )
+{
+  if( afd.fileOpen )
+    return qfalse;
+
+  Com_Memset( &afd, 0, sizeof( aviFileData_t ) );
+
+  // Don't start if a framerate has not been chosen
+  if( cl_aviFrameRate->integer <= 0 )
+  {
+    Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" );
+    return qfalse;
+  }
+
+  if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
+    return qfalse;
+
+  if( ( afd.idxF = FS_FOpenFileWrite(
+          va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 )
+  {
+    FS_FCloseFile( afd.f );
+    return qfalse;
+  }
+
+  Q_strncpyz( afd.fileName, fileName, MAX_QPATH );
+
+  afd.frameRate = cl_aviFrameRate->integer;
+  afd.framePeriod = (int)( 1000000.0f / afd.frameRate );
+  afd.width = cls.glconfig.vidWidth;
+  afd.height = cls.glconfig.vidHeight;
+
+  if( cl_aviMotionJpeg->integer )
+    afd.motionJpeg = qtrue;
+  else
+    afd.motionJpeg = qfalse;
+
+  // Buffers only need to store RGB pixels.
+  // Allocate a bit more space for the capture buffer to account for possible
+  // padding at the end of pixel lines, and padding for alignment
+  #define MAX_PACK_LEN 16
+  afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1);
+  // raw avi files have pixel lines start on 4-byte boundaries
+  afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height);
+
+  afd.a.rate = dma.speed;
+  afd.a.format = WAV_FORMAT_PCM;
+  afd.a.channels = dma.channels;
+  afd.a.bits = dma.samplebits;
+  afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels;
+
+  if( afd.a.rate % afd.frameRate )
+  {
+    int suggestRate = afd.frameRate;
+
+    while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 )
+      suggestRate--;
+
+    Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor "
+        "of the audio rate, suggest %d\n", suggestRate );
+  }
+
+  if( !Cvar_VariableIntegerValue( "s_initsound" ) )
+  {
+    afd.audio = qfalse;
+  }
+  else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) )
+  {
+    if( afd.a.bits != 16 || afd.a.channels != 2 )
+    {
+      Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported",
+          afd.a.bits, afd.a.channels );
+      afd.audio = qfalse;
+    }
+    else
+      afd.audio = qtrue;
+  }
+  else
+  {
+    afd.audio = qfalse;
+    Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported "
+        "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" );
+  }
+
+  // This doesn't write a real header, but allocates the
+  // correct amount of space at the beginning of the file
+  CL_WriteAVIHeader( );
+
+  SafeFS_Write( buffer, bufIndex, afd.f );
+  afd.fileSize = bufIndex;
+
+  bufIndex = 0;
+  START_CHUNK( "idx1" );
+  SafeFS_Write( buffer, bufIndex, afd.idxF );
+
+  afd.moviSize = 4; // For the "movi"
+  afd.fileOpen = qtrue;
+
+  return qtrue;
+}
+
+/*
+===============
+CL_CheckFileSize
+===============
+*/
+static qboolean CL_CheckFileSize( int bytesToAdd )
+{
+  unsigned int newFileSize;
+
+  newFileSize =
+    afd.fileSize +                // Current file size
+    bytesToAdd +                  // What we want to add
+    ( afd.numIndices * 16 ) +     // The index
+    4;                            // The index size
+
+  // I assume all the operating systems
+  // we target can handle a 2Gb file
+  if( newFileSize > INT_MAX )
+  {
+    // Close the current file...
+    CL_CloseAVI( );
+
+    // ...And open a new one
+    CL_OpenAVIForWriting( va( "%s_", afd.fileName ) );
+
+    return qtrue;
+  }
+
+  return qfalse;
+}
+
+/*
+===============
+CL_WriteAVIVideoFrame
+===============
+*/
+void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size )
+{
+  int   chunkOffset = afd.fileSize - afd.moviOffset - 8;
+  int   chunkSize = 8 + size;
+  int   paddingSize = PADLEN(size, 2);
+  byte  padding[ 4 ] = { 0 };
+
+  if( !afd.fileOpen )
+    return;
+
+  // Chunk header + contents + padding
+  if( CL_CheckFileSize( 8 + size + 2 ) )
+    return;
+
+  bufIndex = 0;
+  WRITE_STRING( "00dc" );
+  WRITE_4BYTES( size );
+
+  SafeFS_Write( buffer, 8, afd.f );
+  SafeFS_Write( imageBuffer, size, afd.f );
+  SafeFS_Write( padding, paddingSize, afd.f );
+  afd.fileSize += ( chunkSize + paddingSize );
+
+  afd.numVideoFrames++;
+  afd.moviSize += ( chunkSize + paddingSize );
+
+  if( size > afd.maxRecordSize )
+    afd.maxRecordSize = size;
+
+  // Index
+  bufIndex = 0;
+  WRITE_STRING( "00dc" );           //dwIdentifier
+  WRITE_4BYTES( 0x00000010 );       //dwFlags (all frames are KeyFrames)
+  WRITE_4BYTES( chunkOffset );      //dwOffset
+  WRITE_4BYTES( size );             //dwLength
+  SafeFS_Write( buffer, 16, afd.idxF );
+
+  afd.numIndices++;
+}
+
+#define PCM_BUFFER_SIZE 44100
+
+/*
+===============
+CL_WriteAVIAudioFrame
+===============
+*/
+void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
+{
+  static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 };
+  static int  bytesInBuffer = 0;
+
+  if( !afd.audio )
+    return;
+
+  if( !afd.fileOpen )
+    return;
+
+  // Chunk header + contents + padding
+  if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) )
+    return;
+
+  if( bytesInBuffer + size > PCM_BUFFER_SIZE )
+  {
+    Com_Printf( S_COLOR_YELLOW
+        "WARNING: Audio capture buffer overflow -- truncating\n" );
+    size = PCM_BUFFER_SIZE - bytesInBuffer;
+  }
+
+  Com_Memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size );
+  bytesInBuffer += size;
+
+  // Only write if we have a frame's worth of audio
+  if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) *
+        afd.a.sampleSize )
+  {
+    int   chunkOffset = afd.fileSize - afd.moviOffset - 8;
+    int   chunkSize = 8 + bytesInBuffer;
+    int   paddingSize = PADLEN(bytesInBuffer, 2);
+    byte  padding[ 4 ] = { 0 };
+
+    bufIndex = 0;
+    WRITE_STRING( "01wb" );
+    WRITE_4BYTES( bytesInBuffer );
+
+    SafeFS_Write( buffer, 8, afd.f );
+    SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f );
+    SafeFS_Write( padding, paddingSize, afd.f );
+    afd.fileSize += ( chunkSize + paddingSize );
+
+    afd.numAudioFrames++;
+    afd.moviSize += ( chunkSize + paddingSize );
+    afd.a.totalBytes += bytesInBuffer;
+
+    // Index
+    bufIndex = 0;
+    WRITE_STRING( "01wb" );           //dwIdentifier
+    WRITE_4BYTES( 0 );                //dwFlags
+    WRITE_4BYTES( chunkOffset );      //dwOffset
+    WRITE_4BYTES( bytesInBuffer );    //dwLength
+    SafeFS_Write( buffer, 16, afd.idxF );
+
+    afd.numIndices++;
+
+    bytesInBuffer = 0;
+  }
+}
+
+/*
+===============
+CL_TakeVideoFrame
+===============
+*/
+void CL_TakeVideoFrame( void )
+{
+  // AVI file isn't open
+  if( !afd.fileOpen )
+    return;
+
+  re.TakeVideoFrame( afd.width, afd.height,
+      afd.cBuffer, afd.eBuffer, afd.motionJpeg );
+}
+
+/*
+===============
+CL_CloseAVI
+
+Closes the AVI file and writes an index chunk
+===============
+*/
+qboolean CL_CloseAVI( void )
+{
+  int indexRemainder;
+  int indexSize = afd.numIndices * 16;
+  const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName );
+
+  // AVI file isn't open
+  if( !afd.fileOpen )
+    return qfalse;
+
+  afd.fileOpen = qfalse;
+
+  FS_Seek( afd.idxF, 4, FS_SEEK_SET );
+  bufIndex = 0;
+  WRITE_4BYTES( indexSize );
+  SafeFS_Write( buffer, bufIndex, afd.idxF );
+  FS_FCloseFile( afd.idxF );
+
+  // Write index
+
+  // Open the temp index file
+  if( ( indexSize = FS_FOpenFileRead( idxFileName,
+          &afd.idxF, qtrue ) ) <= 0 )
+  {
+    FS_FCloseFile( afd.f );
+    return qfalse;
+  }
+
+  indexRemainder = indexSize;
+
+  // Append index to end of avi file
+  while( indexRemainder > MAX_AVI_BUFFER )
+  {
+    FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF );
+    SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f );
+    afd.fileSize += MAX_AVI_BUFFER;
+    indexRemainder -= MAX_AVI_BUFFER;
+  }
+  FS_Read( buffer, indexRemainder, afd.idxF );
+  SafeFS_Write( buffer, indexRemainder, afd.f );
+  afd.fileSize += indexRemainder;
+  FS_FCloseFile( afd.idxF );
+
+  // Remove temp index file
+  FS_HomeRemove( idxFileName );
+
+  // Write the real header
+  FS_Seek( afd.f, 0, FS_SEEK_SET );
+  CL_WriteAVIHeader( );
+
+  bufIndex = 4;
+  WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size
+
+  bufIndex = afd.moviOffset + 4;    // Skip "LIST"
+  WRITE_4BYTES( afd.moviSize );
+
+  SafeFS_Write( buffer, bufIndex, afd.f );
+
+  Z_Free( afd.cBuffer );
+  Z_Free( afd.eBuffer );
+  FS_FCloseFile( afd.f );
+
+  Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
+
+  return qtrue;
+}
+
+/*
+===============
+CL_VideoRecording
+===============
+*/
+qboolean CL_VideoRecording( void )
+{
+  return afd.fileOpen;
+}
diff --git a/src/client/cl_cgame.c b/src/client/cl_cgame.c
new file mode 100644
index 0000000..babc524
--- /dev/null
+++ b/src/client/cl_cgame.c
@@ -0,0 +1,1155 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+// cl_cgame.c  -- client system interaction with client game
+
+#include "client.h"
+#include "libmumblelink.h"
+
+extern qboolean loadCamera(const char *name);
+extern void startCamera(int time);
+extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles);
+
+/*
+====================
+CL_GetGameState
+====================
+*/
+void CL_GetGameState( gameState_t *gs ) {
+	*gs = cl.gameState;
+}
+
+/*
+====================
+CL_GetGlconfig
+====================
+*/
+void CL_GetGlconfig( glconfig_t *glconfig ) {
+	*glconfig = cls.glconfig;
+}
+
+
+/*
+====================
+CL_GetUserCmd
+====================
+*/
+qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) {
+	// cmds[cmdNumber] is the last properly generated command
+
+	// can't return anything that we haven't created yet
+	if ( cmdNumber > cl.cmdNumber ) {
+		Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber );
+	}
+
+	// the usercmd has been overwritten in the wrapping
+	// buffer because it is too far out of date
+	if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) {
+		return qfalse;
+	}
+
+	*ucmd = cl.cmds[ cmdNumber & CMD_MASK ];
+
+	return qtrue;
+}
+
+int CL_GetCurrentCmdNumber( void ) {
+	return cl.cmdNumber;
+}
+
+
+/*
+====================
+CL_GetParseEntityState
+====================
+*/
+qboolean	CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) {
+	// can't return anything that hasn't been parsed yet
+	if ( parseEntityNumber >= cl.parseEntitiesNum ) {
+		Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i",
+			parseEntityNumber, cl.parseEntitiesNum );
+	}
+
+	// can't return anything that has been overwritten in the circular buffer
+	if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) {
+		return qfalse;
+	}
+
+	*state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ];
+	return qtrue;
+}
+
+/*
+====================
+CL_GetCurrentSnapshotNumber
+====================
+*/
+void	CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) {
+	*snapshotNumber = cl.snap.messageNum;
+	*serverTime = cl.snap.serverTime;
+}
+
+/*
+====================
+CL_GetSnapshot
+====================
+*/
+qboolean	CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) {
+	clSnapshot_t	*clSnap;
+	int				i, count;
+
+	if ( snapshotNumber > cl.snap.messageNum ) {
+		Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" );
+	}
+
+	// if the frame has fallen out of the circular buffer, we can't return it
+	if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) {
+		return qfalse;
+	}
+
+	// if the frame is not valid, we can't return it
+	clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK];
+	if ( !clSnap->valid ) {
+		return qfalse;
+	}
+
+	// if the entities in the frame have fallen out of their
+	// circular buffer, we can't return it
+	if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) {
+		return qfalse;
+	}
+
+	// write the snapshot
+	snapshot->snapFlags = clSnap->snapFlags;
+	snapshot->serverCommandSequence = clSnap->serverCommandNum;
+	snapshot->ping = clSnap->ping;
+	snapshot->serverTime = clSnap->serverTime;
+	Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) );
+	snapshot->ps = clSnap->ps;
+	count = clSnap->numEntities;
+	if ( count > MAX_ENTITIES_IN_SNAPSHOT ) {
+		Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT );
+		count = MAX_ENTITIES_IN_SNAPSHOT;
+	}
+	snapshot->numEntities = count;
+	for ( i = 0 ; i < count ; i++ ) {
+		snapshot->entities[i] = 
+			cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ];
+	}
+
+	// FIXME: configstring changes and server commands!!!
+
+	return qtrue;
+}
+
+/*
+=====================
+CL_SetUserCmdValue
+=====================
+*/
+void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) {
+	cl.cgameUserCmdValue = userCmdValue;
+	cl.cgameSensitivity = sensitivityScale;
+}
+
+/*
+=====================
+CL_AddCgameCommand
+=====================
+*/
+void CL_AddCgameCommand( const char *cmdName ) {
+	Cmd_AddCommand( cmdName, NULL );
+}
+
+/*
+=====================
+CL_CgameError
+=====================
+*/
+void CL_CgameError( const char *string ) {
+	Com_Error( ERR_DROP, "%s", string );
+}
+
+
+/*
+=====================
+CL_ConfigstringModified
+=====================
+*/
+void CL_ConfigstringModified( void ) {
+	char		*old, *s;
+	int			i, index;
+	char		*dup;
+	gameState_t	oldGs;
+	int			len;
+
+	index = atoi( Cmd_Argv(1) );
+	if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
+		Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
+	}
+	// get everything after "cs <num>"
+	s = Cmd_ArgsFrom(2);
+
+	old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ];
+	if ( !strcmp( old, s ) ) {
+		return;		// unchanged
+	}
+
+	// build the new gameState_t
+	oldGs = cl.gameState;
+
+	Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
+
+	// leave the first 0 for uninitialized strings
+	cl.gameState.dataCount = 1;
+		
+	for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
+		if ( i == index ) {
+			dup = s;
+		} else {
+			dup = oldGs.stringData + oldGs.stringOffsets[ i ];
+		}
+		if ( !dup[0] ) {
+			continue;		// leave with the default empty string
+		}
+
+		len = strlen( dup );
+
+		if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) {
+			Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" );
+		}
+
+		// append it to the gameState string buffer
+		cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
+		Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 );
+		cl.gameState.dataCount += len + 1;
+	}
+
+	if ( index == CS_SYSTEMINFO ) {
+		// parse serverId and other cvars
+		CL_SystemInfoChanged();
+	}
+
+}
+
+
+/*
+===================
+CL_GetServerCommand
+
+Set up argc/argv for the given command
+===================
+*/
+qboolean CL_GetServerCommand( int serverCommandNumber ) {
+	char	*s;
+	char	*cmd;
+	static char bigConfigString[BIG_INFO_STRING];
+	int argc;
+
+	// if we have irretrievably lost a reliable command, drop the connection
+	if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) {
+		// when a demo record was started after the client got a whole bunch of
+		// reliable commands then the client never got those first reliable commands
+		if ( clc.demoplaying )
+			return qfalse;
+		Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" );
+		return qfalse;
+	}
+
+	if ( serverCommandNumber > clc.serverCommandSequence ) {
+		Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" );
+		return qfalse;
+	}
+
+	s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ];
+	clc.lastExecutedServerCommand = serverCommandNumber;
+
+	Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s );
+
+rescan:
+	Cmd_TokenizeString( s );
+	cmd = Cmd_Argv(0);
+	argc = Cmd_Argc();
+
+	if ( !strcmp( cmd, "disconnect" ) ) {
+		// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552
+		// allow server to indicate why they were disconnected
+		if ( argc >= 2 )
+			Com_Error( ERR_SERVERDISCONNECT, "Server disconnected - %s", Cmd_Argv( 1 ) );
+		else
+			Com_Error( ERR_SERVERDISCONNECT, "Server disconnected\n" );
+	}
+
+	if ( !strcmp( cmd, "bcs0" ) ) {
+		Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) );
+		return qfalse;
+	}
+
+	if ( !strcmp( cmd, "bcs1" ) ) {
+		s = Cmd_Argv(2);
+		if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) {
+			Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" );
+		}
+		strcat( bigConfigString, s );
+		return qfalse;
+	}
+
+	if ( !strcmp( cmd, "bcs2" ) ) {
+		s = Cmd_Argv(2);
+		if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) {
+			Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" );
+		}
+		strcat( bigConfigString, s );
+		strcat( bigConfigString, "\"" );
+		s = bigConfigString;
+		goto rescan;
+	}
+
+	if ( !strcmp( cmd, "cs" ) ) {
+		CL_ConfigstringModified();
+		// reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString()
+		Cmd_TokenizeString( s );
+		return qtrue;
+	}
+
+	if ( !strcmp( cmd, "map_restart" ) ) {
+		// clear notify lines and outgoing commands before passing
+		// the restart to the cgame
+		Con_ClearNotify();
+		// reparse the string, because Con_ClearNotify() may have done another Cmd_TokenizeString()
+		Cmd_TokenizeString( s );
+		Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) );
+		return qtrue;
+	}
+
+	// the clientLevelShot command is used during development
+	// to generate 128*128 screenshots from the intermission
+	// point of levels for the menu system to use
+	// we pass it along to the cgame to make apropriate adjustments,
+	// but we also clear the console and notify lines here
+	if ( !strcmp( cmd, "clientLevelShot" ) ) {
+		// don't do it if we aren't running the server locally,
+		// otherwise malicious remote servers could overwrite
+		// the existing thumbnails
+		if ( !com_sv_running->integer ) {
+			return qfalse;
+		}
+		// close the console
+		Con_Close();
+		// take a special screenshot next frame
+		Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" );
+		return qtrue;
+	}
+
+	// we may want to put a "connect to other server" command here
+
+	// cgame can now act on the command
+	return qtrue;
+}
+
+
+/*
+====================
+CL_CM_LoadMap
+
+Just adds default parameters that cgame doesn't need to know about
+====================
+*/
+void CL_CM_LoadMap( const char *mapname ) {
+	int		checksum;
+
+	CM_LoadMap( mapname, qtrue, &checksum );
+}
+
+/*
+====================
+CL_ShutdonwCGame
+
+====================
+*/
+void CL_ShutdownCGame( void ) {
+	Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
+	cls.cgameStarted = qfalse;
+	if ( !cgvm ) {
+		return;
+	}
+	VM_Call( cgvm, CG_SHUTDOWN );
+	VM_Free( cgvm );
+	cgvm = NULL;
+}
+
+static int	FloatAsInt( float f ) {
+	floatint_t fi;
+	fi.f = f;
+	return fi.i;
+}
+
+/*
+====================
+CL_CgameSystemCalls
+
+The cgame module is making a system call
+====================
+*/
+intptr_t CL_CgameSystemCalls( intptr_t *args ) {
+	switch( args[0] ) {
+	case CG_PRINT:
+		Com_Printf( "%s", (const char*)VMA(1) );
+		return 0;
+	case CG_ERROR:
+		Com_Error( ERR_DROP, "%s", (const char*)VMA(1) );
+		return 0;
+	case CG_MILLISECONDS:
+		return Sys_Milliseconds();
+	case CG_CVAR_REGISTER:
+		Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); 
+		return 0;
+	case CG_CVAR_UPDATE:
+		Cvar_Update( VMA(1) );
+		return 0;
+	case CG_CVAR_SET:
+		Cvar_SetSafe( VMA(1), VMA(2) );
+		return 0;
+	case CG_CVAR_VARIABLESTRINGBUFFER:
+		Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] );
+		return 0;
+	case CG_ARGC:
+		return Cmd_Argc();
+	case CG_ARGV:
+		Cmd_ArgvBuffer( args[1], VMA(2), args[3] );
+		return 0;
+	case CG_ARGS:
+		Cmd_ArgsBuffer( VMA(1), args[2] );
+		return 0;
+	case CG_LITERAL_ARGS:
+		Cmd_LiteralArgsBuffer( VMA(1), args[2] );
+		return 0;
+	case CG_FS_FOPENFILE:
+		return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] );
+	case CG_FS_READ:
+		FS_Read2( VMA(1), args[2], args[3] );
+		return 0;
+	case CG_FS_WRITE:
+		FS_Write( VMA(1), args[2], args[3] );
+		return 0;
+	case CG_FS_FCLOSEFILE:
+		FS_FCloseFile( args[1] );
+		return 0;
+	case CG_FS_SEEK:
+		return FS_Seek( args[1], args[2], args[3] );
+	case CG_FS_GETFILELIST:
+		return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] );
+	case CG_SENDCONSOLECOMMAND:
+		Cbuf_AddText( VMA(1) );
+		return 0;
+	case CG_ADDCOMMAND:
+		CL_AddCgameCommand( VMA(1) );
+		return 0;
+	case CG_REMOVECOMMAND:
+		Cmd_RemoveCommandSafe( VMA(1) );
+		return 0;
+	case CG_SENDCLIENTCOMMAND:
+		CL_AddReliableCommand(VMA(1), qfalse);
+		return 0;
+	case CG_UPDATESCREEN:
+		// this is used during lengthy level loading, so pump message loop
+//		Com_EventLoop();	// FIXME: if a server restarts here, BAD THINGS HAPPEN!
+// We can't call Com_EventLoop here, a restart will crash and this _does_ happen
+// if there is a map change while we are downloading at pk3.
+// ZOID
+		SCR_UpdateScreen();
+		return 0;
+	case CG_CM_LOADMAP:
+		CL_CM_LoadMap( VMA(1) );
+		return 0;
+	case CG_CM_NUMINLINEMODELS:
+		return CM_NumInlineModels();
+	case CG_CM_INLINEMODEL:
+		return CM_InlineModel( args[1] );
+	case CG_CM_TEMPBOXMODEL:
+		return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse );
+	case CG_CM_TEMPCAPSULEMODEL:
+		return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue );
+	case CG_CM_POINTCONTENTS:
+		return CM_PointContents( VMA(1), args[2] );
+	case CG_CM_TRANSFORMEDPOINTCONTENTS:
+		return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) );
+	case CG_CM_BOXTRACE:
+		CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_AABB );
+		return 0;
+	case CG_CM_CAPSULETRACE:
+		CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_CAPSULE );
+		return 0;
+	case CG_CM_TRANSFORMEDBOXTRACE:
+		CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5),
+				args[6], args[7], VMA(8), VMA(9), TT_AABB );
+		return 0;
+	case CG_CM_TRANSFORMEDCAPSULETRACE:
+		CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5),
+				args[6], args[7], VMA(8), VMA(9), TT_CAPSULE );
+		return 0;
+	case CG_CM_BISPHERETRACE:
+		CM_BiSphereTrace( VMA(1), VMA(2), VMA(3), VMF(4), VMF(5), args[6], args[7] );
+		return 0;
+	case CG_CM_TRANSFORMEDBISPHERETRACE:
+		CM_TransformedBiSphereTrace( VMA(1), VMA(2), VMA(3), VMF(4), VMF(5),
+				args[6], args[7], VMA(8) );
+		return 0;
+	case CG_CM_MARKFRAGMENTS:
+		return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) );
+	case CG_S_STARTSOUND:
+		S_StartSound( VMA(1), args[2], args[3], args[4] );
+		return 0;
+	case CG_S_STARTLOCALSOUND:
+		S_StartLocalSound( args[1], args[2] );
+		return 0;
+	case CG_S_CLEARLOOPINGSOUNDS:
+		S_ClearLoopingSounds(args[1]);
+		return 0;
+	case CG_S_ADDLOOPINGSOUND:
+		S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] );
+		return 0;
+	case CG_S_ADDREALLOOPINGSOUND:
+		S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] );
+		return 0;
+	case CG_S_STOPLOOPINGSOUND:
+		S_StopLoopingSound( args[1] );
+		return 0;
+	case CG_S_UPDATEENTITYPOSITION:
+		S_UpdateEntityPosition( args[1], VMA(2) );
+		return 0;
+	case CG_S_RESPATIALIZE:
+		S_Respatialize( args[1], VMA(2), VMA(3), args[4] );
+		return 0;
+	case CG_S_REGISTERSOUND:
+		return S_RegisterSound( VMA(1), args[2] );
+	case CG_S_SOUNDDURATION:
+		return S_SoundDuration( args[1] );
+	case CG_S_STARTBACKGROUNDTRACK:
+		S_StartBackgroundTrack( VMA(1), VMA(2) );
+		return 0;
+	case CG_R_LOADWORLDMAP:
+		re.LoadWorld( VMA(1) );
+		return 0; 
+	case CG_R_REGISTERMODEL:
+		return re.RegisterModel( VMA(1) );
+	case CG_R_REGISTERSKIN:
+		return re.RegisterSkin( VMA(1) );
+	case CG_R_REGISTERSHADER:
+		return re.RegisterShader( VMA(1) );
+	case CG_R_REGISTERSHADERNOMIP:
+		return re.RegisterShaderNoMip( VMA(1) );
+	case CG_R_REGISTERFONT:
+		re.RegisterFont( VMA(1), args[2], VMA(3));
+	case CG_R_CLEARSCENE:
+		re.ClearScene();
+		return 0;
+	case CG_R_ADDREFENTITYTOSCENE:
+		re.AddRefEntityToScene( VMA(1) );
+		return 0;
+	case CG_R_ADDPOLYTOSCENE:
+		re.AddPolyToScene( args[1], args[2], VMA(3), 1 );
+		return 0;
+	case CG_R_ADDPOLYSTOSCENE:
+		re.AddPolyToScene( args[1], args[2], VMA(3), args[4] );
+		return 0;
+	case CG_R_LIGHTFORPOINT:
+		return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) );
+	case CG_R_ADDLIGHTTOSCENE:
+		re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+		return 0;
+	case CG_R_ADDADDITIVELIGHTTOSCENE:
+		re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+		return 0;
+	case CG_R_RENDERSCENE:
+		re.RenderScene( VMA(1) );
+		return 0;
+	case CG_R_SETCOLOR:
+		re.SetColor( VMA(1) );
+		return 0;
+	case CG_R_SETCLIPREGION:
+		re.SetClipRegion( VMA(1) );
+		return 0;
+	case CG_R_DRAWSTRETCHPIC:
+		re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] );
+		return 0;
+	case CG_R_MODELBOUNDS:
+		re.ModelBounds( args[1], VMA(2), VMA(3) );
+		return 0;
+	case CG_R_LERPTAG:
+		return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) );
+	case CG_GETGLCONFIG:
+		CL_GetGlconfig( VMA(1) );
+		return 0;
+	case CG_GETGAMESTATE:
+		CL_GetGameState( VMA(1) );
+		return 0;
+	case CG_GETCURRENTSNAPSHOTNUMBER:
+		CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) );
+		return 0;
+	case CG_GETSNAPSHOT:
+		return CL_GetSnapshot( args[1], VMA(2) );
+	case CG_GETSERVERCOMMAND:
+		return CL_GetServerCommand( args[1] );
+	case CG_GETCURRENTCMDNUMBER:
+		return CL_GetCurrentCmdNumber();
+	case CG_GETUSERCMD:
+		return CL_GetUserCmd( args[1], VMA(2) );
+	case CG_SETUSERCMDVALUE:
+		CL_SetUserCmdValue( args[1], VMF(2) );
+		return 0;
+	case CG_MEMORY_REMAINING:
+		return Hunk_MemoryRemaining();
+  case CG_KEY_ISDOWN:
+		return Key_IsDown( args[1] );
+  case CG_KEY_GETCATCHER:
+		return Key_GetCatcher();
+  case CG_KEY_SETCATCHER:
+		// Don't allow the cgame module to close the console
+		Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) );
+    return 0;
+  case CG_KEY_GETKEY:
+		return Key_GetKey( VMA(1) );
+
+	case CG_GETDEMOSTATE:
+		return CL_DemoState( );
+	case CG_GETDEMOPOS:
+		return CL_DemoPos( );
+	case CG_GETDEMONAME:
+		CL_DemoName( VMA(1), args[2] );
+		return 0;
+
+	case CG_KEY_KEYNUMTOSTRINGBUF:
+		Key_KeynumToStringBuf( args[1], VMA(2), args[3] );
+		return 0;
+	case CG_KEY_GETBINDINGBUF:
+		Key_GetBindingBuf( args[1], VMA(2), args[3] );
+		return 0;
+	case CG_KEY_SETBINDING:
+		Key_SetBinding( args[1], VMA(2) );
+		return 0;
+
+	case CG_PARSE_ADD_GLOBAL_DEFINE:
+		return Parse_AddGlobalDefine( VMA(1) );
+	case CG_PARSE_LOAD_SOURCE:
+		return Parse_LoadSourceHandle( VMA(1) );
+	case CG_PARSE_FREE_SOURCE:
+		return Parse_FreeSourceHandle( args[1] );
+	case CG_PARSE_READ_TOKEN:
+		return Parse_ReadTokenHandle( args[1], VMA(2) );
+	case CG_PARSE_SOURCE_FILE_AND_LINE:
+		return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) );
+
+	case CG_KEY_SETOVERSTRIKEMODE:
+		Key_SetOverstrikeMode( args[1] );
+    return 0;
+	case CG_KEY_GETOVERSTRIKEMODE:
+		return Key_GetOverstrikeMode( );
+
+	case CG_MEMSET:
+		Com_Memset( VMA(1), args[2], args[3] );
+		return 0;
+	case CG_MEMCPY:
+		Com_Memcpy( VMA(1), VMA(2), args[3] );
+		return 0;
+	case CG_STRNCPY:
+		strncpy( VMA(1), VMA(2), args[3] );
+		return args[1];
+	case CG_SIN:
+		return FloatAsInt( sin( VMF(1) ) );
+	case CG_COS:
+		return FloatAsInt( cos( VMF(1) ) );
+	case CG_ATAN2:
+		return FloatAsInt( atan2( VMF(1), VMF(2) ) );
+	case CG_SQRT:
+		return FloatAsInt( sqrt( VMF(1) ) );
+	case CG_FLOOR:
+		return FloatAsInt( floor( VMF(1) ) );
+	case CG_CEIL:
+		return FloatAsInt( ceil( VMF(1) ) );
+	case CG_ACOS:
+		return FloatAsInt( Q_acos( VMF(1) ) );
+
+	case CG_S_STOPBACKGROUNDTRACK:
+		S_StopBackgroundTrack();
+		return 0;
+
+	case CG_REAL_TIME:
+		return Com_RealTime( VMA(1) );
+	case CG_SNAPVECTOR:
+		Sys_SnapVector( VMA(1) );
+		return 0;
+
+	case CG_CIN_PLAYCINEMATIC:
+	  return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]);
+
+	case CG_CIN_STOPCINEMATIC:
+	  return CIN_StopCinematic(args[1]);
+
+	case CG_CIN_RUNCINEMATIC:
+	  return CIN_RunCinematic(args[1]);
+
+	case CG_CIN_DRAWCINEMATIC:
+	  CIN_DrawCinematic(args[1]);
+	  return 0;
+
+	case CG_CIN_SETEXTENTS:
+	  CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]);
+	  return 0;
+
+	case CG_R_REMAP_SHADER:
+		re.RemapShader( VMA(1), VMA(2), VMA(3) );
+		return 0;
+
+/*
+	case CG_LOADCAMERA:
+		return loadCamera(VMA(1));
+
+	case CG_STARTCAMERA:
+		startCamera(args[1]);
+		return 0;
+
+	case CG_GETCAMERAINFO:
+		return getCameraInfo(args[1], VMA(2), VMA(3));
+*/
+	case CG_GET_ENTITY_TOKEN:
+		return re.GetEntityToken( VMA(1), args[2] );
+	case CG_R_INPVS:
+		return re.inPVS( VMA(1), VMA(2) );
+
+	default:
+	        assert(0);
+		Com_Error( ERR_DROP, "Bad cgame system trap: %ld", (long int) args[0] );
+	}
+	return 0;
+}
+
+
+/*
+====================
+CL_InitCGame
+
+Should only be called by CL_StartHunkUsers
+====================
+*/
+void CL_InitCGame( void ) {
+	const char			*info;
+	const char			*mapname;
+	int					t1, t2;
+	vmInterpret_t		interpret;
+
+	t1 = Sys_Milliseconds();
+
+	// put away the console
+	Con_Close();
+
+	// find the current mapname
+	info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ];
+	mapname = Info_ValueForKey( info, "mapname" );
+	Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname );
+
+	// load the dll or bytecode
+	if ( cl_connectedToPureServer != 0 ) {
+		// if sv_pure is set we only allow qvms to be loaded
+		interpret = VMI_COMPILED;
+	}
+	else {
+		interpret = Cvar_VariableValue( "vm_cgame" );
+	}
+	cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret );
+	if ( !cgvm ) {
+		Com_Error( ERR_DROP, "VM_Create on cgame failed" );
+	}
+	cls.state = CA_LOADING;
+
+	// init for this gamestate
+	// use the lastExecutedServerCommand instead of the serverCommandSequence
+	// otherwise server commands sent just before a gamestate are dropped
+	VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum );
+
+	// reset any CVAR_CHEAT cvars registered by cgame
+	if ( !clc.demoplaying && !cl_connectedToCheatServer )
+		Cvar_SetCheatState();
+
+	// we will send a usercmd this frame, which
+	// will cause the server to send us the first snapshot
+	cls.state = CA_PRIMED;
+
+	t2 = Sys_Milliseconds();
+
+	Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 );
+
+	// have the renderer touch all its images, so they are present
+	// on the card even if the driver does deferred loading
+	re.EndRegistration();
+
+	// make sure everything is paged in
+	if (!Sys_LowPhysicalMemory()) {
+		Com_TouchMemory();
+	}
+
+	// clear anything that got printed
+	Con_ClearNotify ();
+}
+
+
+/*
+====================
+CL_GameCommand
+
+See if the current console command is claimed by the cgame
+====================
+*/
+qboolean CL_GameCommand( void ) {
+	if ( !cgvm ) {
+		return qfalse;
+	}
+
+	return VM_Call( cgvm, CG_CONSOLE_COMMAND );
+}
+
+/*
+====================
+CL_GameConsoleText
+====================
+*/
+void CL_GameConsoleText( void ) {
+	if ( !cgvm ) {
+		return;
+	}
+
+	VM_Call( cgvm, CG_CONSOLE_TEXT );
+}
+
+
+/*
+=====================
+CL_CGameRendering
+=====================
+*/
+void CL_CGameRendering( stereoFrame_t stereo ) {
+	VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying );
+	VM_Debug( 0 );
+}
+
+
+/*
+=================
+CL_AdjustTimeDelta
+
+Adjust the clients view of server time.
+
+We attempt to have cl.serverTime exactly equal the server's view
+of time plus the timeNudge, but with variable latencies over
+the internet it will often need to drift a bit to match conditions.
+
+Our ideal time would be to have the adjusted time approach, but not pass,
+the very latest snapshot.
+
+Adjustments are only made when a new snapshot arrives with a rational
+latency, which keeps the adjustment process framerate independent and
+prevents massive overadjustment during times of significant packet loss
+or bursted delayed packets.
+=================
+*/
+
+#define	RESET_TIME	500
+
+void CL_AdjustTimeDelta( void ) {
+	int		resetTime;
+	int		newDelta;
+	int		deltaDelta;
+
+	cl.newSnapshots = qfalse;
+
+	// the delta never drifts when replaying a demo
+	if ( clc.demoplaying ) {
+		return;
+	}
+
+	// if the current time is WAY off, just correct to the current value
+	if ( com_sv_running->integer ) {
+		resetTime = 100;
+	} else {
+		resetTime = RESET_TIME;
+	}
+
+	newDelta = cl.snap.serverTime - cls.realtime;
+	deltaDelta = abs( newDelta - cl.serverTimeDelta );
+
+	if ( deltaDelta > RESET_TIME ) {
+		cl.serverTimeDelta = newDelta;
+		cl.oldServerTime = cl.snap.serverTime;	// FIXME: is this a problem for cgame?
+		cl.serverTime = cl.snap.serverTime;
+		if ( cl_showTimeDelta->integer ) {
+			Com_Printf( "<RESET> " );
+		}
+	} else if ( deltaDelta > 100 ) {
+		// fast adjust, cut the difference in half
+		if ( cl_showTimeDelta->integer ) {
+			Com_Printf( "<FAST> " );
+		}
+		cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1;
+	} else {
+		// slow drift adjust, only move 1 or 2 msec
+
+		// if any of the frames between this and the previous snapshot
+		// had to be extrapolated, nudge our sense of time back a little
+		// the granularity of +1 / -2 is too high for timescale modified frametimes
+		if ( com_timescale->value == 0 || com_timescale->value == 1 ) {
+			if ( cl.extrapolatedSnapshot ) {
+				cl.extrapolatedSnapshot = qfalse;
+				cl.serverTimeDelta -= 2;
+			} else {
+				// otherwise, move our sense of time forward to minimize total latency
+				cl.serverTimeDelta++;
+			}
+		}
+	}
+
+	if ( cl_showTimeDelta->integer ) {
+		Com_Printf( "%i ", cl.serverTimeDelta );
+	}
+}
+
+
+/*
+==================
+CL_FirstSnapshot
+==================
+*/
+void CL_FirstSnapshot( void ) {
+	// ignore snapshots that don't have entities
+	if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) {
+		return;
+	}
+	cls.state = CA_ACTIVE;
+
+	// set the timedelta so we are exactly on this first frame
+	cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
+	cl.oldServerTime = cl.snap.serverTime;
+
+	clc.timeDemoBaseTime = cl.snap.serverTime;
+
+	// if this is the first frame of active play,
+	// execute the contents of activeAction now
+	// this is to allow scripting a timedemo to start right
+	// after loading
+	if ( cl_activeAction->string[0] ) {
+		Cbuf_AddText( cl_activeAction->string );
+		Cvar_Set( "activeAction", "" );
+	}
+
+#ifdef USE_MUMBLE
+	if ((cl_useMumble->integer) && !mumble_islinked()) {
+		int ret = mumble_link(CLIENT_WINDOW_TITLE);
+		Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed");
+	}
+#endif
+
+#ifdef USE_VOIP
+	if (!clc.speexInitialized) {
+		int i;
+		speex_bits_init(&clc.speexEncoderBits);
+		speex_bits_reset(&clc.speexEncoderBits);
+
+		clc.speexEncoder = speex_encoder_init(&speex_nb_mode);
+
+		speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE,
+		                  &clc.speexFrameSize);
+		speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE,
+		                  &clc.speexSampleRate);
+
+		clc.speexPreprocessor = speex_preprocess_state_init(clc.speexFrameSize,
+		                                                  clc.speexSampleRate);
+
+		i = 1;
+		speex_preprocess_ctl(clc.speexPreprocessor,
+		                     SPEEX_PREPROCESS_SET_DENOISE, &i);
+
+		i = 1;
+		speex_preprocess_ctl(clc.speexPreprocessor,
+		                     SPEEX_PREPROCESS_SET_AGC, &i);
+
+		for (i = 0; i < MAX_CLIENTS; i++) {
+			speex_bits_init(&clc.speexDecoderBits[i]);
+			speex_bits_reset(&clc.speexDecoderBits[i]);
+			clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode);
+			clc.voipIgnore[i] = qfalse;
+			clc.voipGain[i] = 1.0f;
+		}
+		clc.speexInitialized = qtrue;
+		clc.voipMuteAll = qfalse;
+		Cmd_AddCommand ("voip", CL_Voip_f);
+		Cvar_Set("cl_voipSendTarget", "all");
+		clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF;
+	}
+#endif
+}
+
+/*
+==================
+CL_SetCGameTime
+==================
+*/
+void CL_SetCGameTime( void ) {
+	// getting a valid frame message ends the connection process
+	if ( cls.state != CA_ACTIVE ) {
+		if ( cls.state != CA_PRIMED ) {
+			return;
+		}
+		if ( clc.demoplaying ) {
+			// we shouldn't get the first snapshot on the same frame
+			// as the gamestate, because it causes a bad time skip
+			if ( !clc.firstDemoFrameSkipped ) {
+				clc.firstDemoFrameSkipped = qtrue;
+				return;
+			}
+			CL_ReadDemoMessage();
+		}
+		if ( cl.newSnapshots ) {
+			cl.newSnapshots = qfalse;
+			CL_FirstSnapshot();
+		}
+		if ( cls.state != CA_ACTIVE ) {
+			return;
+		}
+	}	
+
+	// if we have gotten to this point, cl.snap is guaranteed to be valid
+	if ( !cl.snap.valid ) {
+		Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" );
+	}
+
+	// allow pause in single player
+	if ( sv_paused->integer && CL_CheckPaused() && com_sv_running->integer ) {
+		// paused
+		return;
+	}
+
+	if ( cl.snap.serverTime < cl.oldFrameServerTime ) {
+		Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" );
+	}
+	cl.oldFrameServerTime = cl.snap.serverTime;
+
+
+	// get our current view of time
+
+	if ( clc.demoplaying && cl_freezeDemo->integer ) {
+		// cl_freezeDemo is used to lock a demo in place for single frame advances
+
+	} else {
+		// cl_timeNudge is a user adjustable cvar that allows more
+		// or less latency to be added in the interest of better 
+		// smoothness or better responsiveness.
+		int tn;
+		
+		tn = cl_timeNudge->integer;
+		if (tn<-30) {
+			tn = -30;
+		} else if (tn>30) {
+			tn = 30;
+		}
+
+		cl.serverTime = cls.realtime + cl.serverTimeDelta - tn;
+
+		// guarantee that time will never flow backwards, even if
+		// serverTimeDelta made an adjustment or cl_timeNudge was changed
+		if ( cl.serverTime < cl.oldServerTime ) {
+			cl.serverTime = cl.oldServerTime;
+		}
+		cl.oldServerTime = cl.serverTime;
+
+		// note if we are almost past the latest frame (without timeNudge),
+		// so we will try and adjust back a bit when the next snapshot arrives
+		if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) {
+			cl.extrapolatedSnapshot = qtrue;
+		}
+	}
+
+	// if we have gotten new snapshots, drift serverTimeDelta
+	// don't do this every frame, or a period of packet loss would
+	// make a huge adjustment
+	if ( cl.newSnapshots ) {
+		CL_AdjustTimeDelta();
+	}
+
+	if ( !clc.demoplaying ) {
+		return;
+	}
+
+	// if we are playing a demo back, we can just keep reading
+	// messages from the demo file until the cgame definately
+	// has valid snapshots to interpolate between
+
+	// a timedemo will always use a deterministic set of time samples
+	// no matter what speed machine it is run on,
+	// while a normal demo may have different time samples
+	// each time it is played back
+	if ( cl_timedemo->integer ) {
+		int now = Sys_Milliseconds( );
+		int frameDuration;
+
+		if (!clc.timeDemoStart) {
+			clc.timeDemoStart = clc.timeDemoLastFrame = now;
+			clc.timeDemoMinDuration = INT_MAX;
+			clc.timeDemoMaxDuration = 0;
+		}
+
+		frameDuration = now - clc.timeDemoLastFrame;
+		clc.timeDemoLastFrame = now;
+
+		// Ignore the first measurement as it'll always be 0
+		if( clc.timeDemoFrames > 0 )
+		{
+			if( frameDuration > clc.timeDemoMaxDuration )
+				clc.timeDemoMaxDuration = frameDuration;
+
+			if( frameDuration < clc.timeDemoMinDuration )
+				clc.timeDemoMinDuration = frameDuration;
+
+			// 255 ms = about 4fps
+			if( frameDuration > UCHAR_MAX )
+				frameDuration = UCHAR_MAX;
+
+			clc.timeDemoDurations[ ( clc.timeDemoFrames - 1 ) %
+				MAX_TIMEDEMO_DURATIONS ] = frameDuration;
+		}
+
+		clc.timeDemoFrames++;
+		cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50;
+	}
+
+	while ( cl.serverTime >= cl.snap.serverTime ) {
+		// feed another messag, which should change
+		// the contents of cl.snap
+		CL_ReadDemoMessage();
+		if ( cls.state != CA_ACTIVE ) {
+			return;		// end of demo
+		}
+	}
+
+}
+
+
+
diff --git a/src/client/cl_cin.c b/src/client/cl_cin.c
new file mode 100644
index 0000000..213b7a4
--- /dev/null
+++ b/src/client/cl_cin.c
@@ -0,0 +1,1697 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name:		cl_cin.c
+ *
+ * desc:		video and cinematic playback
+ *
+ * $Archive: /MissionPack/code/client/cl_cin.c $
+ *
+ * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256
+ *
+ *****************************************************************************/
+
+#include "client.h"
+#include "snd_local.h"
+
+#define MAXSIZE				8
+#define MINSIZE				4
+
+#define DEFAULT_CIN_WIDTH	512
+#define DEFAULT_CIN_HEIGHT	512
+
+#define ROQ_QUAD			0x1000
+#define ROQ_QUAD_INFO		0x1001
+#define ROQ_CODEBOOK		0x1002
+#define ROQ_QUAD_VQ			0x1011
+#define ROQ_QUAD_JPEG		0x1012
+#define ROQ_QUAD_HANG		0x1013
+#define ROQ_PACKET			0x1030
+#define ZA_SOUND_MONO		0x1020
+#define ZA_SOUND_STEREO		0x1021
+
+#define MAX_VIDEO_HANDLES	16
+
+extern glconfig_t glConfig;
+
+
+static void RoQ_init( void );
+
+/******************************************************************************
+*
+* Class:		trFMV
+*
+* Description:	RoQ/RnR manipulation routines
+*				not entirely complete for first run
+*
+******************************************************************************/
+
+static	long				ROQ_YY_tab[256];
+static	long				ROQ_UB_tab[256];
+static	long				ROQ_UG_tab[256];
+static	long				ROQ_VG_tab[256];
+static	long				ROQ_VR_tab[256];
+static	unsigned short		vq2[256*16*4];
+static	unsigned short		vq4[256*64*4];
+static	unsigned short		vq8[256*256*4];
+
+
+typedef struct {
+	byte				linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2];
+	byte				file[65536];
+	short				sqrTable[256];
+
+	int		mcomp[256];
+	byte				*qStatus[2][32768];
+
+	long				oldXOff, oldYOff, oldysize, oldxsize;
+
+	int					currentHandle;
+} cinematics_t;
+
+typedef struct {
+	char				fileName[MAX_OSPATH];
+	int					CIN_WIDTH, CIN_HEIGHT;
+	int					xpos, ypos, width, height;
+	qboolean			looping, holdAtEnd, dirty, alterGameState, silent, shader;
+	fileHandle_t		iFile;
+	e_status			status;
+	unsigned int		startTime;
+	unsigned int		lastTime;
+	long				tfps;
+	long				RoQPlayed;
+	long				ROQSize;
+	unsigned int		RoQFrameSize;
+	long				onQuad;
+	long				numQuads;
+	long				samplesPerLine;
+	unsigned int		roq_id;
+	long				screenDelta;
+
+	void ( *VQ0)(byte *status, void *qdata );
+	void ( *VQ1)(byte *status, void *qdata );
+	void ( *VQNormal)(byte *status, void *qdata );
+	void ( *VQBuffer)(byte *status, void *qdata );
+
+	long				samplesPerPixel;				// defaults to 2
+	byte*				gray;
+	unsigned int		xsize, ysize, maxsize, minsize;
+
+	qboolean			half, smootheddouble, inMemory;
+	long				normalBuffer0;
+	long				roq_flags;
+	long				roqF0;
+	long				roqF1;
+	long				t[2];
+	long				roqFPS;
+	int					playonwalls;
+	byte*				buf;
+	long				drawX, drawY;
+} cin_cache;
+
+static cinematics_t		cin;
+static cin_cache		cinTable[MAX_VIDEO_HANDLES];
+static int				currentHandle = -1;
+static int				CL_handle = -1;
+
+extern int				s_soundtime;		// sample PAIRS
+
+
+void CIN_CloseAllVideos(void) {
+	int		i;
+
+	for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) {
+		if (cinTable[i].fileName[0] != 0 ) {
+			CIN_StopCinematic(i);
+		}
+	}
+}
+
+
+static int CIN_HandleForVideo(void) {
+	int		i;
+
+	for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) {
+		if ( cinTable[i].fileName[0] == 0 ) {
+			return i;
+		}
+	}
+	Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" );
+	return -1;
+}
+
+
+extern int CL_ScaledMilliseconds(void);
+
+//-----------------------------------------------------------------------------
+// RllSetupTable
+//
+// Allocates and initializes the square table.
+//
+// Parameters:	None
+//
+// Returns:		Nothing
+//-----------------------------------------------------------------------------
+static void RllSetupTable( void )
+{
+	int z;
+
+	for (z=0;z<128;z++) {
+		cin.sqrTable[z] = (short)(z*z);
+		cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]);
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// RllDecodeMonoToMono
+//
+// Decode mono source data into a mono buffer.
+//
+// Parameters:	from -> buffer holding encoded data
+//				to ->	buffer to hold decoded data
+//				size =	number of bytes of input (= # of shorts of output)
+//				signedOutput = 0 for unsigned output, non-zero for signed output
+//				flag = flags from asset header
+//
+// Returns:		Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag)
+{
+	unsigned int z;
+	int prev;
+	
+	if (signedOutput)	
+		prev =  flag - 0x8000;
+	else 
+		prev = flag;
+
+	for (z=0;z<size;z++) {
+		prev = to[z] = (short)(prev + cin.sqrTable[from[z]]); 
+	}
+	return size;	//*sizeof(short));
+}
+
+
+//-----------------------------------------------------------------------------
+// RllDecodeMonoToStereo
+//
+// Decode mono source data into a stereo buffer. Output is 4 times the number
+// of bytes in the input.
+//
+// Parameters:	from -> buffer holding encoded data
+//				to ->	buffer to hold decoded data
+//				size =	number of bytes of input (= 1/4 # of bytes of output)
+//				signedOutput = 0 for unsigned output, non-zero for signed output
+//				flag = flags from asset header
+//
+// Returns:		Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag)
+{
+	unsigned int z;
+	int prev;
+	
+	if (signedOutput)	
+		prev =  flag - 0x8000;
+	else 
+		prev = flag;
+
+	for (z = 0; z < size; z++) {
+		prev = (short)(prev + cin.sqrTable[from[z]]);
+		to[z*2+0] = to[z*2+1] = (short)(prev);
+	}
+	
+	return size;	// * 2 * sizeof(short));
+}
+
+
+//-----------------------------------------------------------------------------
+// RllDecodeStereoToStereo
+//
+// Decode stereo source data into a stereo buffer.
+//
+// Parameters:	from -> buffer holding encoded data
+//				to ->	buffer to hold decoded data
+//				size =	number of bytes of input (= 1/2 # of bytes of output)
+//				signedOutput = 0 for unsigned output, non-zero for signed output
+//				flag = flags from asset header
+//
+// Returns:		Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag)
+{
+	unsigned int z;
+	unsigned char *zz = from;
+	int	prevL, prevR;
+
+	if (signedOutput) {
+		prevL = (flag & 0xff00) - 0x8000;
+		prevR = ((flag & 0x00ff) << 8) - 0x8000;
+	} else {
+		prevL = flag & 0xff00;
+		prevR = (flag & 0x00ff) << 8;
+	}
+
+	for (z=0;z<size;z+=2) {
+                prevL = (short)(prevL + cin.sqrTable[*zz++]); 
+                prevR = (short)(prevR + cin.sqrTable[*zz++]);
+                to[z+0] = (short)(prevL);
+                to[z+1] = (short)(prevR);
+	}
+	
+	return (size>>1);	//*sizeof(short));
+}
+
+
+//-----------------------------------------------------------------------------
+// RllDecodeStereoToMono
+//
+// Decode stereo source data into a mono buffer.
+//
+// Parameters:	from -> buffer holding encoded data
+//				to ->	buffer to hold decoded data
+//				size =	number of bytes of input (= # of bytes of output)
+//				signedOutput = 0 for unsigned output, non-zero for signed output
+//				flag = flags from asset header
+//
+// Returns:		Number of samples placed in output buffer
+//-----------------------------------------------------------------------------
+long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag)
+{
+	unsigned int z;
+	int prevL,prevR;
+	
+	if (signedOutput) {
+		prevL = (flag & 0xff00) - 0x8000;
+		prevR = ((flag & 0x00ff) << 8) -0x8000;
+	} else {
+		prevL = flag & 0xff00;
+		prevR = (flag & 0x00ff) << 8;
+	}
+
+	for (z=0;z<size;z+=1) {
+		prevL= prevL + cin.sqrTable[from[z*2]];
+		prevR = prevR + cin.sqrTable[from[z*2+1]];
+		to[z] = (short)((prevL + prevR)/2);
+	}
+
+	return size;
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void move8_32( byte *src, byte *dst, int spl )
+{
+	int i;
+
+	for(i = 0; i < 8; ++i)
+	{
+		memcpy(dst, src, 32);
+		src += spl;
+		dst += spl;
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void move4_32( byte *src, byte *dst, int spl  )
+{
+	int i;
+
+	for(i = 0; i < 4; ++i)
+	{
+		memcpy(dst, src, 16);
+		src += spl;
+		dst += spl;
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void blit8_32( byte *src, byte *dst, int spl  )
+{
+	int i;
+
+	for(i = 0; i < 8; ++i)
+	{
+		memcpy(dst, src, 32);
+		src += 32;
+		dst += spl;
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+static void blit4_32( byte *src, byte *dst, int spl  )
+{
+	int i;
+
+	for(i = 0; i < 4; ++i)
+	{
+		memmove(dst, src, 16);
+		src += 16;
+		dst += spl;
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void blit2_32( byte *src, byte *dst, int spl  )
+{
+	memcpy(dst, src, 8);
+	memcpy(dst+spl, src+8, 8);
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void blitVQQuad32fs( byte **status, unsigned char *data )
+{
+unsigned short	newd, celdata, code;
+unsigned int	index, i;
+int		spl;
+
+	newd	= 0;
+	celdata = 0;
+	index	= 0;
+	
+        spl = cinTable[currentHandle].samplesPerLine;
+        
+	do {
+		if (!newd) { 
+			newd = 7;
+			celdata = data[0] + data[1]*256;
+			data += 2;
+		} else {
+			newd--;
+		}
+
+		code = (unsigned short)(celdata&0xc000); 
+		celdata <<= 2;
+		
+		switch (code) {
+			case	0x8000:													// vq code
+				blit8_32( (byte *)&vq8[(*data)*128], status[index], spl );
+				data++;
+				index += 5;
+				break;
+			case	0xc000:													// drop
+				index++;													// skip 8x8
+				for(i=0;i<4;i++) {
+					if (!newd) { 
+						newd = 7;
+						celdata = data[0] + data[1]*256;
+						data += 2;
+					} else {
+						newd--;
+					}
+						
+					code = (unsigned short)(celdata&0xc000); celdata <<= 2; 
+
+					switch (code) {											// code in top two bits of code
+						case	0x8000:										// 4x4 vq code
+							blit4_32( (byte *)&vq4[(*data)*32], status[index], spl );
+							data++;
+							break;
+						case	0xc000:										// 2x2 vq code
+							blit2_32( (byte *)&vq2[(*data)*8], status[index], spl );
+							data++;
+							blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, spl );
+							data++;
+							blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2, spl );
+							data++;
+							blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2+8, spl );
+							data++;
+							break;
+						case	0x4000:										// motion compensation
+							move4_32( status[index] + cin.mcomp[(*data)], status[index], spl );
+							data++;
+							break;
+					}
+					index++;
+				}
+				break;
+			case	0x4000:													// motion compensation
+				move8_32( status[index] + cin.mcomp[(*data)], status[index], spl );
+				data++;
+				index += 5;
+				break;
+			case	0x0000:
+				index += 5;
+				break;
+		}
+	} while ( status[index] != NULL );
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void ROQ_GenYUVTables( void )
+{
+	float t_ub,t_vr,t_ug,t_vg;
+	long i;
+
+	t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f;
+	t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f;
+	t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f;
+	t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f;
+	for(i=0;i<256;i++) {
+		float x = (float)(2 * i - 255);
+	
+		ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5));
+		ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5));
+		ROQ_UG_tab[i] = (long)( (-t_ug * x)		 );
+		ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5));
+		ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) );
+	}
+}
+
+#define VQ2TO4(a,b,c,d) { \
+    	*c++ = a[0];	\
+	*d++ = a[0];	\
+	*d++ = a[0];	\
+	*c++ = a[1];	\
+	*d++ = a[1];	\
+	*d++ = a[1];	\
+	*c++ = b[0];	\
+	*d++ = b[0];	\
+	*d++ = b[0];	\
+	*c++ = b[1];	\
+	*d++ = b[1];	\
+	*d++ = b[1];	\
+	*d++ = a[0];	\
+	*d++ = a[0];	\
+	*d++ = a[1];	\
+	*d++ = a[1];	\
+	*d++ = b[0];	\
+	*d++ = b[0];	\
+	*d++ = b[1];	\
+	*d++ = b[1];	\
+	a += 2; b += 2; }
+ 
+#define VQ2TO2(a,b,c,d) { \
+	*c++ = *a;	\
+	*d++ = *a;	\
+	*d++ = *a;	\
+	*c++ = *b;	\
+	*d++ = *b;	\
+	*d++ = *b;	\
+	*d++ = *a;	\
+	*d++ = *a;	\
+	*d++ = *b;	\
+	*d++ = *b;	\
+	a++; b++; }
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static unsigned short yuv_to_rgb( long y, long u, long v )
+{ 
+	long r,g,b,YY = (long)(ROQ_YY_tab[(y)]);
+
+	r = (YY + ROQ_VR_tab[v]) >> 9;
+	g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8;
+	b = (YY + ROQ_UB_tab[u]) >> 9;
+	
+	if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0;
+	if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31;
+
+	return (unsigned short)((r<<11)+(g<<5)+(b));
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+static unsigned int yuv_to_rgb24( long y, long u, long v )
+{ 
+	long r,g,b,YY = (long)(ROQ_YY_tab[(y)]);
+
+	r = (YY + ROQ_VR_tab[v]) >> 6;
+	g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6;
+	b = (YY + ROQ_UB_tab[u]) >> 6;
+	
+	if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0;
+	if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255;
+	
+	return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24));
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void decodeCodeBook( byte *input, unsigned short roq_flags )
+{
+	long	i, j, two, four;
+	unsigned short	*aptr, *bptr, *cptr, *dptr;
+	long	y0,y1,y2,y3,cr,cb;
+	byte	*bbptr, *baptr, *bcptr, *bdptr;
+	union {
+		unsigned int *i;
+		unsigned short *s;
+	} iaptr, ibptr, icptr, idptr;
+
+	if (!roq_flags) {
+		two = four = 256;
+	} else {
+		two  = roq_flags>>8;
+		if (!two) two = 256;
+		four = roq_flags&0xff;
+	}
+
+	four *= 2;
+
+	bptr = (unsigned short *)vq2;
+
+	if (!cinTable[currentHandle].half) {
+		if (!cinTable[currentHandle].smootheddouble) {
+//
+// normal height
+//
+			if (cinTable[currentHandle].samplesPerPixel==2) {
+				for(i=0;i<two;i++) {
+					y0 = (long)*input++;
+					y1 = (long)*input++;
+					y2 = (long)*input++;
+					y3 = (long)*input++;
+					cr = (long)*input++;
+					cb = (long)*input++;
+					*bptr++ = yuv_to_rgb( y0, cr, cb );
+					*bptr++ = yuv_to_rgb( y1, cr, cb );
+					*bptr++ = yuv_to_rgb( y2, cr, cb );
+					*bptr++ = yuv_to_rgb( y3, cr, cb );
+				}
+
+				cptr = (unsigned short *)vq4;
+				dptr = (unsigned short *)vq8;
+		
+				for(i=0;i<four;i++) {
+					aptr = (unsigned short *)vq2 + (*input++)*4;
+					bptr = (unsigned short *)vq2 + (*input++)*4;
+					for(j=0;j<2;j++)
+						VQ2TO4(aptr,bptr,cptr,dptr);
+				}
+			} else if (cinTable[currentHandle].samplesPerPixel==4) {
+				ibptr.s = bptr;
+				for(i=0;i<two;i++) {
+					y0 = (long)*input++;
+					y1 = (long)*input++;
+					y2 = (long)*input++;
+					y3 = (long)*input++;
+					cr = (long)*input++;
+					cb = (long)*input++;
+					*ibptr.i++ = yuv_to_rgb24( y0, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y1, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y2, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y3, cr, cb );
+				}
+
+				icptr.s = vq4;
+				idptr.s = vq8;
+	
+				for(i=0;i<four;i++) {
+					iaptr.s = vq2;
+					iaptr.i += (*input++)*4;
+					ibptr.s = vq2;
+					ibptr.i += (*input++)*4;
+					for(j=0;j<2;j++) 
+						VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+				}
+			} else if (cinTable[currentHandle].samplesPerPixel==1) {
+				bbptr = (byte *)bptr;
+				for(i=0;i<two;i++) {
+					*bbptr++ = cinTable[currentHandle].gray[*input++];
+					*bbptr++ = cinTable[currentHandle].gray[*input++];
+					*bbptr++ = cinTable[currentHandle].gray[*input++];
+					*bbptr++ = cinTable[currentHandle].gray[*input]; input +=3;
+				}
+
+				bcptr = (byte *)vq4;
+				bdptr = (byte *)vq8;
+	
+				for(i=0;i<four;i++) {
+					baptr = (byte *)vq2 + (*input++)*4;
+					bbptr = (byte *)vq2 + (*input++)*4;
+					for(j=0;j<2;j++) 
+						VQ2TO4(baptr,bbptr,bcptr,bdptr);
+				}
+			}
+		} else {
+//
+// double height, smoothed
+//
+			if (cinTable[currentHandle].samplesPerPixel==2) {
+				for(i=0;i<two;i++) {
+					y0 = (long)*input++;
+					y1 = (long)*input++;
+					y2 = (long)*input++;
+					y3 = (long)*input++;
+					cr = (long)*input++;
+					cb = (long)*input++;
+					*bptr++ = yuv_to_rgb( y0, cr, cb );
+					*bptr++ = yuv_to_rgb( y1, cr, cb );
+					*bptr++ = yuv_to_rgb( ((y0*3)+y2)/4, cr, cb );
+					*bptr++ = yuv_to_rgb( ((y1*3)+y3)/4, cr, cb );
+					*bptr++ = yuv_to_rgb( (y0+(y2*3))/4, cr, cb );
+					*bptr++ = yuv_to_rgb( (y1+(y3*3))/4, cr, cb );
+					*bptr++ = yuv_to_rgb( y2, cr, cb );
+					*bptr++ = yuv_to_rgb( y3, cr, cb );
+				}
+
+				cptr = (unsigned short *)vq4;
+				dptr = (unsigned short *)vq8;
+		
+				for(i=0;i<four;i++) {
+					aptr = (unsigned short *)vq2 + (*input++)*8;
+					bptr = (unsigned short *)vq2 + (*input++)*8;
+					for(j=0;j<2;j++) {
+						VQ2TO4(aptr,bptr,cptr,dptr);
+						VQ2TO4(aptr,bptr,cptr,dptr);
+					}
+				}
+			} else if (cinTable[currentHandle].samplesPerPixel==4) {
+				ibptr.s = bptr;
+				for(i=0;i<two;i++) {
+					y0 = (long)*input++;
+					y1 = (long)*input++;
+					y2 = (long)*input++;
+					y3 = (long)*input++;
+					cr = (long)*input++;
+					cb = (long)*input++;
+					*ibptr.i++ = yuv_to_rgb24( y0, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y1, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( ((y0*3)+y2)/4, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( ((y1*3)+y3)/4, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( (y0+(y2*3))/4, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( (y1+(y3*3))/4, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y2, cr, cb );
+					*ibptr.i++ = yuv_to_rgb24( y3, cr, cb );
+				}
+
+				icptr.s = vq4;
+				idptr.s = vq8;
+	
+				for(i=0;i<four;i++) {
+					iaptr.s = vq2;
+					iaptr.i += (*input++)*8;
+					ibptr.s = vq2;
+					ibptr.i += (*input++)*8;
+					for(j=0;j<2;j++) {
+						VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+						VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i);
+					}
+				}
+			} else if (cinTable[currentHandle].samplesPerPixel==1) {
+				bbptr = (byte *)bptr;
+				for(i=0;i<two;i++) {
+					y0 = (long)*input++;
+					y1 = (long)*input++;
+					y2 = (long)*input++;
+					y3 = (long)*input; input+= 3;
+					*bbptr++ = cinTable[currentHandle].gray[y0];
+					*bbptr++ = cinTable[currentHandle].gray[y1];
+					*bbptr++ = cinTable[currentHandle].gray[((y0*3)+y2)/4];
+					*bbptr++ = cinTable[currentHandle].gray[((y1*3)+y3)/4];
+					*bbptr++ = cinTable[currentHandle].gray[(y0+(y2*3))/4];
+					*bbptr++ = cinTable[currentHandle].gray[(y1+(y3*3))/4];						
+					*bbptr++ = cinTable[currentHandle].gray[y2];
+					*bbptr++ = cinTable[currentHandle].gray[y3];
+				}
+
+				bcptr = (byte *)vq4;
+				bdptr = (byte *)vq8;
+	
+				for(i=0;i<four;i++) {
+					baptr = (byte *)vq2 + (*input++)*8;
+					bbptr = (byte *)vq2 + (*input++)*8;
+					for(j=0;j<2;j++) {
+						VQ2TO4(baptr,bbptr,bcptr,bdptr);
+						VQ2TO4(baptr,bbptr,bcptr,bdptr);
+					}
+				}
+			}			
+		}
+	} else {
+//
+// 1/4 screen
+//
+		if (cinTable[currentHandle].samplesPerPixel==2) {
+			for(i=0;i<two;i++) {
+				y0 = (long)*input; input+=2;
+				y2 = (long)*input; input+=2;
+				cr = (long)*input++;
+				cb = (long)*input++;
+				*bptr++ = yuv_to_rgb( y0, cr, cb );
+				*bptr++ = yuv_to_rgb( y2, cr, cb );
+			}
+
+			cptr = (unsigned short *)vq4;
+			dptr = (unsigned short *)vq8;
+	
+			for(i=0;i<four;i++) {
+				aptr = (unsigned short *)vq2 + (*input++)*2;
+				bptr = (unsigned short *)vq2 + (*input++)*2;
+				for(j=0;j<2;j++) { 
+					VQ2TO2(aptr,bptr,cptr,dptr);
+				}
+			}
+		} else if (cinTable[currentHandle].samplesPerPixel == 1) {
+			bbptr = (byte *)bptr;
+				
+			for(i=0;i<two;i++) {
+				*bbptr++ = cinTable[currentHandle].gray[*input]; input+=2;
+				*bbptr++ = cinTable[currentHandle].gray[*input]; input+=4;
+			}
+
+			bcptr = (byte *)vq4;
+			bdptr = (byte *)vq8;
+	
+			for(i=0;i<four;i++) {
+				baptr = (byte *)vq2 + (*input++)*2;
+				bbptr = (byte *)vq2 + (*input++)*2;
+				for(j=0;j<2;j++) { 
+					VQ2TO2(baptr,bbptr,bcptr,bdptr);
+				}
+			}			
+		} else if (cinTable[currentHandle].samplesPerPixel == 4) {
+			ibptr.s = bptr;
+			for(i=0;i<two;i++) {
+				y0 = (long)*input; input+=2;
+				y2 = (long)*input; input+=2;
+				cr = (long)*input++;
+				cb = (long)*input++;
+				*ibptr.i++ = yuv_to_rgb24( y0, cr, cb );
+				*ibptr.i++ = yuv_to_rgb24( y2, cr, cb );
+			}
+
+			icptr.s = vq4;
+			idptr.s = vq8;
+	
+			for(i=0;i<four;i++) {
+				iaptr.s = vq2;
+				iaptr.i += (*input++)*2;
+				ibptr.s = vq2 + (*input++)*2;
+				ibptr.i += (*input++)*2;
+				for(j=0;j<2;j++) { 
+					VQ2TO2(iaptr.i,ibptr.i,icptr.i,idptr.i);
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void recurseQuad( long startX, long startY, long quadSize, long xOff, long yOff )
+{
+	byte *scroff;
+	long bigx, bigy, lowx, lowy, useY;
+	long offset;
+
+	offset = cinTable[currentHandle].screenDelta;
+	
+	lowx = lowy = 0;
+	bigx = cinTable[currentHandle].xsize;
+	bigy = cinTable[currentHandle].ysize;
+
+	if (bigx > cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH;
+	if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT;
+
+	if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) {
+		useY = startY;
+		scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel);
+
+		cin.qStatus[0][cinTable[currentHandle].onQuad  ] = scroff;
+		cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset;
+	}
+
+	if ( quadSize != MINSIZE ) {
+		quadSize >>= 1;
+		recurseQuad( startX,		  startY		  , quadSize, xOff, yOff );
+		recurseQuad( startX+quadSize, startY		  , quadSize, xOff, yOff );
+		recurseQuad( startX,		  startY+quadSize , quadSize, xOff, yOff );
+		recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff );
+	}
+}
+
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void setupQuad( long xOff, long yOff )
+{
+	long numQuadCels, i,x,y;
+	byte *temp;
+
+	if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) {
+		return;
+	}
+
+	cin.oldXOff = xOff;
+	cin.oldYOff = yOff;
+	cin.oldysize = cinTable[currentHandle].ysize;
+	cin.oldxsize = cinTable[currentHandle].xsize;
+
+	numQuadCels  = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16);
+	numQuadCels += numQuadCels/4 + numQuadCels/16;
+	numQuadCels += 64;							  // for overflow
+
+	numQuadCels  = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16);
+	numQuadCels += numQuadCels/4;
+	numQuadCels += 64;							  // for overflow
+
+	cinTable[currentHandle].onQuad = 0;
+
+	for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) 
+		for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) 
+			recurseQuad( x, y, 16, xOff, yOff );
+
+	temp = NULL;
+
+	for(i=(numQuadCels-64);i<numQuadCels;i++) {
+		cin.qStatus[0][i] = temp;			  // eoq
+		cin.qStatus[1][i] = temp;			  // eoq
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void readQuadInfo( byte *qData )
+{
+	if (currentHandle < 0) return;
+
+	cinTable[currentHandle].xsize    = qData[0]+qData[1]*256;
+	cinTable[currentHandle].ysize    = qData[2]+qData[3]*256;
+	cinTable[currentHandle].maxsize  = qData[4]+qData[5]*256;
+	cinTable[currentHandle].minsize  = qData[6]+qData[7]*256;
+	
+	cinTable[currentHandle].CIN_HEIGHT = cinTable[currentHandle].ysize;
+	cinTable[currentHandle].CIN_WIDTH  = cinTable[currentHandle].xsize;
+
+	cinTable[currentHandle].samplesPerLine = cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].samplesPerPixel;
+	cinTable[currentHandle].screenDelta = cinTable[currentHandle].CIN_HEIGHT*cinTable[currentHandle].samplesPerLine;
+
+	cinTable[currentHandle].half = qfalse;
+	cinTable[currentHandle].smootheddouble = qfalse;
+	
+	cinTable[currentHandle].VQ0 = cinTable[currentHandle].VQNormal;
+	cinTable[currentHandle].VQ1 = cinTable[currentHandle].VQBuffer;
+
+	cinTable[currentHandle].t[0] = cinTable[currentHandle].screenDelta;
+	cinTable[currentHandle].t[1] = -cinTable[currentHandle].screenDelta;
+
+        cinTable[currentHandle].drawX = cinTable[currentHandle].CIN_WIDTH;
+        cinTable[currentHandle].drawY = cinTable[currentHandle].CIN_HEIGHT;
+        
+	// rage pro is very slow at 512 wide textures, voodoo can't do it at all
+	if ( glConfig.hardwareType == GLHW_RAGEPRO || glConfig.maxTextureSize <= 256) {
+                if (cinTable[currentHandle].drawX>256) {
+                        cinTable[currentHandle].drawX = 256;
+                }
+                if (cinTable[currentHandle].drawY>256) {
+                        cinTable[currentHandle].drawY = 256;
+                }
+		if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) {
+			Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n");
+		}
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void RoQPrepMcomp( long xoff, long yoff ) 
+{
+	long i, j, x, y, temp, temp2;
+
+	i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel;
+	if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; }
+	
+	for(y=0;y<16;y++) {
+		temp2 = (y+yoff-8)*i;
+		for(x=0;x<16;x++) {
+			temp = (x+xoff-8)*j;
+			cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp);
+		}
+	}
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void initRoQ( void ) 
+{
+	if (currentHandle < 0) return;
+
+	cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs;
+	cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs;
+	cinTable[currentHandle].samplesPerPixel = 4;
+	ROQ_GenYUVTables();
+	RllSetupTable();
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+/*
+static byte* RoQFetchInterlaced( byte *source ) {
+	int x, *src, *dst;
+
+	if (currentHandle < 0) return NULL;
+
+	src = (int *)source;
+	dst = (int *)cinTable[currentHandle].buf2;
+
+	for(x=0;x<256*256;x++) {
+		*dst = *src;
+		dst++; src += 2;
+	}
+	return cinTable[currentHandle].buf2;
+}
+*/
+static void RoQReset( void ) {
+	
+	if (currentHandle < 0) return;
+
+	FS_FCloseFile( cinTable[currentHandle].iFile );
+	FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue);
+	// let the background thread start reading ahead
+	FS_Read (cin.file, 16, cinTable[currentHandle].iFile);
+	RoQ_init();
+	cinTable[currentHandle].status = FMV_LOOPED;
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void RoQInterrupt(void)
+{
+	byte				*framedata;
+        short		sbuf[32768];
+        int		ssize;
+        
+	if (currentHandle < 0) return;
+
+	FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile );
+	if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { 
+		if (cinTable[currentHandle].holdAtEnd==qfalse) {
+			if (cinTable[currentHandle].looping) {
+				RoQReset();
+			} else {
+				cinTable[currentHandle].status = FMV_EOF;
+			}
+		} else {
+			cinTable[currentHandle].status = FMV_IDLE;
+		}
+		return; 
+	}
+
+	framedata = cin.file;
+//
+// new frame is ready
+//
+redump:
+	switch(cinTable[currentHandle].roq_id) 
+	{
+		case	ROQ_QUAD_VQ:
+			if ((cinTable[currentHandle].numQuads&1)) {
+				cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1];
+				RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 );
+				cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata);
+				cinTable[currentHandle].buf = 	cin.linbuf + cinTable[currentHandle].screenDelta;
+			} else {
+				cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0];
+				RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 );
+				cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata );
+				cinTable[currentHandle].buf = 	cin.linbuf;
+			}
+			if (cinTable[currentHandle].numQuads == 0) {		// first frame
+				Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize);
+			}
+			cinTable[currentHandle].numQuads++;
+			cinTable[currentHandle].dirty = qtrue;
+			break;
+		case	ROQ_CODEBOOK:
+			decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags );
+			break;
+		case	ZA_SOUND_MONO:
+			if (!cinTable[currentHandle].silent) {
+				ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags);
+                                S_RawSamples( 0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f );
+			}
+			break;
+		case	ZA_SOUND_STEREO:
+			if (!cinTable[currentHandle].silent) {
+				if (cinTable[currentHandle].numQuads == -1) {
+					S_Update();
+					s_rawend[0] = s_soundtime;
+				}
+				ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags);
+                                S_RawSamples( 0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f );
+			}
+			break;
+		case	ROQ_QUAD_INFO:
+			if (cinTable[currentHandle].numQuads == -1) {
+				readQuadInfo( framedata );
+				setupQuad( 0, 0 );
+				// we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+				cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value;
+			}
+			if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0;
+			break;
+		case	ROQ_PACKET:
+			cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags;
+			cinTable[currentHandle].RoQFrameSize = 0;           // for header
+			break;
+		case	ROQ_QUAD_HANG:
+			cinTable[currentHandle].RoQFrameSize = 0;
+			break;
+		case	ROQ_QUAD_JPEG:
+			break;
+		default:
+			cinTable[currentHandle].status = FMV_EOF;
+			break;
+	}	
+//
+// read in next frame data
+//
+	if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { 
+		if (cinTable[currentHandle].holdAtEnd==qfalse) {
+			if (cinTable[currentHandle].looping) {
+				RoQReset();
+			} else {
+				cinTable[currentHandle].status = FMV_EOF;
+			}
+		} else {
+			cinTable[currentHandle].status = FMV_IDLE;
+		}
+		return; 
+	}
+	
+	framedata		 += cinTable[currentHandle].RoQFrameSize;
+	cinTable[currentHandle].roq_id		 = framedata[0] + framedata[1]*256;
+	cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536;
+	cinTable[currentHandle].roq_flags	 = framedata[6] + framedata[7]*256;
+	cinTable[currentHandle].roqF0		 = (signed char)framedata[7];
+	cinTable[currentHandle].roqF1		 = (signed char)framedata[6];
+
+	if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) {
+		Com_DPrintf("roq_size>65536||roq_id==0x1084\n");
+		cinTable[currentHandle].status = FMV_EOF;
+		if (cinTable[currentHandle].looping) {
+			RoQReset();
+		}
+		return;
+	}
+	if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; }
+//
+// one more frame hits the dust
+//
+//	assert(cinTable[currentHandle].RoQFrameSize <= 65536);
+//	r = FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile );
+	cinTable[currentHandle].RoQPlayed	+= cinTable[currentHandle].RoQFrameSize+8;
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void RoQ_init( void )
+{
+	// we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+	cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value;
+
+	cinTable[currentHandle].RoQPlayed = 24;
+
+/*	get frame rate */	
+	cinTable[currentHandle].roqFPS	 = cin.file[ 6] + cin.file[ 7]*256;
+	
+	if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30;
+
+	cinTable[currentHandle].numQuads = -1;
+
+	cinTable[currentHandle].roq_id		= cin.file[ 8] + cin.file[ 9]*256;
+	cinTable[currentHandle].RoQFrameSize	= cin.file[10] + cin.file[11]*256 + cin.file[12]*65536;
+	cinTable[currentHandle].roq_flags	= cin.file[14] + cin.file[15]*256;
+
+	if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { 
+		return;
+	}
+
+}
+
+/******************************************************************************
+*
+* Function:		
+*
+* Description:	
+*
+******************************************************************************/
+
+static void RoQShutdown( void ) {
+	if (!cinTable[currentHandle].buf) {
+		return;
+	}
+
+	if ( cinTable[currentHandle].status == FMV_IDLE ) {
+		return;
+	}
+	Com_DPrintf("finished cinematic\n");
+	cinTable[currentHandle].status = FMV_IDLE;
+
+	if (cinTable[currentHandle].iFile) {
+		FS_FCloseFile( cinTable[currentHandle].iFile );
+		cinTable[currentHandle].iFile = 0;
+	}
+
+	if (cinTable[currentHandle].alterGameState) {
+		cls.state = CA_DISCONNECTED;
+		CL_handle = -1;
+	}
+	cinTable[currentHandle].fileName[0] = 0;
+	currentHandle = -1;
+}
+
+/*
+==================
+SCR_StopCinematic
+==================
+*/
+e_status CIN_StopCinematic(int handle) {
+	
+	if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF;
+	currentHandle = handle;
+
+	Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName);
+
+	if (!cinTable[currentHandle].buf) {
+		return FMV_EOF;
+	}
+
+	if (cinTable[currentHandle].alterGameState) {
+		if ( cls.state != CA_CINEMATIC ) {
+			return cinTable[currentHandle].status;
+		}
+	}
+	cinTable[currentHandle].status = FMV_EOF;
+	RoQShutdown();
+
+	return FMV_EOF;
+}
+
+/*
+==================
+SCR_RunCinematic
+
+Fetch and decompress the pending frame
+==================
+*/
+
+
+e_status CIN_RunCinematic (int handle)
+{
+	int	start = 0;
+	int     thisTime = 0;
+
+	if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF;
+
+	if (cin.currentHandle != handle) {
+		currentHandle = handle;
+		cin.currentHandle = currentHandle;
+		cinTable[currentHandle].status = FMV_EOF;
+		RoQReset();
+	}
+
+	if (cinTable[handle].playonwalls < -1)
+	{
+		return cinTable[handle].status;
+	}
+
+	currentHandle = handle;
+
+	if (cinTable[currentHandle].alterGameState) {
+		if ( cls.state != CA_CINEMATIC ) {
+			return cinTable[currentHandle].status;
+		}
+	}
+
+	if (cinTable[currentHandle].status == FMV_IDLE) {
+		return cinTable[currentHandle].status;
+	}
+
+	// we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+	thisTime = CL_ScaledMilliseconds()*com_timescale->value;
+	if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) {
+		cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime;
+	}
+	// we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+	cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100);
+
+	start = cinTable[currentHandle].startTime;
+	while(  (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads)
+		&& (cinTable[currentHandle].status == FMV_PLAY) ) 
+	{
+		RoQInterrupt();
+		if (start != cinTable[currentHandle].startTime) {
+			// we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer
+		  cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value)
+							  - cinTable[currentHandle].startTime)*3)/100);
+			start = cinTable[currentHandle].startTime;
+		}
+	}
+
+	cinTable[currentHandle].lastTime = thisTime;
+
+	if (cinTable[currentHandle].status == FMV_LOOPED) {
+		cinTable[currentHandle].status = FMV_PLAY;
+	}
+
+	if (cinTable[currentHandle].status == FMV_EOF) {
+	  if (cinTable[currentHandle].looping) {
+		RoQReset();
+	  } else {
+		RoQShutdown();
+	  }
+	}
+
+	return cinTable[currentHandle].status;
+}
+
+/*
+==================
+CL_PlayCinematic
+
+==================
+*/
+int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) {
+	unsigned short RoQID;
+	char	name[MAX_OSPATH];
+	int		i;
+
+	if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) {
+		Com_sprintf (name, sizeof(name), "video/%s", arg);
+	} else {
+		Com_sprintf (name, sizeof(name), "%s", arg);
+	}
+
+	if (!(systemBits & CIN_system)) {
+		for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) {
+			if (!strcmp(cinTable[i].fileName, name) ) {
+				return i;
+			}
+		}
+	}
+
+	Com_DPrintf("SCR_PlayCinematic( %s )\n", arg);
+
+	Com_Memset(&cin, 0, sizeof(cinematics_t) );
+	currentHandle = CIN_HandleForVideo();
+
+	cin.currentHandle = currentHandle;
+
+	strcpy(cinTable[currentHandle].fileName, name);
+
+	cinTable[currentHandle].ROQSize = 0;
+	cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue);
+
+	if (cinTable[currentHandle].ROQSize<=0) {
+		Com_DPrintf("play(%s), ROQSize<=0\n", arg);
+		cinTable[currentHandle].fileName[0] = 0;
+		return -1;
+	}
+
+	CIN_SetExtents(currentHandle, x, y, w, h);
+	CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0);
+
+	cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT;
+	cinTable[currentHandle].CIN_WIDTH  =  DEFAULT_CIN_WIDTH;
+	cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0;
+	cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0;
+	cinTable[currentHandle].playonwalls = 1;
+	cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0;
+	cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0;
+
+	if (cinTable[currentHandle].alterGameState) {
+		// close the menu
+		if ( uivm ) {
+			VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
+		}
+	} else {
+		cinTable[currentHandle].playonwalls = cl_inGameVideo->integer;
+	}
+
+	initRoQ();
+					
+	FS_Read (cin.file, 16, cinTable[currentHandle].iFile);
+
+	RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256;
+	if (RoQID == 0x1084)
+	{
+		RoQ_init();
+//		FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile);
+
+		cinTable[currentHandle].status = FMV_PLAY;
+		Com_DPrintf("trFMV::play(), playing %s\n", arg);
+
+		if (cinTable[currentHandle].alterGameState) {
+			cls.state = CA_CINEMATIC;
+		}
+		
+		Con_Close();
+
+		s_rawend[0] = s_soundtime;
+
+		return currentHandle;
+	}
+	Com_DPrintf("trFMV::play(), invalid RoQ ID\n");
+
+	RoQShutdown();
+	return -1;
+}
+
+void CIN_SetExtents (int handle, int x, int y, int w, int h) {
+	if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+	cinTable[handle].xpos = x;
+	cinTable[handle].ypos = y;
+	cinTable[handle].width = w;
+	cinTable[handle].height = h;
+	cinTable[handle].dirty = qtrue;
+}
+
+void CIN_SetLooping(int handle, qboolean loop) {
+	if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+	cinTable[handle].looping = loop;
+}
+
+/*
+==================
+CIN_ResampleCinematic
+
+Resample cinematic to 256x256 and store in buf2
+==================
+*/
+void CIN_ResampleCinematic(int handle, int *buf2) {
+	int ix, iy, *buf3, xm, ym, ll;
+	byte	*buf;
+
+	buf = cinTable[handle].buf;
+
+	xm = cinTable[handle].CIN_WIDTH/256;
+	ym = cinTable[handle].CIN_HEIGHT/256;
+	ll = 8;
+	if (cinTable[handle].CIN_WIDTH==512) {
+		ll = 9;
+	}
+
+	buf3 = (int*)buf;
+	if (xm==2 && ym==2) {
+		byte *bc2, *bc3;
+		int	ic, iiy;
+
+		bc2 = (byte *)buf2;
+		bc3 = (byte *)buf3;
+		for (iy = 0; iy<256; iy++) {
+			iiy = iy<<12;
+			for (ix = 0; ix<2048; ix+=8) {
+				for(ic = ix;ic<(ix+4);ic++) {
+					*bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2;
+					bc2++;
+				}
+			}
+		}
+	} else if (xm==2 && ym==1) {
+		byte *bc2, *bc3;
+		int	ic, iiy;
+
+		bc2 = (byte *)buf2;
+		bc3 = (byte *)buf3;
+		for (iy = 0; iy<256; iy++) {
+			iiy = iy<<11;
+			for (ix = 0; ix<2048; ix+=8) {
+				for(ic = ix;ic<(ix+4);ic++) {
+					*bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1;
+					bc2++;
+				}
+			}
+		}
+	} else {
+		for (iy = 0; iy<256; iy++) {
+			for (ix = 0; ix<256; ix++) {
+					buf2[(iy<<8)+ix] = buf3[((iy*ym)<<ll) + (ix*xm)];
+			}
+		}
+	}
+}
+
+/*
+==================
+SCR_DrawCinematic
+
+==================
+*/
+void CIN_DrawCinematic (int handle) {
+	float	x, y, w, h;
+	byte	*buf;
+
+	if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return;
+
+	if (!cinTable[handle].buf) {
+		return;
+	}
+
+	x = cinTable[handle].xpos;
+	y = cinTable[handle].ypos;
+	w = cinTable[handle].width;
+	h = cinTable[handle].height;
+	buf = cinTable[handle].buf;
+	SCR_AdjustFrom640( &x, &y, &w, &h );
+
+	if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) {
+		int *buf2;
+
+		buf2 = Hunk_AllocateTempMemory( 256*256*4 );
+
+		CIN_ResampleCinematic(handle, buf2);
+
+		re.DrawStretchRaw( x, y, w, h, 256, 256, (byte *)buf2, handle, qtrue);
+		cinTable[handle].dirty = qfalse;
+		Hunk_FreeTempMemory(buf2);
+		return;
+	}
+
+	re.DrawStretchRaw( x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, buf, handle, cinTable[handle].dirty);
+	cinTable[handle].dirty = qfalse;
+}
+
+void CL_PlayCinematic_f(void) {
+	char	*arg, *s;
+	qboolean	holdatend;
+	int bits = CIN_system;
+
+	Com_DPrintf("CL_PlayCinematic_f\n");
+	if (cls.state == CA_CINEMATIC) {
+		SCR_StopCinematic();
+	}
+
+	arg = Cmd_Argv( 1 );
+	s = Cmd_Argv(2);
+
+	holdatend = qfalse;
+	if ((s && s[0] == '1') || Q_stricmp(arg,"demoend.roq")==0 || Q_stricmp(arg,"end.roq")==0) {
+		bits |= CIN_hold;
+	}
+	if (s && s[0] == '2') {
+		bits |= CIN_loop;
+	}
+
+	S_StopAllSounds ();
+
+	CL_handle = CIN_PlayCinematic( arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits );
+	if (CL_handle >= 0) {
+		do {
+			SCR_RunCinematic();
+		} while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY);		// wait for first frame (load codebook and sound)
+	}
+}
+
+
+void SCR_DrawCinematic (void) {
+	if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) {
+		CIN_DrawCinematic(CL_handle);
+	}
+}
+
+void SCR_RunCinematic (void)
+{
+	if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) {
+		CIN_RunCinematic(CL_handle);
+	}
+}
+
+void SCR_StopCinematic(void) {
+	if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) {
+		CIN_StopCinematic(CL_handle);
+		S_StopAllSounds ();
+		CL_handle = -1;
+	}
+}
+
+void CIN_UploadCinematic(int handle) {
+	if (handle >= 0 && handle < MAX_VIDEO_HANDLES) {
+		if (!cinTable[handle].buf) {
+			return;
+		}
+		if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) {
+			if (cinTable[handle].playonwalls == 0) {
+				cinTable[handle].playonwalls = -1;
+			} else {
+				if (cinTable[handle].playonwalls == -1) {
+					cinTable[handle].playonwalls = -2;
+				} else {
+					cinTable[handle].dirty = qfalse;
+				}
+			}
+		}
+
+		// Resample the video if needed
+		if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY))  {
+			int *buf2;
+
+			buf2 = Hunk_AllocateTempMemory( 256*256*4 );
+
+			CIN_ResampleCinematic(handle, buf2);
+
+			re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, 256, 256, (byte *)buf2, handle, qtrue);
+			cinTable[handle].dirty = qfalse;
+			Hunk_FreeTempMemory(buf2);
+		} else {
+			// Upload video at normal resolution
+			re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, cinTable[handle].drawX, cinTable[handle].drawY,
+					cinTable[handle].buf, handle, cinTable[handle].dirty);
+			cinTable[handle].dirty = qfalse;
+		}
+
+		if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) {
+			cinTable[handle].playonwalls--;
+		}
+		else if (cl_inGameVideo->integer != 0 && cinTable[handle].playonwalls != 1) {
+			cinTable[handle].playonwalls = 1;
+		}
+	}
+}
+
diff --git a/src/client/cl_console.c b/src/client/cl_console.c
new file mode 100644
index 0000000..c7d01bc
--- /dev/null
+++ b/src/client/cl_console.c
@@ -0,0 +1,624 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// console.c
+
+#include "client.h"
+
+
+int g_console_field_width = 78;
+
+
+#define	NUM_CON_TIMES 4
+
+#define		CON_TEXTSIZE	163840
+typedef struct {
+	qboolean	initialized;
+
+	short	text[CON_TEXTSIZE];
+	int		current;		// line where next message will be printed
+	int		x;				// offset in current line for next print
+	int		display;		// bottom of console displays this line
+
+	int 	linewidth;		// characters across screen
+	int		totallines;		// total lines in console scrollback
+
+	float	xadjust;		// for wide aspect screens
+
+	float	displayFrac;	// aproaches finalFrac at scr_conspeed
+	float	finalFrac;		// 0.0 to 1.0 lines of console to display
+
+	int		vislines;		// in scanlines
+
+	vec4_t	color;
+} console_t;
+
+extern	console_t	con;
+
+console_t	con;
+
+cvar_t		*con_conspeed;
+
+#define	DEFAULT_CONSOLE_WIDTH	78
+
+vec4_t	console_color = {1.0, 1.0, 1.0, 1.0};
+
+
+/*
+================
+Con_ToggleConsole_f
+================
+*/
+void Con_ToggleConsole_f (void) {
+	// Can't toggle the console when it's the only thing available
+	if ( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) {
+		return;
+	}
+
+	Field_Clear( &g_consoleField );
+	g_consoleField.widthInChars = g_console_field_width;
+
+	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE );
+}
+
+/*
+================
+Con_Clear_f
+================
+*/
+void Con_Clear_f (void) {
+	int		i;
+
+	for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) {
+		con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+	}
+
+	Con_Bottom();		// go to end
+}
+
+						
+/*
+================
+Con_Dump_f
+
+Save the console contents out to a file
+================
+*/
+void Con_Dump_f (void)
+{
+	int		l, x, i;
+	short	*line;
+	fileHandle_t	f;
+	char	buffer[1024];
+
+	if (Cmd_Argc() != 2)
+	{
+		Com_Printf ("usage: condump <filename>\n");
+		return;
+	}
+
+	Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) );
+
+	f = FS_FOpenFileWrite( Cmd_Argv( 1 ) );
+	if (!f)
+	{
+		Com_Printf ("ERROR: couldn't open.\n");
+		return;
+	}
+
+	// skip empty lines
+	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
+	{
+		line = con.text + (l%con.totallines)*con.linewidth;
+		for (x=0 ; x<con.linewidth ; x++)
+			if ((line[x] & 0xff) != ' ')
+				break;
+		if (x != con.linewidth)
+			break;
+	}
+
+	// write the remaining lines
+	buffer[con.linewidth] = 0;
+	for ( ; l <= con.current ; l++)
+	{
+		line = con.text + (l%con.totallines)*con.linewidth;
+		for(i=0; i<con.linewidth; i++)
+			buffer[i] = line[i] & 0xff;
+		for (x=con.linewidth-1 ; x>=0 ; x--)
+		{
+			if (buffer[x] == ' ')
+				buffer[x] = 0;
+			else
+				break;
+		}
+		strcat( buffer, "\n" );
+		FS_Write(buffer, strlen(buffer), f);
+	}
+
+	FS_FCloseFile( f );
+}
+
+						
+/*
+================
+Con_ClearNotify
+================
+*/
+void Con_ClearNotify( void ) {
+	Cmd_TokenizeString( NULL );
+	CL_GameConsoleText( );
+}
+
+						
+
+/*
+================
+Con_CheckResize
+
+If the line width has changed, reformat the buffer.
+================
+*/
+void Con_CheckResize (void)
+{
+	int		i, j, width, oldwidth, oldtotallines, numlines, numchars;
+	short	tbuf[CON_TEXTSIZE];
+
+	width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2;
+
+	if (width == con.linewidth)
+		return;
+
+	if (width < 1)			// video hasn't been initialized yet
+	{
+		width = DEFAULT_CONSOLE_WIDTH;
+		con.linewidth = width;
+		con.totallines = CON_TEXTSIZE / con.linewidth;
+		for(i=0; i<CON_TEXTSIZE; i++)
+
+			con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+	}
+	else
+	{
+		oldwidth = con.linewidth;
+		con.linewidth = width;
+		oldtotallines = con.totallines;
+		con.totallines = CON_TEXTSIZE / con.linewidth;
+		numlines = oldtotallines;
+
+		if (con.totallines < numlines)
+			numlines = con.totallines;
+
+		numchars = oldwidth;
+	
+		if (con.linewidth < numchars)
+			numchars = con.linewidth;
+
+		Com_Memcpy (tbuf, con.text, CON_TEXTSIZE * sizeof(short));
+		for(i=0; i<CON_TEXTSIZE; i++)
+
+			con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+
+
+		for (i=0 ; i<numlines ; i++)
+		{
+			for (j=0 ; j<numchars ; j++)
+			{
+				con.text[(con.totallines - 1 - i) * con.linewidth + j] =
+						tbuf[((con.current - i + oldtotallines) %
+							  oldtotallines) * oldwidth + j];
+			}
+		}
+	}
+
+	con.current = con.totallines - 1;
+	con.display = con.current;
+}
+
+/*
+==================
+Cmd_CompleteTxtName
+==================
+*/
+void Cmd_CompleteTxtName( char *args, int argNum ) {
+	if( argNum == 2 ) {
+		Field_CompleteFilename( "", "txt", qfalse, qtrue );
+	}
+}
+
+
+/*
+================
+Con_Init
+================
+*/
+void Con_Init (void) {
+	int		i;
+
+	con_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
+
+	Field_Clear( &g_consoleField );
+	g_consoleField.widthInChars = g_console_field_width;
+	for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
+		Field_Clear( &historyEditLines[i] );
+		historyEditLines[i].widthInChars = g_console_field_width;
+	}
+	CL_LoadConsoleHistory( );
+
+	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
+	Cmd_AddCommand ("clear", Con_Clear_f);
+	Cmd_AddCommand ("condump", Con_Dump_f);
+	Cmd_SetCommandCompletionFunc( "condump", Cmd_CompleteTxtName );
+}
+
+
+/*
+===============
+Con_Linefeed
+===============
+*/
+void Con_Linefeed (qboolean skipnotify)
+{
+	int		i;
+
+	con.x = 0;
+	if (con.display == con.current)
+		con.display++;
+	con.current++;
+	for(i=0; i<con.linewidth; i++)
+		con.text[(con.current%con.totallines)*con.linewidth+i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
+}
+
+/*
+================
+CL_ConsolePrint
+
+Handles cursor positioning, line wrapping, etc
+All console printing must go through this in order to be logged to disk
+If no console is visible, the text will appear at the top of the game window
+================
+*/
+void CL_ConsolePrint( char *txt ) {
+	int		y, l;
+	unsigned char	c;
+	unsigned short	color;
+	qboolean skipnotify = qfalse;		// NERVE - SMF
+
+	// TTimo - prefix for text that shows up in console but not in notify
+	// backported from RTCW
+	if ( !Q_strncmp( txt, "[skipnotify]", 12 ) ) {
+		skipnotify = qtrue;
+		txt += 12;
+	}
+	
+	// for some demos we don't want to ever show anything on the console
+	if ( cl_noprint && cl_noprint->integer ) {
+		return;
+	}
+	
+	if (!con.initialized) {
+		con.color[0] = 
+		con.color[1] = 
+		con.color[2] =
+		con.color[3] = 1.0f;
+		con.linewidth = -1;
+		Con_CheckResize ();
+		con.initialized = qtrue;
+	}
+
+	if( !skipnotify && !( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) {
+		Cmd_SaveCmdContext( );
+
+		// feed the text to cgame
+		Cmd_TokenizeString( txt );
+		CL_GameConsoleText( );
+
+		Cmd_RestoreCmdContext( );
+	}
+
+	color = ColorIndex(COLOR_WHITE);
+
+	while ( (c = *((unsigned char *) txt)) != 0 ) {
+		if ( Q_IsColorString( txt ) ) {
+			color = ColorIndex( *(txt+1) );
+			txt += 2;
+			continue;
+		}
+
+		// count word length
+		for (l=0 ; l< con.linewidth ; l++) {
+			if ( txt[l] <= ' ') {
+				break;
+			}
+
+		}
+
+		// word wrap
+		if (l != con.linewidth && (con.x + l >= con.linewidth) ) {
+			Con_Linefeed(skipnotify);
+
+		}
+
+		txt++;
+
+		switch (c)
+		{
+		case INDENT_MARKER:
+			break;
+		case '\n':
+			Con_Linefeed (skipnotify);
+			break;
+		case '\r':
+			con.x = 0;
+			break;
+		default:	// display character and advance
+			y = con.current % con.totallines;
+			con.text[y*con.linewidth+con.x] = (color << 8) | c;
+			con.x++;
+			if(con.x >= con.linewidth)
+				Con_Linefeed(skipnotify);
+			break;
+		}
+	}
+}
+
+
+/*
+==============================================================================
+
+DRAWING
+
+==============================================================================
+*/
+
+
+/*
+================
+Con_DrawInput
+
+Draw the editline after a ] prompt
+================
+*/
+void Con_DrawInput (void) {
+	int		y;
+
+	if ( cls.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) {
+		return;
+	}
+
+	y = con.vislines - ( SMALLCHAR_HEIGHT * 2 );
+
+	re.SetColor( con.color );
+
+	SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' );
+
+	Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y,
+		SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue, qtrue );
+}
+
+/*
+================
+Con_DrawSolidConsole
+
+Draws the console with the solid background
+================
+*/
+void Con_DrawSolidConsole( float frac ) {
+	int				i, x, y;
+	int				rows;
+	short			*text;
+	int				row;
+	int				lines;
+//	qhandle_t		conShader;
+	int				currentColor;
+	vec4_t			color;
+
+	lines = cls.glconfig.vidHeight * frac;
+	if (lines <= 0)
+		return;
+
+	if (lines > cls.glconfig.vidHeight )
+		lines = cls.glconfig.vidHeight;
+
+	// on wide screens, we will center the text
+	con.xadjust = 0;
+	SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL );
+
+	// draw the background
+	y = frac * SCREEN_HEIGHT;
+	if ( y < 1 ) {
+		y = 0;
+	}
+	else {
+		SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader );
+	}
+
+	color[0] = 1;
+	color[1] = 0;
+	color[2] = 0;
+	color[3] = 1;
+	SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color );
+
+
+	// draw the version number
+
+	re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
+
+	i = strlen( Q3_VERSION );
+
+	for (x=0 ; x<i ; x++) {
+		SCR_DrawSmallChar( cls.glconfig.vidWidth - ( i - x + 1 ) * SMALLCHAR_WIDTH,
+			lines - SMALLCHAR_HEIGHT, Q3_VERSION[x] );
+	}
+
+
+	// draw the text
+	con.vislines = lines;
+	rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH;		// rows of text to draw
+
+	y = lines - (SMALLCHAR_HEIGHT*3);
+
+	// draw from the bottom up
+	if (con.display != con.current)
+	{
+	// draw arrows to show the buffer is backscrolled
+		re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
+		for (x=0 ; x<con.linewidth ; x+=4)
+			SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, '^' );
+		y -= SMALLCHAR_HEIGHT;
+		rows--;
+	}
+	
+	row = con.display;
+
+	if ( con.x == 0 ) {
+		row--;
+	}
+
+	currentColor = 7;
+	re.SetColor( g_color_table[currentColor] );
+
+	for (i=0 ; i<rows ; i++, y -= SMALLCHAR_HEIGHT, row--)
+	{
+		if (row < 0)
+			break;
+		if (con.current - row >= con.totallines) {
+			// past scrollback wrap point
+			continue;	
+		}
+
+		text = con.text + (row % con.totallines)*con.linewidth;
+
+		for (x=0 ; x<con.linewidth ; x++) {
+			if ( ( text[x] & 0xff ) == ' ' ) {
+				continue;
+			}
+
+			if ( ( (text[x]>>8)&7 ) != currentColor ) {
+				currentColor = (text[x]>>8)&7;
+				re.SetColor( g_color_table[currentColor] );
+			}
+			SCR_DrawSmallChar(  con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff );
+		}
+	}
+
+	// draw the input prompt, user text, and cursor if desired
+	Con_DrawInput ();
+
+	re.SetColor( NULL );
+}
+
+
+
+/*
+==================
+Con_DrawConsole
+==================
+*/
+void Con_DrawConsole( void ) {
+	// check for console width changes from a vid mode change
+	Con_CheckResize ();
+
+	// if disconnected, render console full screen
+	if ( cls.state == CA_DISCONNECTED ) {
+		if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) {
+			Con_DrawSolidConsole( 1.0 );
+			return;
+		}
+	}
+
+	if ( con.displayFrac ) {
+		Con_DrawSolidConsole( con.displayFrac );
+	}
+
+	if( Key_GetCatcher( ) & ( KEYCATCH_UI | KEYCATCH_CGAME ) )
+		return;
+}
+
+//================================================================
+
+/*
+==================
+Con_RunConsole
+
+Scroll it up or down
+==================
+*/
+void Con_RunConsole (void) {
+	// decide on the destination height of the console
+	if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
+		con.finalFrac = 0.5;		// half screen
+	else
+		con.finalFrac = 0;				// none visible
+	
+	// scroll towards the destination height
+	if (con.finalFrac < con.displayFrac)
+	{
+		con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001;
+		if (con.finalFrac > con.displayFrac)
+			con.displayFrac = con.finalFrac;
+
+	}
+	else if (con.finalFrac > con.displayFrac)
+	{
+		con.displayFrac += con_conspeed->value*cls.realFrametime*0.001;
+		if (con.finalFrac < con.displayFrac)
+			con.displayFrac = con.finalFrac;
+	}
+
+}
+
+
+void Con_PageUp( void ) {
+	con.display -= 2;
+	if ( con.current - con.display >= con.totallines ) {
+		con.display = con.current - con.totallines + 1;
+	}
+}
+
+void Con_PageDown( void ) {
+	con.display += 2;
+	if (con.display > con.current) {
+		con.display = con.current;
+	}
+}
+
+void Con_Top( void ) {
+	con.display = con.totallines;
+	if ( con.current - con.display >= con.totallines ) {
+		con.display = con.current - con.totallines + 1;
+	}
+}
+
+void Con_Bottom( void ) {
+	con.display = con.current;
+}
+
+
+void Con_Close( void ) {
+	if ( !com_cl_running->integer ) {
+		return;
+	}
+	Field_Clear( &g_consoleField );
+	Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE );
+	con.finalFrac = 0;				// none visible
+	con.displayFrac = 0;
+}
diff --git a/src/client/cl_curl.c b/src/client/cl_curl.c
new file mode 100644
index 0000000..3a59f19
--- /dev/null
+++ b/src/client/cl_curl.c
@@ -0,0 +1,339 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#ifdef USE_CURL
+#include "client.h"
+cvar_t *cl_cURLLib;
+
+#ifdef USE_CURL_DLOPEN
+#include "../sys/sys_loadlib.h"
+
+char* (*qcurl_version)(void);
+
+CURL* (*qcurl_easy_init)(void);
+CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
+CURLcode (*qcurl_easy_perform)(CURL *curl);
+void (*qcurl_easy_cleanup)(CURL *curl);
+CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
+CURL* (*qcurl_easy_duphandle)(CURL *curl);
+void (*qcurl_easy_reset)(CURL *curl);
+const char *(*qcurl_easy_strerror)(CURLcode);
+
+CURLM* (*qcurl_multi_init)(void);
+CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle,
+                                                CURL *curl_handle);
+CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle,
+                                                CURL *curl_handle);
+CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle,
+                                                fd_set *read_fd_set,
+                                                fd_set *write_fd_set,
+                                                fd_set *exc_fd_set,
+                                                int *max_fd);
+CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle,
+                                                int *running_handles);
+CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
+CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle,
+                                                int *msgs_in_queue);
+const char *(*qcurl_multi_strerror)(CURLMcode);
+
+static void *cURLLib = NULL;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(char *str)
+{
+	void *rv;
+
+	rv = Sys_LoadFunction(cURLLib, str);
+	if(!rv)
+	{
+		Com_Printf("Can't load symbol %s\n", str);
+		clc.cURLEnabled = qfalse;
+		return NULL;
+	}
+	else
+	{
+		Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv);
+        return rv;
+	}
+}
+#endif /* USE_CURL_DLOPEN */
+
+/*
+=================
+CL_cURL_Init
+=================
+*/
+qboolean CL_cURL_Init()
+{
+#ifdef USE_CURL_DLOPEN
+	if(cURLLib)
+		return qtrue;
+
+
+	Com_Printf("Loading \"%s\"...", cl_cURLLib->string);
+	if( (cURLLib = Sys_LoadLibrary(cl_cURLLib->string)) == 0 )
+	{
+#ifdef _WIN32
+		return qfalse;
+#else
+		char fn[1024];
+
+		Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) );
+		strncat(fn, "/", sizeof(fn)-strlen(fn)-1);
+		strncat(fn, cl_cURLLib->string, sizeof(fn)-strlen(fn)-1);
+
+		if((cURLLib = Sys_LoadLibrary(fn)) == 0)
+		{
+#ifdef ALTERNATE_CURL_LIB
+			// On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too.
+			if( (cURLLib = Sys_LoadLibrary(ALTERNATE_CURL_LIB)) == 0 )
+			{
+				return qfalse;
+			}
+#else
+			return qfalse;
+#endif
+		}
+#endif /* _WIN32 */
+	}
+
+	clc.cURLEnabled = qtrue;
+
+	qcurl_version = GPA("curl_version");
+
+	qcurl_easy_init = GPA("curl_easy_init");
+	qcurl_easy_setopt = GPA("curl_easy_setopt");
+	qcurl_easy_perform = GPA("curl_easy_perform");
+	qcurl_easy_cleanup = GPA("curl_easy_cleanup");
+	qcurl_easy_getinfo = GPA("curl_easy_getinfo");
+	qcurl_easy_duphandle = GPA("curl_easy_duphandle");
+	qcurl_easy_reset = GPA("curl_easy_reset");
+	qcurl_easy_strerror = GPA("curl_easy_strerror");
+	
+	qcurl_multi_init = GPA("curl_multi_init");
+	qcurl_multi_add_handle = GPA("curl_multi_add_handle");
+	qcurl_multi_remove_handle = GPA("curl_multi_remove_handle");
+	qcurl_multi_fdset = GPA("curl_multi_fdset");
+	qcurl_multi_perform = GPA("curl_multi_perform");
+	qcurl_multi_cleanup = GPA("curl_multi_cleanup");
+	qcurl_multi_info_read = GPA("curl_multi_info_read");
+	qcurl_multi_strerror = GPA("curl_multi_strerror");
+
+	if(!clc.cURLEnabled)
+	{
+		CL_cURL_Shutdown();
+		Com_Printf("FAIL One or more symbols not found\n");
+		return qfalse;
+	}
+	Com_Printf("OK\n");
+
+	return qtrue;
+#else
+	clc.cURLEnabled = qtrue;
+	return qtrue;
+#endif /* USE_CURL_DLOPEN */
+}
+
+/*
+=================
+CL_cURL_Shutdown
+=================
+*/
+void CL_cURL_Shutdown( void )
+{
+	CL_cURL_Cleanup();
+#ifdef USE_CURL_DLOPEN
+	if(cURLLib)
+	{
+		Sys_UnloadLibrary(cURLLib);
+		cURLLib = NULL;
+	}
+	qcurl_easy_init = NULL;
+	qcurl_easy_setopt = NULL;
+	qcurl_easy_perform = NULL;
+	qcurl_easy_cleanup = NULL;
+	qcurl_easy_getinfo = NULL;
+	qcurl_easy_duphandle = NULL;
+	qcurl_easy_reset = NULL;
+
+	qcurl_multi_init = NULL;
+	qcurl_multi_add_handle = NULL;
+	qcurl_multi_remove_handle = NULL;
+	qcurl_multi_fdset = NULL;
+	qcurl_multi_perform = NULL;
+	qcurl_multi_cleanup = NULL;
+	qcurl_multi_info_read = NULL;
+	qcurl_multi_strerror = NULL;
+#endif /* USE_CURL_DLOPEN */
+}
+
+void CL_cURL_Cleanup(void)
+{
+	if(clc.downloadCURLM) {
+		if(clc.downloadCURL) {
+			qcurl_multi_remove_handle(clc.downloadCURLM,
+				clc.downloadCURL);
+			qcurl_easy_cleanup(clc.downloadCURL);
+		}
+		qcurl_multi_cleanup(clc.downloadCURLM);
+		clc.downloadCURLM = NULL;
+		clc.downloadCURL = NULL;
+	}
+	else if(clc.downloadCURL) {
+		qcurl_easy_cleanup(clc.downloadCURL);
+		clc.downloadCURL = NULL;
+	}
+}
+
+static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow,
+	double ultotal, double ulnow )
+{
+	clc.downloadSize = (int)dltotal;
+	Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
+	clc.downloadCount = (int)dlnow;
+	Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
+	return 0;
+}
+
+static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb,
+	void *stream)
+{
+	FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] );
+	return size*nmemb;
+}
+
+void CL_cURL_BeginDownload( const char *localName, const char *remoteURL )
+{
+	clc.cURLUsed = qtrue;
+	Com_Printf("URL: %s\n", remoteURL);
+	Com_DPrintf("***** CL_cURL_BeginDownload *****\n"
+		"Localname: %s\n"
+		"RemoteURL: %s\n"
+		"****************************\n", localName, remoteURL);
+	CL_cURL_Cleanup();
+	Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL));
+	Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName));
+	Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName),
+		"%s.tmp", localName);
+
+	// Set so UI gets access to it
+	Cvar_Set("cl_downloadName", localName);
+	Cvar_Set("cl_downloadSize", "0");
+	Cvar_Set("cl_downloadCount", "0");
+	Cvar_SetValue("cl_downloadTime", cls.realtime);
+
+	clc.downloadBlock = 0; // Starting new file
+	clc.downloadCount = 0;
+
+	clc.downloadCURL = qcurl_easy_init();
+	if(!clc.downloadCURL) {
+		Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() "
+			"failed\n");
+		return;
+	}
+	clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName);
+	if(!clc.download) {
+		Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open "
+			"%s for writing\n", clc.downloadTempName);
+		return;
+	}
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, clc.download);
+	if(com_developer->integer)
+		qcurl_easy_setopt(clc.downloadCURL, CURLOPT_VERBOSE, 1);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_URL, clc.downloadURL);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_REFERER,
+		va("Tremulous://%s", NET_AdrToString(clc.serverAddress)));
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s",
+		Q3_VERSION, qcurl_version()));
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEFUNCTION,
+		CL_cURL_CallbackWrite);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_NOPROGRESS, 0);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION,
+		CL_cURL_CallbackProgress);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FAILONERROR, 1);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1);
+	qcurl_easy_setopt(clc.downloadCURL, CURLOPT_MAXREDIRS, 5);
+	clc.downloadCURLM = qcurl_multi_init();	
+	if(!clc.downloadCURLM) {
+		qcurl_easy_cleanup(clc.downloadCURL);
+		clc.downloadCURL = NULL;
+		Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() "
+			"failed\n");
+		return;
+	}
+	qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL);
+
+	if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) &&
+		!clc.cURLDisconnected) {
+
+		CL_AddReliableCommand("disconnect", qtrue);
+		CL_WritePacket();
+		CL_WritePacket();
+		CL_WritePacket();
+		clc.cURLDisconnected = qtrue;
+	}
+}
+
+void CL_cURL_PerformDownload(void)
+{
+	CURLMcode res;
+	CURLMsg *msg;
+	int c;
+	int i = 0;
+
+	res = qcurl_multi_perform(clc.downloadCURLM, &c);
+	while(res == CURLM_CALL_MULTI_PERFORM && i < 100) {
+		res = qcurl_multi_perform(clc.downloadCURLM, &c);
+		i++;
+	}
+	if(res == CURLM_CALL_MULTI_PERFORM)
+		return;
+	msg = qcurl_multi_info_read(clc.downloadCURLM, &c);
+	if(msg == NULL) {
+		return;
+	}
+	FS_FCloseFile(clc.download);
+	if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) {
+		FS_SV_Rename(clc.downloadTempName, clc.downloadName);
+		clc.downloadRestart = qtrue;
+	}
+	else {
+		long code;
+
+		qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE,
+			&code);	
+		Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s",
+			qcurl_easy_strerror(msg->data.result),
+			code, clc.downloadURL);
+	}
+
+	CL_NextDownload();
+}
+#endif /* USE_CURL */
diff --git a/src/client/cl_curl.h b/src/client/cl_curl.h
new file mode 100644
index 0000000..7333ae2
--- /dev/null
+++ b/src/client/cl_curl.h
@@ -0,0 +1,103 @@
+/*
+===========================================================================
+Copyright (C) 2006 Tony J. White (tjw@tjw.org)
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+
+#ifndef __QCURL_H__
+#define __QCURL_H__
+
+extern cvar_t *cl_cURLLib;
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef WIN32
+#define DEFAULT_CURL_LIB "libcurl-3.dll"
+#elif defined(MACOS_X)
+#define DEFAULT_CURL_LIB "libcurl.dylib"
+#else
+#define DEFAULT_CURL_LIB "libcurl.so.4"
+#define ALTERNATE_CURL_LIB "libcurl.so.3"
+#endif
+
+#ifdef USE_LOCAL_HEADERS
+  #include "../libcurl/curl/curl.h"
+#else
+  #include <curl/curl.h>
+#endif
+
+
+#ifdef USE_CURL_DLOPEN
+extern char* (*qcurl_version)(void);
+
+extern CURL* (*qcurl_easy_init)(void);
+extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
+extern CURLcode (*qcurl_easy_perform)(CURL *curl);
+extern void (*qcurl_easy_cleanup)(CURL *curl);
+extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
+extern void (*qcurl_easy_reset)(CURL *curl);
+extern const char *(*qcurl_easy_strerror)(CURLcode);
+
+extern CURLM* (*qcurl_multi_init)(void);
+extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle,
+						CURL *curl_handle);
+extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle,
+						CURL *curl_handle);
+extern CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle,
+						fd_set *read_fd_set,
+						fd_set *write_fd_set,
+						fd_set *exc_fd_set,
+						int *max_fd);
+extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle,
+						int *running_handles);
+extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
+extern CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle,
+						int *msgs_in_queue);
+extern const char *(*qcurl_multi_strerror)(CURLMcode);
+#else
+#define qcurl_version curl_version
+
+#define qcurl_easy_init curl_easy_init
+#define qcurl_easy_setopt curl_easy_setopt
+#define qcurl_easy_perform curl_easy_perform
+#define qcurl_easy_cleanup curl_easy_cleanup
+#define qcurl_easy_getinfo curl_easy_getinfo
+#define qcurl_easy_duphandle curl_easy_duphandle
+#define qcurl_easy_reset curl_easy_reset
+#define qcurl_easy_strerror curl_easy_strerror
+
+#define qcurl_multi_init curl_multi_init
+#define qcurl_multi_add_handle curl_multi_add_handle
+#define qcurl_multi_remove_handle curl_multi_remove_handle
+#define qcurl_multi_fdset curl_multi_fdset
+#define qcurl_multi_perform curl_multi_perform
+#define qcurl_multi_cleanup curl_multi_cleanup
+#define qcurl_multi_info_read curl_multi_info_read
+#define qcurl_multi_strerror curl_multi_strerror
+#endif
+
+qboolean CL_cURL_Init( void );
+void CL_cURL_Shutdown( void );
+void CL_cURL_BeginDownload( const char *localName, const char *remoteURL );
+void CL_cURL_PerformDownload( void );
+void CL_cURL_Cleanup( void );
+#endif	// __QCURL_H__
diff --git a/src/client/cl_input.c b/src/client/cl_input.c
new file mode 100644
index 0000000..ee5b478
--- /dev/null
+++ b/src/client/cl_input.c
@@ -0,0 +1,1010 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// cl.input.c  -- builds an intended movement command to send to the server
+
+#include "client.h"
+
+unsigned	frame_msec;
+int			old_com_frameTime;
+
+/*
+===============================================================================
+
+KEY BUTTONS
+
+Continuous button event tracking is complicated by the fact that two different
+input sources (say, mouse button 1 and the control key) can both press the
+same button, but the button should only be released when both of the
+pressing key have been released.
+
+When a key event issues a button command (+forward, +attack, etc), it appends
+its key number as argv(1) so it can be matched up with the release.
+
+argv(2) will be set to the time the event happened, which allows exact
+control even at low framerates when the down and up events may both get qued
+at the same time.
+
+===============================================================================
+*/
+
+
+kbutton_t	in_left, in_right, in_forward, in_back;
+kbutton_t	in_lookup, in_lookdown, in_moveleft, in_moveright;
+kbutton_t	in_strafe, in_speed;
+kbutton_t	in_up, in_down;
+
+#ifdef USE_VOIP
+kbutton_t	in_voiprecord;
+#endif
+
+kbutton_t	in_buttons[16];
+
+
+qboolean	in_mlooking;
+
+
+void IN_MLookDown( void ) {
+	in_mlooking = qtrue;
+}
+
+void IN_MLookUp( void ) {
+	in_mlooking = qfalse;
+	if ( !cl_freelook->integer ) {
+		IN_CenterView ();
+	}
+}
+
+void IN_KeyDown( kbutton_t *b ) {
+	int		k;
+	char	*c;
+	
+	c = Cmd_Argv(1);
+	if ( c[0] ) {
+		k = atoi(c);
+	} else {
+		k = -1;		// typed manually at the console for continuous down
+	}
+
+	if ( k == b->down[0] || k == b->down[1] ) {
+		return;		// repeating key
+	}
+	
+	if ( !b->down[0] ) {
+		b->down[0] = k;
+	} else if ( !b->down[1] ) {
+		b->down[1] = k;
+	} else {
+		Com_Printf ("Three keys down for a button!\n");
+		return;
+	}
+	
+	if ( b->active ) {
+		return;		// still down
+	}
+
+	// save timestamp for partial frame summing
+	c = Cmd_Argv(2);
+	b->downtime = atoi(c);
+
+	b->active = qtrue;
+	b->wasPressed = qtrue;
+}
+
+void IN_KeyUp( kbutton_t *b ) {
+	int		k;
+	char	*c;
+	unsigned	uptime;
+
+	c = Cmd_Argv(1);
+	if ( c[0] ) {
+		k = atoi(c);
+	} else {
+		// typed manually at the console, assume for unsticking, so clear all
+		b->down[0] = b->down[1] = 0;
+		b->active = qfalse;
+		return;
+	}
+
+	if ( b->down[0] == k ) {
+		b->down[0] = 0;
+	} else if ( b->down[1] == k ) {
+		b->down[1] = 0;
+	} else {
+		return;		// key up without coresponding down (menu pass through)
+	}
+	if ( b->down[0] || b->down[1] ) {
+		return;		// some other key is still holding it down
+	}
+
+	b->active = qfalse;
+
+	// save timestamp for partial frame summing
+	c = Cmd_Argv(2);
+	uptime = atoi(c);
+	if ( uptime ) {
+		b->msec += uptime - b->downtime;
+	} else {
+		b->msec += frame_msec / 2;
+	}
+
+	b->active = qfalse;
+}
+
+
+
+/*
+===============
+CL_KeyState
+
+Returns the fraction of the frame that the key was down
+===============
+*/
+float CL_KeyState( kbutton_t *key ) {
+	float		val;
+	int			msec;
+
+	msec = key->msec;
+	key->msec = 0;
+
+	if ( key->active ) {
+		// still down
+		if ( !key->downtime ) {
+			msec = com_frameTime;
+		} else {
+			msec += com_frameTime - key->downtime;
+		}
+		key->downtime = com_frameTime;
+	}
+
+#if 0
+	if (msec) {
+		Com_Printf ("%i ", msec);
+	}
+#endif
+
+	val = (float)msec / frame_msec;
+	if ( val < 0 ) {
+		val = 0;
+	}
+	if ( val > 1 ) {
+		val = 1;
+	}
+
+	return val;
+}
+
+
+
+void IN_UpDown(void) {IN_KeyDown(&in_up);}
+void IN_UpUp(void) {IN_KeyUp(&in_up);}
+void IN_DownDown(void) {IN_KeyDown(&in_down);}
+void IN_DownUp(void) {IN_KeyUp(&in_down);}
+void IN_LeftDown(void) {IN_KeyDown(&in_left);}
+void IN_LeftUp(void) {IN_KeyUp(&in_left);}
+void IN_RightDown(void) {IN_KeyDown(&in_right);}
+void IN_RightUp(void) {IN_KeyUp(&in_right);}
+void IN_ForwardDown(void) {IN_KeyDown(&in_forward);}
+void IN_ForwardUp(void) {IN_KeyUp(&in_forward);}
+void IN_BackDown(void) {IN_KeyDown(&in_back);}
+void IN_BackUp(void) {IN_KeyUp(&in_back);}
+void IN_LookupDown(void) {IN_KeyDown(&in_lookup);}
+void IN_LookupUp(void) {IN_KeyUp(&in_lookup);}
+void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);}
+void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);}
+void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);}
+void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);}
+void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);}
+void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);}
+
+void IN_SpeedDown(void) {IN_KeyDown(&in_speed);}
+void IN_SpeedUp(void) {IN_KeyUp(&in_speed);}
+void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);}
+void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);}
+
+#ifdef USE_VOIP
+void IN_VoipRecordDown(void)
+{
+	IN_KeyDown(&in_voiprecord);
+	Cvar_Set("cl_voipSend", "1");
+}
+
+void IN_VoipRecordUp(void)
+{
+	IN_KeyUp(&in_voiprecord);
+	Cvar_Set("cl_voipSend", "0");
+}
+#endif
+
+void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);}
+void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);}
+void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);}
+void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);}
+void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);}
+void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);}
+void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);}
+void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);}
+void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);}
+void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);}
+void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);}
+void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);}
+void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);}
+void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);}
+void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);}
+void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);}
+void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);}
+void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);}
+void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);}
+void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);}
+void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);}
+void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);}
+void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);}
+void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);}
+void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);}
+void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);}
+void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);}
+void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);}
+void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);}
+void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);}
+void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);}
+void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);}
+
+void IN_ButtonDown (void) {
+	IN_KeyDown(&in_buttons[1]);}
+void IN_ButtonUp (void) {
+	IN_KeyUp(&in_buttons[1]);}
+
+void IN_CenterView (void) {
+	cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]);
+}
+
+
+//==========================================================================
+
+cvar_t	*cl_upspeed;
+cvar_t	*cl_forwardspeed;
+cvar_t	*cl_sidespeed;
+
+cvar_t	*cl_yawspeed;
+cvar_t	*cl_pitchspeed;
+
+cvar_t	*cl_run;
+
+cvar_t	*cl_anglespeedkey;
+
+
+/*
+================
+CL_AdjustAngles
+
+Moves the local angle positions
+================
+*/
+void CL_AdjustAngles( void ) {
+	float	speed;
+	
+	if ( in_speed.active ) {
+		speed = 0.001 * cls.frametime * cl_anglespeedkey->value;
+	} else {
+		speed = 0.001 * cls.frametime;
+	}
+
+	if ( !in_strafe.active ) {
+		cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right);
+		cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left);
+	}
+
+	cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup);
+	cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown);
+}
+
+/*
+================
+CL_KeyMove
+
+Sets the usercmd_t based on key states
+================
+*/
+void CL_KeyMove( usercmd_t *cmd ) {
+	int		movespeed;
+	int		forward, side, up;
+
+	//
+	// adjust for speed key / running
+	// the walking flag is to keep animations consistant
+	// even during acceleration and develeration
+	//
+	if ( in_speed.active ^ cl_run->integer ) {
+		movespeed = 127;
+		cmd->buttons &= ~BUTTON_WALKING;
+	} else {
+		cmd->buttons |= BUTTON_WALKING;
+		movespeed = 64;
+	}
+
+	forward = 0;
+	side = 0;
+	up = 0;
+	if ( in_strafe.active ) {
+		side += movespeed * CL_KeyState (&in_right);
+		side -= movespeed * CL_KeyState (&in_left);
+	}
+
+	side += movespeed * CL_KeyState (&in_moveright);
+	side -= movespeed * CL_KeyState (&in_moveleft);
+
+
+	up += movespeed * CL_KeyState (&in_up);
+	up -= movespeed * CL_KeyState (&in_down);
+
+	forward += movespeed * CL_KeyState (&in_forward);
+	forward -= movespeed * CL_KeyState (&in_back);
+
+	cmd->forwardmove = ClampChar( forward );
+	cmd->rightmove = ClampChar( side );
+	cmd->upmove = ClampChar( up );
+}
+
+/*
+=================
+CL_MouseEvent
+=================
+*/
+void CL_MouseEvent( int dx, int dy, int time ) {
+	if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
+		VM_Call( uivm, UI_MOUSE_EVENT, dx, dy );
+	} else if (Key_GetCatcher( ) & KEYCATCH_CGAME) {
+		VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy);
+	} else {
+		cl.mouseDx[cl.mouseIndex] += dx;
+		cl.mouseDy[cl.mouseIndex] += dy;
+	}
+}
+
+/*
+=================
+CL_JoystickEvent
+
+Joystick values stay set until changed
+=================
+*/
+void CL_JoystickEvent( int axis, int value, int time ) {
+	if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) {
+		Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis );
+	}
+	cl.joystickAxis[axis] = value;
+}
+
+/*
+=================
+CL_JoystickMove
+=================
+*/
+void CL_JoystickMove( usercmd_t *cmd ) {
+	int		movespeed;
+	float	anglespeed;
+
+	if ( in_speed.active ^ cl_run->integer ) {
+		movespeed = 2;
+	} else {
+		movespeed = 1;
+		cmd->buttons |= BUTTON_WALKING;
+	}
+
+	if ( in_speed.active ) {
+		anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value;
+	} else {
+		anglespeed = 0.001 * cls.frametime;
+	}
+
+	if ( !in_strafe.active ) {
+		cl.viewangles[YAW] += anglespeed * j_yaw->value * cl.joystickAxis[j_yaw_axis->integer];
+		cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_side->value * cl.joystickAxis[j_side_axis->integer]) );
+	} else {
+		cl.viewangles[YAW] += anglespeed * j_side->value * cl.joystickAxis[j_side_axis->integer];
+		cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]) );
+	}
+
+	if ( in_mlooking ) {
+		cl.viewangles[PITCH] += anglespeed * j_forward->value * cl.joystickAxis[j_forward_axis->integer];
+		cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]) );
+	} else {
+		cl.viewangles[PITCH] += anglespeed * j_pitch->value * cl.joystickAxis[j_pitch_axis->integer];
+		cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_forward->value * cl.joystickAxis[j_forward_axis->integer]) );
+	}
+
+	cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] );
+}
+
+/*
+=================
+CL_MouseMove
+=================
+*/
+
+void CL_MouseMove(usercmd_t *cmd)
+{
+	float mx, my;
+
+	// allow mouse smoothing
+	if (m_filter->integer)
+	{
+		mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f;
+		my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f;
+	}
+	else
+	{
+		mx = cl.mouseDx[cl.mouseIndex];
+		my = cl.mouseDy[cl.mouseIndex];
+	}
+	
+	cl.mouseIndex ^= 1;
+	cl.mouseDx[cl.mouseIndex] = 0;
+	cl.mouseDy[cl.mouseIndex] = 0;
+
+	if (mx == 0.0f && my == 0.0f)
+		return;
+	
+	if (cl_mouseAccel->value != 0.0f)
+	{
+		if(cl_mouseAccelStyle->integer == 0)
+		{
+			float accelSensitivity;
+			float rate;
+			
+			rate = sqrt(mx * mx + my * my) / (float) frame_msec;
+
+			accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value;
+			mx *= accelSensitivity;
+			my *= accelSensitivity;
+			
+			if(cl_showMouseRate->integer)
+				Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity);
+		}
+		else
+		{
+			float rate[2];
+			float power[2];
+
+			// sensitivity remains pretty much unchanged at low speeds
+			// cl_mouseAccel is a power value to how the acceleration is shaped
+			// cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated amplification
+			// NOTE: decouple the config cvars for independent acceleration setup along X and Y?
+
+			rate[0] = fabs(mx) / (float) frame_msec;
+			rate[1] = fabs(my) / (float) frame_msec;
+			power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value);
+			power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value);
+
+			mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value);
+			my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value);
+
+			if(cl_showMouseRate->integer)
+				Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]);
+		}
+	}
+	else
+	{
+		mx *= cl_sensitivity->value;
+		my *= cl_sensitivity->value;
+	}
+
+	// ingame FOV
+	mx *= cl.cgameSensitivity;
+	my *= cl.cgameSensitivity;
+
+	// add mouse X/Y movement to cmd
+	if(in_strafe.active)
+		cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx);
+	else
+		cl.viewangles[YAW] -= m_yaw->value * mx;
+
+	if ((in_mlooking || cl_freelook->integer) && !in_strafe.active)
+		cl.viewangles[PITCH] += m_pitch->value * my;
+	else
+		cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my);
+}
+
+
+/*
+==============
+CL_CmdButtons
+==============
+*/
+void CL_CmdButtons( usercmd_t *cmd ) {
+	int		i;
+
+	//
+	// figure button bits
+	// send a button bit even if the key was pressed and released in
+	// less than a frame
+	//	
+	for (i = 0 ; i < 15 ; i++) {
+		if ( in_buttons[i].active || in_buttons[i].wasPressed ) {
+			cmd->buttons |= 1 << i;
+		}
+		in_buttons[i].wasPressed = qfalse;
+	}
+
+	if ( Key_GetCatcher( ) ) {
+		cmd->buttons |= BUTTON_TALK;
+	}
+
+	// allow the game to know if any key at all is
+	// currently pressed, even if it isn't bound to anything
+	if ( anykeydown && Key_GetCatcher( ) == 0 ) {
+		cmd->buttons |= BUTTON_ANY;
+	}
+}
+
+
+/*
+==============
+CL_FinishMove
+==============
+*/
+void CL_FinishMove( usercmd_t *cmd ) {
+	int		i;
+
+	// copy the state that the cgame is currently sending
+	cmd->weapon = cl.cgameUserCmdValue;
+
+	// send the current server time so the amount of movement
+	// can be determined without allowing cheating
+	cmd->serverTime = cl.serverTime;
+
+	for (i=0 ; i<3 ; i++) {
+		cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
+	}
+}
+
+
+/*
+=================
+CL_CreateCmd
+=================
+*/
+usercmd_t CL_CreateCmd( void ) {
+	usercmd_t	cmd;
+	vec3_t		oldAngles;
+
+	VectorCopy( cl.viewangles, oldAngles );
+
+	// keyboard angle adjustment
+	CL_AdjustAngles ();
+	
+	Com_Memset( &cmd, 0, sizeof( cmd ) );
+
+	CL_CmdButtons( &cmd );
+
+	// get basic movement from keyboard
+	CL_KeyMove( &cmd );
+
+	// get basic movement from mouse
+	CL_MouseMove( &cmd );
+
+	// get basic movement from joystick
+	CL_JoystickMove( &cmd );
+
+	// check to make sure the angles haven't wrapped
+	if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) {
+		cl.viewangles[PITCH] = oldAngles[PITCH] + 90;
+	} else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) {
+		cl.viewangles[PITCH] = oldAngles[PITCH] - 90;
+	} 
+
+	// store out the final values
+	CL_FinishMove( &cmd );
+
+	// draw debug graphs of turning for mouse testing
+	if ( cl_debugMove->integer ) {
+		if ( cl_debugMove->integer == 1 ) {
+			SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 );
+		}
+		if ( cl_debugMove->integer == 2 ) {
+			SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 );
+		}
+	}
+
+	return cmd;
+}
+
+
+/*
+=================
+CL_CreateNewCommands
+
+Create a new usercmd_t structure for this frame
+=================
+*/
+void CL_CreateNewCommands( void ) {
+	usercmd_t	*cmd;
+	int			cmdNum;
+
+	// no need to create usercmds until we have a gamestate
+	if ( cls.state < CA_PRIMED ) {
+		return;
+	}
+
+	frame_msec = com_frameTime - old_com_frameTime;
+
+	// if running less than 5fps, truncate the extra time to prevent
+	// unexpected moves after a hitch
+	if ( frame_msec > 200 ) {
+		frame_msec = 200;
+	}
+	old_com_frameTime = com_frameTime;
+
+
+	// generate a command for this frame
+	cl.cmdNumber++;
+	cmdNum = cl.cmdNumber & CMD_MASK;
+	cl.cmds[cmdNum] = CL_CreateCmd ();
+	cmd = &cl.cmds[cmdNum];
+}
+
+/*
+=================
+CL_ReadyToSendPacket
+
+Returns qfalse if we are over the maxpackets limit
+and should choke back the bandwidth a bit by not sending
+a packet this frame.  All the commands will still get
+delivered in the next packet, but saving a header and
+getting more delta compression will reduce total bandwidth.
+=================
+*/
+qboolean CL_ReadyToSendPacket( void ) {
+	int		oldPacketNum;
+	int		delta;
+
+	// don't send anything if playing back a demo
+	if ( clc.demoplaying || cls.state == CA_CINEMATIC ) {
+		return qfalse;
+	}
+
+	// If we are downloading, we send no less than 50ms between packets
+	if ( *clc.downloadTempName &&
+		cls.realtime - clc.lastPacketSentTime < 50 ) {
+		return qfalse;
+	}
+
+	// if we don't have a valid gamestate yet, only send
+	// one packet a second
+	if ( cls.state != CA_ACTIVE && 
+		cls.state != CA_PRIMED && 
+		!*clc.downloadTempName &&
+		cls.realtime - clc.lastPacketSentTime < 1000 ) {
+		return qfalse;
+	}
+
+	// send every frame for loopbacks
+	if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) {
+		return qtrue;
+	}
+
+	// send every frame for LAN
+	if ( cl_lanForcePackets->integer && Sys_IsLANAddress( clc.netchan.remoteAddress ) ) {
+		return qtrue;
+	}
+
+	// check for exceeding cl_maxpackets
+	if ( cl_maxpackets->integer < 15 ) {
+		Cvar_Set( "cl_maxpackets", "15" );
+	} else if ( cl_maxpackets->integer > 125 ) {
+		Cvar_Set( "cl_maxpackets", "125" );
+	}
+	oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK;
+	delta = cls.realtime -  cl.outPackets[ oldPacketNum ].p_realtime;
+	if ( delta < 1000 / cl_maxpackets->integer ) {
+		// the accumulated commands will go out in the next packet
+		return qfalse;
+	}
+
+	return qtrue;
+}
+
+/*
+===================
+CL_WritePacket
+
+Create and send the command packet to the server
+Including both the reliable commands and the usercmds
+
+During normal gameplay, a client packet will contain something like:
+
+4	sequence number
+2	qport
+4	serverid
+4	acknowledged sequence number
+4	clc.serverCommandSequence
+<optional reliable commands>
+1	clc_move or clc_moveNoDelta
+1	command count
+<count * usercmds>
+
+===================
+*/
+void CL_WritePacket( void ) {
+	msg_t		buf;
+	byte		data[MAX_MSGLEN];
+	int			i, j;
+	usercmd_t	*cmd, *oldcmd;
+	usercmd_t	nullcmd;
+	int			packetNum;
+	int			oldPacketNum;
+	int			count, key;
+
+	// don't send anything if playing back a demo
+	if ( clc.demoplaying || cls.state == CA_CINEMATIC ) {
+		return;
+	}
+
+	Com_Memset( &nullcmd, 0, sizeof(nullcmd) );
+	oldcmd = &nullcmd;
+
+	MSG_Init( &buf, data, sizeof(data) );
+
+	MSG_Bitstream( &buf );
+	// write the current serverId so the server
+	// can tell if this is from the current gameState
+	MSG_WriteLong( &buf, cl.serverId );
+
+	// write the last message we received, which can
+	// be used for delta compression, and is also used
+	// to tell if we dropped a gamestate
+	MSG_WriteLong( &buf, clc.serverMessageSequence );
+
+	// write the last reliable message we received
+	MSG_WriteLong( &buf, clc.serverCommandSequence );
+
+	// write any unacknowledged clientCommands
+	for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) {
+		MSG_WriteByte( &buf, clc_clientCommand );
+		MSG_WriteLong( &buf, i );
+		MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
+	}
+
+	// we want to send all the usercmds that were generated in the last
+	// few packet, so even if a couple packets are dropped in a row,
+	// all the cmds will make it to the server
+	if ( cl_packetdup->integer < 0 ) {
+		Cvar_Set( "cl_packetdup", "0" );
+	} else if ( cl_packetdup->integer > 5 ) {
+		Cvar_Set( "cl_packetdup", "5" );
+	}
+	oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK;
+	count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber;
+	if ( count > MAX_PACKET_USERCMDS ) {
+		count = MAX_PACKET_USERCMDS;
+		Com_Printf("MAX_PACKET_USERCMDS\n");
+	}
+
+#ifdef USE_VOIP
+	if (clc.voipOutgoingDataSize > 0) {  // only send if data.
+		MSG_WriteByte (&buf, clc_EOF);  // placate legacy servers.
+		MSG_WriteByte (&buf, clc_extension);
+		MSG_WriteByte (&buf, clc_voip);
+		MSG_WriteByte (&buf, clc.voipOutgoingGeneration);
+		MSG_WriteLong (&buf, clc.voipOutgoingSequence);
+		MSG_WriteByte (&buf, clc.voipOutgoingDataFrames);
+		MSG_WriteLong (&buf, clc.voipTarget1);
+		MSG_WriteLong (&buf, clc.voipTarget2);
+		MSG_WriteLong (&buf, clc.voipTarget3);
+		MSG_WriteShort (&buf, clc.voipOutgoingDataSize);
+		MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize);
+
+		// If we're recording a demo, we have to fake a server packet with
+		//  this VoIP data so it gets to disk; the server doesn't send it
+		//  back to us, and we might as well eliminate concerns about dropped
+		//  and misordered packets here.
+		if ( clc.demorecording && !clc.demowaiting ) {
+			const int voipSize = clc.voipOutgoingDataSize;
+			msg_t fakemsg;
+			byte fakedata[MAX_MSGLEN];
+			MSG_Init (&fakemsg, fakedata, sizeof (fakedata));
+			MSG_Bitstream (&fakemsg);
+			MSG_WriteLong (&fakemsg, clc.reliableAcknowledge);
+			MSG_WriteByte (&fakemsg, svc_EOF);
+			MSG_WriteByte (&fakemsg, svc_extension);
+			MSG_WriteByte (&fakemsg, svc_voip);
+			MSG_WriteShort (&fakemsg, clc.clientNum);
+			MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration);
+			MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence);
+			MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames);
+			MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize );
+			MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize);
+			MSG_WriteByte (&fakemsg, svc_EOF);
+			CL_WriteDemoMessage (&fakemsg, 0);
+		}
+
+		clc.voipOutgoingSequence += clc.voipOutgoingDataFrames;
+		clc.voipOutgoingDataSize = 0;
+		clc.voipOutgoingDataFrames = 0;
+	} else
+#endif
+
+	if ( count >= 1 ) {
+		if ( cl_showSend->integer ) {
+			Com_Printf( "(%i)", count );
+		}
+
+		// begin a client move command
+		if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting
+			|| clc.serverMessageSequence != cl.snap.messageNum ) {
+			MSG_WriteByte (&buf, clc_moveNoDelta);
+		} else {
+			MSG_WriteByte (&buf, clc_move);
+		}
+
+		// write the command count
+		MSG_WriteByte( &buf, count );
+
+		// use the checksum feed in the key
+		key = clc.checksumFeed;
+		// also use the message acknowledge
+		key ^= clc.serverMessageSequence;
+		// also use the last acknowledged server command in the key
+		key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32);
+
+		// write all the commands, including the predicted command
+		for ( i = 0 ; i < count ; i++ ) {
+			j = (cl.cmdNumber - count + i + 1) & CMD_MASK;
+			cmd = &cl.cmds[j];
+			MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd);
+			oldcmd = cmd;
+		}
+	}
+
+	//
+	// deliver the message
+	//
+	packetNum = clc.netchan.outgoingSequence & PACKET_MASK;
+	cl.outPackets[ packetNum ].p_realtime = cls.realtime;
+	cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime;
+	cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber;
+	clc.lastPacketSentTime = cls.realtime;
+
+	if ( cl_showSend->integer ) {
+		Com_Printf( "%i ", buf.cursize );
+	}
+
+	CL_Netchan_Transmit (&clc.netchan, &buf);	
+
+	// clients never really should have messages large enough
+	// to fragment, but in case they do, fire them all off
+	// at once
+	// TTimo: this causes a packet burst, which is bad karma for winsock
+	// added a WARNING message, we'll see if there are legit situations where this happens
+	while ( clc.netchan.unsentFragments ) {
+		Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" );
+		CL_Netchan_TransmitNextFragment( &clc.netchan );
+	}
+}
+
+/*
+=================
+CL_SendCmd
+
+Called every frame to builds and sends a command packet to the server.
+=================
+*/
+void CL_SendCmd( void ) {
+	// don't send any message if not connected
+	if ( cls.state < CA_CONNECTED ) {
+		return;
+	}
+
+	// don't send commands if paused
+	if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) {
+		return;
+	}
+
+	// we create commands even if a demo is playing,
+	CL_CreateNewCommands();
+
+	// don't send a packet if the last packet was sent too recently
+	if ( !CL_ReadyToSendPacket() ) {
+		if ( cl_showSend->integer ) {
+			Com_Printf( ". " );
+		}
+		return;
+	}
+
+	CL_WritePacket();
+}
+
+/*
+============
+CL_InitInput
+============
+*/
+void CL_InitInput( void ) {
+	Cmd_AddCommand ("centerview",IN_CenterView);
+
+	Cmd_AddCommand ("+moveup",IN_UpDown);
+	Cmd_AddCommand ("-moveup",IN_UpUp);
+	Cmd_AddCommand ("+movedown",IN_DownDown);
+	Cmd_AddCommand ("-movedown",IN_DownUp);
+	Cmd_AddCommand ("+left",IN_LeftDown);
+	Cmd_AddCommand ("-left",IN_LeftUp);
+	Cmd_AddCommand ("+right",IN_RightDown);
+	Cmd_AddCommand ("-right",IN_RightUp);
+	Cmd_AddCommand ("+forward",IN_ForwardDown);
+	Cmd_AddCommand ("-forward",IN_ForwardUp);
+	Cmd_AddCommand ("+back",IN_BackDown);
+	Cmd_AddCommand ("-back",IN_BackUp);
+	Cmd_AddCommand ("+lookup", IN_LookupDown);
+	Cmd_AddCommand ("-lookup", IN_LookupUp);
+	Cmd_AddCommand ("+lookdown", IN_LookdownDown);
+	Cmd_AddCommand ("-lookdown", IN_LookdownUp);
+	Cmd_AddCommand ("+strafe", IN_StrafeDown);
+	Cmd_AddCommand ("-strafe", IN_StrafeUp);
+	Cmd_AddCommand ("+moveleft", IN_MoveleftDown);
+	Cmd_AddCommand ("-moveleft", IN_MoveleftUp);
+	Cmd_AddCommand ("+moveright", IN_MoverightDown);
+	Cmd_AddCommand ("-moveright", IN_MoverightUp);
+	Cmd_AddCommand ("+speed", IN_SpeedDown);
+	Cmd_AddCommand ("-speed", IN_SpeedUp);
+	Cmd_AddCommand ("+attack", IN_Button0Down);
+	Cmd_AddCommand ("-attack", IN_Button0Up);
+	Cmd_AddCommand ("+button0", IN_Button0Down);
+	Cmd_AddCommand ("-button0", IN_Button0Up);
+	Cmd_AddCommand ("+button1", IN_Button1Down);
+	Cmd_AddCommand ("-button1", IN_Button1Up);
+	Cmd_AddCommand ("+button2", IN_Button2Down);
+	Cmd_AddCommand ("-button2", IN_Button2Up);
+	Cmd_AddCommand ("+button3", IN_Button3Down);
+	Cmd_AddCommand ("-button3", IN_Button3Up);
+	Cmd_AddCommand ("+button4", IN_Button4Down);
+	Cmd_AddCommand ("-button4", IN_Button4Up);
+	Cmd_AddCommand ("+button5", IN_Button5Down);
+	Cmd_AddCommand ("-button5", IN_Button5Up);
+	Cmd_AddCommand ("+button6", IN_Button6Down);
+	Cmd_AddCommand ("-button6", IN_Button6Up);
+	Cmd_AddCommand ("+button7", IN_Button7Down);
+	Cmd_AddCommand ("-button7", IN_Button7Up);
+	Cmd_AddCommand ("+button8", IN_Button8Down);
+	Cmd_AddCommand ("-button8", IN_Button8Up);
+	Cmd_AddCommand ("+button9", IN_Button9Down);
+	Cmd_AddCommand ("-button9", IN_Button9Up);
+	Cmd_AddCommand ("+button10", IN_Button10Down);
+	Cmd_AddCommand ("-button10", IN_Button10Up);
+	Cmd_AddCommand ("+button11", IN_Button11Down);
+	Cmd_AddCommand ("-button11", IN_Button11Up);
+	Cmd_AddCommand ("+button12", IN_Button12Down);
+	Cmd_AddCommand ("-button12", IN_Button12Up);
+	Cmd_AddCommand ("+button13", IN_Button13Down);
+	Cmd_AddCommand ("-button13", IN_Button13Up);
+	Cmd_AddCommand ("+button14", IN_Button14Down);
+	Cmd_AddCommand ("-button14", IN_Button14Up);
+	Cmd_AddCommand ("+mlook", IN_MLookDown);
+	Cmd_AddCommand ("-mlook", IN_MLookUp);
+
+#ifdef USE_VOIP
+	Cmd_AddCommand ("+voiprecord", IN_VoipRecordDown);
+	Cmd_AddCommand ("-voiprecord", IN_VoipRecordUp);
+#endif
+
+	cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
+	cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0);
+}
diff --git a/src/client/cl_keys.c b/src/client/cl_keys.c
new file mode 100644
index 0000000..71569fe
--- /dev/null
+++ b/src/client/cl_keys.c
@@ -0,0 +1,1490 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "client.h"
+
+/*
+
+key up events are sent even if in console mode
+
+*/
+
+field_t	historyEditLines[COMMAND_HISTORY];
+
+int			nextHistoryLine;		// the last line in the history buffer, not masked
+int			historyLine;	// the line being displayed from history buffer
+							// will be <= nextHistoryLine
+
+field_t		g_consoleField;
+
+
+qboolean	key_overstrikeMode;
+
+int				anykeydown;
+qkey_t		keys[MAX_KEYS];
+
+
+typedef struct {
+	char	*name;
+	int		keynum;
+} keyname_t;
+
+
+// names not in this list can either be lowercase ascii, or '0xnn' hex sequences
+keyname_t keynames[] =
+{
+	{"TAB", K_TAB},
+	{"ENTER", K_ENTER},
+	{"ESCAPE", K_ESCAPE},
+	{"SPACE", K_SPACE},
+	{"BACKSPACE", K_BACKSPACE},
+	{"UPARROW", K_UPARROW},
+	{"DOWNARROW", K_DOWNARROW},
+	{"LEFTARROW", K_LEFTARROW},
+	{"RIGHTARROW", K_RIGHTARROW},
+
+	{"ALT", K_ALT},
+	{"CTRL", K_CTRL},
+	{"SHIFT", K_SHIFT},
+
+	{"COMMAND", K_COMMAND},
+
+	{"CAPSLOCK", K_CAPSLOCK},
+
+	
+	{"F1", K_F1},
+	{"F2", K_F2},
+	{"F3", K_F3},
+	{"F4", K_F4},
+	{"F5", K_F5},
+	{"F6", K_F6},
+	{"F7", K_F7},
+	{"F8", K_F8},
+	{"F9", K_F9},
+	{"F10", K_F10},
+	{"F11", K_F11},
+	{"F12", K_F12},
+	{"F13", K_F13},
+	{"F14", K_F14},
+	{"F15", K_F15},
+
+	{"INS", K_INS},
+	{"DEL", K_DEL},
+	{"PGDN", K_PGDN},
+	{"PGUP", K_PGUP},
+	{"HOME", K_HOME},
+	{"END", K_END},
+
+	{"MOUSE1", K_MOUSE1},
+	{"MOUSE2", K_MOUSE2},
+	{"MOUSE3", K_MOUSE3},
+	{"MOUSE4", K_MOUSE4},
+	{"MOUSE5", K_MOUSE5},
+
+	{"MWHEELUP",	K_MWHEELUP },
+	{"MWHEELDOWN",	K_MWHEELDOWN },
+
+	{"JOY1", K_JOY1},
+	{"JOY2", K_JOY2},
+	{"JOY3", K_JOY3},
+	{"JOY4", K_JOY4},
+	{"JOY5", K_JOY5},
+	{"JOY6", K_JOY6},
+	{"JOY7", K_JOY7},
+	{"JOY8", K_JOY8},
+	{"JOY9", K_JOY9},
+	{"JOY10", K_JOY10},
+	{"JOY11", K_JOY11},
+	{"JOY12", K_JOY12},
+	{"JOY13", K_JOY13},
+	{"JOY14", K_JOY14},
+	{"JOY15", K_JOY15},
+	{"JOY16", K_JOY16},
+	{"JOY17", K_JOY17},
+	{"JOY18", K_JOY18},
+	{"JOY19", K_JOY19},
+	{"JOY20", K_JOY20},
+	{"JOY21", K_JOY21},
+	{"JOY22", K_JOY22},
+	{"JOY23", K_JOY23},
+	{"JOY24", K_JOY24},
+	{"JOY25", K_JOY25},
+	{"JOY26", K_JOY26},
+	{"JOY27", K_JOY27},
+	{"JOY28", K_JOY28},
+	{"JOY29", K_JOY29},
+	{"JOY30", K_JOY30},
+	{"JOY31", K_JOY31},
+	{"JOY32", K_JOY32},
+
+	{"AUX1", K_AUX1},
+	{"AUX2", K_AUX2},
+	{"AUX3", K_AUX3},
+	{"AUX4", K_AUX4},
+	{"AUX5", K_AUX5},
+	{"AUX6", K_AUX6},
+	{"AUX7", K_AUX7},
+	{"AUX8", K_AUX8},
+	{"AUX9", K_AUX9},
+	{"AUX10", K_AUX10},
+	{"AUX11", K_AUX11},
+	{"AUX12", K_AUX12},
+	{"AUX13", K_AUX13},
+	{"AUX14", K_AUX14},
+	{"AUX15", K_AUX15},
+	{"AUX16", K_AUX16},
+
+	{"KP_HOME",			K_KP_HOME },
+	{"KP_UPARROW",		K_KP_UPARROW },
+	{"KP_PGUP",			K_KP_PGUP },
+	{"KP_LEFTARROW",	K_KP_LEFTARROW },
+	{"KP_5",			K_KP_5 },
+	{"KP_RIGHTARROW",	K_KP_RIGHTARROW },
+	{"KP_END",			K_KP_END },
+	{"KP_DOWNARROW",	K_KP_DOWNARROW },
+	{"KP_PGDN",			K_KP_PGDN },
+	{"KP_ENTER",		K_KP_ENTER },
+	{"KP_INS",			K_KP_INS },
+	{"KP_DEL",			K_KP_DEL },
+	{"KP_SLASH",		K_KP_SLASH },
+	{"KP_MINUS",		K_KP_MINUS },
+	{"KP_PLUS",			K_KP_PLUS },
+	{"KP_NUMLOCK",		K_KP_NUMLOCK },
+	{"KP_STAR",			K_KP_STAR },
+	{"KP_EQUALS",		K_KP_EQUALS },
+
+	{"PAUSE", K_PAUSE},
+	
+	{"SEMICOLON", ';'},	// because a raw semicolon seperates commands
+
+	{"WORLD_0", K_WORLD_0},
+	{"WORLD_1", K_WORLD_1},
+	{"WORLD_2", K_WORLD_2},
+	{"WORLD_3", K_WORLD_3},
+	{"WORLD_4", K_WORLD_4},
+	{"WORLD_5", K_WORLD_5},
+	{"WORLD_6", K_WORLD_6},
+	{"WORLD_7", K_WORLD_7},
+	{"WORLD_8", K_WORLD_8},
+	{"WORLD_9", K_WORLD_9},
+	{"WORLD_10", K_WORLD_10},
+	{"WORLD_11", K_WORLD_11},
+	{"WORLD_12", K_WORLD_12},
+	{"WORLD_13", K_WORLD_13},
+	{"WORLD_14", K_WORLD_14},
+	{"WORLD_15", K_WORLD_15},
+	{"WORLD_16", K_WORLD_16},
+	{"WORLD_17", K_WORLD_17},
+	{"WORLD_18", K_WORLD_18},
+	{"WORLD_19", K_WORLD_19},
+	{"WORLD_20", K_WORLD_20},
+	{"WORLD_21", K_WORLD_21},
+	{"WORLD_22", K_WORLD_22},
+	{"WORLD_23", K_WORLD_23},
+	{"WORLD_24", K_WORLD_24},
+	{"WORLD_25", K_WORLD_25},
+	{"WORLD_26", K_WORLD_26},
+	{"WORLD_27", K_WORLD_27},
+	{"WORLD_28", K_WORLD_28},
+	{"WORLD_29", K_WORLD_29},
+	{"WORLD_30", K_WORLD_30},
+	{"WORLD_31", K_WORLD_31},
+	{"WORLD_32", K_WORLD_32},
+	{"WORLD_33", K_WORLD_33},
+	{"WORLD_34", K_WORLD_34},
+	{"WORLD_35", K_WORLD_35},
+	{"WORLD_36", K_WORLD_36},
+	{"WORLD_37", K_WORLD_37},
+	{"WORLD_38", K_WORLD_38},
+	{"WORLD_39", K_WORLD_39},
+	{"WORLD_40", K_WORLD_40},
+	{"WORLD_41", K_WORLD_41},
+	{"WORLD_42", K_WORLD_42},
+	{"WORLD_43", K_WORLD_43},
+	{"WORLD_44", K_WORLD_44},
+	{"WORLD_45", K_WORLD_45},
+	{"WORLD_46", K_WORLD_46},
+	{"WORLD_47", K_WORLD_47},
+	{"WORLD_48", K_WORLD_48},
+	{"WORLD_49", K_WORLD_49},
+	{"WORLD_50", K_WORLD_50},
+	{"WORLD_51", K_WORLD_51},
+	{"WORLD_52", K_WORLD_52},
+	{"WORLD_53", K_WORLD_53},
+	{"WORLD_54", K_WORLD_54},
+	{"WORLD_55", K_WORLD_55},
+	{"WORLD_56", K_WORLD_56},
+	{"WORLD_57", K_WORLD_57},
+	{"WORLD_58", K_WORLD_58},
+	{"WORLD_59", K_WORLD_59},
+	{"WORLD_60", K_WORLD_60},
+	{"WORLD_61", K_WORLD_61},
+	{"WORLD_62", K_WORLD_62},
+	{"WORLD_63", K_WORLD_63},
+	{"WORLD_64", K_WORLD_64},
+	{"WORLD_65", K_WORLD_65},
+	{"WORLD_66", K_WORLD_66},
+	{"WORLD_67", K_WORLD_67},
+	{"WORLD_68", K_WORLD_68},
+	{"WORLD_69", K_WORLD_69},
+	{"WORLD_70", K_WORLD_70},
+	{"WORLD_71", K_WORLD_71},
+	{"WORLD_72", K_WORLD_72},
+	{"WORLD_73", K_WORLD_73},
+	{"WORLD_74", K_WORLD_74},
+	{"WORLD_75", K_WORLD_75},
+	{"WORLD_76", K_WORLD_76},
+	{"WORLD_77", K_WORLD_77},
+	{"WORLD_78", K_WORLD_78},
+	{"WORLD_79", K_WORLD_79},
+	{"WORLD_80", K_WORLD_80},
+	{"WORLD_81", K_WORLD_81},
+	{"WORLD_82", K_WORLD_82},
+	{"WORLD_83", K_WORLD_83},
+	{"WORLD_84", K_WORLD_84},
+	{"WORLD_85", K_WORLD_85},
+	{"WORLD_86", K_WORLD_86},
+	{"WORLD_87", K_WORLD_87},
+	{"WORLD_88", K_WORLD_88},
+	{"WORLD_89", K_WORLD_89},
+	{"WORLD_90", K_WORLD_90},
+	{"WORLD_91", K_WORLD_91},
+	{"WORLD_92", K_WORLD_92},
+	{"WORLD_93", K_WORLD_93},
+	{"WORLD_94", K_WORLD_94},
+	{"WORLD_95", K_WORLD_95},
+
+	{"WINDOWS", K_SUPER},
+	{"COMPOSE", K_COMPOSE},
+	{"MODE", K_MODE},
+	{"HELP", K_HELP},
+	{"PRINT", K_PRINT},
+	{"SYSREQ", K_SYSREQ},
+	{"SCROLLOCK", K_SCROLLOCK },
+	{"BREAK", K_BREAK},
+	{"MENU", K_MENU},
+	{"POWER", K_POWER},
+	{"EURO", K_EURO},
+	{"UNDO", K_UNDO},
+
+	{NULL,0}
+};
+
+/*
+=============================================================================
+
+EDIT FIELDS
+
+=============================================================================
+*/
+
+
+/*
+===================
+Field_Draw
+
+Handles horizontal scrolling and cursor blinking
+x, y, and width are in pixels
+===================
+*/
+void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor,
+		qboolean noColorEscape ) {
+	int		len;
+	int		drawLen;
+	int		prestep;
+	int		cursorChar;
+	char	str[MAX_STRING_CHARS];
+	int		i;
+
+	drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor
+	len = strlen( edit->buffer );
+
+	// guarantee that cursor will be visible
+	if ( len <= drawLen ) {
+		prestep = 0;
+	} else {
+		if ( edit->scroll + drawLen > len ) {
+			edit->scroll = len - drawLen;
+			if ( edit->scroll < 0 ) {
+				edit->scroll = 0;
+			}
+		}
+		prestep = edit->scroll;
+	}
+
+	if ( prestep + drawLen > len ) {
+		drawLen = len - prestep;
+	}
+
+	// extract <drawLen> characters from the field at <prestep>
+	if ( drawLen >= MAX_STRING_CHARS ) {
+		Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" );
+	}
+
+	Com_Memcpy( str, edit->buffer + prestep, drawLen );
+	str[ drawLen ] = 0;
+
+	// draw it
+	if ( size == SMALLCHAR_WIDTH ) {
+		float	color[4];
+
+		color[0] = color[1] = color[2] = color[3] = 1.0;
+		SCR_DrawSmallStringExt( x, y, str, color, qfalse, noColorEscape );
+	} else {
+		// draw big string with drop shadow
+		SCR_DrawBigString( x, y, str, 1.0, noColorEscape );
+	}
+
+	// draw the cursor
+	if ( showCursor ) {
+		if ( (int)( cls.realtime >> 8 ) & 1 ) {
+			return;		// off blink
+		}
+
+		if ( key_overstrikeMode ) {
+			cursorChar = 11;
+		} else {
+			cursorChar = 10;
+		}
+
+		i = drawLen - strlen( str );
+
+		if ( size == SMALLCHAR_WIDTH ) {
+			SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar );
+		} else {
+			str[0] = cursorChar;
+			str[1] = 0;
+			SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, qfalse );
+
+		}
+	}
+}
+
+void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) 
+{
+	Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) 
+{
+	Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape );
+}
+
+/*
+================
+Field_Paste
+================
+*/
+void Field_Paste( field_t *edit ) {
+	char	*cbd;
+	int		pasteLen, i;
+
+	cbd = Sys_GetClipboardData();
+
+	if ( !cbd ) {
+		return;
+	}
+
+	// send as if typed, so insert / overstrike works properly
+	pasteLen = strlen( cbd );
+	for ( i = 0 ; i < pasteLen ; i++ ) {
+		Field_CharEvent( edit, cbd[i] );
+	}
+
+	Z_Free( cbd );
+}
+
+/*
+=================
+Field_KeyDownEvent
+
+Performs the basic line editing functions for the console,
+in-game talk, and menu fields
+
+Key events are used for non-printable characters, others are gotten from char events.
+=================
+*/
+void Field_KeyDownEvent( field_t *edit, int key ) {
+	int		len;
+
+	// shift-insert is paste
+	if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) {
+		Field_Paste( edit );
+		return;
+	}
+
+	key = tolower( key );
+	len = strlen( edit->buffer );
+
+	switch ( key ) {
+		case K_DEL:
+			if ( edit->cursor < len ) {
+				memmove( edit->buffer + edit->cursor, 
+					edit->buffer + edit->cursor + 1, len - edit->cursor );
+			}
+			break;
+
+		case K_RIGHTARROW:
+			if ( edit->cursor < len ) {
+				edit->cursor++;
+			}
+			break;
+
+		case K_LEFTARROW:
+			if ( edit->cursor > 0 ) {
+				edit->cursor--;
+			}
+			break;
+
+		case K_HOME:
+			edit->cursor = 0;
+			break;
+
+		case K_END:
+			edit->cursor = len;
+			break;
+
+		case K_INS:
+			key_overstrikeMode = !key_overstrikeMode;
+			break;
+
+		default:
+			break;
+	}
+
+	// Change scroll if cursor is no longer visible
+	if ( edit->cursor < edit->scroll ) {
+		edit->scroll = edit->cursor;
+	} else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) {
+		edit->scroll = edit->cursor - edit->widthInChars + 1;
+	}
+}
+
+/*
+==================
+Field_CharEvent
+==================
+*/
+void Field_CharEvent( field_t *edit, int ch ) {
+	int		len;
+
+	if ( ch == 'v' - 'a' + 1 ) {	// ctrl-v is paste
+		Field_Paste( edit );
+		return;
+	}
+
+	if ( ch == 'c' - 'a' + 1 ) {	// ctrl-c clears the field
+		Field_Clear( edit );
+		return;
+	}
+
+	len = strlen( edit->buffer );
+
+	if ( ch == 'h' - 'a' + 1 )	{	// ctrl-h is backspace
+		if ( edit->cursor > 0 ) {
+			memmove( edit->buffer + edit->cursor - 1, 
+				edit->buffer + edit->cursor, len + 1 - edit->cursor );
+			edit->cursor--;
+			if ( edit->cursor < edit->scroll )
+			{
+				edit->scroll--;
+			}
+		}
+		return;
+	}
+
+	if ( ch == 'a' - 'a' + 1 ) {	// ctrl-a is home
+		edit->cursor = 0;
+		edit->scroll = 0;
+		return;
+	}
+
+	if ( ch == 'e' - 'a' + 1 ) {	// ctrl-e is end
+		edit->cursor = len;
+		edit->scroll = edit->cursor - edit->widthInChars;
+		return;
+	}
+
+	//
+	// ignore any other non printable chars
+	//
+	if ( ch < 32 ) {
+		return;
+	}
+
+	if ( key_overstrikeMode ) {	
+		// - 2 to leave room for the leading slash and trailing \0
+		if ( edit->cursor == MAX_EDIT_LINE - 2 )
+			return;
+		edit->buffer[edit->cursor] = ch;
+		edit->cursor++;
+	} else {	// insert mode
+		// - 2 to leave room for the leading slash and trailing \0
+		if ( len == MAX_EDIT_LINE - 2 ) {
+			return; // all full
+		}
+		memmove( edit->buffer + edit->cursor + 1, 
+			edit->buffer + edit->cursor, len + 1 - edit->cursor );
+		edit->buffer[edit->cursor] = ch;
+		edit->cursor++;
+	}
+
+
+	if ( edit->cursor >= edit->widthInChars ) {
+		edit->scroll++;
+	}
+
+	if ( edit->cursor == len + 1) {
+		edit->buffer[edit->cursor] = 0;
+	}
+}
+
+/*
+=============================================================================
+
+CONSOLE LINE EDITING
+
+==============================================================================
+*/
+
+/*
+====================
+Console_Key
+
+Handles history and console scrollback
+====================
+*/
+void Console_Key (int key) {
+	// ctrl-L clears screen
+	if ( key == 'l' && keys[K_CTRL].down ) {
+		Cbuf_AddText ("clear\n");
+		return;
+	}
+
+	// enter finishes the line
+	if ( key == K_ENTER || key == K_KP_ENTER ) {
+		// if not in the game explicitly prepend a slash if needed
+		if ( cls.state != CA_ACTIVE &&
+				g_consoleField.buffer[0] &&
+				g_consoleField.buffer[0] != '\\' &&
+				g_consoleField.buffer[0] != '/' ) {
+			char	temp[MAX_EDIT_LINE-1];
+
+			Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) );
+			Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp );
+			g_consoleField.cursor++;
+		}
+
+		Com_Printf ( "]%s\n", g_consoleField.buffer );
+
+		// leading slash is an explicit command
+		if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) {
+			Cbuf_AddText( g_consoleField.buffer+1 );	// valid command
+			Cbuf_AddText ("\n");
+		} else {
+			// other text will be chat messages
+			if ( !g_consoleField.buffer[0] ) {
+				return;	// empty lines just scroll the console without adding to history
+			} else {
+				Cbuf_AddText ("cmd say ");
+				Cbuf_AddText( g_consoleField.buffer );
+				Cbuf_AddText ("\n");
+			}
+		}
+
+		// copy line to history buffer
+		historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField;
+		nextHistoryLine++;
+		historyLine = nextHistoryLine;
+
+		Field_Clear( &g_consoleField );
+
+		g_consoleField.widthInChars = g_console_field_width;
+
+		CL_SaveConsoleHistory( );
+
+		if ( cls.state == CA_DISCONNECTED ) {
+			SCR_UpdateScreen ();	// force an update, because the command
+		}							// may take some time
+		return;
+	}
+
+	// command completion
+
+	if (key == K_TAB) {
+		Field_AutoComplete(&g_consoleField);
+		return;
+	}
+
+	// command history (ctrl-p ctrl-n for unix style)
+
+	if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
+		 ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) {
+		if ( nextHistoryLine - historyLine < COMMAND_HISTORY 
+			&& historyLine > 0 ) {
+			historyLine--;
+		}
+		g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
+		return;
+	}
+
+	if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
+		 ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) {
+		historyLine++;
+		if (historyLine >= nextHistoryLine) {
+			historyLine = nextHistoryLine;
+			Field_Clear( &g_consoleField );
+			g_consoleField.widthInChars = g_console_field_width;
+			return;
+		}
+		g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
+		return;
+	}
+
+	// console scrolling
+	if ( key == K_PGUP ) {
+		Con_PageUp();
+		return;
+	}
+
+	if ( key == K_PGDN) {
+		Con_PageDown();
+		return;
+	}
+
+	if ( key == K_MWHEELUP) {	//----(SA)	added some mousewheel functionality to the console
+		Con_PageUp();
+		if(keys[K_CTRL].down) {	// hold <ctrl> to accelerate scrolling
+			Con_PageUp();
+			Con_PageUp();
+		}
+		return;
+	}
+
+	if ( key == K_MWHEELDOWN) {	//----(SA)	added some mousewheel functionality to the console
+		Con_PageDown();
+		if(keys[K_CTRL].down) {	// hold <ctrl> to accelerate scrolling
+			Con_PageDown();
+			Con_PageDown();
+		}
+		return;
+	}
+
+	// ctrl-home = top of console
+	if ( key == K_HOME && keys[K_CTRL].down ) {
+		Con_Top();
+		return;
+	}
+
+	// ctrl-end = bottom of console
+	if ( key == K_END && keys[K_CTRL].down ) {
+		Con_Bottom();
+		return;
+	}
+
+	// pass to the normal editline routine
+	Field_KeyDownEvent( &g_consoleField, key );
+}
+
+
+//============================================================================
+
+
+qboolean Key_GetOverstrikeMode( void ) {
+	return key_overstrikeMode;
+}
+
+
+void Key_SetOverstrikeMode( qboolean state ) {
+	key_overstrikeMode = state;
+}
+
+
+/*
+===================
+Key_IsDown
+===================
+*/
+qboolean Key_IsDown( int keynum ) {
+	if ( keynum < 0 || keynum >= MAX_KEYS ) {
+		return qfalse;
+	}
+
+	return keys[keynum].down;
+}
+
+/*
+===================
+Key_StringToKeynum
+
+Returns a key number to be used to index keys[] by looking at
+the given string.  Single ascii characters return themselves, while
+the K_* names are matched up.
+
+0x11 will be interpreted as raw hex, which will allow new controlers
+
+to be configured even if they don't have defined names.
+===================
+*/
+int Key_StringToKeynum( char *str ) {
+	keyname_t	*kn;
+	
+	if ( !str || !str[0] ) {
+		return -1;
+	}
+	if ( !str[1] ) {
+		return str[0];
+	}
+
+	// check for hex code
+	if ( strlen( str ) == 4 ) {
+		int n = Com_HexStrToInt( str );
+
+		if ( n >= 0 ) {
+			return n;
+		}
+	}
+
+	// scan for a text match
+	for ( kn=keynames ; kn->name ; kn++ ) {
+		if ( !Q_stricmp( str,kn->name ) )
+			return kn->keynum;
+	}
+
+	return -1;
+}
+
+/*
+===================
+Key_KeynumToString
+
+Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the
+given keynum.
+===================
+*/
+char *Key_KeynumToString( int keynum ) {
+	keyname_t	*kn;	
+	static	char	tinystr[5];
+	int			i, j;
+
+	if ( keynum == -1 ) {
+		return "<KEY NOT FOUND>";
+	}
+
+	if ( keynum < 0 || keynum >= MAX_KEYS ) {
+		return "<OUT OF RANGE>";
+	}
+
+	// check for printable ascii (don't use quote)
+	if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) {
+		tinystr[0] = keynum;
+		tinystr[1] = 0;
+		return tinystr;
+	}
+
+	// check for a key string
+	for ( kn=keynames ; kn->name ; kn++ ) {
+		if (keynum == kn->keynum) {
+			return kn->name;
+		}
+	}
+
+	// make a hex string
+	i = keynum >> 4;
+	j = keynum & 15;
+
+	tinystr[0] = '0';
+	tinystr[1] = 'x';
+	tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';
+	tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';
+	tinystr[4] = 0;
+
+	return tinystr;
+}
+
+
+/*
+===================
+Key_SetBinding
+===================
+*/
+void Key_SetBinding( int keynum, const char *binding ) {
+	if ( keynum < 0 || keynum >= MAX_KEYS ) {
+		return;
+	}
+
+	// free old bindings
+	if ( keys[ keynum ].binding ) {
+		Z_Free( keys[ keynum ].binding );
+	}
+		
+	// allocate memory for new binding
+	keys[keynum].binding = CopyString( binding );
+
+	// consider this like modifying an archived cvar, so the
+	// file write will be triggered at the next oportunity
+	cvar_modifiedFlags |= CVAR_ARCHIVE;
+}
+
+
+/*
+===================
+Key_GetBinding
+===================
+*/
+char *Key_GetBinding( int keynum ) {
+	if ( keynum < 0 || keynum >= MAX_KEYS ) {
+		return "";
+	}
+
+	return keys[ keynum ].binding;
+}
+
+/* 
+===================
+Key_GetKey
+===================
+*/
+
+int Key_GetKey(const char *binding) {
+  int i;
+
+  if (binding) {
+  	for (i=0 ; i < MAX_KEYS ; i++) {
+      if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) {
+        return i;
+      }
+    }
+  }
+  return -1;
+}
+
+/*
+===================
+Key_Unbind_f
+===================
+*/
+void Key_Unbind_f (void)
+{
+	int		b;
+
+	if (Cmd_Argc() != 2)
+	{
+		Com_Printf ("unbind <key> : remove commands from a key\n");
+		return;
+	}
+	
+	b = Key_StringToKeynum (Cmd_Argv(1));
+	if (b==-1)
+	{
+		Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+		return;
+	}
+
+	Key_SetBinding (b, "");
+}
+
+/*
+===================
+Key_Unbindall_f
+===================
+*/
+void Key_Unbindall_f (void)
+{
+	int		i;
+	
+	for (i=0 ; i < MAX_KEYS; i++)
+		if (keys[i].binding)
+			Key_SetBinding (i, "");
+}
+
+
+/*
+===================
+Key_Bind_f
+===================
+*/
+void Key_Bind_f (void)
+{
+	int			i, c, b;
+	char		cmd[1024];
+	
+	c = Cmd_Argc();
+
+	if (c < 2)
+	{
+		Com_Printf ("bind <key> [command] : attach a command to a key\n");
+		return;
+	}
+	b = Key_StringToKeynum (Cmd_Argv(1));
+	if (b==-1)
+	{
+		Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
+		return;
+	}
+
+	if (c == 2)
+	{
+		if (keys[b].binding)
+			Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding );
+		else
+			Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) );
+		return;
+	}
+	
+// copy the rest of the command line
+	cmd[0] = 0;		// start out with a null string
+	for (i=2 ; i< c ; i++)
+	{
+		strcat (cmd, Cmd_Argv(i));
+		if (i != (c-1))
+			strcat (cmd, " ");
+	}
+
+	Key_SetBinding (b, cmd);
+}
+
+/*
+============
+Key_WriteBindings
+
+Writes lines containing "bind key value"
+============
+*/
+void Key_WriteBindings( fileHandle_t f ) {
+	int		i;
+
+	FS_Printf (f, "unbindall\n" );
+
+	for (i=0 ; i<MAX_KEYS ; i++) {
+		if (keys[i].binding && keys[i].binding[0] ) {
+			FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding);
+
+		}
+
+	}
+}
+
+
+/*
+============
+Key_Bindlist_f
+
+============
+*/
+void Key_Bindlist_f( void ) {
+	int		i;
+
+	for ( i = 0 ; i < MAX_KEYS ; i++ ) {
+		if ( keys[i].binding && keys[i].binding[0] ) {
+			Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding );
+		}
+	}
+}
+
+/*
+============
+Key_KeynameCompletion
+============
+*/
+void Key_KeynameCompletion( void(*callback)(const char *s) ) {
+	int		i;
+
+	for( i = 0; keynames[ i ].name != NULL; i++ )
+		callback( keynames[ i ].name );
+}
+
+/*
+====================
+Key_CompleteUnbind
+====================
+*/
+static void Key_CompleteUnbind( char *args, int argNum )
+{
+	if( argNum == 2 )
+	{
+		// Skip "unbind "
+		char *p = Com_SkipTokens( args, 1, " " );
+
+		if( p > args )
+			Field_CompleteKeyname( );
+	}
+}
+
+/*
+====================
+Key_CompleteBind
+====================
+*/
+static void Key_CompleteBind( char *args, int argNum )
+{
+	char *p;
+
+	if( argNum == 2 )
+	{
+		// Skip "bind "
+		p = Com_SkipTokens( args, 1, " " );
+
+		if( p > args )
+			Field_CompleteKeyname( );
+	}
+	else if( argNum >= 3 )
+	{
+		// Skip "bind <key> "
+		p = Com_SkipTokens( args, 2, " " );
+
+		if( p > args )
+			Field_CompleteCommand( p, qtrue, qtrue );
+	}
+}
+
+/*
+===================
+CL_InitKeyCommands
+===================
+*/
+void CL_InitKeyCommands( void ) {
+	// register our functions
+	Cmd_AddCommand ("bind",Key_Bind_f);
+	Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind );
+	Cmd_AddCommand ("unbind",Key_Unbind_f);
+	Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind );
+	Cmd_AddCommand ("unbindall",Key_Unbindall_f);
+	Cmd_AddCommand ("bindlist",Key_Bindlist_f);
+}
+
+/*
+===================
+CL_ParseBinding
+
+Execute the commands in the bind string
+===================
+*/
+void CL_ParseBinding( int key, qboolean down, unsigned time )
+{
+	char buf[ MAX_STRING_CHARS ], *p = buf, *end;
+
+	if( !keys[key].binding || !keys[key].binding[0] )
+		return;
+	Q_strncpyz( buf, keys[key].binding, sizeof( buf ) );
+
+	while( 1 )
+	{
+		while( isspace( *p ) )
+			p++;
+		end = strchr( p, ';' );
+		if( end )
+			*end = '\0';
+		if( *p == '+' )
+		{
+			// button commands add keynum and time as parameters
+			// so that multiple sources can be discriminated and
+			// subframe corrected
+			char cmd[1024];
+			Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n",
+				( down ) ? '+' : '-', p + 1, key, time );
+			Cbuf_AddText( cmd );
+		}
+		else if( down )
+		{
+			// normal commands only execute on key press
+			Cbuf_AddText( p );
+			Cbuf_AddText( "\n" );
+		}
+		if( !end )
+			break;
+		p = end + 1;
+	}
+}
+
+/*
+===================
+CL_KeyDownEvent
+
+Called by CL_KeyEvent to handle a keypress
+===================
+*/
+void CL_KeyDownEvent( int key, unsigned time )
+{
+	keys[key].down = qtrue;
+	keys[key].repeats++;
+	if( keys[key].repeats == 1 && key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK )
+		anykeydown++;
+
+	if( keys[K_ALT].down && key == K_ENTER )
+	{
+		Cvar_SetValue( "r_fullscreen",
+			!Cvar_VariableIntegerValue( "r_fullscreen" ) );
+		return;
+	}
+
+	// console key is hardcoded, so the user can never unbind it
+	if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) )
+	{
+		Con_ToggleConsole_f ();
+		Key_ClearStates ();
+		return;
+	}
+
+
+	// keys can still be used for bound actions
+	if ( ( key < 128 || key == K_MOUSE1 ) &&
+		( !clc.demoplaying && cls.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) {
+
+		if (Cvar_VariableValue ("com_cameraMode") == 0) {
+			Cvar_Set ("nextdemo","");
+			key = K_ESCAPE;
+		}
+	}
+
+	// escape is always handled special
+	if ( key == K_ESCAPE ) {
+		// escape always gets out of CGAME stuff
+		if (Key_GetCatcher( ) & KEYCATCH_CGAME) {
+			Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME );
+			VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE);
+			return;
+		}
+
+		if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) {
+			if ( cls.state == CA_ACTIVE && !clc.demoplaying ) {
+				VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME );
+			}
+			else if ( cls.state != CA_DISCONNECTED ) {
+				CL_Disconnect_f();
+				S_StopAllSounds();
+				VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
+			}
+			return;
+		}
+
+		VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
+		return;
+	}
+
+	// distribute the key down event to the apropriate handler
+	if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) {
+		Console_Key( key );
+	} else if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
+		if ( uivm ) {
+			VM_Call( uivm, UI_KEY_EVENT, key, qtrue );
+		} 
+	} else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) {
+		if ( cgvm ) {
+			VM_Call( cgvm, CG_KEY_EVENT, key, qtrue );
+		} 
+	} else if ( cls.state == CA_DISCONNECTED ) {
+		Console_Key( key );
+	} else {
+		// send the bound action
+		CL_ParseBinding( key, qtrue, time );
+	}
+	return;
+}
+
+/*
+===================
+CL_KeyUpEvent
+
+Called by CL_KeyEvent to handle a keyrelease
+===================
+*/
+void CL_KeyUpEvent( int key, unsigned time )
+{
+	keys[key].repeats = 0;
+	keys[key].down = qfalse;
+	if (key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK)
+		anykeydown--;
+
+	if (anykeydown < 0) {
+		anykeydown = 0;
+	}
+
+	// don't process key-up events for the console key
+	if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) )
+		return;
+
+	//
+	// key up events only perform actions if the game key binding is
+	// a button command (leading + sign).  These will be processed even in
+	// console mode and menu mode, to keep the character from continuing
+	// an action started before a mode switch.
+	//
+	if( cls.state != CA_DISCONNECTED )
+		CL_ParseBinding( key, qfalse, time );
+
+	if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) {
+		VM_Call( uivm, UI_KEY_EVENT, key, qfalse );
+	} else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cgvm ) {
+		VM_Call( cgvm, CG_KEY_EVENT, key, qfalse );
+	}
+}
+
+/*
+===================
+CL_KeyEvent
+
+Called by the system for both key up and key down events
+===================
+*/
+void CL_KeyEvent (int key, qboolean down, unsigned time) {
+	if( down )
+		CL_KeyDownEvent( key, time );
+	else
+		CL_KeyUpEvent( key, time );
+}
+
+/*
+===================
+CL_CharEvent
+
+Normal keyboard characters, already shifted / capslocked / etc
+===================
+*/
+void CL_CharEvent( int key ) {
+	// delete is not a printable character and is
+	// otherwise handled by Field_KeyDownEvent
+	if ( key == 127 ) {
+		return;
+	}
+
+	// distribute the key down event to the apropriate handler
+	if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
+	{
+		Field_CharEvent( &g_consoleField, key );
+	}
+	else if ( Key_GetCatcher( ) & KEYCATCH_UI )
+	{
+		VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue );
+	}
+	else if ( cls.state == CA_DISCONNECTED )
+	{
+		Field_CharEvent( &g_consoleField, key );
+	}
+}
+
+
+/*
+===================
+Key_ClearStates
+===================
+*/
+void Key_ClearStates (void)
+{
+	int		i;
+
+	anykeydown = 0;
+
+	for ( i=0 ; i < MAX_KEYS ; i++ ) {
+		if (i == K_SCROLLOCK || i == K_KP_NUMLOCK || i == K_CAPSLOCK)
+			continue;
+
+		if ( keys[i].down ) {
+			CL_KeyEvent( i, qfalse, 0 );
+
+		}
+		keys[i].down = 0;
+		keys[i].repeats = 0;
+	}
+}
+
+/*
+====================
+Key_KeynumToStringBuf
+====================
+*/
+void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+	Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen );
+}
+
+/*
+====================
+Key_GetBindingBuf
+====================
+*/
+void Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+	char	*value;
+
+	value = Key_GetBinding( keynum );
+	if ( value ) {
+		Q_strncpyz( buf, value, buflen );
+	}
+	else {
+		*buf = 0;
+	}
+}
+
+static int keyCatchers = 0;
+
+/*
+====================
+Key_GetCatcher
+====================
+*/
+int Key_GetCatcher( void ) {
+	return keyCatchers;
+}
+
+/*
+====================
+Key_SetCatcher
+====================
+*/
+void Key_SetCatcher( int catcher ) {
+	// If the catcher state is changing, clear all key states
+	if( catcher != keyCatchers )
+		Key_ClearStates( );
+
+	keyCatchers = catcher;
+}
+
+// This must not exceed MAX_CMD_LINE
+#define			MAX_CONSOLE_SAVE_BUFFER	1024
+#define			CONSOLE_HISTORY_FILE    "q3history"
+static char	consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ];
+static int	consoleSaveBufferSize = 0;
+
+/*
+================
+CL_LoadConsoleHistory
+
+Load the console history from cl_consoleHistory
+================
+*/
+void CL_LoadConsoleHistory( void )
+{
+	char					*token, *text_p;
+	int						i, numChars, numLines = 0;
+	fileHandle_t	f;
+
+	consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, qfalse );
+	if( !f )
+	{
+		Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
+		return;
+	}
+
+	if( consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER &&
+			FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize )
+	{
+		text_p = consoleSaveBuffer;
+
+		for( i = COMMAND_HISTORY - 1; i >= 0; i-- )
+		{
+			if( !*( token = COM_Parse( &text_p ) ) )
+				break;
+
+			historyEditLines[ i ].cursor = atoi( token );
+
+			if( !*( token = COM_Parse( &text_p ) ) )
+				break;
+
+			historyEditLines[ i ].scroll = atoi( token );
+
+			if( !*( token = COM_Parse( &text_p ) ) )
+				break;
+
+			numChars = atoi( token );
+			text_p++;
+			if( numChars > ( strlen( consoleSaveBuffer ) -	( text_p - consoleSaveBuffer ) ) )
+			{
+				Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" );
+				break;
+			}
+			Com_Memcpy( historyEditLines[ i ].buffer,
+					text_p, numChars );
+			historyEditLines[ i ].buffer[ numChars ] = '\0';
+			text_p += numChars;
+
+			numLines++;
+		}
+
+		memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ],
+				numLines * sizeof( field_t ) );
+		for( i = numLines; i < COMMAND_HISTORY; i++ )
+			Field_Clear( &historyEditLines[ i ] );
+
+		historyLine = nextHistoryLine = numLines;
+	}
+	else
+		Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE );
+
+	FS_FCloseFile( f );
+}
+
+/*
+================
+CL_SaveConsoleHistory
+
+Save the console history into the cvar cl_consoleHistory
+so that it persists across invocations of q3
+================
+*/
+void CL_SaveConsoleHistory( void )
+{
+	int						i;
+	int						lineLength, saveBufferLength, additionalLength;
+	fileHandle_t	f;
+
+	consoleSaveBuffer[ 0 ] = '\0';
+
+	i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY;
+	do
+	{
+		if( historyEditLines[ i ].buffer[ 0 ] )
+		{
+			lineLength = strlen( historyEditLines[ i ].buffer );
+			saveBufferLength = strlen( consoleSaveBuffer );
+
+			//ICK
+			additionalLength = lineLength + strlen( "999 999 999  " );
+
+			if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER )
+			{
+				Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER,
+						va( "%d %d %d %s ",
+						historyEditLines[ i ].cursor,
+						historyEditLines[ i ].scroll,
+						lineLength,
+						historyEditLines[ i ].buffer ) );
+			}
+			else
+				break;
+		}
+		i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY;
+	}
+	while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY );
+
+	consoleSaveBufferSize = strlen( consoleSaveBuffer );
+
+	f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE );
+	if( !f )
+	{
+		Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
+		return;
+	}
+
+	if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize )
+		Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE );
+
+	FS_FCloseFile( f );
+}
diff --git a/src/client/cl_main.c b/src/client/cl_main.c
new file mode 100644
index 0000000..d545fc9
--- /dev/null
+++ b/src/client/cl_main.c
@@ -0,0 +1,4418 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// cl_main.c  -- client main loop
+
+#include "client.h"
+#include <limits.h>
+
+#ifdef USE_MUMBLE
+#include "libmumblelink.h"
+#endif
+
+#ifdef USE_MUMBLE
+cvar_t	*cl_useMumble;
+cvar_t	*cl_mumbleScale;
+#endif
+
+#ifdef USE_VOIP
+cvar_t	*cl_voipUseVAD;
+cvar_t	*cl_voipVADThreshold;
+cvar_t	*cl_voipSend;
+cvar_t	*cl_voipSendTarget;
+cvar_t	*cl_voipGainDuringCapture;
+cvar_t	*cl_voipCaptureMult;
+cvar_t	*cl_voipShowMeter;
+cvar_t	*cl_voip;
+#endif
+
+cvar_t	*cl_nodelta;
+cvar_t	*cl_debugMove;
+
+cvar_t	*cl_noprint;
+cvar_t	*cl_motd;
+
+cvar_t	*rcon_client_password;
+cvar_t	*rconAddress;
+
+cvar_t	*cl_timeout;
+cvar_t	*cl_maxpackets;
+cvar_t	*cl_packetdup;
+cvar_t	*cl_timeNudge;
+cvar_t	*cl_showTimeDelta;
+cvar_t	*cl_freezeDemo;
+
+cvar_t	*cl_shownet;
+cvar_t	*cl_showSend;
+cvar_t	*cl_timedemo;
+cvar_t	*cl_timedemoLog;
+cvar_t	*cl_autoRecordDemo;
+cvar_t	*cl_aviFrameRate;
+cvar_t	*cl_aviMotionJpeg;
+cvar_t	*cl_forceavidemo;
+
+cvar_t	*cl_freelook;
+cvar_t	*cl_sensitivity;
+
+cvar_t	*cl_mouseAccel;
+cvar_t	*cl_mouseAccelOffset;
+cvar_t	*cl_mouseAccelStyle;
+cvar_t	*cl_showMouseRate;
+
+cvar_t	*m_pitch;
+cvar_t	*m_yaw;
+cvar_t	*m_forward;
+cvar_t	*m_side;
+cvar_t	*m_filter;
+
+cvar_t	*j_pitch;
+cvar_t	*j_yaw;
+cvar_t	*j_forward;
+cvar_t	*j_side;
+cvar_t	*j_pitch_axis;
+cvar_t	*j_yaw_axis;
+cvar_t	*j_forward_axis;
+cvar_t	*j_side_axis;
+
+cvar_t	*cl_activeAction;
+
+cvar_t	*cl_motdString;
+
+cvar_t	*cl_allowDownload;
+cvar_t	*com_downloadPrompt;
+cvar_t	*cl_conXOffset;
+cvar_t	*cl_inGameVideo;
+
+cvar_t	*cl_serverStatusResendTime;
+cvar_t	*cl_trn;
+
+cvar_t	*cl_lanForcePackets;
+
+cvar_t	*cl_guidServerUniq;
+
+cvar_t	*cl_consoleKeys;
+
+cvar_t  *cl_gamename;
+
+clientActive_t		cl;
+clientConnection_t	clc;
+clientStatic_t		cls;
+vm_t				*cgvm;
+
+// Structure containing functions exported from refresh DLL
+refexport_t	re;
+
+ping_t	cl_pinglist[MAX_PINGREQUESTS];
+
+typedef struct serverStatus_s
+{
+	char string[BIG_INFO_STRING];
+	netadr_t address;
+	int time, startTime;
+	qboolean pending;
+	qboolean print;
+	qboolean retrieved;
+} serverStatus_t;
+
+serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
+int serverStatusCount;
+
+#if defined __USEA3D && defined __A3D_GEOM
+	void hA3Dg_ExportRenderGeom (refexport_t *incoming_re);
+#endif
+
+extern void GLimp_Minimize(void);
+extern void SV_BotFrame( int time );
+void CL_CheckForResend( void );
+void CL_ShowIP_f(void);
+void CL_ServerStatus_f(void);
+void CL_ServerStatusResponse( netadr_t from, msg_t *msg );
+
+/*
+===============
+CL_CDDialog
+
+Called by Com_Error when a cd is needed
+===============
+*/
+void CL_CDDialog( void ) {
+	cls.cddialog = qtrue;	// start it next frame
+}
+
+#ifdef USE_MUMBLE
+static
+void CL_UpdateMumble(void)
+{
+	vec3_t pos, forward, up;
+	float scale = cl_mumbleScale->value;
+	float tmp;
+	
+	if(!cl_useMumble->integer)
+		return;
+
+	// !!! FIXME: not sure if this is even close to correct.
+	AngleVectors( cl.snap.ps.viewangles, forward, NULL, up);
+
+	pos[0] = cl.snap.ps.origin[0] * scale;
+	pos[1] = cl.snap.ps.origin[2] * scale;
+	pos[2] = cl.snap.ps.origin[1] * scale;
+
+	tmp = forward[1];
+	forward[1] = forward[2];
+	forward[2] = tmp;
+
+	tmp = up[1];
+	up[1] = up[2];
+	up[2] = tmp;
+
+	if(cl_useMumble->integer > 1) {
+		fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n",
+			pos[0], pos[1], pos[2],
+			forward[0], forward[1], forward[2],
+			up[0], up[1], up[2]);
+	}
+
+	mumble_update_coordinates(pos, forward, up);
+}
+#endif
+
+
+#ifdef USE_VOIP
+static
+void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore)
+{
+	if ((*idstr >= '0') && (*idstr <= '9')) {
+		const int id = atoi(idstr);
+		if ((id >= 0) && (id < MAX_CLIENTS)) {
+			clc.voipIgnore[id] = ignore;
+			CL_AddReliableCommand(va("voip %s %d",
+			                         ignore ? "ignore" : "unignore", id), qfalse);
+			Com_Printf("VoIP: %s ignoring player #%d\n",
+			            ignore ? "Now" : "No longer", id);
+			return;
+		}
+	}
+	Com_Printf("VoIP: invalid player ID#\n");
+}
+
+static
+void CL_UpdateVoipGain(const char *idstr, float gain)
+{
+	if ((*idstr >= '0') && (*idstr <= '9')) {
+		const int id = atoi(idstr);
+		if (gain < 0.0f)
+			gain = 0.0f;
+		if ((id >= 0) && (id < MAX_CLIENTS)) {
+			clc.voipGain[id] = gain;
+			Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain);
+		}
+	}
+}
+
+/*
+================
+CL_VoipParseTargets
+
+Sets clc.voipTarget{1,2,3} by asking the cgame to produce a string and then
+parsing it as a series of client numbers
+Perhaps it would be better to allow the cgame to set the three integers
+directly, but this way we can change the net protocol without changing the
+vmcall
+================
+*/
+static void CL_VoipParseTargets( void )
+{
+  const char *target = cl_voipSendTarget->string;
+  intptr_t p = VM_Call( cgvm, CG_VOIP_STRING );
+
+  if( p )
+    target = VM_ExplicitArgPtr( cgvm, p );
+
+  if( !target[ 0 ] || Q_stricmp( target, "all" ) == 0 )
+    clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF;
+  else if( Q_stricmp( target, "none" ) == 0 )
+    clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0;
+  else
+  {
+    char *end;
+    int val;
+    clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0;
+
+    while( 1 )
+    {
+      while( *target && !isdigit( *target ) )
+        target++;
+      if( !*target )
+        break;
+
+      val = strtol( target, &end, 10 );
+      assert( target != end );
+      if( val < 0 || val >= MAX_CLIENTS )
+        Com_Printf( S_COLOR_YELLOW "WARNING: VoIP target %d is not a valid "
+                    "client number\n", val );
+      else if( val < 31 )
+        clc.voipTarget1 |= 1 << val;
+      else if( ( val -= 31 ) < 31 )
+        clc.voipTarget2 |= 1 << val;
+      else if( ( val -= 31 ) < 31 )
+        clc.voipTarget3 |= 1 << val;
+      target = end;
+    }
+  }
+}
+
+void CL_Voip_f( void )
+{
+	const char *cmd = Cmd_Argv(1);
+	const char *reason = NULL;
+
+	if (cls.state != CA_ACTIVE)
+		reason = "Not connected to a server";
+	else if (!clc.speexInitialized)
+		reason = "Speex not initialized";
+	else if (!cl_connectedToVoipServer)
+		reason = "Server doesn't support VoIP";
+
+	if (reason != NULL) {
+		Com_Printf("VoIP: command ignored: %s\n", reason);
+		return;
+	}
+
+	if (strcmp(cmd, "ignore") == 0) {
+		CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue);
+	} else if (strcmp(cmd, "unignore") == 0) {
+		CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse);
+	} else if (strcmp(cmd, "gain") == 0) {
+		if (Cmd_Argc() > 3) {
+			CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3)));
+		} else if (Q_isanumber(Cmd_Argv(2))) {
+			int id = atoi(Cmd_Argv(2));
+			if (id >= 0 && id < MAX_CLIENTS) {
+				Com_Printf("VoIP: current gain for player #%d "
+					"is %f\n", id, clc.voipGain[id]);
+			} else {
+				Com_Printf("VoIP: invalid player ID#\n");
+			}
+		} else {
+			Com_Printf("usage: voip gain <playerID#> [value]\n");
+		}
+	} else if (strcmp(cmd, "muteall") == 0) {
+		Com_Printf("VoIP: muting incoming voice\n");
+		CL_AddReliableCommand("voip muteall", qfalse);
+		clc.voipMuteAll = qtrue;
+	} else if (strcmp(cmd, "unmuteall") == 0) {
+		Com_Printf("VoIP: unmuting incoming voice\n");
+		CL_AddReliableCommand("voip unmuteall", qfalse);
+		clc.voipMuteAll = qfalse;
+	} else {
+		Com_Printf("usage: voip [un]ignore <playerID#>\n"
+		           "       voip [un]muteall\n"
+		           "       voip gain <playerID#> [value]\n");
+	}
+}
+
+
+static
+void CL_VoipNewGeneration(void)
+{
+	// don't have a zero generation so new clients won't match, and don't
+	//  wrap to negative so MSG_ReadLong() doesn't "fail."
+	clc.voipOutgoingGeneration++;
+	if (clc.voipOutgoingGeneration <= 0)
+		clc.voipOutgoingGeneration = 1;
+	clc.voipPower = 0.0f;
+	clc.voipOutgoingSequence = 0;
+}
+
+/*
+===============
+CL_CaptureVoip
+
+Record more audio from the hardware if required and encode it into Speex
+ data for later transmission.
+===============
+*/
+static
+void CL_CaptureVoip(void)
+{
+	const float audioMult = cl_voipCaptureMult->value;
+	const qboolean useVad = (cl_voipUseVAD->integer != 0);
+	qboolean initialFrame = qfalse;
+	qboolean finalFrame = qfalse;
+
+#if USE_MUMBLE
+	// if we're using Mumble, don't try to handle VoIP transmission ourselves.
+	if (cl_useMumble->integer)
+		return;
+#endif
+
+	if (!clc.speexInitialized)
+		return;  // just in case this gets called at a bad time.
+
+	if (clc.voipOutgoingDataSize > 0)
+		return;  // packet is pending transmission, don't record more yet.
+
+	if (cl_voipUseVAD->modified) {
+		Cvar_Set("cl_voipSend", (useVad) ? "1" : "0");
+		cl_voipUseVAD->modified = qfalse;
+	}
+
+	if ((useVad) && (!cl_voipSend->integer))
+		Cvar_Set("cl_voipSend", "1");  // lots of things reset this.
+
+	if (cl_voipSend->modified) {
+		qboolean dontCapture = qfalse;
+		if (cls.state != CA_ACTIVE)
+			dontCapture = qtrue;  // not connected to a server.
+		else if (!cl_connectedToVoipServer)
+			dontCapture = qtrue;  // server doesn't support VoIP.
+		else if (clc.demoplaying)
+			dontCapture = qtrue;  // playing back a demo.
+		else if ( cl_voip->integer == 0 )
+			dontCapture = qtrue;  // client has VoIP support disabled.
+		else if ( audioMult == 0.0f )
+			dontCapture = qtrue;  // basically silenced incoming audio.
+
+		cl_voipSend->modified = qfalse;
+
+		if (dontCapture) {
+			cl_voipSend->integer = 0;
+			return;
+		}
+
+		if (cl_voipSend->integer) {
+			initialFrame = qtrue;
+		} else {
+			finalFrame = qtrue;
+		}
+	}
+
+	// try to get more audio data from the sound card...
+
+	if (initialFrame) {
+		float gain = cl_voipGainDuringCapture->value;
+		if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f;
+		S_MasterGain(cl_voipGainDuringCapture->value);
+		S_StartCapture();
+		CL_VoipNewGeneration();
+		CL_VoipParseTargets();
+	}
+
+	if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio?
+		int samples = S_AvailableCaptureSamples();
+		const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio.
+
+		// enough data buffered in audio hardware to process yet?
+		if (samples >= (clc.speexFrameSize * mult)) {
+			// audio capture is always MONO16 (and that's what speex wants!).
+			//  2048 will cover 12 uncompressed frames in narrowband mode.
+			static int16_t sampbuffer[2048];
+			float voipPower = 0.0f;
+			int speexFrames = 0;
+			int wpos = 0;
+			int pos = 0;
+
+			if (samples > (clc.speexFrameSize * 12))
+				samples = (clc.speexFrameSize * 12);
+
+			// !!! FIXME: maybe separate recording from encoding, so voipPower
+			// !!! FIXME:  updates faster than 4Hz?
+
+			samples -= samples % clc.speexFrameSize;
+			S_Capture(samples, (byte *) sampbuffer);  // grab from audio card.
+
+			// this will probably generate multiple speex packets each time.
+			while (samples > 0) {
+				int16_t *sampptr = &sampbuffer[pos];
+				int i, bytes;
+
+				// preprocess samples to remove noise...
+				speex_preprocess_run(clc.speexPreprocessor, sampptr);
+
+				// check the "power" of this packet...
+				for (i = 0; i < clc.speexFrameSize; i++) {
+					const float flsamp = (float) sampptr[i];
+					const float s = fabs(flsamp);
+					voipPower += s * s;
+					sampptr[i] = (int16_t) ((flsamp) * audioMult);
+				}
+
+				// encode raw audio samples into Speex data...
+				speex_bits_reset(&clc.speexEncoderBits);
+				speex_encode_int(clc.speexEncoder, sampptr,
+				                 &clc.speexEncoderBits);
+				bytes = speex_bits_write(&clc.speexEncoderBits,
+				                         (char *) &clc.voipOutgoingData[wpos+1],
+				                         sizeof (clc.voipOutgoingData) - (wpos+1));
+				assert((bytes > 0) && (bytes < 256));
+				clc.voipOutgoingData[wpos] = (byte) bytes;
+				wpos += bytes + 1;
+
+				// look at the data for the next packet...
+				pos += clc.speexFrameSize;
+				samples -= clc.speexFrameSize;
+				speexFrames++;
+			}
+
+			clc.voipPower = (voipPower / (32768.0f * 32768.0f *
+			                 ((float) (clc.speexFrameSize * speexFrames)))) *
+			                 100.0f;
+
+			if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) {
+				CL_VoipNewGeneration();  // no "talk" for at least 1/4 second.
+			} else {
+				clc.voipOutgoingDataSize = wpos;
+				clc.voipOutgoingDataFrames = speexFrames;
+
+				Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n",
+				            speexFrames, wpos, clc.voipPower);
+
+				#if 0
+				static FILE *encio = NULL;
+				if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
+				if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); }
+				static FILE *decio = NULL;
+				if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
+				if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); }
+				#endif
+			}
+		}
+	}
+
+	// User requested we stop recording, and we've now processed the last of
+	//  any previously-buffered data. Pause the capture device, etc.
+	if (finalFrame) {
+		S_StopCapture();
+		S_MasterGain(1.0f);
+		clc.voipPower = 0.0f;  // force this value so it doesn't linger.
+	}
+}
+#endif
+
+/*
+=======================================================================
+
+CLIENT RELIABLE COMMAND COMMUNICATION
+
+=======================================================================
+*/
+
+/*
+======================
+CL_AddReliableCommand
+
+The given command will be transmitted to the server, and is gauranteed to
+not have future usercmd_t executed before it is executed
+======================
+*/
+void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd)
+{
+	int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge;
+	
+	// if we would be losing an old command that hasn't been acknowledged,
+	// we must drop the connection
+	// also leave one slot open for the disconnect command in this case.
+	
+	if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) ||
+	    (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS))
+	{
+		if(com_errorEntered)
+			return;
+		else
+			Com_Error(ERR_DROP, "Client command overflow");
+	}
+
+	Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)],
+		   cmd, sizeof(*clc.reliableCommands));
+}
+
+/*
+======================
+CL_ChangeReliableCommand
+======================
+*/
+void CL_ChangeReliableCommand( void ) {
+	int r, index, l;
+
+	r = clc.reliableSequence - (random() * 5);
+	index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
+	l = strlen(clc.reliableCommands[ index ]);
+	if ( l >= MAX_STRING_CHARS - 1 ) {
+		l = MAX_STRING_CHARS - 2;
+	}
+	clc.reliableCommands[ index ][ l ] = '\n';
+	clc.reliableCommands[ index ][ l+1 ] = '\0';
+}
+
+/*
+=======================================================================
+
+CLIENT SIDE DEMO RECORDING
+
+=======================================================================
+*/
+
+/*
+====================
+CL_WriteDemoMessage
+
+Dumps the current net message, prefixed by the length
+====================
+*/
+void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) {
+	int		len, swlen;
+
+	// write the packet sequence
+	len = clc.serverMessageSequence;
+	swlen = LittleLong( len );
+	FS_Write (&swlen, 4, clc.demofile);
+
+	// skip the packet sequencing information
+	len = msg->cursize - headerBytes;
+	swlen = LittleLong(len);
+	FS_Write (&swlen, 4, clc.demofile);
+	FS_Write ( msg->data + headerBytes, len, clc.demofile );
+}
+
+
+/*
+====================
+CL_StopRecording_f
+
+stop recording a demo
+====================
+*/
+void CL_StopRecord_f( void ) {
+	int		len;
+
+	if ( !clc.demorecording ) {
+		Com_Printf ("Not recording a demo.\n");
+		return;
+	}
+
+	// finish up
+	len = -1;
+	FS_Write (&len, 4, clc.demofile);
+	FS_Write (&len, 4, clc.demofile);
+	FS_FCloseFile (clc.demofile);
+	clc.demofile = 0;
+	clc.demorecording = qfalse;
+	clc.spDemoRecording = qfalse;
+	Com_Printf ("Stopped demo.\n");
+}
+
+/* 
+================== 
+CL_DemoFilename
+================== 
+*/  
+void CL_DemoFilename( int number, char *fileName ) {
+	int		a,b,c,d;
+
+	if(number < 0 || number > 9999)
+		number = 9999;
+
+	a = number / 1000;
+	number -= a*1000;
+	b = number / 100;
+	number -= b*100;
+	c = number / 10;
+	number -= c*10;
+	d = number;
+
+	Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i"
+		, a, b, c, d );
+}
+
+/*
+====================
+CL_Record_f
+
+record <demoname>
+
+Begins recording a demo from the current position
+====================
+*/
+static char		demoName[MAX_QPATH];	// compiler bug workaround
+void CL_Record_f( void ) {
+	char		name[MAX_OSPATH];
+	byte		bufData[MAX_MSGLEN];
+	msg_t	buf;
+	int			i;
+	int			len;
+	entityState_t	*ent;
+	entityState_t	nullstate;
+	char		*s;
+
+	if ( Cmd_Argc() > 2 ) {
+		Com_Printf ("record <demoname>\n");
+		return;
+	}
+
+	if ( clc.demorecording ) {
+		if (!clc.spDemoRecording) {
+			Com_Printf ("Already recording.\n");
+		}
+		return;
+	}
+
+	if ( cls.state != CA_ACTIVE ) {
+		Com_Printf ("You must be in a level to record.\n");
+		return;
+	}
+
+  // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 ..
+	if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) {
+		Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n");
+	}
+
+	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, PROTOCOL_VERSION );
+	} 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, PROTOCOL_VERSION );
+
+			if (!FS_FileExists(name))
+				break;	// file doesn't exist
+		}
+	}
+
+	// open the demo file
+
+	Com_Printf ("recording to %s.\n", name);
+	clc.demofile = FS_FOpenFileWrite( name );
+	if ( !clc.demofile ) {
+		Com_Printf ("ERROR: couldn't open.\n");
+		return;
+	}
+	clc.demorecording = qtrue;
+	if (Cvar_VariableValue("ui_recordSPDemo")) {
+	  clc.spDemoRecording = qtrue;
+	} else {
+	  clc.spDemoRecording = qfalse;
+	}
+
+
+	Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) );
+
+	// don't start saving messages until a non-delta compressed message is received
+	clc.demowaiting = qtrue;
+
+	// write out the gamestate message
+	MSG_Init (&buf, bufData, sizeof(bufData));
+	MSG_Bitstream(&buf);
+
+	// NOTE, MRE: all server->client messages now acknowledge
+	MSG_WriteLong( &buf, clc.reliableSequence );
+
+	MSG_WriteByte (&buf, svc_gamestate);
+	MSG_WriteLong (&buf, clc.serverCommandSequence );
+
+	// configstrings
+	for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
+		if ( !cl.gameState.stringOffsets[i] ) {
+			continue;
+		}
+		s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
+		MSG_WriteByte (&buf, svc_configstring);
+		MSG_WriteShort (&buf, i);
+		MSG_WriteBigString (&buf, s);
+	}
+
+	// baselines
+	Com_Memset (&nullstate, 0, sizeof(nullstate));
+	for ( i = 0; i < MAX_GENTITIES ; i++ ) {
+		ent = &cl.entityBaselines[i];
+		if ( !ent->number ) {
+			continue;
+		}
+		MSG_WriteByte (&buf, svc_baseline);		
+		MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue );
+	}
+
+	MSG_WriteByte( &buf, svc_EOF );
+	
+	// finished writing the gamestate stuff
+
+	// write the client num
+	MSG_WriteLong(&buf, clc.clientNum);
+	// write the checksum feed
+	MSG_WriteLong(&buf, clc.checksumFeed);
+
+	// finished writing the client packet
+	MSG_WriteByte( &buf, svc_EOF );
+
+	// write it to the demo file
+	len = LittleLong( clc.serverMessageSequence - 1 );
+	FS_Write (&len, 4, clc.demofile);
+
+	len = LittleLong (buf.cursize);
+	FS_Write (&len, 4, clc.demofile);
+	FS_Write (buf.data, buf.cursize, clc.demofile);
+
+	// the rest of the demo file will be copied from net messages
+}
+
+/*
+=======================================================================
+
+CLIENT SIDE DEMO PLAYBACK
+
+=======================================================================
+*/
+
+/*
+=================
+CL_DemoFrameDurationSDev
+=================
+*/
+static float CL_DemoFrameDurationSDev( void )
+{
+	int i;
+	int numFrames;
+	float mean = 0.0f;
+	float variance = 0.0f;
+
+	if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS )
+		numFrames = MAX_TIMEDEMO_DURATIONS;
+	else
+		numFrames = clc.timeDemoFrames - 1;
+
+	for( i = 0; i < numFrames; i++ )
+		mean += clc.timeDemoDurations[ i ];
+	mean /= numFrames;
+
+	for( i = 0; i < numFrames; i++ )
+	{
+		float x = clc.timeDemoDurations[ i ];
+
+		variance += ( ( x - mean ) * ( x - mean ) );
+	}
+	variance /= numFrames;
+
+	return sqrt( variance );
+}
+
+/*
+=================
+CL_DemoCompleted
+=================
+*/
+void CL_DemoCompleted( void )
+{
+	char buffer[ MAX_STRING_CHARS ];
+
+	if( cl_timedemo && cl_timedemo->integer )
+	{
+		int	time;
+		
+		time = Sys_Milliseconds() - clc.timeDemoStart;
+		if( time > 0 )
+		{
+			// Millisecond times are frame durations:
+			// minimum/average/maximum/std deviation
+			Com_sprintf( buffer, sizeof( buffer ),
+					"%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n",
+					clc.timeDemoFrames,
+					time/1000.0,
+					clc.timeDemoFrames*1000.0 / time,
+					clc.timeDemoMinDuration,
+					time / (float)clc.timeDemoFrames,
+					clc.timeDemoMaxDuration,
+					CL_DemoFrameDurationSDev( ) );
+			Com_Printf( "%s", buffer );
+
+			// Write a log of all the frame durations
+			if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 )
+			{
+				int i;
+				int numFrames;
+				fileHandle_t f;
+
+				if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS )
+					numFrames = MAX_TIMEDEMO_DURATIONS;
+				else
+					numFrames = clc.timeDemoFrames - 1;
+
+				f = FS_FOpenFileWrite( cl_timedemoLog->string );
+				if( f )
+				{
+					FS_Printf( f, "# %s", buffer );
+
+					for( i = 0; i < numFrames; i++ )
+						FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] );
+
+					FS_FCloseFile( f );
+					Com_Printf( "%s written\n", cl_timedemoLog->string );
+				}
+				else
+				{
+					Com_Printf( "Couldn't open %s for writing\n",
+							cl_timedemoLog->string );
+				}
+			}
+		}
+	}
+
+	CL_Disconnect( qtrue );
+	CL_NextDemo();
+}
+
+/*
+=================
+CL_ReadDemoMessage
+=================
+*/
+void CL_ReadDemoMessage( void ) {
+	int			r;
+	msg_t		buf;
+	byte		bufData[ MAX_MSGLEN ];
+	int			s;
+
+	if ( !clc.demofile ) {
+		CL_DemoCompleted ();
+		return;
+	}
+
+	// get the sequence number
+	r = FS_Read( &s, 4, clc.demofile);
+	if ( r != 4 ) {
+		CL_DemoCompleted ();
+		return;
+	}
+	clc.serverMessageSequence = LittleLong( s );
+
+	// init the message
+	MSG_Init( &buf, bufData, sizeof( bufData ) );
+
+	// get the length
+	r = FS_Read (&buf.cursize, 4, clc.demofile);
+	if ( r != 4 ) {
+		CL_DemoCompleted ();
+		return;
+	}
+	buf.cursize = LittleLong( buf.cursize );
+	if ( buf.cursize == -1 ) {
+		CL_DemoCompleted ();
+		return;
+	}
+	if ( buf.cursize > buf.maxsize ) {
+		Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN");
+	}
+	r = FS_Read( buf.data, buf.cursize, clc.demofile );
+	if ( r != buf.cursize ) {
+		Com_Printf( "Demo file was truncated.\n");
+		CL_DemoCompleted ();
+		return;
+	}
+
+	clc.lastPacketTime = cls.realtime;
+	buf.readcount = 0;
+	CL_ParseServerMessage( &buf );
+}
+
+/*
+====================
+CL_WalkDemoExt
+====================
+*/
+static void CL_WalkDemoExt(char *arg, char *name, int *demofile)
+{
+	int i = 0;
+	*demofile = 0;
+
+	Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION);
+
+	FS_FOpenFileRead( name, demofile, qtrue );
+
+	if (*demofile)
+	{
+		Com_Printf("Demo file: %s\n", name);
+		return;
+	}
+
+	Com_Printf("Not found: %s\n", name);
+
+	while(demo_protocols[i])
+	{
+		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;
+		}
+		else
+			Com_Printf("Not found: %s\n", name);
+		i++;
+	}
+}
+
+/*
+====================
+CL_CompleteDemoName
+====================
+*/
+static void CL_CompleteDemoName( char *args, int argNum )
+{
+	if( argNum == 2 )
+	{
+		char demoExt[ 16 ];
+
+		Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, PROTOCOL_VERSION);
+		Field_CompleteFilename( "demos", demoExt, qtrue, qtrue );
+	}
+}
+
+/*
+====================
+CL_PlayDemo_f
+
+demo <demoname>
+
+====================
+*/
+void CL_PlayDemo_f( void ) {
+	char		name[MAX_OSPATH];
+	char		*arg, *ext_test;
+	int			protocol, i;
+	char		retry[MAX_OSPATH];
+
+	if (Cmd_Argc() != 2) {
+		Com_Printf ("demo <demoname>\n");
+		return;
+	}
+
+	// make sure a local server is killed
+	// 2 means don't force disconnect of local client
+	Cvar_Set( "sv_killserver", "2" );
+
+	// open the demo file
+	arg = Cmd_Argv(1);
+	
+	CL_Disconnect( qtrue );
+
+	// check for an extension .DEMOEXT_?? (?? is protocol)
+	ext_test = Q_strrchr(arg, '.');
+	
+	if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
+	{
+		protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
+
+		for(i = 0; demo_protocols[i]; i++)
+		{
+			if(demo_protocols[i] == protocol)
+				break;
+		}
+
+		if(demo_protocols[i] || protocol == PROTOCOL_VERSION)
+		{
+			Com_sprintf(name, sizeof(name), "demos/%s", arg);
+			FS_FOpenFileRead(name, &clc.demofile, qtrue);
+		}
+		else
+		{
+			int len;
+
+			Com_Printf("Protocol %d not supported for demos\n", protocol);
+			len = ext_test - arg;
+
+			if(len >= ARRAY_LEN(retry))
+				len = ARRAY_LEN(retry) - 1;
+
+			Q_strncpyz(retry, arg, len + 1);
+			retry[len] = '\0';
+			CL_WalkDemoExt(retry, name, &clc.demofile);
+		}
+	}
+	else
+		CL_WalkDemoExt(arg, name, &clc.demofile);
+	
+	if (!clc.demofile) {
+		Com_Error( ERR_DROP, "couldn't open %s", name);
+		return;
+	}
+	Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) );
+
+	Con_Close();
+
+	cls.state = CA_CONNECTED;
+	clc.demoplaying = qtrue;
+	Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) );
+
+	// read demo messages until connected
+	while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) {
+		CL_ReadDemoMessage();
+	}
+	// don't get the first snapshot this frame, to prevent the long
+	// time from the gamestate load from messing causing a time skip
+	clc.firstDemoFrameSkipped = qfalse;
+}
+
+
+/*
+====================
+CL_StartDemoLoop
+
+Closing the main menu will restart the demo loop
+====================
+*/
+void CL_StartDemoLoop( void ) {
+	// start the demo loop again
+	Cbuf_AddText ("d1\n");
+	Key_SetCatcher( 0 );
+}
+
+/*
+==================
+CL_NextDemo
+
+Called when a demo or cinematic finishes
+If the "nextdemo" cvar is set, that command will be issued
+==================
+*/
+void CL_NextDemo( void ) {
+	char	v[MAX_STRING_CHARS];
+
+	Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) );
+	v[MAX_STRING_CHARS-1] = 0;
+	Com_DPrintf("CL_NextDemo: %s\n", v );
+	if (!v[0]) {
+		return;
+	}
+
+	Cvar_Set ("nextdemo","");
+	Cbuf_AddText (v);
+	Cbuf_AddText ("\n");
+	Cbuf_Execute();
+}
+
+/*
+==================
+CL_DemoState
+
+Returns the current state of the demo system
+==================
+*/
+demoState_t CL_DemoState( void ) {
+	if( clc.demoplaying ) {
+		return DS_PLAYBACK;
+	} else if( clc.demorecording ) {
+		return DS_RECORDING;
+	} else {
+		return DS_NONE;
+	}
+}
+
+/*
+==================
+CL_DemoPos
+
+Returns the current position of the demo
+==================
+*/
+int CL_DemoPos( void ) {
+	if( clc.demoplaying || clc.demorecording ) {
+		return FS_FTell( clc.demofile );
+	} else {
+		return 0;
+	}
+}
+
+/*
+==================
+CL_DemoName
+
+Returns the name of the demo
+==================
+*/
+void CL_DemoName( char *buffer, int size ) {
+	if( clc.demoplaying || clc.demorecording ) {
+		Q_strncpyz( buffer, clc.demoName, size );
+	} else if( size >= 1 ) {
+		buffer[ 0 ] = '\0';
+	}
+}
+
+//======================================================================
+
+/*
+=====================
+CL_ShutdownAll
+=====================
+*/
+void CL_ShutdownAll(void) {
+
+#ifdef USE_CURL
+	CL_cURL_Shutdown();
+#endif
+	// clear sounds
+	S_DisableSounds();
+	// shutdown CGame
+	CL_ShutdownCGame();
+	// shutdown UI
+	CL_ShutdownUI();
+
+	// shutdown the renderer
+	if ( re.Shutdown ) {
+		re.Shutdown( qfalse );		// don't destroy window or context
+	}
+
+	cls.uiStarted = qfalse;
+	cls.cgameStarted = qfalse;
+	cls.rendererStarted = qfalse;
+	cls.soundRegistered = qfalse;
+}
+
+/*
+=================
+CL_FlushMemory
+
+Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only
+ways a client gets into a game
+Also called by Com_Error
+=================
+*/
+void CL_FlushMemory( void ) {
+
+	// shutdown all the client stuff
+	CL_ShutdownAll();
+
+	// if not running a server clear the whole hunk
+	if ( !com_sv_running->integer ) {
+		// clear the whole hunk
+		Hunk_Clear();
+		// clear collision map data
+		CM_ClearMap();
+	}
+	else {
+		// clear all the client data on the hunk
+		Hunk_ClearToMark();
+	}
+
+	CL_StartHunkUsers( qfalse );
+}
+
+/*
+=====================
+CL_MapLoading
+
+A local server is starting to load a map, so update the
+screen to let the user know about it, then dump all client
+memory on the hunk from cgame, ui, and renderer
+=====================
+*/
+void CL_MapLoading( void ) {
+	if ( com_dedicated->integer ) {
+		cls.state = CA_DISCONNECTED;
+		Key_SetCatcher( KEYCATCH_CONSOLE );
+		return;
+	}
+
+	if ( !com_cl_running->integer ) {
+		return;
+	}
+
+	Con_Close();
+	Key_SetCatcher( 0 );
+
+	// if we are already connected to the local host, stay connected
+	if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) {
+		cls.state = CA_CONNECTED;		// so the connect screen is drawn
+		Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
+		Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
+		Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
+		clc.lastPacketSentTime = -9999;
+		SCR_UpdateScreen();
+	} else {
+		CL_Disconnect( qtrue );
+		Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) );
+		cls.state = CA_CHALLENGING;		// so the connect screen is drawn
+		Key_SetCatcher( 0 );
+		SCR_UpdateScreen();
+		clc.connectTime = -RETRANSMIT_TIMEOUT;
+		NET_StringToAdr( cls.servername, &clc.serverAddress, NA_UNSPEC);
+		// we don't need a challenge on the localhost
+
+		CL_CheckForResend();
+	}
+}
+
+/*
+=====================
+CL_ClearState
+
+Called before parsing a gamestate
+=====================
+*/
+void CL_ClearState (void) {
+
+//	S_StopAllSounds();
+
+	Com_Memset( &cl, 0, sizeof( cl ) );
+}
+
+/*
+====================
+CL_UpdateGUID
+
+update cl_guid using QKEY_FILE and optional prefix
+====================
+*/
+static void CL_UpdateGUID( const char *prefix, int prefix_len )
+{
+	fileHandle_t f;
+	int len;
+
+	len = FS_SV_FOpenFileRead( QKEY_FILE, &f );
+	FS_FCloseFile( f );
+
+	if( len != QKEY_SIZE ) 
+		Cvar_Set( "cl_guid", "" );
+	else
+		Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE,
+			prefix, prefix_len ) );
+}
+
+
+/*
+=====================
+CL_Disconnect
+
+Called when a connection, demo, or cinematic is being terminated.
+Goes from a connected state to either a menu state or a console state
+Sends a disconnect message to the server
+This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors
+=====================
+*/
+void CL_Disconnect( qboolean showMainMenu ) {
+	if ( !com_cl_running || !com_cl_running->integer ) {
+		return;
+	}
+
+	Cvar_Set("fs_game", "gpp");
+
+	// shutting down the client so enter full screen ui mode
+	Cvar_Set("r_uiFullScreen", "1");
+
+	if ( clc.demorecording ) {
+		CL_StopRecord_f ();
+	}
+
+	if (clc.download) {
+		FS_FCloseFile( clc.download );
+		clc.download = 0;
+	}
+	*clc.downloadTempName = *clc.downloadName = 0;
+	Cvar_Set( "cl_downloadName", "" );
+
+#ifdef USE_MUMBLE
+	if (cl_useMumble->integer && mumble_islinked()) {
+		Com_Printf("Mumble: Unlinking from Mumble application\n");
+		mumble_unlink();
+	}
+#endif
+
+#ifdef USE_VOIP
+	if (cl_voipSend->integer) {
+		int tmp = cl_voipUseVAD->integer;
+		cl_voipUseVAD->integer = 0;  // disable this for a moment.
+		clc.voipOutgoingDataSize = 0;  // dump any pending VoIP transmission.
+		Cvar_Set("cl_voipSend", "0");
+		CL_CaptureVoip();  // clean up any state...
+		cl_voipUseVAD->integer = tmp;
+	}
+
+	if (clc.speexInitialized) {
+		int i;
+		speex_bits_destroy(&clc.speexEncoderBits);
+		speex_encoder_destroy(clc.speexEncoder);
+		speex_preprocess_state_destroy(clc.speexPreprocessor);
+		for (i = 0; i < MAX_CLIENTS; i++) {
+			speex_bits_destroy(&clc.speexDecoderBits[i]);
+			speex_decoder_destroy(clc.speexDecoder[i]);
+		}
+	}
+	Cmd_RemoveCommand ("voip");
+#endif
+
+	if ( clc.demofile ) {
+		FS_FCloseFile( clc.demofile );
+		clc.demofile = 0;
+	}
+
+	if ( uivm && showMainMenu ) {
+		VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
+	}
+
+	SCR_StopCinematic ();
+	S_ClearSoundBuffer();
+
+	// send a disconnect message to the server
+	// send it a few times in case one is dropped
+	if ( cls.state >= CA_CONNECTED ) {
+		CL_AddReliableCommand("disconnect", qtrue);
+		CL_WritePacket();
+		CL_WritePacket();
+		CL_WritePacket();
+	}
+	
+	// Remove pure paks
+	FS_PureServerSetLoadedPaks("", "");
+	
+	CL_ClearState ();
+
+	// wipe the client connection
+	Com_Memset( &clc, 0, sizeof( clc ) );
+
+	cls.state = CA_DISCONNECTED;
+
+	// allow cheats locally
+	Cvar_Set( "sv_cheats", "1" );
+
+	// not connected to a pure server anymore
+	cl_connectedToPureServer = qfalse;
+
+#ifdef USE_VOIP
+	// not connected to voip server anymore.
+	cl_connectedToVoipServer = qfalse;
+#endif
+
+	// Stop recording any video
+	if( CL_VideoRecording( ) ) {
+		// Finish rendering current frame
+		SCR_UpdateScreen( );
+		CL_CloseAVI( );
+	}
+	CL_UpdateGUID( NULL, 0 );
+}
+
+
+/*
+===================
+CL_ForwardCommandToServer
+
+adds the current command line as a clientCommand
+things like godmode, noclip, etc, are commands directed to the server,
+so when they are typed in at the console, they will need to be forwarded.
+===================
+*/
+void CL_ForwardCommandToServer( const char *string ) {
+	char	*cmd;
+
+	cmd = Cmd_Argv(0);
+
+	// ignore key up commands
+	if ( cmd[0] == '-' ) {
+		return;
+	}
+
+	if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) {
+		Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd);
+		return;
+	}
+
+	if ( Cmd_Argc() > 1 ) {
+		CL_AddReliableCommand(string, qfalse);
+	} else {
+		CL_AddReliableCommand(cmd, qfalse);
+	}
+}
+
+/*
+===================
+CL_RequestMotd
+
+===================
+*/
+void CL_RequestMotd( void ) {
+	char		info[MAX_INFO_STRING];
+
+	if ( !cl_motd->integer ) {
+		return;
+	}
+	Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME );
+
+	switch( NET_StringToAdr( MASTER_SERVER_NAME, &cls.updateServer,
+	                         NA_UNSPEC ) )
+	{
+		case 0:
+			Com_Printf( "Couldn't resolve master address\n" );
+			return;
+
+		case 2:
+			cls.updateServer.port = BigShort( PORT_MASTER );
+		default:
+			break;
+	}
+
+	Com_Printf( "%s resolved to %s\n", MASTER_SERVER_NAME,
+	            NET_AdrToStringwPort( cls.updateServer ) );
+
+	info[0] = 0;
+
+	Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ),
+			"%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds());
+
+	Info_SetValueForKey( info, "challenge", cls.updateChallenge );
+	Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string );
+	Info_SetValueForKey( info, "version", com_version->string );
+
+	NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd%s", info );
+}
+
+/*
+======================================================================
+
+CONSOLE COMMANDS
+
+======================================================================
+*/
+
+/*
+==================
+CL_ForwardToServer_f
+==================
+*/
+void CL_ForwardToServer_f( void ) {
+	if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
+		Com_Printf ("Not connected to a server.\n");
+		return;
+	}
+	
+	// don't forward the first argument
+	if ( Cmd_Argc() > 1 ) {
+		CL_AddReliableCommand(Cmd_Args(), qfalse);
+	}
+}
+
+/*
+==================
+CL_Disconnect_f
+==================
+*/
+void CL_Disconnect_f( void ) {
+	SCR_StopCinematic();
+	Cvar_Set("ui_singlePlayerActive", "0");
+	if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) {
+		Com_Error (ERR_DISCONNECT, "Disconnected from server");
+	}
+}
+
+
+/*
+================
+CL_Reconnect_f
+
+================
+*/
+void CL_Reconnect_f( void ) {
+	if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) {
+		Com_Printf( "Can't reconnect to localhost.\n" );
+		return;
+	}
+	Cvar_Set("ui_singlePlayerActive", "0");
+	Cbuf_AddText( va("connect %s\n", cls.servername ) );
+}
+
+/*
+================
+CL_Connect_f
+
+================
+*/
+void CL_Connect_f( void ) {
+	char	*server;
+	const char	*serverString;
+	int argc = Cmd_Argc();
+	netadrtype_t family = NA_UNSPEC;
+
+	if ( argc != 2 && argc != 3 ) {
+		Com_Printf( "usage: connect [-4|-6] server\n");
+		return;	
+	}
+	
+	if(argc == 2)
+		server = Cmd_Argv(1);
+	else
+	{
+		if(!strcmp(Cmd_Argv(1), "-4"))
+			family = NA_IP;
+		else if(!strcmp(Cmd_Argv(1), "-6"))
+			family = NA_IP6;
+		else
+			Com_Printf( "warning: only -4 or -6 as address type understood.\n");
+		
+		server = Cmd_Argv(2);
+	}
+
+	Cvar_Set("ui_singlePlayerActive", "0");
+
+	// fire a message off to the motd server
+	CL_RequestMotd();
+
+	// clear any previous "server full" type messages
+	clc.serverMessage[0] = 0;
+
+	if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) {
+		// if running a local server, kill it
+		SV_Shutdown( "Server quit" );
+	}
+
+	// make sure a local server is killed
+	Cvar_Set( "sv_killserver", "1" );
+	SV_Frame( 0 );
+
+	CL_Disconnect( qtrue );
+	Con_Close();
+
+	Q_strncpyz( cls.servername, server, sizeof(cls.servername) );
+
+	if (!NET_StringToAdr(cls.servername, &clc.serverAddress, family) ) {
+		Com_Printf ("Bad server address\n");
+		cls.state = CA_DISCONNECTED;
+		return;
+	}
+	if (clc.serverAddress.port == 0) {
+		clc.serverAddress.port = BigShort( PORT_SERVER );
+	}
+
+	serverString = NET_AdrToStringwPort(clc.serverAddress);
+
+	Com_Printf( "%s resolved to %s\n", cls.servername, serverString);
+
+	if( cl_guidServerUniq->integer )
+		CL_UpdateGUID( serverString, strlen( serverString ) );
+	else
+		CL_UpdateGUID( NULL, 0 );
+
+	// if we aren't playing on a lan, we need to authenticate
+	// with the cd key
+	if(NET_IsLocalAddress(clc.serverAddress))
+		cls.state = CA_CHALLENGING;
+	else
+	{
+		cls.state = CA_CONNECTING;
+		
+		// Set a client challenge number that ideally is mirrored back by the server.
+		clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds();
+	}
+
+	Key_SetCatcher( 0 );
+	clc.connectTime = -99999;	// CL_CheckForResend() will fire immediately
+	clc.connectPacketCount = 0;
+
+	// server connection string
+	Cvar_Set( "cl_currentServerAddress", server );
+}
+
+#define MAX_RCON_MESSAGE 1024
+
+/*
+==================
+CL_CompleteRcon
+==================
+*/
+static void CL_CompleteRcon( char *args, int argNum )
+{
+	if( argNum == 2 )
+	{
+		// Skip "rcon "
+		char *p = Com_SkipTokens( args, 1, " " );
+
+		if( p > args )
+			Field_CompleteCommand( p, qtrue, qtrue );
+	}
+}
+
+/*
+=====================
+CL_Rcon_f
+
+  Send the rest of the command line over as
+  an unconnected command.
+=====================
+*/
+void CL_Rcon_f( void ) {
+	char	message[MAX_RCON_MESSAGE];
+	netadr_t	to;
+
+	if ( !rcon_client_password->string ) {
+		Com_Printf ("You must set 'rconpassword' before\n"
+					"issuing an rcon command.\n");
+		return;
+	}
+
+	message[0] = -1;
+	message[1] = -1;
+	message[2] = -1;
+	message[3] = -1;
+	message[4] = 0;
+
+	Q_strcat (message, MAX_RCON_MESSAGE, "rcon ");
+
+	Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string);
+	Q_strcat (message, MAX_RCON_MESSAGE, " ");
+
+	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
+	Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5);
+
+	if ( cls.state >= CA_CONNECTED ) {
+		to = clc.netchan.remoteAddress;
+	} else {
+		if (!strlen(rconAddress->string)) {
+			Com_Printf ("You must either be connected,\n"
+						"or set the 'rconAddress' cvar\n"
+						"to issue rcon commands\n");
+
+			return;
+		}
+		NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC);
+		if (to.port == 0) {
+			to.port = BigShort (PORT_SERVER);
+		}
+	}
+	
+	NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
+}
+
+/*
+=================
+CL_SendPureChecksums
+=================
+*/
+void CL_SendPureChecksums( void ) {
+	char cMsg[MAX_INFO_VALUE];
+
+	// if we are pure we need to send back a command with our referenced pk3 checksums
+	Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums());
+
+	CL_AddReliableCommand(cMsg, qfalse);
+}
+
+/*
+=================
+CL_ResetPureClientAtServer
+=================
+*/
+void CL_ResetPureClientAtServer( void ) {
+	CL_AddReliableCommand("vdr", qfalse);
+}
+
+/*
+=================
+CL_Vid_Restart_f
+
+Restart the video subsystem
+
+we also have to reload the UI and CGame because the renderer
+doesn't know what graphics to reload
+=================
+*/
+void CL_Vid_Restart_f( void ) {
+
+	// Settings may have changed so stop recording now
+	if( CL_VideoRecording( ) ) {
+		CL_CloseAVI( );
+	}
+
+	if(clc.demorecording)
+		CL_StopRecord_f();
+
+	// don't let them loop during the restart
+	S_StopAllSounds();
+	// shutdown the UI
+	CL_ShutdownUI();
+	// shutdown the CGame
+	CL_ShutdownCGame();
+	// shutdown the renderer and clear the renderer interface
+	CL_ShutdownRef();
+	// client is no longer pure untill new checksums are sent
+	CL_ResetPureClientAtServer();
+	// clear pak references
+	FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF );
+	// reinitialize the filesystem if the game directory or checksum has changed
+	FS_ConditionalRestart( clc.checksumFeed );
+
+	cls.rendererStarted = qfalse;
+	cls.uiStarted = qfalse;
+	cls.cgameStarted = qfalse;
+	cls.soundRegistered = qfalse;
+
+	// unpause so the cgame definately gets a snapshot and renders a frame
+	Cvar_Set( "cl_paused", "0" );
+
+	// if not running a server clear the whole hunk
+	if ( !com_sv_running->integer ) {
+		// clear the whole hunk
+		Hunk_Clear();
+	}
+	else {
+		// clear all the client data on the hunk
+		Hunk_ClearToMark();
+	}
+
+	// initialize the renderer interface
+	CL_InitRef();
+
+	// startup all the client stuff
+	CL_StartHunkUsers( qfalse );
+
+	// start the cgame if connected
+	if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
+		cls.cgameStarted = qtrue;
+		CL_InitCGame();
+		// send pure checksums
+		CL_SendPureChecksums();
+	}
+}
+
+/*
+=================
+CL_Snd_Restart
+
+Restart the sound subsystem
+=================
+*/
+void CL_Snd_Restart(void)
+{
+	S_Shutdown();
+	S_Init();
+}
+
+/*
+=================
+CL_Snd_Restart_f
+
+Restart the sound subsystem
+The cgame and game must also be forced to restart because
+handles will be invalid
+=================
+*/
+void CL_Snd_Restart_f(void)
+{
+	CL_Snd_Restart();
+	CL_Vid_Restart_f();
+}
+
+
+/*
+==================
+CL_PK3List_f
+==================
+*/
+void CL_OpenedPK3List_f( void ) {
+	Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames());
+}
+
+/*
+==================
+CL_PureList_f
+==================
+*/
+void CL_ReferencedPK3List_f( void ) {
+	Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames());
+}
+
+/*
+==================
+CL_Configstrings_f
+==================
+*/
+void CL_Configstrings_f( void ) {
+	int		i;
+	int		ofs;
+
+	if ( cls.state != CA_ACTIVE ) {
+		Com_Printf( "Not connected to a server.\n");
+		return;
+	}
+
+	for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
+		ofs = cl.gameState.stringOffsets[ i ];
+		if ( !ofs ) {
+			continue;
+		}
+		Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs );
+	}
+}
+
+/*
+==============
+CL_Clientinfo_f
+==============
+*/
+void CL_Clientinfo_f( void ) {
+	Com_Printf( "--------- Client Information ---------\n" );
+	Com_Printf( "state: %i\n", cls.state );
+	Com_Printf( "Server: %s\n", cls.servername );
+	Com_Printf ("User info settings:\n");
+	Info_Print( Cvar_InfoString( CVAR_USERINFO ) );
+	Com_Printf( "--------------------------------------\n" );
+}
+
+
+//====================================================================
+
+/*
+=================
+CL_DownloadsComplete
+
+Called when all downloading has been completed
+=================
+*/
+void CL_DownloadsComplete( void ) {
+	Com_Printf("Downloads complete\n");
+
+#ifdef USE_CURL
+	// if we downloaded with cURL
+	if(clc.cURLUsed) { 
+		clc.cURLUsed = qfalse;
+		CL_cURL_Shutdown();
+		if( clc.cURLDisconnected ) {
+			if(clc.downloadRestart) {
+                if( !clc.activeCURLNotGameRelated )
+                    FS_Restart(clc.checksumFeed);
+				clc.downloadRestart = qfalse;
+			}
+			clc.cURLDisconnected = qfalse;
+            if( !clc.activeCURLNotGameRelated )
+                CL_Reconnect_f();
+			return;
+		}
+	}
+#endif
+
+	// if we downloaded files we need to restart the file system
+	if (clc.downloadRestart) {
+		clc.downloadRestart = qfalse;
+
+		FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it
+
+		// inform the server so we get new gamestate info
+		CL_AddReliableCommand("donedl", qfalse);
+
+		// by sending the donedl command we request a new gamestate
+		// so we don't want to load stuff yet
+		return;
+	}
+
+	// let the client game init and load data
+	cls.state = CA_LOADING;
+
+	// Pump the loop, this may change gamestate!
+	Com_EventLoop();
+
+	// if the gamestate was changed by calling Com_EventLoop
+	// then we loaded everything already and we don't want to do it again.
+	if ( cls.state != CA_LOADING ) {
+		return;
+	}
+
+	// starting to load a map so we get out of full screen ui mode
+	Cvar_Set("r_uiFullScreen", "0");
+
+	// flush client memory and start loading stuff
+	// this will also (re)load the UI
+	// if this is a local client then only the client part of the hunk
+	// will be cleared, note that this is done after the hunk mark has been set
+	CL_FlushMemory();
+
+	// initialize the CGame
+	cls.cgameStarted = qtrue;
+	CL_InitCGame();
+
+	// set pure checksums
+	CL_SendPureChecksums();
+
+	CL_WritePacket();
+	CL_WritePacket();
+	CL_WritePacket();
+}
+
+/*
+=================
+CL_BeginDownload
+
+Requests a file to download from the server.  Stores it in the current
+game directory.
+=================
+*/
+void CL_BeginDownload( const char *localName, const char *remoteName ) {
+
+	Com_DPrintf("***** CL_BeginDownload *****\n"
+				"Localname: %s\n"
+				"Remotename: %s\n"
+				"****************************\n", localName, remoteName);
+
+	Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) );
+	Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName );
+
+	// Set so UI gets access to it
+	Cvar_Set( "cl_downloadName", remoteName );
+	Cvar_Set( "cl_downloadSize", "0" );
+	Cvar_Set( "cl_downloadCount", "0" );
+	Cvar_SetValue( "cl_downloadTime", cls.realtime );
+
+	clc.downloadBlock = 0; // Starting new file
+	clc.downloadCount = 0;
+
+	// Stop any errant looping sounds that may be playing
+	S_ClearLoopingSounds( qtrue );
+
+	CL_AddReliableCommand( va("download %s", remoteName), qfalse );
+}
+
+/*
+=================
+CL_NextDownload
+
+A download completed or failed
+=================
+*/
+void CL_NextDownload(void)
+{
+	char *s;
+	char *remoteName, *localName;
+	qboolean useCURL = qfalse;
+	int prompt;
+
+	// A download has finished, check whether this matches a referenced checksum
+	if(*clc.downloadName && !clc.activeCURLNotGameRelated)
+	{
+		char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, "");
+		zippath[strlen(zippath)-1] = '\0';
+
+		if(!FS_CompareZipChecksum(zippath))
+			Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName);
+	}
+
+	*clc.downloadTempName = *clc.downloadName = 0;
+	Cvar_Set("cl_downloadName", "");
+
+	// We are looking to start a download here
+	if (*clc.downloadList) {
+
+		// Prompt if we do not allow automatic downloads
+		prompt = com_downloadPrompt->integer;
+		if( !( prompt & DLP_TYPE_MASK ) &&
+		    !( cl_allowDownload->integer & DLF_ENABLE ) ) {
+			char files[ MAX_INFO_STRING ] = "";
+			char *name, *head, *pure_msg;
+			char *url_msg = "";
+			int i = 0, others = 0, swap = 0, max_list = 12;
+
+			// Set the download URL message
+			if( ( clc.sv_allowDownload & DLF_ENABLE ) &&
+			    !( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) {
+				url_msg = va("The server redirects to the following URL:\n%s",
+				             clc.sv_dlURL);
+				max_list -= 6;
+			}
+
+			// Make a pretty version of the download list
+			name = clc.downloadList;
+			if( *name == '@' )
+				name++;
+
+			do {
+				// Copy remote name
+				head = name;
+				while( *head && *head != '@' )
+					head++;
+
+				swap = *head;
+				*head = 0;
+
+				if( i++ < max_list ) {
+					if( i > 1 )
+						Q_strcat( files, sizeof( files ), ", " );
+					Q_strcat( files, sizeof( files ), name );
+				} else {
+					others++;
+				}
+
+				*head = swap;
+				if( !swap )
+					break;
+
+				// Skip local name
+				head++;
+				while( *head && *head != '@' )
+					head++;
+
+				name = head + 1;
+			} while( *head );
+
+			if( others ) {
+				Q_strcat( files, sizeof( files ), va( "(%d other file%s)\n", 
+						  others, others > 1 ? "s" : "" ) );
+			}
+
+			// Set the pure message
+			if( cl_connectedToPureServer ) {
+				if( !( clc.sv_allowDownload & DLF_ENABLE ) ||
+				    ( ( clc.sv_allowDownload & DLF_NO_UDP ) &&
+				    ( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) ) {
+					pure_msg = "You are missing files required by the server. "
+					"The server does not allow downloading. "
+					"You must install these files manually:";
+				} else {
+					pure_msg = "You are missing files required by the server. "
+					"You must download these files or disconnect:";
+				}
+			} else {
+				pure_msg = "You are missing optional files provided by the "
+				"server. You may not need them to play but can "
+				"choose to download them anyway:";
+			}
+
+			Cvar_Set( "com_downloadPromptText",
+			          va("%s\n\n%s\n%s", pure_msg, files, url_msg ) );
+			Cvar_Set( "com_downloadPrompt", va("%d", DLP_SHOW ) );
+			return;
+		}
+
+		if( !( prompt & DLP_PROMPTED ) )
+			Cvar_Set( "com_downloadPrompt", va("%d", prompt | DLP_PROMPTED ) );
+
+		prompt &= DLP_TYPE_MASK;
+
+		s = clc.downloadList;
+
+		// format is:
+		//  @remotename@localname@remotename@localname, etc.
+
+		if (*s == '@')
+			s++;
+		remoteName = s;
+		
+		if ( (s = strchr(s, '@')) == NULL ) {
+			CL_DownloadsComplete();
+			return;
+		}
+
+		*s++ = 0;
+		localName = s;
+		if ( (s = strchr(s, '@')) != NULL )
+			*s++ = 0;
+		else
+			s = localName + strlen(localName); // point at the nul byte
+#ifdef USE_CURL
+		if( ( ( cl_allowDownload->integer & DLF_ENABLE ) &&
+		    !( cl_allowDownload->integer & DLF_NO_REDIRECT ) ) ||
+		    prompt == DLP_CURL ) {
+			Com_Printf("Trying CURL download: %s; %s\n", localName, remoteName);
+			if(clc.sv_allowDownload & DLF_NO_REDIRECT) {
+				Com_Printf("WARNING: server does not "
+				           "allow download redirection "
+				           "(sv_allowDownload is %d)\n",
+				           clc.sv_allowDownload);
+			}
+			else if(!*clc.sv_dlURL) {
+				Com_Printf("WARNING: server allows "
+				           "download redirection, but does not "
+				           "have sv_dlURL set\n");
+			}
+			else if(!CL_cURL_Init()) {
+				Com_Printf("WARNING: could not load "
+				           "cURL library\n");
+			}
+			else {
+				CL_cURL_BeginDownload(localName, va("%s/%s",
+				                      clc.sv_dlURL, remoteName));
+				useCURL = qtrue;
+			}
+		}
+		else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) {
+		        Com_Printf("WARNING: server allows download "
+			           "redirection, but it disabled by client "
+			           "configuration (cl_allowDownload is %d)\n",
+			           cl_allowDownload->integer);
+		}
+#endif /* USE_CURL */
+		if(!useCURL) {
+			Com_Printf("Trying UDP download: %s; %s\n", localName, remoteName);
+
+			if( ( !( cl_allowDownload->integer & DLF_ENABLE ) ||
+			    ( cl_allowDownload->integer & DLF_NO_UDP ) ) &&
+			    prompt != DLP_UDP ) {
+				if( cl_connectedToPureServer ) {
+					Com_Error(ERR_DROP, "Automatic downloads are "
+					          "disabled on your client (cl_allowDownload is %d). "
+					          "You can enable automatic downloads in the Options "
+					          "menu.",
+				                  cl_allowDownload->integer);
+					return;
+				}
+
+				Com_Printf("WARNING: UDP downloads are disabled.\n");
+				CL_DownloadsComplete();
+				return;
+			}
+			else {
+				CL_BeginDownload( localName, remoteName );
+			}
+		}
+		clc.downloadRestart = qtrue;
+
+		// move over the rest
+		memmove( clc.downloadList, s, strlen(s) + 1);
+
+		return;
+	}
+
+	CL_DownloadsComplete();
+}
+
+/*
+=================
+CL_InitDownloads
+
+After receiving a valid game state, we valid the cgame and local zip files here
+and determine if we need to download them
+=================
+*/
+void CL_InitDownloads(void) {
+	if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) {
+    Com_Printf("Need paks: %s\n", clc.downloadList );
+		Cvar_Set( "com_downloadPrompt", "0" );
+		if ( *clc.downloadList ) {
+			cls.state = CA_CONNECTED;
+
+			*clc.downloadTempName = *clc.downloadName = 0;
+			Cvar_Set( "cl_downloadName", "" );
+
+			CL_NextDownload();
+			return;
+		}
+	}
+	CL_DownloadsComplete();
+}
+
+/*
+=================
+CL_CheckForResend
+
+Resend a connect message if the last one has timed out
+=================
+*/
+void CL_CheckForResend( void ) {
+	int		port, i;
+	char	info[MAX_INFO_STRING];
+	char	data[MAX_INFO_STRING];
+
+	// don't send anything if playing back a demo
+	if ( clc.demoplaying ) {
+		return;
+	}
+
+	// resend if we haven't gotten a reply yet
+	if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) {
+		return;
+	}
+
+	if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
+		return;
+	}
+
+	clc.connectTime = cls.realtime;	// for retransmit requests
+	clc.connectPacketCount++;
+
+
+	switch ( cls.state ) {
+	case CA_CONNECTING:
+		// requesting a challenge
+
+		// 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);
+
+		NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data);
+		break;
+		
+	case CA_CHALLENGING:
+		// sending back the challenge
+		port = Cvar_VariableValue ("net_qport");
+
+		Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
+		Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) );
+		Info_SetValueForKey( info, "qport", va("%i", port ) );
+		Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
+		
+		strcpy(data, "connect ");
+    // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server
+    //   (Com_TokenizeString tokenizes around spaces)
+    data[8] = '"';
+
+		for(i=0;i<strlen(info);i++) {
+			data[9+i] = info[i];	// + (clc.challenge)&0x3;
+		}
+    data[9+i] = '"';
+		data[10+i] = 0;
+
+    // NOTE TTimo don't forget to set the right data length!
+		NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) &data[0], i+10 );
+		// the most current userinfo has been sent, so watch for any
+		// newer changes to userinfo variables
+		cvar_modifiedFlags &= ~CVAR_USERINFO;
+		break;
+
+	default:
+		Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" );
+	}
+}
+
+/*
+===================
+CL_DisconnectPacket
+
+Sometimes the server can drop the client and the netchan based
+disconnect can be lost.  If the client continues to send packets
+to the server, the server will send out of band disconnect packets
+to the client so it doesn't have to wait for the full timeout period.
+===================
+*/
+void CL_DisconnectPacket( netadr_t from ) {
+	if ( cls.state < CA_AUTHORIZING ) {
+		return;
+	}
+
+	// if not from our server, ignore it
+	if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
+		return;
+	}
+
+	// if we have received packets within three seconds, ignore it
+	// (it might be a malicious spoof)
+	if ( cls.realtime - clc.lastPacketTime < 3000 ) {
+		return;
+	}
+
+	// drop the connection
+	Com_Printf( "Server disconnected for unknown reason\n" );
+	Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" );
+	CL_Disconnect( qtrue );
+}
+
+
+/*
+===================
+CL_MotdPacket
+
+===================
+*/
+void CL_MotdPacket( netadr_t from, const char *info ) {
+	const char *v;
+
+	// if not from our server, ignore it
+	if ( !NET_CompareAdr( from, cls.updateServer ) ) {
+		Com_DPrintf( "MOTD packet from unexpected source\n" );
+		return;
+	}
+
+	Com_DPrintf( "MOTD packet: %s\n", info );
+	while( *info != '\\' )
+		info++;
+
+	// check challenge
+	v = Info_ValueForKey( info, "challenge" );
+	if ( strcmp( v, cls.updateChallenge ) ) {
+		Com_DPrintf( "MOTD packet mismatched challenge: "
+		             "'%s' != '%s'\n", v, cls.updateChallenge );
+		return;
+	}
+
+	v = Info_ValueForKey( info, "motd" );
+
+	Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
+	Cvar_Set( "cl_motdString", v );
+}
+
+/*
+===================
+CL_InitServerInfo
+===================
+*/
+void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
+	server->adr = *address;
+	server->clients = 0;
+	server->hostName[0] = '\0';
+	server->mapName[0] = '\0';
+	server->label[0] = '\0';
+	server->maxClients = 0;
+	server->maxPing = 0;
+	server->minPing = 0;
+	server->ping = -1;
+	server->game[0] = '\0';
+	server->gameType = 0;
+	server->netType = 0;
+}
+
+/*
+===================
+CL_GSRSequenceInformation
+
+Parses this packet's index and the number of packets from a master server's
+response. Updates the packet count and returns the index. Advances the data
+pointer as appropriate (but only when parsing was successful)
+
+The sequencing information isn't terribly useful at present (we can skip
+duplicate packets, but we don't bother to make sure we've got all of them).
+===================
+*/
+int CL_GSRSequenceInformation( byte **data )
+{
+	char *p = (char *)*data, *e;
+	int ind, num;
+	// '\0'-delimited fields: this packet's index, total number of packets
+	if( *p++ != '\0' )
+		return -1;
+
+	ind = strtol( p, (char **)&e, 10 );
+	if( *e++ != '\0' )
+		return -1;
+
+	num = strtol( e, (char **)&p, 10 );
+	if( *p++ != '\0' )
+		return -1;
+
+	if( num <= 0 || ind <= 0 || ind > num )
+		return -1; // nonsensical response
+
+	if( cls.numMasterPackets > 0 && num != cls.numMasterPackets )
+	{
+		// Assume we sent two getservers and somehow they changed in
+		// between - only use the results that arrive later
+		Com_DPrintf( "Master changed its mind about packet count!\n" );
+		cls.receivedMasterPackets = 0;
+		cls.numglobalservers = 0;
+		cls.numGlobalServerAddresses = 0;
+	}
+	cls.numMasterPackets = num;
+
+	// successfully parsed
+	*data = (byte *)p;
+	return ind;
+}
+
+/*
+===================
+CL_GSRFeaturedLabel
+
+Parses from the data an arbitrary text string labelling the servers in the
+following getserversresponse packet.
+The result is copied to *buf, and *data is advanced as appropriate
+===================
+*/
+void CL_GSRFeaturedLabel( byte **data, char *buf, int size )
+{
+	char *l = buf;
+	buf[0] = '\0';
+
+	// copy until '\0' which indicates field break
+	// or slash which indicates beginning of server list
+	while( **data && **data != '\\' && **data != '/' )
+	{
+		if( l < &buf[ size - 1 ] )
+			*l = **data;
+		else if( l == &buf[ size - 1 ] )
+			Com_DPrintf( S_COLOR_YELLOW "Warning: "
+				"CL_GSRFeaturedLabel: overflow\n" );
+		l++, (*data)++;
+	}
+}
+
+#define MAX_SERVERSPERPACKET	256
+
+/*
+===================
+CL_ServersResponsePacket
+===================
+*/
+void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) {
+	int				i, count, total;
+	netadr_t addresses[MAX_SERVERSPERPACKET];
+	int				numservers;
+	byte*			buffptr;
+	byte*			buffend;
+	char			label[MAX_FEATLABEL_CHARS] = "";
+	
+	Com_DPrintf("CL_ServersResponsePacket%s\n",
+		(extended) ? " (extended)" : "");
+
+	if (cls.numglobalservers == -1) {
+		// state to detect lack of servers or lack of response
+		cls.numglobalservers = 0;
+		cls.numGlobalServerAddresses = 0;
+		cls.numMasterPackets = 0;
+		cls.receivedMasterPackets = 0;
+	}
+
+	// parse through server response string
+	numservers = 0;
+	buffptr    = msg->data;
+	buffend    = buffptr + msg->cursize;
+
+	// skip header
+	buffptr += 4;
+
+	// advance to initial token
+	// I considered using strchr for this but I don't feel like relying
+	// on its behaviour with '\0'
+	while( *buffptr && *buffptr != '\\' && *buffptr != '/' )
+	{
+		buffptr++;
+
+		if( buffptr >= buffend )
+			break;
+	}
+
+	if( *buffptr == '\0' )
+	{
+		int ind = CL_GSRSequenceInformation( &buffptr );
+		if( ind >= 0 )
+		{
+			// this denotes the start of new-syntax stuff
+			// have we already received this packet?
+			if( cls.receivedMasterPackets & ( 1 << ( ind - 1 ) ) )
+			{
+				Com_DPrintf( "CL_ServersResponsePacket: "
+					"received packet %d again, ignoring\n",
+					ind );
+				return;
+			}
+			// TODO: detect dropped packets and make another
+			// request
+			Com_DPrintf( "CL_ServersResponsePacket: packet "
+				"%d of %d\n", ind, cls.numMasterPackets );
+			cls.receivedMasterPackets |= ( 1 << ( ind - 1 ) );
+
+			CL_GSRFeaturedLabel( &buffptr, label, sizeof( label ) );
+		}
+		// now skip to the server list
+		for(; buffptr < buffend && *buffptr != '\\' && *buffptr != '/';
+			buffptr++ );
+	}
+
+	while (buffptr + 1 < buffend)
+	{
+		// IPv4 address
+		if (*buffptr == '\\')
+		{
+			buffptr++;
+
+			if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1)
+				break;
+
+			for(i = 0; i < sizeof(addresses[numservers].ip); i++)
+				addresses[numservers].ip[i] = *buffptr++;
+
+			addresses[numservers].type = NA_IP;
+		}
+		// IPv6 address, if it's an extended response
+		else if (extended && *buffptr == '/')
+		{
+			buffptr++;
+
+			if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1)
+				break;
+			
+			for(i = 0; i < sizeof(addresses[numservers].ip6); i++)
+				addresses[numservers].ip6[i] = *buffptr++;
+			
+			addresses[numservers].type = NA_IP6;
+			addresses[numservers].scope_id = from->scope_id;
+		}
+		else
+			// syntax error!
+			break;
+			
+		// parse out port
+		addresses[numservers].port = (*buffptr++) << 8;
+		addresses[numservers].port += *buffptr++;
+		addresses[numservers].port = BigShort( addresses[numservers].port );
+
+		// syntax check
+		if (*buffptr != '\\' && *buffptr != '/')
+			break;
+	
+		numservers++;
+		if (numservers >= MAX_SERVERSPERPACKET)
+			break;
+	}
+
+	count = cls.numglobalservers;
+
+	for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) {
+		// build net address
+		serverInfo_t *server = &cls.globalServers[count];
+
+		CL_InitServerInfo( server, &addresses[i] );
+		Q_strncpyz( server->label, label, sizeof( server->label ) );
+		// advance to next slot
+		count++;
+	}
+
+	// if getting the global list
+	if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS )
+	{
+		// if we couldn't store the servers in the main list anymore
+		for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++)
+		{
+			// just store the addresses in an additional list
+			cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i];
+		}
+	}
+
+	cls.numglobalservers = count;
+	total = count + cls.numGlobalServerAddresses;
+
+	Com_Printf("%d servers parsed (total %d)\n", numservers, total);
+}
+
+/*
+=================
+CL_ConnectionlessPacket
+
+Responses to broadcasts, etc
+=================
+*/
+void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
+	char	*s;
+	char	*c;
+
+	MSG_BeginReadingOOB( msg );
+	MSG_ReadLong( msg );	// skip the -1
+
+	s = MSG_ReadStringLine( msg );
+
+	Cmd_TokenizeString( s );
+
+	c = Cmd_Argv(0);
+
+	Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c);
+
+	// challenge from the server we are connecting to
+	if (!Q_stricmp(c, "challengeResponse"))
+	{
+		if (cls.state != CA_CONNECTING)
+		{
+			Com_DPrintf("Unwanted challenge response received.  Ignored.\n");
+			return;
+		}
+		
+		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)
+			{
+				Com_DPrintf("Challenge response received from unexpected source. Ignored.\n");
+				return;
+			}
+		}
+
+		// start sending challenge response instead of challenge request packets
+		clc.challenge = atoi(Cmd_Argv(1));
+		cls.state = CA_CHALLENGING;
+		clc.connectPacketCount = 0;
+		clc.connectTime = -99999;
+
+		// take this address as the new server address.  This allows
+		// a server proxy to hand off connections to multiple servers
+		clc.serverAddress = from;
+		Com_DPrintf ("challengeResponse: %d\n", clc.challenge);
+		return;
+	}
+
+	// server connection
+	if ( !Q_stricmp(c, "connectResponse") ) {
+		if ( cls.state >= CA_CONNECTED ) {
+			Com_Printf ("Dup connect received.  Ignored.\n");
+			return;
+		}
+		if ( cls.state != CA_CHALLENGING ) {
+			Com_Printf ("connectResponse packet while not connecting. Ignored.\n");
+			return;
+		}
+		if ( !NET_CompareAdr( from, clc.serverAddress ) ) {
+			Com_Printf( "connectResponse from wrong address. Ignored.\n" );
+			return;
+		}
+		Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) );
+		cls.state = CA_CONNECTED;
+		clc.lastPacketSentTime = -9999;		// send first packet immediately
+		return;
+	}
+
+	// server responding to an info broadcast
+	if ( !Q_stricmp(c, "infoResponse") ) {
+		CL_ServerInfoPacket( from, msg );
+		return;
+	}
+
+	// server responding to a get playerlist
+	if ( !Q_stricmp(c, "statusResponse") ) {
+		CL_ServerStatusResponse( from, 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) );
+		return;
+	}
+
+	// global MOTD from trem master
+	if ( !Q_stricmp(c, "motd") ) {
+		CL_MotdPacket( from, s );
+		return;
+	}
+
+	// echo request from server
+	if ( !Q_stricmp(c, "print") ) {
+		s = MSG_ReadString( msg );
+
+		Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
+
+		while( clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] == '\n' )
+			clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] = '\0';
+
+		Com_Printf( "%s", s );
+		return;
+	}
+
+	// list of servers sent back by a master server (classic)
+	if ( !Q_strncmp(c, "getserversResponse", 18) ) {
+		CL_ServersResponsePacket( &from, msg, qfalse );
+		return;
+	}
+
+	// list of servers sent back by a master server (extended)
+	if ( !Q_strncmp(c, "getserversExtResponse", 21) ) {
+		CL_ServersResponsePacket( &from, msg, qtrue );
+		return;
+	}
+
+	Com_DPrintf ("Unknown connectionless packet command.\n");
+}
+
+
+/*
+=================
+CL_PacketEvent
+
+A packet has arrived from the main event loop
+=================
+*/
+void CL_PacketEvent( netadr_t from, msg_t *msg ) {
+	int		headerBytes;
+
+	clc.lastPacketTime = cls.realtime;
+
+	if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) {
+		CL_ConnectionlessPacket( from, msg );
+		return;
+	}
+
+	if ( cls.state < CA_CONNECTED ) {
+		return;		// can't be a valid sequenced packet
+	}
+
+	if ( msg->cursize < 4 ) {
+		Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from ));
+		return;
+	}
+
+	//
+	// packet from server
+	//
+	if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
+		Com_DPrintf ("%s:sequenced packet without connection\n"
+			, NET_AdrToStringwPort( from ) );
+		// FIXME: send a client disconnect?
+		return;
+	}
+
+	if (!CL_Netchan_Process( &clc.netchan, msg) ) {
+		return;		// out of order, duplicated, etc
+	}
+
+	// the header is different lengths for reliable and unreliable messages
+	headerBytes = msg->readcount;
+
+	// track the last message received so it can be returned in 
+	// client messages, allowing the server to detect a dropped
+	// gamestate
+	clc.serverMessageSequence = LittleLong( *(int *)msg->data );
+
+	clc.lastPacketTime = cls.realtime;
+	CL_ParseServerMessage( msg );
+
+	//
+	// we don't know if it is ok to save a demo message until
+	// after we have parsed the frame
+	//
+	if ( clc.demorecording && !clc.demowaiting ) {
+		CL_WriteDemoMessage( msg, headerBytes );
+	}
+}
+
+/*
+==================
+CL_CheckTimeout
+
+==================
+*/
+void CL_CheckTimeout( void ) {
+	//
+	// check timeout
+	//
+	if ( ( !CL_CheckPaused() || !sv_paused->integer ) 
+		&& cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC
+	    && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) {
+		if (++cl.timeoutcount > 5) {	// timeoutcount saves debugger
+			Com_Printf ("\nServer connection timed out.\n");
+			CL_Disconnect( qtrue );
+			return;
+		}
+	} else {
+		cl.timeoutcount = 0;
+	}
+}
+
+/*
+==================
+CL_CheckPaused
+Check whether client has been paused.
+==================
+*/
+qboolean CL_CheckPaused(void)
+{
+	// if cl_paused->modified is set, the cvar has only been changed in
+	// this frame. Keep paused in this frame to ensure the server doesn't
+	// lag behind.
+	if(cl_paused->integer || cl_paused->modified)
+		return qtrue;
+	
+	return qfalse;
+}
+
+//============================================================================
+
+/*
+==================
+CL_CheckUserinfo
+
+==================
+*/
+void CL_CheckUserinfo( void ) {
+	// don't add reliable commands when not yet connected
+	if(cls.state < CA_CHALLENGING)
+		return;
+
+	// don't overflow the reliable command buffer when paused
+	if(CL_CheckPaused())
+		return;
+
+	// send a reliable userinfo update if needed
+	if(cvar_modifiedFlags & CVAR_USERINFO)
+	{
+		cvar_modifiedFlags &= ~CVAR_USERINFO;
+		CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse);
+	}
+}
+
+/*
+==================
+CL_Frame
+
+==================
+*/
+void CL_Frame ( int msec ) {
+
+	if ( !com_cl_running->integer ) {
+		return;
+	}
+
+	// We may have a download prompt ready
+	if( ( com_downloadPrompt->integer & DLP_TYPE_MASK ) &&
+	    !( com_downloadPrompt->integer & DLP_PROMPTED ) ) {
+		Com_Printf( "Download prompt returned %d\n",
+		            com_downloadPrompt->integer );
+		CL_NextDownload( );
+	}
+	else if( com_downloadPrompt->integer & DLP_SHOW ) {
+		// If the UI VM does not support the download prompt, we need to catch
+		// the prompt here and replicate regular behavior.
+		// One frame will always run between requesting and showing the prompt.
+
+		if( com_downloadPrompt->integer & DLP_STALE ) {
+			Com_Printf( "WARNING: UI VM does not support download prompt\n" );
+			Cvar_Set( "com_downloadPrompt", va( "%d", DLP_IGNORE ) );
+			CL_NextDownload( );
+		} else {
+			Cvar_Set( "com_downloadPrompt",
+			          va( "%d", com_downloadPrompt->integer | DLP_STALE ) );
+		}
+	}
+
+#ifdef USE_CURL
+	if(clc.downloadCURLM) {
+		CL_cURL_PerformDownload();
+		// we can't process frames normally when in disconnected
+		// download mode since the ui vm expects cls.state to be
+		// CA_CONNECTED
+		if(clc.cURLDisconnected) {
+			cls.realFrametime = msec;
+			cls.frametime = msec;
+			cls.realtime += cls.frametime;
+			SCR_UpdateScreen();
+			S_Update();
+			Con_RunConsole();
+			cls.framecount++;
+			return;
+		}
+	}
+#endif
+
+	if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI )
+		&& !com_sv_running->integer && uivm ) {
+		// if disconnected, bring up the menu
+		S_StopAllSounds();
+		VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
+	}
+
+	// if recording an avi, lock to a fixed fps
+	if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) {
+		// save the current screen
+		if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) {
+			CL_TakeVideoFrame( );
+
+			// fixed time for next frame'
+			msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value );
+			if (msec == 0) {
+				msec = 1;
+			}
+		}
+	}
+	
+	if( cl_autoRecordDemo->integer ) {
+		if( cls.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) {
+			// If not recording a demo, and we should be, start one
+			qtime_t	now;
+			char		*nowString;
+			char		*p;
+			char		mapName[ MAX_QPATH ];
+			char		serverName[ MAX_OSPATH ];
+
+			Com_RealTime( &now );
+			nowString = va( "%04d%02d%02d%02d%02d%02d",
+					1900 + now.tm_year,
+					1 + now.tm_mon,
+					now.tm_mday,
+					now.tm_hour,
+					now.tm_min,
+					now.tm_sec );
+
+			Q_strncpyz( serverName, cls.servername, MAX_OSPATH );
+			// Replace the ":" in the address as it is not a valid
+			// file name character
+			p = strstr( serverName, ":" );
+			if( p ) {
+				*p = '.';
+			}
+
+			Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) );
+			COM_StripExtension(mapName, mapName, sizeof(mapName));
+
+			Cbuf_ExecuteText( EXEC_NOW,
+					va( "record %s-%s-%s", nowString, serverName, mapName ) );
+		}
+		else if( cls.state != CA_ACTIVE && clc.demorecording ) {
+			// Recording, but not CA_ACTIVE, so stop recording
+			CL_StopRecord_f( );
+		}
+	}
+
+	// save the msec before checking pause
+	cls.realFrametime = msec;
+
+	// decide the simulation time
+	cls.frametime = msec;
+
+	cls.realtime += cls.frametime;
+
+	if ( cl_timegraph->integer ) {
+		SCR_DebugGraph ( cls.realFrametime * 0.25, 0 );
+	}
+
+	// see if we need to update any userinfo
+	CL_CheckUserinfo();
+
+	// if we haven't gotten a packet in a long time,
+	// drop the connection
+	CL_CheckTimeout();
+
+	// send intentions now
+	CL_SendCmd();
+
+	// resend a connection request if necessary
+	CL_CheckForResend();
+
+	// decide on the serverTime to render
+	CL_SetCGameTime();
+
+	// update the screen
+	SCR_UpdateScreen();
+
+	// update audio
+	S_Update();
+
+#ifdef USE_VOIP
+	CL_CaptureVoip();
+#endif
+
+#ifdef USE_MUMBLE
+	CL_UpdateMumble();
+#endif
+
+	// advance local effects for next frame
+	SCR_RunCinematic();
+
+	Con_RunConsole();
+
+	cls.framecount++;
+}
+
+
+//============================================================================
+
+/*
+================
+CL_RefPrintf
+
+DLL glue
+================
+*/
+void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) {
+	va_list		argptr;
+	char		msg[MAXPRINTMSG];
+	
+	va_start (argptr,fmt);
+	Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+	va_end (argptr);
+
+	if ( print_level == PRINT_ALL ) {
+		Com_Printf ("%s", msg);
+	} else if ( print_level == PRINT_WARNING ) {
+		Com_Printf (S_COLOR_YELLOW "%s", msg);		// yellow
+	} else if ( print_level == PRINT_DEVELOPER ) {
+		Com_DPrintf (S_COLOR_RED "%s", msg);		// red
+	}
+}
+
+
+
+/*
+============
+CL_ShutdownRef
+============
+*/
+void CL_ShutdownRef( void ) {
+	if ( !re.Shutdown ) {
+		return;
+	}
+	re.Shutdown( qtrue );
+	Com_Memset( &re, 0, sizeof( re ) );
+}
+
+/*
+============
+CL_InitRenderer
+============
+*/
+void CL_InitRenderer( void ) {
+	// this sets up the renderer and calls R_Init
+	re.BeginRegistration( &cls.glconfig );
+
+	// load character sets
+	cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" );
+	cls.whiteShader = re.RegisterShader( "white" );
+	cls.consoleShader = re.RegisterShader( "console" );
+	g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2;
+	g_consoleField.widthInChars = g_console_field_width;
+}
+
+/*
+============================
+CL_StartHunkUsers
+
+After the server has cleared the hunk, these will need to be restarted
+This is the only place that any of these functions are called from
+============================
+*/
+void CL_StartHunkUsers( qboolean rendererOnly ) {
+	if (!com_cl_running) {
+		return;
+	}
+
+	if ( !com_cl_running->integer ) {
+		return;
+	}
+
+	if ( !cls.rendererStarted ) {
+		cls.rendererStarted = qtrue;
+		CL_InitRenderer();
+	}
+
+	if ( rendererOnly ) {
+		return;
+	}
+
+	if ( !cls.soundStarted ) {
+		cls.soundStarted = qtrue;
+		S_Init();
+	}
+
+	if ( !cls.soundRegistered ) {
+		cls.soundRegistered = qtrue;
+		S_BeginRegistration();
+	}
+
+	if( com_dedicated->integer ) {
+		return;
+	}
+
+	if ( !cls.uiStarted ) {
+		cls.uiStarted = qtrue;
+		CL_InitUI();
+	}
+}
+
+/*
+============
+CL_RefMalloc
+============
+*/
+void *CL_RefMalloc( int size ) {
+	return Z_TagMalloc( size, TAG_RENDERER );
+}
+
+int CL_ScaledMilliseconds(void) {
+	return Sys_Milliseconds()*com_timescale->value;
+}
+
+/*
+============
+CL_InitRef
+============
+*/
+void CL_InitRef( void ) {
+	refimport_t	ri;
+	refexport_t	*ret;
+
+	Com_Printf( "----- Initializing Renderer ----\n" );
+
+	ri.Cmd_AddCommand = Cmd_AddCommand;
+	ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
+	ri.Cmd_Argc = Cmd_Argc;
+	ri.Cmd_Argv = Cmd_Argv;
+	ri.Cmd_ExecuteText = Cbuf_ExecuteText;
+	ri.Printf = CL_RefPrintf;
+	ri.Error = Com_Error;
+	ri.Milliseconds = CL_ScaledMilliseconds;
+	ri.Malloc = CL_RefMalloc;
+	ri.Free = Z_Free;
+#ifdef HUNK_DEBUG
+	ri.Hunk_AllocDebug = Hunk_AllocDebug;
+#else
+	ri.Hunk_Alloc = Hunk_Alloc;
+#endif
+	ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory;
+	ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory;
+	ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
+	ri.FS_ReadFile = FS_ReadFile;
+	ri.FS_FreeFile = FS_FreeFile;
+	ri.FS_WriteFile = FS_WriteFile;
+	ri.FS_FreeFileList = FS_FreeFileList;
+	ri.FS_ListFiles = FS_ListFiles;
+	ri.FS_FileIsInPAK = FS_FileIsInPAK;
+	ri.FS_FileExists = FS_FileExists;
+	ri.Cvar_Get = Cvar_Get;
+	ri.Cvar_Set = Cvar_Set;
+	ri.Cvar_CheckRange = Cvar_CheckRange;
+
+	// cinematic stuff
+
+	ri.CIN_UploadCinematic = CIN_UploadCinematic;
+	ri.CIN_PlayCinematic = CIN_PlayCinematic;
+	ri.CIN_RunCinematic = CIN_RunCinematic;
+  
+	ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;
+
+	ret = GetRefAPI( REF_API_VERSION, &ri );
+
+#if defined __USEA3D && defined __A3D_GEOM
+	hA3Dg_ExportRenderGeom (ret);
+#endif
+
+	Com_Printf( "-------------------------------\n");
+
+	if ( !ret ) {
+		Com_Error (ERR_FATAL, "Couldn't initialize refresh" );
+	}
+
+	re = *ret;
+
+	// unpause so the cgame definately gets a snapshot and renders a frame
+	Cvar_Set( "cl_paused", "0" );
+}
+
+
+//===========================================================================================
+
+
+void CL_SetModel_f( void ) {
+	char	*arg;
+	char	name[256];
+
+	arg = Cmd_Argv( 1 );
+	if (arg[0]) {
+		Cvar_Set( "model", arg );
+		Cvar_Set( "headmodel", arg );
+	} else {
+		Cvar_VariableStringBuffer( "model", name, sizeof(name) );
+		Com_Printf("model is set to %s\n", name);
+	}
+}
+
+
+//===========================================================================================
+
+
+/*
+===============
+CL_Video_f
+
+video
+video [filename]
+===============
+*/
+void CL_Video_f( void )
+{
+  char  filename[ MAX_OSPATH ];
+  int   i, last;
+
+  if( !clc.demoplaying )
+  {
+    Com_Printf( "The video command can only be used when playing back demos\n" );
+    return;
+  }
+
+  if( Cmd_Argc( ) == 2 )
+  {
+    // explicit filename
+    Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) );
+  }
+  else
+  {
+    // scan for a free filename
+    for( i = 0; i <= 9999; i++ )
+    {
+      int a, b, c, d;
+
+      last = i;
+
+      a = last / 1000;
+      last -= a * 1000;
+      b = last / 100;
+      last -= b * 100;
+      c = last / 10;
+      last -= c * 10;
+      d = last;
+
+      Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi",
+          a, b, c, d );
+
+      if( !FS_FileExists( filename ) )
+        break; // file doesn't exist
+    }
+
+    if( i > 9999 )
+    {
+      Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" );
+      return;
+    }
+  }
+
+  CL_OpenAVIForWriting( filename );
+}
+
+/*
+===============
+CL_StopVideo_f
+===============
+*/
+void CL_StopVideo_f( void )
+{
+  CL_CloseAVI( );
+}
+
+/*
+===============
+CL_GenerateQKey
+
+test to see if a valid QKEY_FILE exists.  If one does not, try to generate
+it by filling it with 2048 bytes of random data.
+===============
+*/
+static void CL_GenerateQKey(void)
+{
+	int len = 0;
+	unsigned char buff[ QKEY_SIZE ];
+	fileHandle_t f;
+
+	len = FS_SV_FOpenFileRead( QKEY_FILE, &f );
+	FS_FCloseFile( f );
+	if( len == QKEY_SIZE ) {
+		Com_Printf( "QKEY found.\n" );
+		return;
+	}
+	else {
+		if( len > 0 ) {
+			Com_Printf( "QKEY file size != %d, regenerating\n",
+				QKEY_SIZE );
+		}
+
+		Com_Printf( "QKEY building random string\n" );
+		Com_RandomBytes( buff, sizeof(buff) );
+
+		f = FS_SV_FOpenFileWrite( QKEY_FILE );
+		if( !f ) {
+			Com_Printf( "QKEY could not open %s for write\n",
+				QKEY_FILE );
+			return;
+		}
+		FS_Write( buff, sizeof(buff), f );
+		FS_FCloseFile( f );
+		Com_Printf( "QKEY generated\n" );
+	}
+} 
+
+/*
+====================
+CL_Init
+====================
+*/
+void CL_Init( void ) {
+	Com_Printf( "----- Client Initialization -----\n" );
+
+	Con_Init ();
+
+	CL_ClearState ();
+
+	cls.state = CA_DISCONNECTED;	// no longer CA_UNINITIALIZED
+
+	cls.realtime = 0;
+
+	CL_InitInput ();
+
+	//
+	// register our variables
+	//
+	cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
+	cl_motd = Cvar_Get ("cl_motd", "1", 0);
+
+	cl_timeout = Cvar_Get ("cl_timeout", "200", 0);
+
+	cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP );
+	cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP );
+	cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP );
+	cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP );
+	cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP );
+	rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP );
+	cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );
+
+	cl_timedemo = Cvar_Get ("timedemo", "0", 0);
+	cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE);
+	cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE);
+	cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE);
+	cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE);
+	cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0);
+
+	rconAddress = Cvar_Get ("rconAddress", "", 0);
+
+	cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE);
+	cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE);
+	cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);
+
+	cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE );
+	cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE );
+
+	cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE);
+	cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE);
+	cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE);
+	cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE );
+
+	// 0: legacy mouse acceleration
+	// 1: new implementation
+	cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE );
+	// offset for the power function (for style 1, ignored otherwise)
+	// this should be set to the max rate value
+	cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE );
+	Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse);
+
+	cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
+
+	cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE);
+#ifdef USE_CURL
+	cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE);
+#endif
+	com_downloadPrompt = Cvar_Get ("com_downloadPrompt", "0", CVAR_ROM);
+	Cvar_Get( "com_downloadPromptText", "", CVAR_TEMP );
+
+	cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0);
+#ifdef MACOS_X
+	// In game video is REALLY slow in Mac OS X right now due to driver slowness
+	cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE);
+#else
+	cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE);
+#endif
+
+	cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0);
+
+	m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE);
+	m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE);
+	m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE);
+	m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE);
+#ifdef MACOS_X
+	// Input is jittery on OS X w/o this
+	m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE);
+#else
+	m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE);
+#endif
+
+	j_pitch =        Cvar_Get ("j_pitch",        "0.022", CVAR_ARCHIVE);
+	j_yaw =          Cvar_Get ("j_yaw",          "-0.022", CVAR_ARCHIVE);
+	j_forward =      Cvar_Get ("j_forward",      "-0.25", CVAR_ARCHIVE);
+	j_side =         Cvar_Get ("j_side",         "0.25", CVAR_ARCHIVE);
+	j_pitch_axis =   Cvar_Get ("j_pitch_axis",   "3", CVAR_ARCHIVE);
+	j_yaw_axis =     Cvar_Get ("j_yaw_axis",     "4", CVAR_ARCHIVE);
+	j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE);
+	j_side_axis =    Cvar_Get ("j_side_axis",    "0", CVAR_ARCHIVE);
+
+	cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM );
+
+	Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE );
+
+	cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE);
+
+	cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE);
+
+	// ~ and `, as keys and characters
+	cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE);
+
+	cl_gamename = Cvar_Get("cl_gamename", GAMENAME_FOR_MASTER, CVAR_TEMP);
+
+	// userinfo
+	Cvar_Get ("name", Sys_GetCurrentUser( ), CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("color1",  "4", CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE );
+	Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
+
+	Cvar_Get ("password", "", CVAR_USERINFO);
+
+#ifdef USE_MUMBLE
+	cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH);
+	cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE);
+#endif
+
+#ifdef USE_VOIP
+	cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0);
+	cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "all", 0);
+	cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE);
+	cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE);
+	cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE);
+	cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE);
+	cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE);
+
+	// This is a protocol version number.
+	cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH);
+	Cvar_CheckRange( cl_voip, 0, 1, qtrue );
+
+	// If your data rate is too low, you'll get Connection Interrupted warnings
+	//  when VoIP packets arrive, even if you have a broadband connection.
+	//  This might work on rates lower than 25000, but for safety's sake, we'll
+	//  just demand it. Who doesn't have at least a DSL line now, anyhow? If
+	//  you don't, you don't need VoIP.  :)
+	if ((cl_voip->integer) && (Cvar_VariableIntegerValue("rate") < 25000)) {
+		Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n");
+		Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network' and restart.\n");
+		Com_Printf("Until then, VoIP is disabled.\n");
+		Cvar_Set("cl_voip", "0");
+	}
+#endif
+
+
+	// cgame might not be initialized before menu is used
+	Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE );
+	// Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore.
+	Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM);
+
+	//
+	// register our commands
+	//
+	Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
+	Cmd_AddCommand ("configstrings", CL_Configstrings_f);
+	Cmd_AddCommand ("clientinfo", CL_Clientinfo_f);
+	Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f);
+	Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f);
+	Cmd_AddCommand ("disconnect", CL_Disconnect_f);
+	Cmd_AddCommand ("record", CL_Record_f);
+	Cmd_AddCommand ("demo", CL_PlayDemo_f);
+	Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName );
+	Cmd_AddCommand ("cinematic", CL_PlayCinematic_f);
+	Cmd_AddCommand ("stoprecord", CL_StopRecord_f);
+	Cmd_AddCommand ("connect", CL_Connect_f);
+	Cmd_AddCommand ("reconnect", CL_Reconnect_f);
+	Cmd_AddCommand ("localservers", CL_LocalServers_f);
+	Cmd_AddCommand ("globalservers", CL_GlobalServers_f);
+	Cmd_AddCommand ("rcon", CL_Rcon_f);
+	Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon );
+	Cmd_AddCommand ("ping", CL_Ping_f );
+	Cmd_AddCommand ("serverstatus", CL_ServerStatus_f );
+	Cmd_AddCommand ("showip", CL_ShowIP_f );
+	Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f );
+	Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f );
+	Cmd_AddCommand ("model", CL_SetModel_f );
+	Cmd_AddCommand ("video", CL_Video_f );
+	Cmd_AddCommand ("stopvideo", CL_StopVideo_f );
+	Cmd_AddCommand("minimize", GLimp_Minimize);
+	CL_InitRef();
+
+	SCR_Init ();
+
+//	Cbuf_Execute ();
+
+	Cvar_Set( "cl_running", "1" );
+
+	CL_GenerateQKey();
+	Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM );
+	CL_UpdateGUID( NULL, 0 );
+
+	Com_Printf( "----- Client Initialization Complete -----\n" );
+}
+
+
+/*
+===============
+CL_Shutdown
+
+===============
+*/
+void CL_Shutdown( char *finalmsg ) {
+	static qboolean recursive = qfalse;
+	
+	// check whether the client is running at all.
+	if(!(com_cl_running && com_cl_running->integer))
+		return;
+	
+	Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg );
+
+	if ( recursive ) {
+		Com_Printf( "WARNING: Recursive shutdown\n" );
+		return;
+	}
+	recursive = qtrue;
+
+	CL_Disconnect( qtrue );
+
+	S_Shutdown();
+	CL_ShutdownRef();
+	
+	CL_ShutdownUI();
+
+	Cmd_RemoveCommand ("cmd");
+	Cmd_RemoveCommand ("configstrings");
+	Cmd_RemoveCommand ("userinfo");
+	Cmd_RemoveCommand ("snd_restart");
+	Cmd_RemoveCommand ("vid_restart");
+	Cmd_RemoveCommand ("disconnect");
+	Cmd_RemoveCommand ("record");
+	Cmd_RemoveCommand ("demo");
+	Cmd_RemoveCommand ("cinematic");
+	Cmd_RemoveCommand ("stoprecord");
+	Cmd_RemoveCommand ("connect");
+	Cmd_RemoveCommand ("localservers");
+	Cmd_RemoveCommand ("globalservers");
+	Cmd_RemoveCommand ("rcon");
+	Cmd_RemoveCommand ("ping");
+	Cmd_RemoveCommand ("serverstatus");
+	Cmd_RemoveCommand ("showip");
+	Cmd_RemoveCommand ("model");
+	Cmd_RemoveCommand ("video");
+	Cmd_RemoveCommand ("stopvideo");
+
+	Cvar_Set( "cl_running", "0" );
+
+	recursive = qfalse;
+
+	Com_Memset( &cls, 0, sizeof( cls ) );
+	Key_SetCatcher( 0 );
+
+	Com_Printf( "-----------------------\n" );
+
+}
+
+static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) {
+	if (server) {
+		if (info) {
+			const char *game;
+
+			server->clients = atoi(Info_ValueForKey(info, "clients"));
+			Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH );
+			Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
+			server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
+			game = Info_ValueForKey(info, "game");
+			Q_strncpyz(server->game, (game[0]) ? game : BASEGAME, MAX_NAME_LENGTH);
+			server->gameType = atoi(Info_ValueForKey(info, "gametype"));
+			server->netType = atoi(Info_ValueForKey(info, "nettype"));
+			server->minPing = atoi(Info_ValueForKey(info, "minping"));
+			server->maxPing = atoi(Info_ValueForKey(info, "maxping"));
+		}
+		server->ping = ping;
+	}
+}
+
+static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) {
+	int i;
+
+	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
+		if (NET_CompareAdr(from, cls.localServers[i].adr)) {
+			CL_SetServerInfo(&cls.localServers[i], info, ping);
+		}
+	}
+
+	for (i = 0; i < MAX_GLOBAL_SERVERS; i++) {
+		if (NET_CompareAdr(from, cls.globalServers[i].adr)) {
+			CL_SetServerInfo(&cls.globalServers[i], info, ping);
+		}
+	}
+
+	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
+		if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) {
+			CL_SetServerInfo(&cls.favoriteServers[i], info, ping);
+		}
+	}
+
+}
+
+/*
+===================
+CL_ServerInfoPacket
+===================
+*/
+void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
+	int		i, type;
+	char	info[MAX_INFO_STRING];
+	char	*infoString;
+	int		prot;
+
+	infoString = MSG_ReadString( msg );
+
+	// if this isn't the correct protocol version, ignore it
+	prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
+	if ( prot != PROTOCOL_VERSION ) {
+		Com_DPrintf( "Different protocol info packet: %s\n", infoString );
+		return;
+	}
+
+	// iterate servers waiting for ping response
+	for (i=0; i<MAX_PINGREQUESTS; i++)
+	{
+		if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) )
+		{
+			// calc ping time
+			cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start;
+			Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) );
+
+			// save of info
+			Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) );
+
+			// tack on the net type
+			// NOTE: make sure these types are in sync with the netnames strings in the UI
+			switch (from.type)
+			{
+				case NA_BROADCAST:
+				case NA_IP:
+					type = 1;
+					break;
+				case NA_IP6:
+					type = 2;
+					break;
+				default:
+					type = 0;
+					break;
+			}
+			Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type) );
+			CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time);
+
+			return;
+		}
+	}
+
+	// if not just sent a local broadcast or pinging local servers
+	if (cls.pingUpdateSource != AS_LOCAL) {
+		return;
+	}
+
+	for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) {
+		// empty slot
+		if ( cls.localServers[i].adr.port == 0 ) {
+			break;
+		}
+
+		// avoid duplicate
+		if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) {
+			return;
+		}
+	}
+
+	if ( i == MAX_OTHER_SERVERS ) {
+		Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" );
+		return;
+	}
+
+	// add this to the list
+	cls.numlocalservers = i+1;
+	cls.localServers[i].adr = from;
+	cls.localServers[i].clients = 0;
+	cls.localServers[i].hostName[0] = '\0';
+	cls.localServers[i].mapName[0] = '\0';
+	cls.localServers[i].maxClients = 0;
+	cls.localServers[i].maxPing = 0;
+	cls.localServers[i].minPing = 0;
+	cls.localServers[i].ping = -1;
+	cls.localServers[i].game[0] = '\0';
+	cls.localServers[i].gameType = 0;
+	cls.localServers[i].netType = from.type;
+									 
+	Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
+	if (strlen(info)) {
+		if (info[strlen(info)-1] != '\n') {
+			strncat(info, "\n", sizeof(info) - 1);
+		}
+		Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info );
+	}
+}
+
+/*
+===================
+CL_GetServerStatus
+===================
+*/
+serverStatus_t *CL_GetServerStatus( netadr_t from ) {
+	serverStatus_t *serverStatus;
+	int i, oldest, oldestTime;
+
+	serverStatus = NULL;
+	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+		if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
+			return &cl_serverStatusList[i];
+		}
+	}
+	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+		if ( cl_serverStatusList[i].retrieved ) {
+			return &cl_serverStatusList[i];
+		}
+	}
+	oldest = -1;
+	oldestTime = 0;
+	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+		if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) {
+			oldest = i;
+			oldestTime = cl_serverStatusList[i].startTime;
+		}
+	}
+	if (oldest != -1) {
+		return &cl_serverStatusList[oldest];
+	}
+	serverStatusCount++;
+	return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)];
+}
+
+/*
+===================
+CL_ServerStatus
+===================
+*/
+int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) {
+	int i;
+	netadr_t	to;
+	serverStatus_t *serverStatus;
+
+	// if no server address then reset all server status requests
+	if ( !serverAddress ) {
+		for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+			cl_serverStatusList[i].address.port = 0;
+			cl_serverStatusList[i].retrieved = qtrue;
+		}
+		return qfalse;
+	}
+	// get the address
+	if ( !NET_StringToAdr( serverAddress, &to, NA_UNSPEC) ) {
+		return qfalse;
+	}
+	serverStatus = CL_GetServerStatus( to );
+	// if no server status string then reset the server status request for this address
+	if ( !serverStatusString ) {
+		serverStatus->retrieved = qtrue;
+		return qfalse;
+	}
+
+	// if this server status request has the same address
+	if ( NET_CompareAdr( to, serverStatus->address) ) {
+		// if we recieved an response for this server status request
+		if (!serverStatus->pending) {
+			Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
+			serverStatus->retrieved = qtrue;
+			serverStatus->startTime = 0;
+			return qtrue;
+		}
+		// resend the request regularly
+		else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
+			serverStatus->print = qfalse;
+			serverStatus->pending = qtrue;
+			serverStatus->retrieved = qfalse;
+			serverStatus->time = 0;
+			serverStatus->startTime = Com_Milliseconds();
+			NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
+			return qfalse;
+		}
+	}
+	// if retrieved
+	else if ( serverStatus->retrieved ) {
+		serverStatus->address = to;
+		serverStatus->print = qfalse;
+		serverStatus->pending = qtrue;
+		serverStatus->retrieved = qfalse;
+		serverStatus->startTime = Com_Milliseconds();
+		serverStatus->time = 0;
+		NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
+		return qfalse;
+	}
+	return qfalse;
+}
+
+/*
+===================
+CL_ServerStatusResponse
+===================
+*/
+void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) {
+	char	*s;
+	char	info[MAX_INFO_STRING];
+	int		i, l, score, ping;
+	int		len;
+	serverStatus_t *serverStatus;
+
+	serverStatus = NULL;
+	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+		if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
+			serverStatus = &cl_serverStatusList[i];
+			break;
+		}
+	}
+	// if we didn't request this server status
+	if (!serverStatus) {
+		return;
+	}
+
+	s = MSG_ReadStringLine( msg );
+
+	len = 0;
+	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s);
+
+	if (serverStatus->print) {
+		Com_Printf("Server settings:\n");
+		// print cvars
+		while (*s) {
+			for (i = 0; i < 2 && *s; i++) {
+				if (*s == '\\')
+					s++;
+				l = 0;
+				while (*s) {
+					info[l++] = *s;
+					if (l >= MAX_INFO_STRING-1)
+						break;
+					s++;
+					if (*s == '\\') {
+						break;
+					}
+				}
+				info[l] = '\0';
+				if (i) {
+					Com_Printf("%s\n", info);
+				}
+				else {
+					Com_Printf("%-24s", info);
+				}
+			}
+		}
+	}
+
+	len = strlen(serverStatus->string);
+	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
+
+	if (serverStatus->print) {
+		Com_Printf("\nPlayers:\n");
+		Com_Printf("num: score: ping: name:\n");
+	}
+	for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) {
+
+		len = strlen(serverStatus->string);
+		Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s);
+
+		if (serverStatus->print) {
+			score = ping = 0;
+			sscanf(s, "%d %d", &score, &ping);
+			s = strchr(s, ' ');
+			if (s)
+				s = strchr(s+1, ' ');
+			if (s)
+				s++;
+			else
+				s = "unknown";
+			Com_Printf("%-2d   %-3d    %-3d   %s\n", i, score, ping, s );
+		}
+	}
+	len = strlen(serverStatus->string);
+	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
+
+	serverStatus->time = Com_Milliseconds();
+	serverStatus->address = from;
+	serverStatus->pending = qfalse;
+	if (serverStatus->print) {
+		serverStatus->retrieved = qtrue;
+	}
+}
+
+/*
+==================
+CL_LocalServers_f
+==================
+*/
+void CL_LocalServers_f( void ) {
+	char		*message;
+	int			i, j;
+	netadr_t	to;
+
+	Com_Printf( "Scanning for servers on the local network...\n");
+
+	// reset the list, waiting for response
+	cls.numlocalservers = 0;
+	cls.pingUpdateSource = AS_LOCAL;
+
+	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
+		qboolean b = cls.localServers[i].visible;
+		Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i]));
+		cls.localServers[i].visible = b;
+	}
+	Com_Memset( &to, 0, sizeof( to ) );
+
+	// The 'xxx' in the message is a challenge that will be echoed back
+	// by the server.  We don't care about that here, but master servers
+	// can use that to prevent spoofed server responses from invalid ip
+	message = "\377\377\377\377getinfo xxx";
+
+	// send each message twice in case one is dropped
+	for ( i = 0 ; i < 2 ; i++ ) {
+		// send a broadcast packet on each server port
+		// we support multiple server ports so a single machine
+		// can nicely run multiple servers
+		for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) {
+			to.port = BigShort( (short)(PORT_SERVER + j) );
+
+			to.type = NA_BROADCAST;
+			NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
+			to.type = NA_MULTICAST6;
+			NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
+		}
+	}
+}
+
+/*
+==================
+CL_GlobalServers_f
+==================
+*/
+void CL_GlobalServers_f( void ) {
+	netadr_t	to;
+	int			count, i, masterNum;
+	char		command[1024], *masteraddress;
+	
+	if ((count = Cmd_Argc()) < 2 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS - 1)
+	{
+		Com_Printf("usage: globalservers <master# 0-%d> [keywords]\n", MAX_MASTER_SERVERS - 1);
+		return;	
+	}
+
+	sprintf(command, "sv_master%d", masterNum + 1);
+	masteraddress = Cvar_VariableString(command);
+	
+	if(!*masteraddress)
+	{
+		Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n");
+		return;	
+	}
+
+	// reset the list, waiting for response
+	// -1 is used to distinguish a "no response"
+
+	i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC);
+	
+	if(!i)
+	{
+		Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress);
+		return;	
+	}
+	else if(i == 2)
+		to.port = BigShort(PORT_MASTER);
+
+	Com_Printf("Requesting servers from master %s...\n", masteraddress);
+
+	cls.numglobalservers = -1;
+	cls.pingUpdateSource = AS_GLOBAL;
+
+	Com_sprintf( command, sizeof( command ), "getserversExt %s %d",
+                     cl_gamename->string, PROTOCOL_VERSION );
+	// TODO: test if we only have IPv4/IPv6, if so request only the relevant
+	// servers with getserversExt %s %d ipvX
+	// not that big a deal since the extra servers won't respond to getinfo
+	// anyway.
+
+	for (i=3; i < count; i++)
+	{
+		Q_strcat(command, sizeof(command), " ");
+		Q_strcat(command, sizeof(command), Cmd_Argv(i));
+	}
+
+	NET_OutOfBandPrint( NS_SERVER, to, "%s", command );
+	CL_RequestMotd();
+}
+
+
+/*
+==================
+CL_GetPing
+==================
+*/
+void CL_GetPing( int n, char *buf, int buflen, int *pingtime )
+{
+	const char	*str;
+	int		time;
+	int		maxPing;
+
+	if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port)
+	{
+		// empty or invalid slot
+		buf[0]    = '\0';
+		*pingtime = 0;
+		return;
+	}
+
+	str = NET_AdrToStringwPort( cl_pinglist[n].adr );
+	Q_strncpyz( buf, str, buflen );
+
+	time = cl_pinglist[n].time;
+	if (!time)
+	{
+		// check for timeout
+		time = Sys_Milliseconds() - cl_pinglist[n].start;
+		maxPing = Cvar_VariableIntegerValue( "cl_maxPing" );
+		if( maxPing < 100 ) {
+			maxPing = 100;
+		}
+		if (time < maxPing)
+		{
+			// not timed out yet
+			time = 0;
+		}
+	}
+
+	CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time);
+
+	*pingtime = time;
+}
+
+/*
+==================
+CL_GetPingInfo
+==================
+*/
+void CL_GetPingInfo( int n, char *buf, int buflen )
+{
+	if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port)
+	{
+		// empty or invalid slot
+		if (buflen)
+			buf[0] = '\0';
+		return;
+	}
+
+	Q_strncpyz( buf, cl_pinglist[n].info, buflen );
+}
+
+/*
+==================
+CL_ClearPing
+==================
+*/
+void CL_ClearPing( int n )
+{
+	if (n < 0 || n >= MAX_PINGREQUESTS)
+		return;
+
+	cl_pinglist[n].adr.port = 0;
+}
+
+/*
+==================
+CL_GetPingQueueCount
+==================
+*/
+int CL_GetPingQueueCount( void )
+{
+	int		i;
+	int		count;
+	ping_t*	pingptr;
+
+	count   = 0;
+	pingptr = cl_pinglist;
+
+	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) {
+		if (pingptr->adr.port) {
+			count++;
+		}
+	}
+
+	return (count);
+}
+
+/*
+==================
+CL_GetFreePing
+==================
+*/
+ping_t* CL_GetFreePing( void )
+{
+	ping_t*	pingptr;
+	ping_t*	best;	
+	int		oldest;
+	int		i;
+	int		time;
+
+	pingptr = cl_pinglist;
+	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
+	{
+		// find free ping slot
+		if (pingptr->adr.port)
+		{
+			if (!pingptr->time)
+			{
+				if (Sys_Milliseconds() - pingptr->start < 500)
+				{
+					// still waiting for response
+					continue;
+				}
+			}
+			else if (pingptr->time < 500)
+			{
+				// results have not been queried
+				continue;
+			}
+		}
+
+		// clear it
+		pingptr->adr.port = 0;
+		return (pingptr);
+	}
+
+	// use oldest entry
+	pingptr = cl_pinglist;
+	best    = cl_pinglist;
+	oldest  = INT_MIN;
+	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
+	{
+		// scan for oldest
+		time = Sys_Milliseconds() - pingptr->start;
+		if (time > oldest)
+		{
+			oldest = time;
+			best   = pingptr;
+		}
+	}
+
+	return (best);
+}
+
+/*
+==================
+CL_Ping_f
+==================
+*/
+void CL_Ping_f( void ) {
+	netadr_t	to;
+	ping_t*		pingptr;
+	char*		server;
+	int			argc;
+	netadrtype_t	family = NA_UNSPEC;
+
+	argc = Cmd_Argc();
+
+	if ( argc != 2 && argc != 3 ) {
+		Com_Printf( "usage: ping [-4|-6] server\n");
+		return;	
+	}
+	
+	if(argc == 2)
+		server = Cmd_Argv(1);
+	else
+	{
+		if(!strcmp(Cmd_Argv(1), "-4"))
+			family = NA_IP;
+		else if(!strcmp(Cmd_Argv(1), "-6"))
+			family = NA_IP6;
+		else
+			Com_Printf( "warning: only -4 or -6 as address type understood.\n");
+		
+		server = Cmd_Argv(2);
+	}
+
+	Com_Memset( &to, 0, sizeof(netadr_t) );
+
+	if ( !NET_StringToAdr( server, &to, family ) ) {
+		return;
+	}
+
+	pingptr = CL_GetFreePing();
+
+	memcpy( &pingptr->adr, &to, sizeof (netadr_t) );
+	pingptr->start = Sys_Milliseconds();
+	pingptr->time  = 0;
+
+	CL_SetServerInfoByAddress(pingptr->adr, NULL, 0);
+		
+	NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
+}
+
+/*
+==================
+CL_UpdateVisiblePings_f
+==================
+*/
+qboolean CL_UpdateVisiblePings_f(int source) {
+	int			slots, i;
+	char		buff[MAX_STRING_CHARS];
+	int			pingTime;
+	int			max;
+	qboolean status = qfalse;
+
+	if (source < 0 || source > AS_FAVORITES) {
+		return qfalse;
+	}
+
+	cls.pingUpdateSource = source;
+
+	slots = CL_GetPingQueueCount();
+	if (slots < MAX_PINGREQUESTS) {
+		serverInfo_t *server = NULL;
+
+		max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS;
+		switch (source) {
+			case AS_LOCAL :
+				server = &cls.localServers[0];
+				max = cls.numlocalservers;
+			break;
+			case AS_GLOBAL :
+				server = &cls.globalServers[0];
+				max = cls.numglobalservers;
+			break;
+			case AS_FAVORITES :
+				server = &cls.favoriteServers[0];
+				max = cls.numfavoriteservers;
+			break;
+			default:
+				return qfalse;
+		}
+		for (i = 0; i < max; i++) {
+			if (server[i].visible) {
+				if (server[i].ping == -1) {
+					int j;
+
+					if (slots >= MAX_PINGREQUESTS) {
+						break;
+					}
+					for (j = 0; j < MAX_PINGREQUESTS; j++) {
+						if (!cl_pinglist[j].adr.port) {
+							continue;
+						}
+						if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) {
+							// already on the list
+							break;
+						}
+					}
+					if (j >= MAX_PINGREQUESTS) {
+						status = qtrue;
+						for (j = 0; j < MAX_PINGREQUESTS; j++) {
+							if (!cl_pinglist[j].adr.port) {
+								break;
+							}
+						}
+						memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t));
+						cl_pinglist[j].start = Sys_Milliseconds();
+						cl_pinglist[j].time = 0;
+						NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" );
+						slots++;
+					}
+				}
+				// if the server has a ping higher than cl_maxPing or
+				// the ping packet got lost
+				else if (server[i].ping == 0) {
+					// if we are updating global servers
+					if (source == AS_GLOBAL) {
+						//
+						if ( cls.numGlobalServerAddresses > 0 ) {
+							// overwrite this server with one from the additional global servers
+							cls.numGlobalServerAddresses--;
+							CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]);
+							// NOTE: the server[i].visible flag stays untouched
+						}
+					}
+				}
+			}
+		}
+	} 
+
+	if (slots) {
+		status = qtrue;
+	}
+	for (i = 0; i < MAX_PINGREQUESTS; i++) {
+		if (!cl_pinglist[i].adr.port) {
+			continue;
+		}
+		CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime );
+		if (pingTime != 0) {
+			CL_ClearPing(i);
+			status = qtrue;
+		}
+	}
+
+	return status;
+}
+
+/*
+==================
+CL_ServerStatus_f
+==================
+*/
+void CL_ServerStatus_f(void) {
+	netadr_t	to, *toptr = NULL;
+	char		*server;
+	serverStatus_t *serverStatus;
+	int			argc;
+	netadrtype_t	family = NA_UNSPEC;
+
+	argc = Cmd_Argc();
+
+	if ( argc != 2 && argc != 3 )
+	{
+		if (cls.state != CA_ACTIVE || clc.demoplaying)
+		{
+			Com_Printf ("Not connected to a server.\n");
+			Com_Printf( "usage: serverstatus [-4|-6] server\n");
+			return;
+		}
+
+		toptr = &clc.serverAddress;
+	}
+	
+	if(!toptr)
+	{
+		Com_Memset( &to, 0, sizeof(netadr_t) );
+	
+		if(argc == 2)
+			server = Cmd_Argv(1);
+		else
+		{
+			if(!strcmp(Cmd_Argv(1), "-4"))
+				family = NA_IP;
+			else if(!strcmp(Cmd_Argv(1), "-6"))
+				family = NA_IP6;
+			else
+				Com_Printf( "warning: only -4 or -6 as address type understood.\n");
+		
+			server = Cmd_Argv(2);
+		}
+
+		toptr = &to;
+		if ( !NET_StringToAdr( server, toptr, family ) )
+			return;
+	}
+
+	NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" );
+
+	serverStatus = CL_GetServerStatus( *toptr );
+	serverStatus->address = *toptr;
+	serverStatus->print = qtrue;
+	serverStatus->pending = qtrue;
+}
+
+/*
+==================
+CL_ShowIP_f
+==================
+*/
+void CL_ShowIP_f(void) {
+	Sys_ShowIP();
+}
diff --git a/src/client/cl_net_chan.c b/src/client/cl_net_chan.c
new file mode 100644
index 0000000..561f322
--- /dev/null
+++ b/src/client/cl_net_chan.c
@@ -0,0 +1,168 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "client.h"
+
+/*
+==============
+CL_Netchan_Encode
+
+	// first 12 bytes of the data are always:
+	long serverId;
+	long messageAcknowledge;
+	long reliableAcknowledge;
+
+==============
+*/
+static void CL_Netchan_Encode( msg_t *msg ) {
+	int serverId, messageAcknowledge, reliableAcknowledge;
+	int i, index, srdc, sbit, soob;
+	byte key, *string;
+
+	if ( msg->cursize <= CL_ENCODE_START ) {
+		return;
+	}
+
+        srdc = msg->readcount;
+        sbit = msg->bit;
+        soob = msg->oob;
+        
+        msg->bit = 0;
+        msg->readcount = 0;
+        msg->oob = 0;
+        
+        serverId = MSG_ReadLong(msg);
+	messageAcknowledge = MSG_ReadLong(msg);
+	reliableAcknowledge = MSG_ReadLong(msg);
+
+        msg->oob = soob;
+        msg->bit = sbit;
+        msg->readcount = srdc;
+        
+	string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
+	index = 0;
+	//
+	key = clc.challenge ^ serverId ^ messageAcknowledge;
+	for (i = CL_ENCODE_START; i < msg->cursize; i++) {
+		// modify the key with the last received now acknowledged server command
+		if (!string[index])
+			index = 0;
+		if (string[index] > 127) {
+			key ^= '.' << (i & 1);
+		}
+		else {
+			key ^= string[index] << (i & 1);
+		}
+		index++;
+		// encode the data with this key
+		*(msg->data + i) = (*(msg->data + i)) ^ key;
+	}
+}
+
+/*
+==============
+CL_Netchan_Decode
+
+	// first four bytes of the data are always:
+	long reliableAcknowledge;
+
+==============
+*/
+static void CL_Netchan_Decode( msg_t *msg ) {
+	long reliableAcknowledge, i, index;
+	byte key, *string;
+        int	srdc, sbit, soob;
+
+        srdc = msg->readcount;
+        sbit = msg->bit;
+        soob = msg->oob;
+        
+        msg->oob = 0;
+        
+	reliableAcknowledge = MSG_ReadLong(msg);
+
+        msg->oob = soob;
+        msg->bit = sbit;
+        msg->readcount = srdc;
+
+	string = (byte *) clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ];
+	index = 0;
+	// xor the client challenge with the netchan sequence number (need something that changes every message)
+	key = clc.challenge ^ LittleLong( *(unsigned *)msg->data );
+	for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) {
+		// modify the key with the last sent and with this message acknowledged client command
+		if (!string[index])
+			index = 0;
+		if (string[index] > 127) {
+			key ^= '.' << (i & 1);
+		}
+		else {
+			key ^= string[index] << (i & 1);
+		}
+		index++;
+		// decode the data with this key
+		*(msg->data + i) = *(msg->data + i) ^ key;
+	}
+}
+
+/*
+=================
+CL_Netchan_TransmitNextFragment
+=================
+*/
+void CL_Netchan_TransmitNextFragment( netchan_t *chan ) {
+	Netchan_TransmitNextFragment( chan );
+}
+
+/*
+===============
+CL_Netchan_Transmit
+================
+*/
+void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) {
+	MSG_WriteByte( msg, clc_EOF );
+
+	CL_Netchan_Encode( msg );
+	Netchan_Transmit( chan, msg->cursize, msg->data );
+}
+
+extern 	int oldsize;
+int newsize = 0;
+
+/*
+=================
+CL_Netchan_Process
+=================
+*/
+qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) {
+	int ret;
+
+	ret = Netchan_Process( chan, msg );
+	if (!ret)
+		return qfalse;
+	CL_Netchan_Decode( msg );
+	newsize += msg->cursize;
+	return qtrue;
+}
diff --git a/src/client/cl_parse.c b/src/client/cl_parse.c
new file mode 100644
index 0000000..42465f1
--- /dev/null
+++ b/src/client/cl_parse.c
@@ -0,0 +1,910 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// cl_parse.c  -- parse a message received from the server
+
+#include "client.h"
+
+char *svc_strings[256] = {
+	"svc_bad",
+
+	"svc_nop",
+	"svc_gamestate",
+	"svc_configstring",
+	"svc_baseline",	
+	"svc_serverCommand",
+	"svc_download",
+	"svc_snapshot",
+	"svc_EOF",
+	"svc_extension",
+	"svc_voip",
+};
+
+void SHOWNET( msg_t *msg, char *s) {
+	if ( cl_shownet->integer >= 2) {
+		Com_Printf ("%3i:%s\n", msg->readcount-1, s);
+	}
+}
+
+
+/*
+=========================================================================
+
+MESSAGE PARSING
+
+=========================================================================
+*/
+
+/*
+==================
+CL_DeltaEntity
+
+Parses deltas from the given base and adds the resulting entity
+to the current frame
+==================
+*/
+void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, 
+					 qboolean unchanged) {
+	entityState_t	*state;
+
+	// save the parsed entity state into the big circular buffer so
+	// it can be used as the source for a later delta
+	state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)];
+
+	if ( unchanged ) {
+		*state = *old;
+	} else {
+		MSG_ReadDeltaEntity( msg, old, state, newnum );
+	}
+
+	if ( state->number == (MAX_GENTITIES-1) ) {
+		return;		// entity was delta removed
+	}
+	cl.parseEntitiesNum++;
+	frame->numEntities++;
+}
+
+/*
+==================
+CL_ParsePacketEntities
+
+==================
+*/
+void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) {
+	int			newnum;
+	entityState_t	*oldstate;
+	int			oldindex, oldnum;
+
+	newframe->parseEntitiesNum = cl.parseEntitiesNum;
+	newframe->numEntities = 0;
+
+	// delta from the entities present in oldframe
+	oldindex = 0;
+	oldstate = NULL;
+	if (!oldframe) {
+		oldnum = 99999;
+	} else {
+		if ( oldindex >= oldframe->numEntities ) {
+			oldnum = 99999;
+		} else {
+			oldstate = &cl.parseEntities[
+				(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+			oldnum = oldstate->number;
+		}
+	}
+
+	while ( 1 ) {
+		// read the entity index number
+		newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
+
+		if ( newnum == (MAX_GENTITIES-1) ) {
+			break;
+		}
+
+		if ( msg->readcount > msg->cursize ) {
+			Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");
+		}
+
+		while ( oldnum < newnum ) {
+			// one or more entities from the old packet are unchanged
+			if ( cl_shownet->integer == 3 ) {
+				Com_Printf ("%3i:  unchanged: %i\n", msg->readcount, oldnum);
+			}
+			CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
+			
+			oldindex++;
+
+			if ( oldindex >= oldframe->numEntities ) {
+				oldnum = 99999;
+			} else {
+				oldstate = &cl.parseEntities[
+					(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+				oldnum = oldstate->number;
+			}
+		}
+		if (oldnum == newnum) {
+			// delta from previous state
+			if ( cl_shownet->integer == 3 ) {
+				Com_Printf ("%3i:  delta: %i\n", msg->readcount, newnum);
+			}
+			CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse );
+
+			oldindex++;
+
+			if ( oldindex >= oldframe->numEntities ) {
+				oldnum = 99999;
+			} else {
+				oldstate = &cl.parseEntities[
+					(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+				oldnum = oldstate->number;
+			}
+			continue;
+		}
+
+		if ( oldnum > newnum ) {
+			// delta from baseline
+			if ( cl_shownet->integer == 3 ) {
+				Com_Printf ("%3i:  baseline: %i\n", msg->readcount, newnum);
+			}
+			CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse );
+			continue;
+		}
+
+	}
+
+	// any remaining entities in the old frame are copied over
+	while ( oldnum != 99999 ) {
+		// one or more entities from the old packet are unchanged
+		if ( cl_shownet->integer == 3 ) {
+			Com_Printf ("%3i:  unchanged: %i\n", msg->readcount, oldnum);
+		}
+		CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
+		
+		oldindex++;
+
+		if ( oldindex >= oldframe->numEntities ) {
+			oldnum = 99999;
+		} else {
+			oldstate = &cl.parseEntities[
+				(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
+			oldnum = oldstate->number;
+		}
+	}
+}
+
+
+/*
+================
+CL_ParseSnapshot
+
+If the snapshot is parsed properly, it will be copied to
+cl.snap and saved in cl.snapshots[].  If the snapshot is invalid
+for any reason, no changes to the state will be made at all.
+================
+*/
+void CL_ParseSnapshot( msg_t *msg ) {
+	int			len;
+	clSnapshot_t	*old;
+	clSnapshot_t	newSnap;
+	int			deltaNum;
+	int			oldMessageNum;
+	int			i, packetNum;
+
+	// get the reliable sequence acknowledge number
+	// NOTE: now sent with all server to client messages
+	//clc.reliableAcknowledge = MSG_ReadLong( msg );
+
+	// read in the new snapshot to a temporary buffer
+	// we will only copy to cl.snap if it is valid
+	Com_Memset (&newSnap, 0, sizeof(newSnap));
+
+	// we will have read any new server commands in this
+	// message before we got to svc_snapshot
+	newSnap.serverCommandNum = clc.serverCommandSequence;
+
+	newSnap.serverTime = MSG_ReadLong( msg );
+
+	// if we were just unpaused, we can only *now* really let the
+	// change come into effect or the client hangs.
+	cl_paused->modified = 0;
+
+	newSnap.messageNum = clc.serverMessageSequence;
+
+	deltaNum = MSG_ReadByte( msg );
+	if ( !deltaNum ) {
+		newSnap.deltaNum = -1;
+	} else {
+		newSnap.deltaNum = newSnap.messageNum - deltaNum;
+	}
+	newSnap.snapFlags = MSG_ReadByte( msg );
+
+	// If the frame is delta compressed from data that we
+	// no longer have available, we must suck up the rest of
+	// the frame, but not use it, then ask for a non-compressed
+	// message 
+	if ( newSnap.deltaNum <= 0 ) {
+		newSnap.valid = qtrue;		// uncompressed frame
+		old = NULL;
+		clc.demowaiting = qfalse;	// we can start recording now
+	} else {
+		old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
+		if ( !old->valid ) {
+			// should never happen
+			Com_Printf ("Delta from invalid frame (not supposed to happen!).\n");
+		} else if ( old->messageNum != newSnap.deltaNum ) {
+			// The frame that the server did the delta from
+			// is too old, so we can't reconstruct it properly.
+			Com_Printf ("Delta frame too old.\n");
+		} else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) {
+			Com_Printf ("Delta parseEntitiesNum too old.\n");
+		} else {
+			newSnap.valid = qtrue;	// valid delta parse
+		}
+	}
+
+	// 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
+	SHOWNET( msg, "playerstate" );
+	if ( old ) {
+		MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps );
+	} else {
+		MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps );
+	}
+
+	// read packet entities
+	SHOWNET( msg, "packet entities" );
+	CL_ParsePacketEntities( msg, old, &newSnap );
+
+	// if not valid, dump the entire thing now that it has
+	// been properly read
+	if ( !newSnap.valid ) {
+		return;
+	}
+
+	// clear the valid flags of any snapshots between the last
+	// received and this one, so if there was a dropped packet
+	// it won't look like something valid to delta from next
+	// time we wrap around in the buffer
+	oldMessageNum = cl.snap.messageNum + 1;
+
+	if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) {
+		oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 );
+	}
+	for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) {
+		cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
+	}
+
+	// copy to the current good spot
+	cl.snap = newSnap;
+	cl.snap.ping = 999;
+	// calculate ping time
+	for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
+		packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
+		if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
+			cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
+			break;
+		}
+	}
+	// save the frame off in the backup array for later delta comparisons
+	cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
+
+	if (cl_shownet->integer == 3) {
+		Com_Printf( "   snapshot:%i  delta:%i  ping:%i\n", cl.snap.messageNum,
+		cl.snap.deltaNum, cl.snap.ping );
+	}
+
+	cl.newSnapshots = qtrue;
+}
+
+
+//=====================================================================
+
+int cl_connectedToPureServer;
+int cl_connectedToCheatServer;
+
+#ifdef USE_VOIP
+int cl_connectedToVoipServer;
+#endif
+
+/*
+==================
+CL_SystemInfoChanged
+
+The systeminfo configstring has been changed, so parse
+new information out of it.  This will happen at every
+gamestate, and possibly during gameplay.
+==================
+*/
+void CL_SystemInfoChanged( void ) {
+	char			*systemInfo;
+	const char		*s, *t;
+	char			key[BIG_INFO_KEY];
+	char			value[BIG_INFO_VALUE];
+	qboolean		gameSet;
+
+	systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
+	// NOTE TTimo:
+	// when the serverId changes, any further messages we send to the server will use this new serverId
+	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
+	// in some cases, outdated cp commands might get sent with this news serverId
+	cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
+
+	// don't set any vars when playing a demo
+	if ( clc.demoplaying ) {
+		return;
+	}
+
+#ifdef USE_VOIP
+	// in the future, (val) will be a protocol version string, so only
+	//  accept explicitly 1, not generally non-zero.
+	s = Info_ValueForKey( systemInfo, "sv_voip" );
+	cl_connectedToVoipServer = (atoi( s ) == 1);
+#endif
+
+	s = Info_ValueForKey( systemInfo, "sv_cheats" );
+	cl_connectedToCheatServer = atoi( s );
+	if ( !cl_connectedToCheatServer ) {
+		Cvar_SetCheatState();
+	}
+
+	// check pure server string
+	s = Info_ValueForKey( systemInfo, "sv_paks" );
+	t = Info_ValueForKey( systemInfo, "sv_pakNames" );
+	FS_PureServerSetLoadedPaks( s, t );
+
+	s = Info_ValueForKey( systemInfo, "sv_referencedPaks" );
+	t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" );
+	FS_PureServerSetReferencedPaks( s, t );
+
+	gameSet = qfalse;
+	// scan through all the variables in the systeminfo and locally set cvars to match
+	s = systemInfo;
+	while ( s ) {
+		int cvar_flags;
+		
+		Info_NextPair( &s, key, value );
+		if ( !key[0] ) {
+			break;
+		}
+		
+		// ehw!
+		if (!Q_stricmp(key, "fs_game"))
+		{
+			if(FS_CheckDirTraversal(value))
+			{
+				Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value);
+				continue;
+			}
+				
+			gameSet = qtrue;
+		}
+
+		if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT)
+			Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM);
+		else
+		{
+			// If this cvar may not be modified by a server discard the value.
+			if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED)))
+			{
+				Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
+				continue;
+			}
+
+			Cvar_SetSafe(key, value);
+		}
+	}
+	// if game folder should not be set and it is set at the client side
+	if ( !gameSet && *Cvar_VariableString("fs_game") ) {
+		Cvar_Set( "fs_game", "" );
+	}
+	cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" );
+}
+
+/*
+==================
+CL_ParseServerInfo
+==================
+*/
+static void CL_ParseServerInfo(void)
+{
+	const char *serverInfo;
+
+	serverInfo = cl.gameState.stringData
+		+ cl.gameState.stringOffsets[ CS_SERVERINFO ];
+
+	clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo,
+		"sv_allowDownload"));
+	Q_strncpyz(clc.sv_dlURL,
+		Info_ValueForKey(serverInfo, "sv_dlURL"),
+		sizeof(clc.sv_dlURL));
+}
+
+/*
+==================
+CL_ParseGamestate
+==================
+*/
+void CL_ParseGamestate( msg_t *msg ) {
+	int				i;
+	entityState_t	*es;
+	int				newnum;
+	entityState_t	nullstate;
+	int				cmd;
+	char			*s;
+
+	Con_Close();
+
+	clc.connectPacketCount = 0;
+
+	// wipe local client state
+	CL_ClearState();
+
+	// a gamestate always marks a server command sequence
+	clc.serverCommandSequence = MSG_ReadLong( msg );
+
+	// parse all the configstrings and baselines
+	cl.gameState.dataCount = 1;	// leave a 0 at the beginning for uninitialized configstrings
+	while ( 1 ) {
+		cmd = MSG_ReadByte( msg );
+
+		if ( cmd == svc_EOF ) {
+			break;
+		}
+		
+		if ( cmd == svc_configstring ) {
+			int		len;
+
+			i = MSG_ReadShort( msg );
+			if ( i < 0 || i >= MAX_CONFIGSTRINGS ) {
+				Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
+			}
+			s = MSG_ReadBigString( msg );
+			len = strlen( s );
+
+			if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) {
+				Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" );
+			}
+
+			// append it to the gameState string buffer
+			cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
+			Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 );
+			cl.gameState.dataCount += len + 1;
+		} else if ( cmd == svc_baseline ) {
+			newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
+			if ( newnum < 0 || newnum >= MAX_GENTITIES ) {
+				Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum );
+			}
+			Com_Memset (&nullstate, 0, sizeof(nullstate));
+			es = &cl.entityBaselines[ newnum ];
+			MSG_ReadDeltaEntity( msg, &nullstate, es, newnum );
+		} else {
+			Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" );
+		}
+	}
+
+	clc.clientNum = MSG_ReadLong(msg);
+	// read the checksum feed
+	clc.checksumFeed = MSG_ReadLong( msg );
+
+	// parse useful values out of CS_SERVERINFO
+	CL_ParseServerInfo();
+
+	// parse serverId and other cvars
+	CL_SystemInfoChanged();
+
+	// stop recording now so the demo won't have an unnecessary level load at the end.
+	if(cl_autoRecordDemo->integer && clc.demorecording)
+		CL_StopRecord_f();
+	
+	// reinitialize the filesystem if the game directory has changed
+	FS_ConditionalRestart( clc.checksumFeed );
+
+	// This used to call CL_StartHunkUsers, but now we enter the download state before loading the
+	// cgame
+	CL_InitDownloads();
+
+	// make sure the game starts
+	Cvar_Set( "cl_paused", "0" );
+}
+
+
+//=====================================================================
+
+/*
+=====================
+CL_ParseDownload
+
+A download message has been received from the server
+=====================
+*/
+void CL_ParseDownload ( msg_t *msg ) {
+	int		size;
+	unsigned char data[MAX_MSGLEN];
+	int block;
+
+	if (!*clc.downloadTempName) {
+		Com_Printf("Server sending download, but no download was requested\n");
+		CL_AddReliableCommand("stopdl", qfalse);
+		return;
+	}
+
+	// read the data
+	block = MSG_ReadShort ( msg );
+
+	if ( !block )
+	{
+		// block zero is special, contains file size
+		clc.downloadSize = MSG_ReadLong ( msg );
+
+		Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
+
+		if (clc.downloadSize < 0)
+		{
+			Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) );
+			return;
+		}
+	}
+
+	size = MSG_ReadShort ( msg );
+	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);
+		return;
+	}
+
+	// open the file if not opened yet
+	if (!clc.download)
+	{
+		clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName );
+
+		if (!clc.download) {
+			Com_Printf( "Could not create %s\n", clc.downloadTempName );
+			CL_AddReliableCommand("stopdl", qfalse);
+			CL_NextDownload();
+			return;
+		}
+	}
+
+	if (size)
+		FS_Write( data, size, clc.download );
+
+	CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse);
+	clc.downloadBlock++;
+
+	clc.downloadCount += size;
+
+	// So UI gets access to it
+	Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
+
+	if (!size) { // A zero length block means EOF
+		if (clc.download) {
+			FS_FCloseFile( clc.download );
+			clc.download = 0;
+
+			// rename the file
+			FS_SV_Rename ( clc.downloadTempName, clc.downloadName );
+		}
+
+		// send intentions now
+		// We need this because without it, we would hold the last nextdl and then start
+		// loading right away.  If we take a while to load, the server is happily trying
+		// to send us that last block over and over.
+		// Write it twice to help make sure we acknowledge the download
+		CL_WritePacket();
+		CL_WritePacket();
+
+		// get another file if needed
+		CL_NextDownload ();
+	}
+}
+
+#ifdef USE_VOIP
+static
+qboolean CL_ShouldIgnoreVoipSender(int sender)
+{
+	if (!cl_voip->integer)
+		return qtrue;  // VoIP is disabled.
+	else if ((sender == clc.clientNum) && (!clc.demoplaying))
+		return qtrue;  // ignore own voice (unless playing back a demo).
+	else if (clc.voipMuteAll)
+		return qtrue;  // all channels are muted with extreme prejudice.
+	else if (clc.voipIgnore[sender])
+		return qtrue;  // just ignoring this guy.
+	else if (clc.voipGain[sender] == 0.0f)
+		return qtrue;  // too quiet to play.
+
+	return qfalse;
+}
+
+/*
+=====================
+CL_ParseVoip
+
+A VoIP message has been received from the server
+=====================
+*/
+static
+void CL_ParseVoip ( msg_t *msg ) {
+	static short decoded[4096];  // !!! FIXME: don't hardcode.
+
+	const int sender = MSG_ReadShort(msg);
+	const int generation = MSG_ReadByte(msg);
+	const int sequence = MSG_ReadLong(msg);
+	const int frames = MSG_ReadByte(msg);
+	const int packetsize = MSG_ReadShort(msg);
+	char encoded[1024];
+	int seqdiff = sequence - clc.voipIncomingSequence[sender];
+	int written = 0;
+	int i;
+
+	Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);
+
+	if (sender < 0)
+		return;   // short/invalid packet, bail.
+	else if (generation < 0)
+		return;   // short/invalid packet, bail.
+	else if (sequence < 0)
+		return;   // short/invalid packet, bail.
+	else if (frames < 0)
+		return;   // short/invalid packet, bail.
+	else if (packetsize < 0)
+		return;   // short/invalid packet, bail.
+
+	if (packetsize > sizeof (encoded)) {  // overlarge packet?
+		int bytesleft = packetsize;
+		while (bytesleft) {
+			int br = bytesleft;
+			if (br > sizeof (encoded))
+				br = sizeof (encoded);
+			MSG_ReadData(msg, encoded, br);
+			bytesleft -= br;
+		}
+		return;   // overlarge packet, bail.
+	}
+
+	if (!clc.speexInitialized) {
+		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
+		return;   // can't handle VoIP without libspeex!
+	} else if (sender >= MAX_CLIENTS) {
+		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
+		return;   // bogus sender.
+	} else if (CL_ShouldIgnoreVoipSender(sender)) {
+		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
+		return;   // Channel is muted, bail.
+	}
+
+	// !!! FIXME: make sure data is narrowband? Does decoder handle this?
+
+	Com_DPrintf("VoIP: packet accepted!\n");
+
+	// This is a new "generation" ... a new recording started, reset the bits.
+	if (generation != clc.voipIncomingGeneration[sender]) {
+		Com_DPrintf("VoIP: new generation %d!\n", generation);
+		speex_bits_reset(&clc.speexDecoderBits[sender]);
+		clc.voipIncomingGeneration[sender] = generation;
+		seqdiff = 0;
+	} else if (seqdiff < 0) {   // we're ahead of the sequence?!
+		// This shouldn't happen unless the packet is corrupted or something.
+		Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
+		            sequence, clc.voipIncomingSequence[sender]);
+		// reset the bits just in case.
+		speex_bits_reset(&clc.speexDecoderBits[sender]);
+		seqdiff = 0;
+	} else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
+		// just start over.
+		Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
+		            seqdiff, sender);
+		speex_bits_reset(&clc.speexDecoderBits[sender]);
+		seqdiff = 0;
+	}
+
+	if (seqdiff != 0) {
+		Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
+		            seqdiff, sender);
+		// tell speex that we're missing frames...
+		for (i = 0; i < seqdiff; i++) {
+			assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
+			speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
+			written += clc.speexFrameSize;
+		}
+	}
+
+	for (i = 0; i < frames; i++) {
+		char encoded[256];
+		const int len = MSG_ReadByte(msg);
+		if (len < 0) {
+			Com_DPrintf("VoIP: Short packet!\n");
+			break;
+		}
+		MSG_ReadData(msg, encoded, len);
+
+		// shouldn't happen, but just in case...
+		if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
+			Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+			            written * 2, written, i);
+			S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
+			             (const byte *) decoded, clc.voipGain[sender]);
+			written = 0;
+		}
+
+		speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
+		speex_decode_int(clc.speexDecoder[sender],
+		                 &clc.speexDecoderBits[sender], decoded + written);
+
+		#if 0
+		static FILE *encio = NULL;
+		if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
+		if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
+		static FILE *decio = NULL;
+		if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
+		if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
+		#endif
+
+		written += clc.speexFrameSize;
+	}
+
+	Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+	            written * 2, written, i);
+
+	if (written > 0) {
+		S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
+		             (const byte *) decoded, clc.voipGain[sender]);
+	}
+
+	clc.voipIncomingSequence[sender] = sequence + frames;
+}
+#endif
+
+
+/*
+=====================
+CL_ParseCommandString
+
+Command strings are just saved off until cgame asks for them
+when it transitions a snapshot
+=====================
+*/
+void CL_ParseCommandString( msg_t *msg ) {
+	char	*s;
+	int		seq;
+	int		index;
+
+	seq = MSG_ReadLong( msg );
+	s = MSG_ReadString( msg );
+
+	// see if we have already executed stored it off
+	if ( clc.serverCommandSequence >= seq ) {
+		return;
+	}
+	clc.serverCommandSequence = seq;
+
+	index = seq & (MAX_RELIABLE_COMMANDS-1);
+	Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) );
+}
+
+
+/*
+=====================
+CL_ParseServerMessage
+=====================
+*/
+void CL_ParseServerMessage( msg_t *msg ) {
+	int			cmd;
+
+	if ( cl_shownet->integer == 1 ) {
+		Com_Printf ("%i ",msg->cursize);
+	} else if ( cl_shownet->integer >= 2 ) {
+		Com_Printf ("------------------\n");
+	}
+
+	MSG_Bitstream(msg);
+
+	// get the reliable sequence acknowledge number
+	clc.reliableAcknowledge = MSG_ReadLong( msg );
+	// 
+	if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) {
+		clc.reliableAcknowledge = clc.reliableSequence;
+	}
+
+	//
+	// parse the message
+	//
+	while ( 1 ) {
+		if ( msg->readcount > msg->cursize ) {
+			Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message");
+			break;
+		}
+
+		cmd = MSG_ReadByte( msg );
+
+		// See if this is an extension command after the EOF, which means we
+		//  got data that a legacy client should ignore.
+		if ((cmd == svc_EOF) && (MSG_LookaheadByte( msg ) == svc_extension)) {
+			SHOWNET( msg, "EXTENSION" );
+			MSG_ReadByte( msg );  // throw the svc_extension byte away.
+			cmd = MSG_ReadByte( msg );  // something legacy clients can't do!
+			// sometimes you get a svc_extension at end of stream...dangling
+			//  bits in the huffman decoder giving a bogus value?
+			if (cmd == -1) {
+				cmd = svc_EOF;
+			}
+		}
+
+		if (cmd == svc_EOF) {
+			SHOWNET( msg, "END OF MESSAGE" );
+			break;
+		}
+
+		if ( cl_shownet->integer >= 2 ) {
+			if ( (cmd < 0) || (!svc_strings[cmd]) ) {
+				Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd );
+			} else {
+				SHOWNET( msg, svc_strings[cmd] );
+			}
+		}
+	
+	// other commands
+		switch ( cmd ) {
+		default:
+			Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n");
+			break;			
+		case svc_nop:
+			break;
+		case svc_serverCommand:
+			CL_ParseCommandString( msg );
+			break;
+		case svc_gamestate:
+			CL_ParseGamestate( msg );
+			break;
+		case svc_snapshot:
+			CL_ParseSnapshot( msg );
+			break;
+		case svc_download:
+			CL_ParseDownload( msg );
+			break;
+		case svc_voip:
+#ifdef USE_VOIP
+			CL_ParseVoip( msg );
+#endif
+			break;
+		}
+	}
+}
+
+
diff --git a/src/client/cl_scrn.c b/src/client/cl_scrn.c
new file mode 100644
index 0000000..c856847
--- /dev/null
+++ b/src/client/cl_scrn.c
@@ -0,0 +1,571 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
+
+#include "client.h"
+
+qboolean	scr_initialized;		// ready to draw
+
+cvar_t		*cl_timegraph;
+cvar_t		*cl_debuggraph;
+cvar_t		*cl_graphheight;
+cvar_t		*cl_graphscale;
+cvar_t		*cl_graphshift;
+
+/*
+================
+SCR_DrawNamedPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) {
+	qhandle_t	hShader;
+
+	assert( width != 0 );
+
+	hShader = re.RegisterShader( picname );
+	SCR_AdjustFrom640( &x, &y, &width, &height );
+	re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+
+/*
+================
+SCR_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) {
+	float	xscale;
+	float	yscale;
+
+#if 0
+		// adjust for wide screens
+		if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) {
+			*x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) );
+		}
+#endif
+
+	// scale for screen sizes
+	xscale = cls.glconfig.vidWidth / 640.0;
+	yscale = cls.glconfig.vidHeight / 480.0;
+	if ( x ) {
+		*x *= xscale;
+	}
+	if ( y ) {
+		*y *= yscale;
+	}
+	if ( w ) {
+		*w *= xscale;
+	}
+	if ( h ) {
+		*h *= yscale;
+	}
+}
+
+/*
+================
+SCR_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_FillRect( float x, float y, float width, float height, const float *color ) {
+	re.SetColor( color );
+
+	SCR_AdjustFrom640( &x, &y, &width, &height );
+	re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader );
+
+	re.SetColor( NULL );
+}
+
+
+/*
+================
+SCR_DrawPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) {
+	SCR_AdjustFrom640( &x, &y, &width, &height );
+	re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+
+
+/*
+** SCR_DrawChar
+** chars are drawn at 640*480 virtual screen size
+*/
+static void SCR_DrawChar( int x, int y, float size, int ch ) {
+	int row, col;
+	float frow, fcol;
+	float	ax, ay, aw, ah;
+
+	ch &= 255;
+
+	if ( ch == ' ' ) {
+		return;
+	}
+
+	if ( y < -size ) {
+		return;
+	}
+
+	ax = x;
+	ay = y;
+	aw = size;
+	ah = size;
+	SCR_AdjustFrom640( &ax, &ay, &aw, &ah );
+
+	row = ch>>4;
+	col = ch&15;
+
+	frow = row*0.0625;
+	fcol = col*0.0625;
+	size = 0.0625;
+
+	re.DrawStretchPic( ax, ay, aw, ah,
+					   fcol, frow, 
+					   fcol + size, frow + size, 
+					   cls.charSetShader );
+}
+
+/*
+** SCR_DrawSmallChar
+** small chars are drawn at native screen resolution
+*/
+void SCR_DrawSmallChar( int x, int y, int ch ) {
+	int row, col;
+	float frow, fcol;
+	float size;
+
+	ch &= 255;
+
+	if ( ch == ' ' ) {
+		return;
+	}
+
+	if ( y < -SMALLCHAR_HEIGHT ) {
+		return;
+	}
+
+	row = ch>>4;
+	col = ch&15;
+
+	frow = row*0.0625;
+	fcol = col*0.0625;
+	size = 0.0625;
+
+	re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT,
+					   fcol, frow, 
+					   fcol + size, frow + size, 
+					   cls.charSetShader );
+}
+
+
+/*
+==================
+SCR_DrawBigString[Color]
+
+Draws a multi-colored string with a drop shadow, optionally forcing
+to a fixed color.
+
+Coordinates are at 640 by 480 virtual resolution
+==================
+*/
+void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor,
+		qboolean noColorEscape ) {
+	vec4_t		color;
+	const char	*s;
+	int			xx;
+
+	// draw the drop shadow
+	color[0] = color[1] = color[2] = 0;
+	color[3] = setColor[3];
+	re.SetColor( color );
+	s = string;
+	xx = x;
+	while ( *s ) {
+		if ( !noColorEscape && Q_IsColorString( s ) ) {
+			s += 2;
+			continue;
+		}
+		SCR_DrawChar( xx+2, y+2, size, *s );
+		xx += size;
+		s++;
+	}
+
+
+	// draw the colored text
+	s = string;
+	xx = x;
+	re.SetColor( setColor );
+	while ( *s ) {
+		if ( !noColorEscape && Q_IsColorString( s ) ) {
+			if ( !forceColor ) {
+				Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) );
+				color[3] = setColor[3];
+				re.SetColor( color );
+			}
+			s += 2;
+			continue;
+		}
+		SCR_DrawChar( xx, y, size, *s );
+		xx += size;
+		s++;
+	}
+	re.SetColor( NULL );
+}
+
+
+void SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape ) {
+	float	color[4];
+
+	color[0] = color[1] = color[2] = 1.0;
+	color[3] = alpha;
+	SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse, noColorEscape );
+}
+
+void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ) {
+	SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue, noColorEscape );
+}
+
+
+/*
+==================
+SCR_DrawSmallString[Color]
+
+Draws a multi-colored string with a drop shadow, optionally forcing
+to a fixed color.
+==================
+*/
+void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor,
+		qboolean noColorEscape ) {
+	vec4_t		color;
+	const char	*s;
+	int			xx;
+
+	// draw the colored text
+	s = string;
+	xx = x;
+	re.SetColor( setColor );
+	while ( *s ) {
+		if ( Q_IsColorString( s ) ) {
+			if ( !forceColor ) {
+				Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) );
+				color[3] = setColor[3];
+				re.SetColor( color );
+			}
+			if ( !noColorEscape ) {
+				s += 2;
+				continue;
+			}
+		}
+		SCR_DrawSmallChar( xx, y, *s );
+		xx += SMALLCHAR_WIDTH;
+		s++;
+	}
+	re.SetColor( NULL );
+}
+
+
+
+/*
+** SCR_Strlen -- skips color escape codes
+*/
+static int SCR_Strlen( const char *str ) {
+	const char *s = str;
+	int count = 0;
+
+	while ( *s ) {
+		if ( Q_IsColorString( s ) ) {
+			s += 2;
+		} else {
+			count++;
+			s++;
+		}
+	}
+
+	return count;
+}
+
+/*
+** SCR_GetBigStringWidth
+*/ 
+int	SCR_GetBigStringWidth( const char *str ) {
+	return SCR_Strlen( str ) * 16;
+}
+
+
+//===============================================================================
+
+
+#ifdef USE_VOIP
+/*
+=================
+SCR_DrawVoipMeter
+
+FIXME: inherited from ioq3, move to cgame/ui
+=================
+*/
+void SCR_DrawVoipMeter( void ) {
+	char	buffer[16];
+	char	string[256];
+	int limit, i;
+
+	if (!cl_voipShowMeter->integer)
+		return;  // player doesn't want to show meter at all.
+	else if (!cl_voipSend->integer)
+		return;  // not recording at the moment.
+	else if (cls.state != CA_ACTIVE)
+		return;  // not connected to a server.
+	else if (!cl_connectedToVoipServer)
+		return;  // server doesn't support VoIP.
+	else if (clc.demoplaying)
+		return;  // playing back a demo.
+	else if (!cl_voip->integer)
+		return;  // client has VoIP support disabled.
+
+	limit = (int) (clc.voipPower * 10.0f);
+	if (limit > 10)
+		limit = 10;
+
+	for (i = 0; i < limit; i++)
+		buffer[i] = '*';
+	while (i < 10)
+		buffer[i++] = ' ';
+	buffer[i] = '\0';
+
+	sprintf( string, "VoIP: [%s]", buffer );
+	SCR_DrawStringExt( 320 - strlen( string ) * 4, 10, 8, string, g_color_table[7], qtrue, qfalse );
+}
+#endif
+
+
+
+
+/*
+===============================================================================
+
+DEBUG GRAPH
+
+===============================================================================
+*/
+
+typedef struct
+{
+	float	value;
+	int		color;
+} graphsamp_t;
+
+static	int			current;
+static	graphsamp_t	values[1024];
+
+/*
+==============
+SCR_DebugGraph
+==============
+*/
+void SCR_DebugGraph (float value, int color)
+{
+	values[current&1023].value = value;
+	values[current&1023].color = color;
+	current++;
+}
+
+/*
+==============
+SCR_DrawDebugGraph
+==============
+*/
+void SCR_DrawDebugGraph (void)
+{
+	int		a, x, y, w, i, h;
+	float	v;
+	int		color;
+
+	//
+	// draw the graph
+	//
+	w = cls.glconfig.vidWidth;
+	x = 0;
+	y = cls.glconfig.vidHeight;
+	re.SetColor( g_color_table[0] );
+	re.DrawStretchPic(x, y - cl_graphheight->integer, 
+		w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader );
+	re.SetColor( NULL );
+
+	for (a=0 ; a<w ; a++)
+	{
+		i = (current-1-a+1024) & 1023;
+		v = values[i].value;
+		color = values[i].color;
+		v = v * cl_graphscale->integer + cl_graphshift->integer;
+		
+		if (v < 0)
+			v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer));
+		h = (int)v % cl_graphheight->integer;
+		re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader );
+	}
+}
+
+//=============================================================================
+
+/*
+==================
+SCR_Init
+==================
+*/
+void SCR_Init( void ) {
+	cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT);
+	cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT);
+	cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT);
+	cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT);
+	cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT);
+
+	scr_initialized = qtrue;
+}
+
+
+//=======================================================
+
+/*
+==================
+SCR_DrawScreenField
+
+This will be called twice if rendering in stereo mode
+==================
+*/
+void SCR_DrawScreenField( stereoFrame_t stereoFrame ) {
+	re.BeginFrame( stereoFrame );
+
+	// wide aspect ratio screens need to have the sides cleared
+	// unless they are displaying game renderings
+	if ( cls.state != CA_ACTIVE && cls.state != CA_CINEMATIC ) {
+		if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) {
+			re.SetColor( g_color_table[0] );
+			re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader );
+			re.SetColor( NULL );
+		}
+	}
+
+	// if the menu is going to cover the entire screen, we
+	// don't need to render anything under it
+	if ( uivm && !VM_Call( uivm, UI_IS_FULLSCREEN )) {
+		switch( cls.state ) {
+		default:
+			Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" );
+			break;
+		case CA_CINEMATIC:
+			SCR_DrawCinematic();
+			break;
+		case CA_DISCONNECTED:
+			// force menu up
+			S_StopAllSounds();
+			VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
+			break;
+		case CA_CONNECTING:
+		case CA_CHALLENGING:
+		case CA_CONNECTED:
+			// connecting clients will only show the connection dialog
+			// refresh to update the time
+			VM_Call( uivm, UI_REFRESH, cls.realtime );
+			VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse );
+			break;
+		case CA_LOADING:
+		case CA_PRIMED:
+			// draw the game information screen and loading progress
+			CL_CGameRendering(stereoFrame);
+			break;
+		case CA_ACTIVE:
+			// always supply STEREO_CENTER as vieworg offset is now done by the engine.
+			CL_CGameRendering(stereoFrame);
+#ifdef USE_VOIP
+			SCR_DrawVoipMeter();
+#endif
+			break;
+		}
+	}
+
+	// the menu draws next
+	if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) {
+		VM_Call( uivm, UI_REFRESH, cls.realtime );
+	}
+
+	// console draws next
+	Con_DrawConsole ();
+
+	// debug graph can be drawn on top of anything
+	if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) {
+		SCR_DrawDebugGraph ();
+	}
+}
+
+/*
+==================
+SCR_UpdateScreen
+
+This is called every frame, and can also be called explicitly to flush
+text to the screen.
+==================
+*/
+void SCR_UpdateScreen( void ) {
+	static int	recursive;
+
+	if ( !scr_initialized ) {
+		return;				// not initialized yet
+	}
+
+	if ( ++recursive > 2 ) {
+		Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" );
+	}
+	recursive = 1;
+
+	// If there is no VM, there are also no rendering commands issued. Stop the renderer in
+	// that case.
+	if( uivm || com_dedicated->integer )
+	{
+		// XXX
+		extern cvar_t* r_anaglyphMode;
+		// if running in stereo, we need to draw the frame twice
+		if ( cls.glconfig.stereoEnabled || r_anaglyphMode->integer) {
+			SCR_DrawScreenField( STEREO_LEFT );
+			SCR_DrawScreenField( STEREO_RIGHT );
+		} else {
+			SCR_DrawScreenField( STEREO_CENTER );
+		}
+
+		if ( com_speeds->integer ) {
+			re.EndFrame( &time_frontend, &time_backend );
+		} else {
+			re.EndFrame( NULL, NULL );
+		}
+	}
+	
+	recursive = 0;
+}
+
diff --git a/src/client/cl_ui.c b/src/client/cl_ui.c
new file mode 100644
index 0000000..edf5ef3
--- /dev/null
+++ b/src/client/cl_ui.c
@@ -0,0 +1,1140 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "client.h"
+
+vm_t *uivm;
+
+/*
+====================
+GetClientState
+====================
+*/
+static void GetClientState( uiClientState_t *state ) {
+	state->connectPacketCount = clc.connectPacketCount;
+	state->connState = cls.state;
+	Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) );
+	Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) );
+	Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) );
+	state->clientNum = cl.snap.ps.clientNum;
+}
+
+/*
+====================
+LAN_LoadCachedServers
+====================
+*/
+void LAN_LoadCachedServers( void ) {
+	int size;
+	fileHandle_t fileIn;
+	cls.numglobalservers = cls.numfavoriteservers = 0;
+	cls.numGlobalServerAddresses = 0;
+	if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) {
+		FS_Read(&cls.numglobalservers, sizeof(int), fileIn);
+		FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn);
+		FS_Read(&size, sizeof(int), fileIn);
+		if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers)) {
+			FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn);
+			FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn);
+		} else {
+			cls.numglobalservers = cls.numfavoriteservers = 0;
+			cls.numGlobalServerAddresses = 0;
+		}
+		FS_FCloseFile(fileIn);
+	}
+}
+
+/*
+====================
+LAN_SaveServersToCache
+====================
+*/
+void LAN_SaveServersToCache( void ) {
+	int size;
+	fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat");
+	FS_Write(&cls.numglobalservers, sizeof(int), fileOut);
+	FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut);
+	size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers);
+	FS_Write(&size, sizeof(int), fileOut);
+	FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut);
+	FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut);
+	FS_FCloseFile(fileOut);
+}
+
+/*
+====================
+GetNews
+====================
+*/
+qboolean GetNews( qboolean begin )
+{
+#ifdef USE_CURL
+	qboolean finished = qfalse;
+	fileHandle_t fileIn;
+	int readSize;
+
+	if( begin ) { // if not already using curl, start the download
+		if( !clc.downloadCURLM ) { 
+			if(!CL_cURL_Init()) {
+				Cvar_Set( "cl_newsString", "^1Error: Could not load cURL library" );
+				return qtrue;
+			}
+			clc.activeCURLNotGameRelated = qtrue;
+			CL_cURL_BeginDownload("news.dat", 
+				"http://tremulous.net/clientnews.txt");
+			return qfalse;
+		}
+	}
+
+	if ( !clc.downloadCURLM && FS_SV_FOpenFileRead("news.dat", &fileIn)) {
+		readSize = FS_Read(clc.newsString, sizeof( clc.newsString ), fileIn);
+		FS_FCloseFile(fileIn);
+		clc.newsString[ readSize ] = '\0';
+		if( readSize > 0 ) {
+			finished = qtrue;
+			clc.cURLUsed = qfalse;
+			CL_cURL_Shutdown();
+			clc.activeCURLNotGameRelated = qfalse;
+		}
+	}
+	if( !finished ) 
+		strcpy( clc.newsString, "Retrieving..." );
+	Cvar_Set( "cl_newsString", clc.newsString );
+	return finished;
+#else
+	Cvar_Set( "cl_newsString", 
+		"^1You must compile your client with CURL support to use this feature" );
+	return qtrue;
+#endif
+}
+
+/*
+====================
+LAN_ResetPings
+====================
+*/
+static void LAN_ResetPings(int source) {
+	int count,i;
+	serverInfo_t *servers = NULL;
+	count = 0;
+
+	switch (source) {
+		case AS_LOCAL :
+			servers = &cls.localServers[0];
+			count = MAX_OTHER_SERVERS;
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			servers = &cls.globalServers[0];
+			count = MAX_GLOBAL_SERVERS;
+			break;
+		case AS_FAVORITES :
+			servers = &cls.favoriteServers[0];
+			count = MAX_OTHER_SERVERS;
+			break;
+	}
+	if (servers) {
+		for (i = 0; i < count; i++) {
+			servers[i].ping = -1;
+		}
+	}
+}
+
+/*
+====================
+LAN_AddServer
+====================
+*/
+static int LAN_AddServer(int source, const char *name, const char *address) {
+	int max, *count, i;
+	netadr_t adr;
+	serverInfo_t *servers = NULL;
+	max = MAX_OTHER_SERVERS;
+	count = NULL;
+
+	switch (source) {
+		case AS_LOCAL :
+			count = &cls.numlocalservers;
+			servers = &cls.localServers[0];
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			max = MAX_GLOBAL_SERVERS;
+			count = &cls.numglobalservers;
+			servers = &cls.globalServers[0];
+			break;
+		case AS_FAVORITES :
+			count = &cls.numfavoriteservers;
+			servers = &cls.favoriteServers[0];
+			break;
+	}
+	if (servers && *count < max) {
+		NET_StringToAdr( address, &adr, NA_IP );
+		for ( i = 0; i < *count; i++ ) {
+			if (NET_CompareAdr(servers[i].adr, adr)) {
+				break;
+			}
+		}
+		if (i >= *count) {
+			servers[*count].adr = adr;
+			Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName));
+			servers[*count].visible = qtrue;
+			(*count)++;
+			return 1;
+		}
+		return 0;
+	}
+	return -1;
+}
+
+/*
+====================
+LAN_RemoveServer
+====================
+*/
+static void LAN_RemoveServer(int source, const char *addr) {
+	int *count, i;
+	serverInfo_t *servers = NULL;
+	count = NULL;
+	switch (source) {
+		case AS_LOCAL :
+			count = &cls.numlocalservers;
+			servers = &cls.localServers[0];
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			count = &cls.numglobalservers;
+			servers = &cls.globalServers[0];
+			break;
+		case AS_FAVORITES :
+			count = &cls.numfavoriteservers;
+			servers = &cls.favoriteServers[0];
+			break;
+	}
+	if (servers) {
+		netadr_t comp;
+		NET_StringToAdr( addr, &comp, NA_IP );
+		for (i = 0; i < *count; i++) {
+			if (NET_CompareAdr( comp, servers[i].adr)) {
+				int j = i;
+				while (j < *count - 1) {
+					Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j]));
+					j++;
+				}
+				(*count)--;
+				break;
+			}
+		}
+	}
+}
+
+
+/*
+====================
+LAN_GetServerCount
+====================
+*/
+static int LAN_GetServerCount( int source ) {
+	switch (source) {
+		case AS_LOCAL :
+			return cls.numlocalservers;
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			return cls.numglobalservers;
+			break;
+		case AS_FAVORITES :
+			return cls.numfavoriteservers;
+			break;
+	}
+	return 0;
+}
+
+/*
+====================
+LAN_GetLocalServerAddressString
+====================
+*/
+static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) {
+	switch (source) {
+		case AS_LOCAL :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				Q_strncpyz(buf, NET_AdrToStringwPort( cls.localServers[n].adr) , buflen );
+				return;
+			}
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+				Q_strncpyz(buf, NET_AdrToStringwPort( cls.globalServers[n].adr) , buflen );
+				return;
+			}
+			break;
+		case AS_FAVORITES :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				Q_strncpyz(buf, NET_AdrToStringwPort( cls.favoriteServers[n].adr) , buflen );
+				return;
+			}
+			break;
+	}
+	buf[0] = '\0';
+}
+
+/*
+====================
+LAN_GetServerInfo
+====================
+*/
+static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) {
+	char info[MAX_STRING_CHARS];
+	serverInfo_t *server = NULL;
+	info[0] = '\0';
+	switch (source) {
+		case AS_LOCAL :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				server = &cls.localServers[n];
+			}
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+				server = &cls.globalServers[n];
+			}
+			break;
+		case AS_FAVORITES :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				server = &cls.favoriteServers[n];
+			}
+			break;
+	}
+	if (server && buf) {
+		buf[0] = '\0';
+		Info_SetValueForKey( info, "hostname", server->hostName);
+		Info_SetValueForKey( info, "mapname", server->mapName);
+		Info_SetValueForKey( info, "label", server->label);
+		Info_SetValueForKey( info, "clients", va("%i",server->clients));
+		Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients));
+		Info_SetValueForKey( info, "ping", va("%i",server->ping));
+		Info_SetValueForKey( info, "minping", va("%i",server->minPing));
+		Info_SetValueForKey( info, "maxping", va("%i",server->maxPing));
+		Info_SetValueForKey( info, "game", server->game);
+		Info_SetValueForKey( info, "gametype", va("%i",server->gameType));
+		Info_SetValueForKey( info, "nettype", va("%i",server->netType));
+		Info_SetValueForKey( info, "addr", NET_AdrToStringwPort(server->adr));
+		Q_strncpyz(buf, info, buflen);
+	} else {
+		if (buf) {
+			buf[0] = '\0';
+		}
+	}
+}
+
+/*
+====================
+LAN_GetServerPing
+====================
+*/
+static int LAN_GetServerPing( int source, int n ) {
+	serverInfo_t *server = NULL;
+	switch (source) {
+		case AS_LOCAL :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				server = &cls.localServers[n];
+			}
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+				server = &cls.globalServers[n];
+			}
+			break;
+		case AS_FAVORITES :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				server = &cls.favoriteServers[n];
+			}
+			break;
+	}
+	if (server) {
+		return server->ping;
+	}
+	return -1;
+}
+
+/*
+====================
+LAN_GetServerPtr
+====================
+*/
+static serverInfo_t *LAN_GetServerPtr( int source, int n ) {
+	switch (source) {
+		case AS_LOCAL :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				return &cls.localServers[n];
+			}
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+				return &cls.globalServers[n];
+			}
+			break;
+		case AS_FAVORITES :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				return &cls.favoriteServers[n];
+			}
+			break;
+	}
+	return NULL;
+}
+
+#define FEATURED_MAXPING 200
+/*
+====================
+LAN_CompareServers
+====================
+*/
+static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) {
+	int res;
+	serverInfo_t *server1, *server2;
+
+	server1 = LAN_GetServerPtr(source, s1);
+	server2 = LAN_GetServerPtr(source, s2);
+	if (!server1 || !server2) {
+		return 0;
+	}
+
+	// featured servers on top
+	if( ( server1->label[ 0 ] && server1->ping <= FEATURED_MAXPING  ) || 
+		( server2->label[ 0 ] && server2->ping <= FEATURED_MAXPING ) ) {
+		res = Q_stricmpn( server1->label, server2->label, MAX_FEATLABEL_CHARS );
+		if( res )
+			return -res;
+	}
+ 
+	res = 0;
+	switch( sortKey ) {
+		case SORT_HOST:
+			{
+				char	hostName1[ MAX_HOSTNAME_LENGTH ];
+				char	hostName2[ MAX_HOSTNAME_LENGTH ];
+				char	*p;
+				int		i;
+
+				for( p = server1->hostName, i = 0; *p != '\0'; p++ )
+				{
+					if( Q_isalpha( *p ) )
+						hostName1[ i++ ] = *p;
+				}
+				hostName1[ i ] = '\0';
+
+				for( p = server2->hostName, i = 0; *p != '\0'; p++ )
+				{
+					if( Q_isalpha( *p ) )
+						hostName2[ i++ ] = *p;
+				}
+				hostName2[ i ] = '\0';
+
+				res = Q_stricmp( hostName1, hostName2 );
+			}
+			break;
+
+		case SORT_GAME:
+			res = Q_stricmp( server1->game, server2->game );
+			break;
+		case SORT_MAP:
+			res = Q_stricmp( server1->mapName, server2->mapName );
+			break;
+		case SORT_CLIENTS:
+			if (server1->clients < server2->clients) {
+				res = -1;
+			}
+			else if (server1->clients > server2->clients) {
+				res = 1;
+			}
+			else {
+				res = 0;
+			}
+			break;
+		case SORT_PING:
+			if (server1->ping < server2->ping) {
+				res = -1;
+			}
+			else if (server1->ping > server2->ping) {
+				res = 1;
+			}
+			else {
+				res = 0;
+			}
+			break;
+	}
+
+	if (sortDir) {
+		if (res < 0)
+			return 1;
+		if (res > 0)
+			return -1;
+		return 0;
+	}
+	return res;
+}
+
+/*
+====================
+LAN_GetPingQueueCount
+====================
+*/
+static int LAN_GetPingQueueCount( void ) {
+	return (CL_GetPingQueueCount());
+}
+
+/*
+====================
+LAN_ClearPing
+====================
+*/
+static void LAN_ClearPing( int n ) {
+	CL_ClearPing( n );
+}
+
+/*
+====================
+LAN_GetPing
+====================
+*/
+static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) {
+	CL_GetPing( n, buf, buflen, pingtime );
+}
+
+/*
+====================
+LAN_GetPingInfo
+====================
+*/
+static void LAN_GetPingInfo( int n, char *buf, int buflen ) {
+	CL_GetPingInfo( n, buf, buflen );
+}
+
+/*
+====================
+LAN_MarkServerVisible
+====================
+*/
+static void LAN_MarkServerVisible(int source, int n, qboolean visible ) {
+	if (n == -1) {
+		int count = MAX_OTHER_SERVERS;
+		serverInfo_t *server = NULL;
+		switch (source) {
+			case AS_LOCAL :
+				server = &cls.localServers[0];
+				break;
+			case AS_MPLAYER:
+			case AS_GLOBAL :
+				server = &cls.globalServers[0];
+				count = MAX_GLOBAL_SERVERS;
+				break;
+			case AS_FAVORITES :
+				server = &cls.favoriteServers[0];
+				break;
+		}
+		if (server) {
+			for (n = 0; n < count; n++) {
+				server[n].visible = visible;
+			}
+		}
+
+	} else {
+		switch (source) {
+			case AS_LOCAL :
+				if (n >= 0 && n < MAX_OTHER_SERVERS) {
+					cls.localServers[n].visible = visible;
+				}
+				break;
+			case AS_MPLAYER:
+			case AS_GLOBAL :
+				if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+					cls.globalServers[n].visible = visible;
+				}
+				break;
+			case AS_FAVORITES :
+				if (n >= 0 && n < MAX_OTHER_SERVERS) {
+					cls.favoriteServers[n].visible = visible;
+				}
+				break;
+		}
+	}
+}
+
+
+/*
+=======================
+LAN_ServerIsVisible
+=======================
+*/
+static int LAN_ServerIsVisible(int source, int n ) {
+	switch (source) {
+		case AS_LOCAL :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				return cls.localServers[n].visible;
+			}
+			break;
+		case AS_MPLAYER:
+		case AS_GLOBAL :
+			if (n >= 0 && n < MAX_GLOBAL_SERVERS) {
+				return cls.globalServers[n].visible;
+			}
+			break;
+		case AS_FAVORITES :
+			if (n >= 0 && n < MAX_OTHER_SERVERS) {
+				return cls.favoriteServers[n].visible;
+			}
+			break;
+	}
+	return qfalse;
+}
+
+/*
+=======================
+LAN_UpdateVisiblePings
+=======================
+*/
+qboolean LAN_UpdateVisiblePings(int source ) {
+	return CL_UpdateVisiblePings_f(source);
+}
+
+/*
+====================
+LAN_GetServerStatus
+====================
+*/
+int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) {
+	return CL_ServerStatus( serverAddress, serverStatus, maxLen );
+}
+
+/*
+====================
+CL_GetGlConfig
+====================
+*/
+static void CL_GetGlconfig( glconfig_t *config ) {
+	*config = cls.glconfig;
+}
+
+/*
+====================
+CL_GetClipboardData
+====================
+*/
+static void CL_GetClipboardData( char *buf, int buflen ) {
+	char	*cbd;
+
+	cbd = Sys_GetClipboardData();
+
+	if ( !cbd ) {
+		*buf = 0;
+		return;
+	}
+
+	Q_strncpyz( buf, cbd, buflen );
+
+	Z_Free( cbd );
+}
+
+/*
+====================
+GetConfigString
+====================
+*/
+static int GetConfigString(int index, char *buf, int size)
+{
+	int		offset;
+
+	if (index < 0 || index >= MAX_CONFIGSTRINGS)
+		return qfalse;
+
+	offset = cl.gameState.stringOffsets[index];
+	if (!offset) {
+		if( size ) {
+			buf[0] = 0;
+		}
+		return qfalse;
+	}
+
+	Q_strncpyz( buf, cl.gameState.stringData+offset, size);
+ 
+	return qtrue;
+}
+
+/*
+====================
+FloatAsInt
+====================
+*/
+static int FloatAsInt( float f ) {
+	floatint_t fi;
+	fi.f = f;
+	return fi.i;
+}
+
+/*
+====================
+CL_UISystemCalls
+
+The ui module is making a system call
+====================
+*/
+intptr_t CL_UISystemCalls( intptr_t *args ) {
+	switch( args[0] ) {
+	case UI_ERROR:
+		Com_Error( ERR_DROP, "%s", (const char*)VMA(1) );
+		return 0;
+
+	case UI_PRINT:
+		Com_Printf( "%s", (const char*)VMA(1) );
+		return 0;
+
+	case UI_MILLISECONDS:
+		return Sys_Milliseconds();
+
+	case UI_CVAR_REGISTER:
+		Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); 
+		return 0;
+
+	case UI_CVAR_UPDATE:
+		Cvar_Update( VMA(1) );
+		return 0;
+
+	case UI_CVAR_SET:
+		Cvar_SetSafe( VMA(1), VMA(2) );
+		return 0;
+
+	case UI_CVAR_VARIABLEVALUE:
+		return FloatAsInt( Cvar_VariableValue( VMA(1) ) );
+
+	case UI_CVAR_VARIABLESTRINGBUFFER:
+		Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] );
+		return 0;
+
+	case UI_CVAR_SETVALUE:
+		Cvar_SetValueSafe( VMA(1), VMF(2) );
+		return 0;
+
+	case UI_CVAR_RESET:
+		Cvar_Reset( VMA(1) );
+		return 0;
+
+	case UI_CVAR_CREATE:
+		Cvar_Get( VMA(1), VMA(2), args[3] );
+		return 0;
+
+	case UI_CVAR_INFOSTRINGBUFFER:
+		Cvar_InfoStringBuffer( args[1], VMA(2), args[3] );
+		return 0;
+
+	case UI_ARGC:
+		return Cmd_Argc();
+
+	case UI_ARGV:
+		Cmd_ArgvBuffer( args[1], VMA(2), args[3] );
+		return 0;
+
+	case UI_CMD_EXECUTETEXT:
+		if(args[1] == 0
+		&& (!strncmp(VMA(2), "snd_restart", 11)
+		|| !strncmp(VMA(2), "vid_restart", 11)
+		|| !strncmp(VMA(2), "quit", 5)))
+		{
+			Com_Printf (S_COLOR_YELLOW "turning EXEC_NOW '%.11s' into EXEC_INSERT\n", (const char*)VMA(2));
+			args[1] = EXEC_INSERT;
+		}
+		Cbuf_ExecuteText( args[1], VMA(2) );
+		return 0;
+
+	case UI_FS_FOPENFILE:
+		return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] );
+
+	case UI_FS_READ:
+		FS_Read2( VMA(1), args[2], args[3] );
+		return 0;
+
+	case UI_FS_WRITE:
+		FS_Write( VMA(1), args[2], args[3] );
+		return 0;
+
+	case UI_FS_FCLOSEFILE:
+		FS_FCloseFile( args[1] );
+		return 0;
+
+	case UI_FS_GETFILELIST:
+		return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] );
+
+	case UI_FS_SEEK:
+		return FS_Seek( args[1], args[2], args[3] );
+	
+	case UI_R_REGISTERMODEL:
+		return re.RegisterModel( VMA(1) );
+
+	case UI_R_REGISTERSKIN:
+		return re.RegisterSkin( VMA(1) );
+
+	case UI_R_REGISTERSHADERNOMIP:
+		return re.RegisterShaderNoMip( VMA(1) );
+
+	case UI_R_CLEARSCENE:
+		re.ClearScene();
+		return 0;
+
+	case UI_R_ADDREFENTITYTOSCENE:
+		re.AddRefEntityToScene( VMA(1) );
+		return 0;
+
+	case UI_R_ADDPOLYTOSCENE:
+		re.AddPolyToScene( args[1], args[2], VMA(3), 1 );
+		return 0;
+
+	case UI_R_ADDLIGHTTOSCENE:
+		re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) );
+		return 0;
+
+	case UI_R_RENDERSCENE:
+		re.RenderScene( VMA(1) );
+		return 0;
+
+	case UI_R_SETCOLOR:
+		re.SetColor( VMA(1) );
+		return 0;
+
+	case UI_R_SETCLIPREGION:
+		re.SetClipRegion( VMA(1) );
+		return 0;
+
+	case UI_R_DRAWSTRETCHPIC:
+		re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] );
+		return 0;
+
+  case UI_R_MODELBOUNDS:
+		re.ModelBounds( args[1], VMA(2), VMA(3) );
+		return 0;
+
+	case UI_UPDATESCREEN:
+		SCR_UpdateScreen();
+		return 0;
+
+	case UI_CM_LERPTAG:
+		re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) );
+		return 0;
+
+	case UI_S_REGISTERSOUND:
+		return S_RegisterSound( VMA(1), args[2] );
+
+	case UI_S_STARTLOCALSOUND:
+		S_StartLocalSound( args[1], args[2] );
+		return 0;
+
+	case UI_KEY_KEYNUMTOSTRINGBUF:
+		Key_KeynumToStringBuf( args[1], VMA(2), args[3] );
+		return 0;
+
+	case UI_KEY_GETBINDINGBUF:
+		Key_GetBindingBuf( args[1], VMA(2), args[3] );
+		return 0;
+
+	case UI_KEY_SETBINDING:
+		Key_SetBinding( args[1], VMA(2) );
+		return 0;
+
+	case UI_KEY_ISDOWN:
+		return Key_IsDown( args[1] );
+
+	case UI_KEY_GETOVERSTRIKEMODE:
+		return Key_GetOverstrikeMode();
+
+	case UI_KEY_SETOVERSTRIKEMODE:
+		Key_SetOverstrikeMode( args[1] );
+		return 0;
+
+	case UI_KEY_CLEARSTATES:
+		Key_ClearStates();
+		return 0;
+
+	case UI_KEY_GETCATCHER:
+		return Key_GetCatcher();
+
+	case UI_KEY_SETCATCHER:
+		// Don't allow the ui module to close the console
+		Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) );
+		return 0;
+
+	case UI_GETCLIPBOARDDATA:
+		CL_GetClipboardData( VMA(1), args[2] );
+		return 0;
+
+	case UI_GETCLIENTSTATE:
+		GetClientState( VMA(1) );
+		return 0;		
+
+	case UI_GETGLCONFIG:
+		CL_GetGlconfig( VMA(1) );
+		return 0;
+
+	case UI_GETCONFIGSTRING:
+		return GetConfigString( args[1], VMA(2), args[3] );
+
+	case UI_LAN_LOADCACHEDSERVERS:
+		LAN_LoadCachedServers();
+		return 0;
+
+	case UI_LAN_SAVECACHEDSERVERS:
+		LAN_SaveServersToCache();
+		return 0;
+
+	case UI_LAN_ADDSERVER:
+		return LAN_AddServer(args[1], VMA(2), VMA(3));
+
+	case UI_LAN_REMOVESERVER:
+		LAN_RemoveServer(args[1], VMA(2));
+		return 0;
+
+	case UI_LAN_GETPINGQUEUECOUNT:
+		return LAN_GetPingQueueCount();
+
+	case UI_LAN_CLEARPING:
+		LAN_ClearPing( args[1] );
+		return 0;
+
+	case UI_LAN_GETPING:
+		LAN_GetPing( args[1], VMA(2), args[3], VMA(4) );
+		return 0;
+
+	case UI_LAN_GETPINGINFO:
+		LAN_GetPingInfo( args[1], VMA(2), args[3] );
+		return 0;
+
+	case UI_LAN_GETSERVERCOUNT:
+		return LAN_GetServerCount(args[1]);
+
+	case UI_LAN_GETSERVERADDRESSSTRING:
+		LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] );
+		return 0;
+
+	case UI_LAN_GETSERVERINFO:
+		LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] );
+		return 0;
+
+	case UI_LAN_GETSERVERPING:
+		return LAN_GetServerPing( args[1], args[2] );
+
+	case UI_LAN_MARKSERVERVISIBLE:
+		LAN_MarkServerVisible( args[1], args[2], args[3] );
+		return 0;
+
+	case UI_LAN_SERVERISVISIBLE:
+		return LAN_ServerIsVisible( args[1], args[2] );
+
+	case UI_LAN_UPDATEVISIBLEPINGS:
+		return LAN_UpdateVisiblePings( args[1] );
+
+	case UI_LAN_RESETPINGS:
+		LAN_ResetPings( args[1] );
+		return 0;
+
+	case UI_LAN_SERVERSTATUS:
+		return LAN_GetServerStatus( VMA(1), VMA(2), args[3] );
+
+	case UI_GETNEWS:
+		return GetNews( args[1] );
+
+	case UI_LAN_COMPARESERVERS:
+		return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] );
+
+	case UI_MEMORY_REMAINING:
+		return Hunk_MemoryRemaining();
+
+	case UI_SET_PBCLSTATUS:
+		return 0;	
+
+	case UI_R_REGISTERFONT:
+		re.RegisterFont( VMA(1), args[2], VMA(3));
+		return 0;
+
+	case UI_MEMSET:
+		Com_Memset( VMA(1), args[2], args[3] );
+		return 0;
+
+	case UI_MEMCPY:
+		Com_Memcpy( VMA(1), VMA(2), args[3] );
+		return 0;
+
+	case UI_STRNCPY:
+		strncpy( VMA(1), VMA(2), args[3] );
+		return args[1];
+
+	case UI_SIN:
+		return FloatAsInt( sin( VMF(1) ) );
+
+	case UI_COS:
+		return FloatAsInt( cos( VMF(1) ) );
+
+	case UI_ATAN2:
+		return FloatAsInt( atan2( VMF(1), VMF(2) ) );
+
+	case UI_SQRT:
+		return FloatAsInt( sqrt( VMF(1) ) );
+
+	case UI_FLOOR:
+		return FloatAsInt( floor( VMF(1) ) );
+
+	case UI_CEIL:
+		return FloatAsInt( ceil( VMF(1) ) );
+
+	case UI_PARSE_ADD_GLOBAL_DEFINE:
+		return Parse_AddGlobalDefine( VMA(1) );
+	case UI_PARSE_LOAD_SOURCE:
+		return Parse_LoadSourceHandle( VMA(1) );
+	case UI_PARSE_FREE_SOURCE:
+		return Parse_FreeSourceHandle( args[1] );
+	case UI_PARSE_READ_TOKEN:
+		return Parse_ReadTokenHandle( args[1], VMA(2) );
+	case UI_PARSE_SOURCE_FILE_AND_LINE:
+		return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) );
+
+	case UI_S_STOPBACKGROUNDTRACK:
+		S_StopBackgroundTrack();
+		return 0;
+	case UI_S_STARTBACKGROUNDTRACK:
+		S_StartBackgroundTrack( VMA(1), VMA(2));
+		return 0;
+
+	case UI_REAL_TIME:
+		return Com_RealTime( VMA(1) );
+
+	case UI_CIN_PLAYCINEMATIC:
+	  Com_DPrintf("UI_CIN_PlayCinematic\n");
+	  return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]);
+
+	case UI_CIN_STOPCINEMATIC:
+	  return CIN_StopCinematic(args[1]);
+
+	case UI_CIN_RUNCINEMATIC:
+	  return CIN_RunCinematic(args[1]);
+
+	case UI_CIN_DRAWCINEMATIC:
+	  CIN_DrawCinematic(args[1]);
+	  return 0;
+
+	case UI_CIN_SETEXTENTS:
+	  CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]);
+	  return 0;
+
+	case UI_R_REMAP_SHADER:
+		re.RemapShader( VMA(1), VMA(2), VMA(3) );
+		return 0;
+
+	default:
+		Com_Error( ERR_DROP, "Bad UI system trap: %ld", (long int) args[0] );
+
+	}
+
+	return 0;
+}
+
+/*
+====================
+CL_ShutdownUI
+====================
+*/
+void CL_ShutdownUI( void ) {
+	Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_UI );
+	cls.uiStarted = qfalse;
+	if ( !uivm ) {
+		return;
+	}
+	VM_Call( uivm, UI_SHUTDOWN );
+	VM_Free( uivm );
+	uivm = NULL;
+}
+
+/*
+====================
+CL_InitUI
+====================
+*/
+#define UI_OLD_API_VERSION	4
+
+void CL_InitUI( void ) {
+	int		v;
+	vmInterpret_t		interpret;
+
+	// load the dll or bytecode
+	if ( cl_connectedToPureServer != 0 ) {
+		// if sv_pure is set we only allow qvms to be loaded
+		interpret = VMI_COMPILED;
+	}
+	else {
+		interpret = Cvar_VariableValue( "vm_ui" );
+	}
+	uivm = VM_Create( "ui", CL_UISystemCalls, interpret );
+	if ( !uivm ) {
+		Com_Printf( "Failed to find a valid UI vm. The following paths were searched:\n" );
+		Cmd_ExecuteString( "path /\n" );
+		Com_Error( ERR_FATAL, "VM_Create on UI failed" );
+	}
+
+	// sanity check
+	v = VM_Call( uivm, UI_GETAPIVERSION );
+	if (v == UI_OLD_API_VERSION) {
+		// init for this gamestate
+		VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE));
+	}
+	else if (v != UI_API_VERSION) {
+		Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION );
+		cls.uiStarted = qfalse;
+	}
+	else {
+		// init for this gamestate
+		VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) );
+
+		// show where the ui folder was loaded from
+		Cmd_ExecuteString( "which ui/\n" );
+	}
+
+	// reset any CVAR_CHEAT cvars registered by ui
+	if ( !clc.demoplaying && !cl_connectedToCheatServer ) 
+		Cvar_SetCheatState();
+
+    clc.newsString[ 0 ] = '\0';
+}
+
+/*
+====================
+UI_GameCommand
+
+See if the current console command is claimed by the ui
+====================
+*/
+qboolean UI_GameCommand( void ) {
+	if ( !uivm ) {
+		return qfalse;
+	}
+
+	return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime );
+}
diff --git a/src/client/client.h b/src/client/client.h
new file mode 100644
index 0000000..26af507
--- /dev/null
+++ b/src/client/client.h
@@ -0,0 +1,645 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// client.h -- primary header for client
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+#include "../ui/ui_public.h"
+#include "keys.h"
+#include "snd_public.h"
+#include "../cgame/cg_public.h"
+#include "../game/bg_public.h"
+
+#ifdef USE_CURL
+#include "cl_curl.h"
+#endif /* USE_CURL */
+
+#ifdef USE_VOIP
+#include "speex/speex.h"
+#include "speex/speex_preprocess.h"
+#endif
+
+// file full of random crap that gets used to create cl_guid
+#define QKEY_FILE "qkey"
+#define QKEY_SIZE 2048
+
+#define	RETRANSMIT_TIMEOUT	3000	// time between connection packet retransmits
+
+// snapshots are a view of the server at a given time
+typedef struct {
+	qboolean		valid;			// cleared if delta parsing was invalid
+	int				snapFlags;		// rate delayed and dropped commands
+
+	int				serverTime;		// server time the message is valid for (in msec)
+
+	int				messageNum;		// copied from netchan->incoming_sequence
+	int				deltaNum;		// messageNum the delta is from
+	int				ping;			// time from when cmdNum-1 was sent to time packet was reeceived
+	byte			areamask[MAX_MAP_AREA_BYTES];		// portalarea visibility bits
+
+	int				cmdNum;			// the next cmdNum the server is expecting
+	playerState_t	ps;						// complete information about the current player at this time
+
+	int				numEntities;			// all of the entities that need to be presented
+	int				parseEntitiesNum;		// at the time of this snapshot
+
+	int				serverCommandNum;		// execute all commands up to this before
+											// making the snapshot current
+} clSnapshot_t;
+
+
+
+/*
+=============================================================================
+
+the clientActive_t structure is wiped completely at every
+new gamestate_t, potentially several times during an established connection
+
+=============================================================================
+*/
+
+typedef struct {
+	int		p_cmdNumber;		// cl.cmdNumber when packet was sent
+	int		p_serverTime;		// usercmd->serverTime when packet was sent
+	int		p_realtime;			// cls.realtime when packet was sent
+} outPacket_t;
+
+// the parseEntities array must be large enough to hold PACKET_BACKUP frames of
+// entities, so that when a delta compressed message arives from the server
+// it can be un-deltad from the original 
+#define	MAX_PARSE_ENTITIES	2048
+
+extern int g_console_field_width;
+
+typedef struct {
+	int			timeoutcount;		// it requres several frames in a timeout condition
+									// to disconnect, preventing debugging breaks from
+									// causing immediate disconnects on continue
+	clSnapshot_t	snap;			// latest received from server
+
+	int			serverTime;			// may be paused during play
+	int			oldServerTime;		// to prevent time from flowing bakcwards
+	int			oldFrameServerTime;	// to check tournament restarts
+	int			serverTimeDelta;	// cl.serverTime = cls.realtime + cl.serverTimeDelta
+									// this value changes as net lag varies
+	qboolean	extrapolatedSnapshot;	// set if any cgame frame has been forced to extrapolate
+									// cleared when CL_AdjustTimeDelta looks at it
+	qboolean	newSnapshots;		// set on parse of any valid packet
+
+	gameState_t	gameState;			// configstrings
+	char		mapname[MAX_QPATH];	// extracted from CS_SERVERINFO
+
+	int			parseEntitiesNum;	// index (not anded off) into cl_parse_entities[]
+
+	int			mouseDx[2], mouseDy[2];	// added to by mouse events
+	int			mouseIndex;
+	int			joystickAxis[MAX_JOYSTICK_AXIS];	// set by joystick events
+
+	// cgame communicates a few values to the client system
+	int			cgameUserCmdValue;	// current weapon to add to usercmd_t
+	float		cgameSensitivity;
+
+	// cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last
+	// properly generated command
+	usercmd_t	cmds[CMD_BACKUP];	// each mesage will send several old cmds
+	int			cmdNumber;			// incremented each frame, because multiple
+									// frames may need to be packed into a single packet
+
+	outPacket_t	outPackets[PACKET_BACKUP];	// information about each packet we have sent out
+
+	// the client maintains its own idea of view angles, which are
+	// sent to the server each frame.  It is cleared to 0 upon entering each level.
+	// the server sends a delta each frame which is added to the locally
+	// tracked view angles to account for standing on rotating objects,
+	// and teleport direction changes
+	vec3_t		viewangles;
+
+	int			serverId;			// included in each client message so the server
+												// can tell if it is for a prior map_restart
+	// big stuff at end of structure so most offsets are 15 bits or less
+	clSnapshot_t	snapshots[PACKET_BACKUP];
+
+	entityState_t	entityBaselines[MAX_GENTITIES];	// for delta compression when not in previous frame
+
+	entityState_t	parseEntities[MAX_PARSE_ENTITIES];
+} clientActive_t;
+
+extern	clientActive_t		cl;
+
+/*
+=============================================================================
+
+the clientConnection_t structure is wiped when disconnecting from a server,
+either to go to a full screen console, play a demo, or connect to a different server
+
+A connection can be to either a server through the network layer or a
+demo through a file.
+
+=============================================================================
+*/
+
+#define MAX_TIMEDEMO_DURATIONS	4096
+
+typedef struct {
+
+	int			clientNum;
+	int			lastPacketSentTime;			// for retransmits during connection
+	int			lastPacketTime;				// for timeouts
+
+	netadr_t	serverAddress;
+	int			connectTime;				// for connection retransmits
+	int			connectPacketCount;			// for display on connection dialog
+	char		serverMessage[MAX_STRING_TOKENS];	// for display on connection dialog
+
+	int			challenge;					// from the server to use for connecting
+	int			checksumFeed;				// from the server for checksum calculations
+
+	// these are our reliable messages that go to the server
+	int			reliableSequence;
+	int			reliableAcknowledge;		// the last one the server has executed
+	char		reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
+
+	// server message (unreliable) and command (reliable) sequence
+	// numbers are NOT cleared at level changes, but continue to
+	// increase as long as the connection is valid
+
+	// message sequence is used by both the network layer and the
+	// delta compression layer
+	int			serverMessageSequence;
+
+	// reliable messages received from server
+	int			serverCommandSequence;
+	int			lastExecutedServerCommand;		// last server command grabbed or executed with CL_GetServerCommand
+	char		serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS];
+
+	// file transfer from server
+	fileHandle_t download;
+	char		downloadTempName[MAX_OSPATH];
+	char		downloadName[MAX_OSPATH];
+#ifdef USE_CURL
+	qboolean	cURLEnabled;
+	qboolean	cURLUsed;
+	qboolean	cURLDisconnected;
+	char		downloadURL[MAX_OSPATH];
+	CURL		*downloadCURL;
+	CURLM		*downloadCURLM;
+	qboolean	activeCURLNotGameRelated;
+#endif /* USE_CURL */
+	int		sv_allowDownload;
+	char		sv_dlURL[MAX_CVAR_VALUE_STRING];
+	int			downloadNumber;
+	int			downloadBlock;	// block we are waiting for
+	int			downloadCount;	// how many bytes we got
+	int			downloadSize;	// how many bytes we got
+	char		downloadList[MAX_INFO_STRING]; // list of paks we need to download
+	qboolean	downloadRestart;	// if true, we need to do another FS_Restart because we downloaded a pak
+	char		newsString[ MAX_NEWS_STRING ];
+
+	// demo information
+	char		demoName[MAX_QPATH];
+	qboolean	spDemoRecording;
+	qboolean	demorecording;
+	qboolean	demoplaying;
+	qboolean	demowaiting;	// don't record until a non-delta message is received
+	qboolean	firstDemoFrameSkipped;
+	fileHandle_t	demofile;
+
+	int			timeDemoFrames;		// counter of rendered frames
+	int			timeDemoStart;		// cls.realtime before first frame
+	int			timeDemoBaseTime;	// each frame will be at this time + frameNum * 50
+	int			timeDemoLastFrame;// time the last frame was rendered
+	int			timeDemoMinDuration;	// minimum frame duration
+	int			timeDemoMaxDuration;	// maximum frame duration
+	unsigned char	timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ];	// log of frame durations
+
+#ifdef USE_VOIP
+	qboolean speexInitialized;
+	int speexFrameSize;
+	int speexSampleRate;
+
+	// incoming data...
+	// !!! FIXME: convert from parallel arrays to array of a struct.
+	SpeexBits speexDecoderBits[MAX_CLIENTS];
+	void *speexDecoder[MAX_CLIENTS];
+	byte voipIncomingGeneration[MAX_CLIENTS];
+	int voipIncomingSequence[MAX_CLIENTS];
+	float voipGain[MAX_CLIENTS];
+	qboolean voipIgnore[MAX_CLIENTS];
+	qboolean voipMuteAll;
+
+	// outgoing data...
+	int voipTarget1;  // these three ints make up a bit mask of 92 bits.
+	int voipTarget2;  //  the bits say who a VoIP pack is addressed to:
+	int voipTarget3;  //  (1 << clientnum). See cl_voipSendTarget cvar.
+	SpeexPreprocessState *speexPreprocessor;
+	SpeexBits speexEncoderBits;
+	void *speexEncoder;
+	int voipOutgoingDataSize;
+	int voipOutgoingDataFrames;
+	int voipOutgoingSequence;
+	byte voipOutgoingGeneration;
+	byte voipOutgoingData[1024];
+	float voipPower;
+#endif
+
+	// big stuff at end of structure so most offsets are 15 bits or less
+	netchan_t	netchan;
+} clientConnection_t;
+
+extern	clientConnection_t clc;
+
+/*
+==================================================================
+
+the clientStatic_t structure is never wiped, and is used even when
+no client connection is active at all
+
+==================================================================
+*/
+
+typedef struct {
+	netadr_t	adr;
+	int			start;
+	int			time;
+	char		info[MAX_INFO_STRING];
+} ping_t;
+
+#define MAX_FEATLABEL_CHARS  32
+typedef struct {
+	netadr_t	adr;
+	char	  	hostName[MAX_HOSTNAME_LENGTH];
+	char	  	mapName[MAX_NAME_LENGTH];
+	char	  	game[MAX_NAME_LENGTH];
+	char		label[MAX_FEATLABEL_CHARS]; // for featured servers, NULL otherwise
+	int			netType;
+	int			gameType;
+	int		  	clients;
+	int		  	maxClients;
+	int			minPing;
+	int			maxPing;
+	int			ping;
+	qboolean	visible;
+} serverInfo_t;
+
+typedef struct {
+	connstate_t	state;				// connection status
+
+	qboolean	cddialog;			// bring up the cd needed dialog next frame
+
+	char		servername[MAX_OSPATH];		// name of server from original connect (used by reconnect)
+
+	// when the server clears the hunk, all of these must be restarted
+	qboolean	rendererStarted;
+	qboolean	soundStarted;
+	qboolean	soundRegistered;
+	qboolean	uiStarted;
+	qboolean	cgameStarted;
+
+	int			framecount;
+	int			frametime;			// msec since last frame
+
+	int			realtime;			// ignores pause
+	int			realFrametime;		// ignoring pause, so console always works
+
+	// master server sequence information
+	int			numMasterPackets;
+	unsigned int		receivedMasterPackets; // bitfield
+
+	int			numlocalservers;
+	serverInfo_t	localServers[MAX_OTHER_SERVERS];
+
+	int			numglobalservers;
+	serverInfo_t  globalServers[MAX_GLOBAL_SERVERS];
+	// additional global servers
+	int			numGlobalServerAddresses;
+	netadr_t		globalServerAddresses[MAX_GLOBAL_SERVERS];
+
+	int			numfavoriteservers;
+	serverInfo_t	favoriteServers[MAX_OTHER_SERVERS];
+
+	int pingUpdateSource;		// source currently pinging or updating
+
+	// update server info
+	netadr_t	updateServer;
+	char		updateChallenge[MAX_TOKEN_CHARS];
+	char		updateInfoString[MAX_INFO_STRING];
+
+	netadr_t	authorizeServer;
+
+	// rendering info
+	glconfig_t	glconfig;
+	qhandle_t	charSetShader;
+	qhandle_t	whiteShader;
+	qhandle_t	consoleShader;
+} clientStatic_t;
+
+extern	clientStatic_t		cls;
+
+//=============================================================================
+
+extern	vm_t			*cgvm;	// interface to cgame dll or vm
+extern	vm_t			*uivm;	// interface to ui dll or vm
+extern	refexport_t		re;		// interface to refresh .dll
+
+
+//
+// cvars
+//
+extern	cvar_t	*cl_nodelta;
+extern	cvar_t	*cl_debugMove;
+extern	cvar_t	*cl_noprint;
+extern	cvar_t	*cl_timegraph;
+extern	cvar_t	*cl_maxpackets;
+extern	cvar_t	*cl_packetdup;
+extern	cvar_t	*cl_shownet;
+extern	cvar_t	*cl_showSend;
+extern	cvar_t	*cl_timeNudge;
+extern	cvar_t	*cl_showTimeDelta;
+extern	cvar_t	*cl_freezeDemo;
+
+extern	cvar_t	*cl_yawspeed;
+extern	cvar_t	*cl_pitchspeed;
+extern	cvar_t	*cl_run;
+extern	cvar_t	*cl_anglespeedkey;
+
+extern	cvar_t	*cl_sensitivity;
+extern	cvar_t	*cl_freelook;
+
+extern	cvar_t	*cl_mouseAccel;
+extern	cvar_t	*cl_mouseAccelOffset;
+extern	cvar_t	*cl_mouseAccelStyle;
+extern	cvar_t	*cl_showMouseRate;
+
+extern	cvar_t	*m_pitch;
+extern	cvar_t	*m_yaw;
+extern	cvar_t	*m_forward;
+extern	cvar_t	*m_side;
+extern	cvar_t	*m_filter;
+
+extern	cvar_t	*j_pitch;
+extern	cvar_t	*j_yaw;
+extern	cvar_t	*j_forward;
+extern	cvar_t	*j_side;
+extern	cvar_t	*j_pitch_axis;
+extern	cvar_t	*j_yaw_axis;
+extern	cvar_t	*j_forward_axis;
+extern	cvar_t	*j_side_axis;
+
+extern	cvar_t	*cl_timedemo;
+extern	cvar_t	*cl_aviFrameRate;
+extern	cvar_t	*cl_aviMotionJpeg;
+
+extern	cvar_t	*cl_activeAction;
+
+extern	cvar_t	*cl_allowDownload;
+extern  cvar_t  *cl_downloadMethod;
+extern	cvar_t	*cl_conXOffset;
+extern	cvar_t	*cl_inGameVideo;
+
+extern	cvar_t	*cl_lanForcePackets;
+extern	cvar_t	*cl_autoRecordDemo;
+
+extern	cvar_t	*cl_consoleKeys;
+
+#ifdef USE_MUMBLE
+extern	cvar_t	*cl_useMumble;
+extern	cvar_t	*cl_mumbleScale;
+#endif
+
+#ifdef USE_VOIP
+// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to
+//  send to no one, or a comma-separated list of client numbers:
+//  "0,7,2,23" ... an empty string is treated like "all".
+extern	cvar_t	*cl_voipUseVAD;
+extern	cvar_t	*cl_voipVADThreshold;
+extern	cvar_t	*cl_voipSend;
+extern	cvar_t	*cl_voipSendTarget;
+extern	cvar_t	*cl_voipGainDuringCapture;
+extern	cvar_t	*cl_voipCaptureMult;
+extern	cvar_t	*cl_voipShowMeter;
+extern	cvar_t	*cl_voip;
+#endif
+
+//=================================================
+
+//
+// cl_main
+//
+
+void CL_Init (void);
+void CL_FlushMemory(void);
+void CL_ShutdownAll(void);
+void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd);
+
+void CL_StartHunkUsers( qboolean rendererOnly );
+
+void CL_Disconnect_f (void);
+void CL_GetChallengePacket (void);
+void CL_Vid_Restart_f( void );
+void CL_Snd_Restart_f (void);
+void CL_StartDemoLoop( void );
+void CL_NextDemo( void );
+void CL_ReadDemoMessage( void );
+demoState_t CL_DemoState( void );
+int CL_DemoPos( void );
+void CL_DemoName( char *buffer, int size );
+void CL_StopRecord_f( void );
+
+void CL_InitDownloads(void);
+void CL_NextDownload(void);
+
+void CL_GetPing( int n, char *buf, int buflen, int *pingtime );
+void CL_GetPingInfo( int n, char *buf, int buflen );
+void CL_ClearPing( int n );
+int CL_GetPingQueueCount( void );
+
+void CL_ShutdownRef( void );
+void CL_InitRef( void );
+int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen );
+
+qboolean CL_CheckPaused(void);
+
+//
+// cl_input
+//
+typedef struct {
+	int			down[2];		// key nums holding it down
+	unsigned	downtime;		// msec timestamp
+	unsigned	msec;			// msec down this frame if both a down and up happened
+	qboolean	active;			// current state
+	qboolean	wasPressed;		// set when down, not cleared when up
+} kbutton_t;
+
+extern	kbutton_t	in_mlook, in_klook;
+extern 	kbutton_t 	in_strafe;
+extern 	kbutton_t 	in_speed;
+
+#ifdef USE_VOIP
+extern 	kbutton_t 	in_voiprecord;
+#endif
+
+void CL_InitInput (void);
+void CL_SendCmd (void);
+void CL_ClearState (void);
+void CL_ReadPackets (void);
+
+void CL_WritePacket( void );
+void IN_CenterView (void);
+
+void CL_VerifyCode( void );
+
+float CL_KeyState (kbutton_t *key);
+int Key_StringToKeynum( char *str );
+char *Key_KeynumToString (int keynum);
+
+//
+// cl_parse.c
+//
+extern int cl_connectedToPureServer;
+extern int cl_connectedToCheatServer;
+
+#ifdef USE_VOIP
+extern int cl_connectedToVoipServer;
+void CL_Voip_f( void );
+#endif
+
+void CL_SystemInfoChanged( void );
+void CL_ParseServerMessage( msg_t *msg );
+
+//====================================================================
+
+void	CL_ServerInfoPacket( netadr_t from, msg_t *msg );
+void	CL_LocalServers_f( void );
+void	CL_GlobalServers_f( void );
+void	CL_FavoriteServers_f( void );
+void	CL_Ping_f( void );
+qboolean CL_UpdateVisiblePings_f( int source );
+
+
+//
+// console
+//
+void Con_DrawCharacter (int cx, int line, int num);
+
+void Con_CheckResize (void);
+void Con_Init (void);
+void Con_Clear_f (void);
+void Con_ToggleConsole_f (void);
+void Con_DrawNotify (void);
+void Con_ClearNotify (void);
+void Con_RunConsole (void);
+void Con_DrawConsole (void);
+void Con_PageUp( void );
+void Con_PageDown( void );
+void Con_Top( void );
+void Con_Bottom( void );
+void Con_Close( void );
+
+void CL_LoadConsoleHistory( void );
+void CL_SaveConsoleHistory( void );
+
+//
+// cl_scrn.c
+//
+void	SCR_Init (void);
+void	SCR_UpdateScreen (void);
+
+void	SCR_DebugGraph (float value, int color);
+
+int		SCR_GetBigStringWidth( const char *str );	// returns in virtual 640x480 coordinates
+
+void	SCR_AdjustFrom640( float *x, float *y, float *w, float *h );
+void	SCR_FillRect( float x, float y, float width, float height, 
+					 const float *color );
+void	SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader );
+void	SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname );
+
+void	SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape );			// draws a string with embedded color control characters with fade
+void	SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape );	// ignores embedded color control characters
+void	SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, qboolean noColorEscape );
+void	SCR_DrawSmallChar( int x, int y, int ch );
+
+
+//
+// cl_cin.c
+//
+
+void CL_PlayCinematic_f( void );
+void SCR_DrawCinematic (void);
+void SCR_RunCinematic (void);
+void SCR_StopCinematic (void);
+int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits);
+e_status CIN_StopCinematic(int handle);
+e_status CIN_RunCinematic (int handle);
+void CIN_DrawCinematic (int handle);
+void CIN_SetExtents (int handle, int x, int y, int w, int h);
+void CIN_SetLooping (int handle, qboolean loop);
+void CIN_UploadCinematic(int handle);
+void CIN_CloseAllVideos(void);
+
+//
+// cl_cgame.c
+//
+void CL_InitCGame( void );
+void CL_ShutdownCGame( void );
+qboolean CL_GameCommand( void );
+void CL_GameConsoleText( void );
+void CL_CGameRendering( stereoFrame_t stereo );
+void CL_SetCGameTime( void );
+void CL_FirstSnapshot( void );
+void CL_ShaderStateChanged(void);
+
+//
+// cl_ui.c
+//
+void CL_InitUI( void );
+void CL_ShutdownUI( void );
+int Key_GetCatcher( void );
+void Key_SetCatcher( int catcher );
+void LAN_LoadCachedServers( void );
+void LAN_SaveServersToCache( void );
+
+
+//
+// cl_net_chan.c
+//
+void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg);	//int length, const byte *data );
+void CL_Netchan_TransmitNextFragment( netchan_t *chan );
+qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg );
+
+//
+// cl_avi.c
+//
+qboolean CL_OpenAVIForWriting( const char *filename );
+void CL_TakeVideoFrame( void );
+void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size );
+void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size );
+qboolean CL_CloseAVI( void );
+qboolean CL_VideoRecording( void );
+
+//
+// cl_main.c
+//
+void CL_WriteDemoMessage ( msg_t *msg, int headerBytes );
+
diff --git a/src/client/keycodes.h b/src/client/keycodes.h
new file mode 100644
index 0000000..c13194c
--- /dev/null
+++ b/src/client/keycodes.h
@@ -0,0 +1,281 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+//
+#ifndef __KEYCODES_H__
+#define __KEYCODES_H__
+
+//
+// these are the key numbers that should be passed to KeyEvent
+//
+
+// normal keys should be passed as lowercased ascii
+
+typedef enum {
+	K_NONE = -1,
+	K_TAB = 9,
+	K_ENTER = 13,
+	K_ESCAPE = 27,
+	K_SPACE = 32,
+
+	K_BACKSPACE = 127,
+
+	K_COMMAND = 128,
+	K_CAPSLOCK,
+	K_POWER,
+	K_PAUSE,
+
+	K_UPARROW,
+	K_DOWNARROW,
+	K_LEFTARROW,
+	K_RIGHTARROW,
+
+	K_ALT,
+	K_CTRL,
+	K_SHIFT,
+	K_INS,
+	K_DEL,
+	K_PGDN,
+	K_PGUP,
+	K_HOME,
+	K_END,
+
+	K_F1,
+	K_F2,
+	K_F3,
+	K_F4,
+	K_F5,
+	K_F6,
+	K_F7,
+	K_F8,
+	K_F9,
+	K_F10,
+	K_F11,
+	K_F12,
+	K_F13,
+	K_F14,
+	K_F15,
+
+	K_KP_HOME,
+	K_KP_UPARROW,
+	K_KP_PGUP,
+	K_KP_LEFTARROW,
+	K_KP_5,
+	K_KP_RIGHTARROW,
+	K_KP_END,
+	K_KP_DOWNARROW,
+	K_KP_PGDN,
+	K_KP_ENTER,
+	K_KP_INS,
+	K_KP_DEL,
+	K_KP_SLASH,
+	K_KP_MINUS,
+	K_KP_PLUS,
+	K_KP_NUMLOCK,
+	K_KP_STAR,
+	K_KP_EQUALS,
+
+	K_MOUSE1,
+	K_MOUSE2,
+	K_MOUSE3,
+	K_MOUSE4,
+	K_MOUSE5,
+
+	K_MWHEELDOWN,
+	K_MWHEELUP,
+
+	K_JOY1,
+	K_JOY2,
+	K_JOY3,
+	K_JOY4,
+	K_JOY5,
+	K_JOY6,
+	K_JOY7,
+	K_JOY8,
+	K_JOY9,
+	K_JOY10,
+	K_JOY11,
+	K_JOY12,
+	K_JOY13,
+	K_JOY14,
+	K_JOY15,
+	K_JOY16,
+	K_JOY17,
+	K_JOY18,
+	K_JOY19,
+	K_JOY20,
+	K_JOY21,
+	K_JOY22,
+	K_JOY23,
+	K_JOY24,
+	K_JOY25,
+	K_JOY26,
+	K_JOY27,
+	K_JOY28,
+	K_JOY29,
+	K_JOY30,
+	K_JOY31,
+	K_JOY32,
+
+	K_AUX1,
+	K_AUX2,
+	K_AUX3,
+	K_AUX4,
+	K_AUX5,
+	K_AUX6,
+	K_AUX7,
+	K_AUX8,
+	K_AUX9,
+	K_AUX10,
+	K_AUX11,
+	K_AUX12,
+	K_AUX13,
+	K_AUX14,
+	K_AUX15,
+	K_AUX16,
+
+	K_WORLD_0,
+	K_WORLD_1,
+	K_WORLD_2,
+	K_WORLD_3,
+	K_WORLD_4,
+	K_WORLD_5,
+	K_WORLD_6,
+	K_WORLD_7,
+	K_WORLD_8,
+	K_WORLD_9,
+	K_WORLD_10,
+	K_WORLD_11,
+	K_WORLD_12,
+	K_WORLD_13,
+	K_WORLD_14,
+	K_WORLD_15,
+	K_WORLD_16,
+	K_WORLD_17,
+	K_WORLD_18,
+	K_WORLD_19,
+	K_WORLD_20,
+	K_WORLD_21,
+	K_WORLD_22,
+	K_WORLD_23,
+	K_WORLD_24,
+	K_WORLD_25,
+	K_WORLD_26,
+	K_WORLD_27,
+	K_WORLD_28,
+	K_WORLD_29,
+	K_WORLD_30,
+	K_WORLD_31,
+	K_WORLD_32,
+	K_WORLD_33,
+	K_WORLD_34,
+	K_WORLD_35,
+	K_WORLD_36,
+	K_WORLD_37,
+	K_WORLD_38,
+	K_WORLD_39,
+	K_WORLD_40,
+	K_WORLD_41,
+	K_WORLD_42,
+	K_WORLD_43,
+	K_WORLD_44,
+	K_WORLD_45,
+	K_WORLD_46,
+	K_WORLD_47,
+	K_WORLD_48,
+	K_WORLD_49,
+	K_WORLD_50,
+	K_WORLD_51,
+	K_WORLD_52,
+	K_WORLD_53,
+	K_WORLD_54,
+	K_WORLD_55,
+	K_WORLD_56,
+	K_WORLD_57,
+	K_WORLD_58,
+	K_WORLD_59,
+	K_WORLD_60,
+	K_WORLD_61,
+	K_WORLD_62,
+	K_WORLD_63,
+	K_WORLD_64,
+	K_WORLD_65,
+	K_WORLD_66,
+	K_WORLD_67,
+	K_WORLD_68,
+	K_WORLD_69,
+	K_WORLD_70,
+	K_WORLD_71,
+	K_WORLD_72,
+	K_WORLD_73,
+	K_WORLD_74,
+	K_WORLD_75,
+	K_WORLD_76,
+	K_WORLD_77,
+	K_WORLD_78,
+	K_WORLD_79,
+	K_WORLD_80,
+	K_WORLD_81,
+	K_WORLD_82,
+	K_WORLD_83,
+	K_WORLD_84,
+	K_WORLD_85,
+	K_WORLD_86,
+	K_WORLD_87,
+	K_WORLD_88,
+	K_WORLD_89,
+	K_WORLD_90,
+	K_WORLD_91,
+	K_WORLD_92,
+	K_WORLD_93,
+	K_WORLD_94,
+	K_WORLD_95,
+
+	K_SUPER,
+	K_COMPOSE,
+	K_MODE,
+	K_HELP,
+	K_PRINT,
+	K_SYSREQ,
+	K_SCROLLOCK,
+	K_BREAK,
+	K_MENU,
+	K_EURO,
+	K_UNDO,
+
+	// Pseudo-key that brings the console down
+	K_CONSOLE,
+
+	MAX_KEYS
+} keyNum_t;
+
+// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY
+// in detecting binds, so we leave it defined to the old hardcoded value
+// of maxiumum keys to prevent mods from crashing older versions of the engine
+#define K_LAST_KEY              256
+
+// The menu code needs to get both key and char events, but
+// to avoid duplicating the paths, the char events are just
+// distinguished by or'ing in K_CHAR_FLAG (ugly)
+#define	K_CHAR_FLAG		1024
+
+#endif
diff --git a/src/client/keys.h b/src/client/keys.h
new file mode 100644
index 0000000..adb9537
--- /dev/null
+++ b/src/client/keys.h
@@ -0,0 +1,58 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "keycodes.h"
+
+typedef struct {
+	qboolean	down;
+	int			repeats;		// if > 1, it is autorepeating
+	char		*binding;
+} qkey_t;
+
+extern	qboolean	key_overstrikeMode;
+extern	qkey_t		keys[MAX_KEYS];
+
+// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h
+void Field_KeyDownEvent( field_t *edit, int key );
+void Field_CharEvent( field_t *edit, int ch );
+void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape );
+void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape );
+
+#define		COMMAND_HISTORY		32
+extern	field_t	historyEditLines[COMMAND_HISTORY];
+
+extern	field_t	g_consoleField;
+extern	field_t	chatField;
+extern	int				anykeydown;
+extern	qboolean	chat_team;
+extern	int			chat_playerNum;
+
+void Key_WriteBindings( fileHandle_t f );
+void Key_SetBinding( int keynum, const char *binding );
+char *Key_GetBinding( int keynum );
+qboolean Key_IsDown( int keynum );
+qboolean Key_GetOverstrikeMode( void );
+void Key_SetOverstrikeMode( qboolean state );
+void Key_ClearStates( void );
+int Key_GetKey(const char *binding);
+void Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void Key_GetBindingBuf( int keynum, char *buf, int buflen );
diff --git a/src/client/libmumblelink.c b/src/client/libmumblelink.c
new file mode 100644
index 0000000..25ca678
--- /dev/null
+++ b/src/client/libmumblelink.c
@@ -0,0 +1,189 @@
+/* libmumblelink.c -- mumble link interface
+
+  Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+  Copyright (C) 2000-2009 Darklegion Development
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+#ifdef WIN32
+#include <windows.h>
+#define uint32_t UINT32
+#else
+#include <unistd.h>
+#ifdef __sun
+#define _POSIX_C_SOURCE 199309L
+#endif
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#endif
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "libmumblelink.h"
+
+#ifndef MIN
+#define MIN(a, b) ((a)<(b)?(a):(b))
+#endif
+
+typedef struct
+{
+	uint32_t uiVersion;
+	uint32_t uiTick;
+	float   fAvatarPosition[3];
+	float   fAvatarFront[3];
+	float   fAvatarTop[3];
+	wchar_t name[256];
+	/* new in mumble 1.2 */
+	float   fCameraPosition[3];
+	float   fCameraFront[3];
+	float   fCameraTop[3];
+	wchar_t identity[256];
+	uint32_t context_len;
+	unsigned char context[256];
+	wchar_t description[2048];
+} LinkedMem;
+
+static LinkedMem *lm = NULL;
+
+#ifdef WIN32
+static HANDLE hMapObject = NULL;
+#else
+static int32_t GetTickCount(void)
+{
+	struct timeval tv;
+	gettimeofday(&tv,NULL);
+
+	return tv.tv_usec / 1000 + tv.tv_sec * 1000;
+}
+#endif
+
+int mumble_link(const char* name)
+{
+#ifdef WIN32
+	if(lm)
+		return 0;
+
+	hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink");
+	if (hMapObject == NULL)
+		return -1;
+
+	lm = (LinkedMem *) MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem));
+	if (lm == NULL) {
+		CloseHandle(hMapObject);
+		hMapObject = NULL;
+		return -1;
+	}
+#else
+	char file[256];
+	int shmfd;
+	if(lm)
+		return 0;
+
+	snprintf(file, sizeof (file), "/MumbleLink.%d", getuid());
+	shmfd = shm_open(file, O_RDWR, S_IRUSR | S_IWUSR);
+	if(shmfd < 0) {
+		return -1;
+	}
+
+	lm = (LinkedMem *) (mmap(NULL, sizeof(LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd,0));
+	if (lm == (void *) (-1)) {
+		lm = NULL;
+		close(shmfd);
+		return -1;
+	}
+	close(shmfd);
+#endif
+	memset(lm, 0, sizeof(LinkedMem));
+	mbstowcs(lm->name, name, sizeof(lm->name));
+
+	return 0;
+}
+
+void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3])
+{
+	mumble_update_coordinates2(fPosition, fFront, fTop, fPosition, fFront, fTop);
+}
+
+void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3],
+		float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3])
+{
+	if (!lm)
+		return;
+
+	memcpy(lm->fAvatarPosition, fAvatarPosition, sizeof(lm->fAvatarPosition));
+	memcpy(lm->fAvatarFront, fAvatarFront, sizeof(lm->fAvatarFront));
+	memcpy(lm->fAvatarTop, fAvatarTop, sizeof(lm->fAvatarTop));
+	memcpy(lm->fCameraPosition, fCameraPosition, sizeof(lm->fCameraPosition));
+	memcpy(lm->fCameraFront, fCameraFront, sizeof(lm->fCameraFront));
+	memcpy(lm->fCameraTop, fCameraTop, sizeof(lm->fCameraTop));
+	lm->uiVersion = 2;
+	lm->uiTick = GetTickCount();
+}
+
+void mumble_set_identity(const char* identity)
+{
+	size_t len;
+	if (!lm)
+		return;
+	len = MIN(sizeof(lm->identity), strlen(identity)+1);
+	mbstowcs(lm->identity, identity, len);
+}
+
+void mumble_set_context(const unsigned char* context, size_t len)
+{
+	if (!lm)
+		return;
+	len = MIN(sizeof(lm->context), len);
+	lm->context_len = len;
+	memcpy(lm->context, context, len);
+}
+
+void mumble_set_description(const char* description)
+{
+	size_t len;
+	if (!lm)
+		return;
+	len = MIN(sizeof(lm->description), strlen(description)+1);
+	mbstowcs(lm->description, description, len);
+}
+
+void mumble_unlink()
+{
+	if(!lm)
+		return;
+#ifdef WIN32
+	UnmapViewOfFile(lm);
+	CloseHandle(hMapObject);
+	hMapObject = NULL;
+#else
+	munmap(lm, sizeof(LinkedMem));
+#endif
+	lm = NULL;
+}
+
+int mumble_islinked(void)
+{
+	return lm != NULL;
+}
diff --git a/src/client/libmumblelink.h b/src/client/libmumblelink.h
new file mode 100644
index 0000000..fc4929f
--- /dev/null
+++ b/src/client/libmumblelink.h
@@ -0,0 +1,36 @@
+/* libmumblelink.h -- mumble link interface
+
+  Copyright (C) 2008 Ludwig Nussel <ludwig.nussel@suse.de>
+  Copyright (C) 2000-2009 Darklegion Development
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+int mumble_link(const char* name);
+int mumble_islinked(void);
+void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]);
+
+/* new for mumble 1.2: also set camera position */
+void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3],
+		float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]);
+
+void mumble_set_description(const char* description);
+void mumble_set_context(const unsigned char* context, size_t len);
+void mumble_set_identity(const char* identity);
+
+void mumble_unlink(void);
diff --git a/src/client/qal.c b/src/client/qal.c
new file mode 100644
index 0000000..57db8ed
--- /dev/null
+++ b/src/client/qal.c
@@ -0,0 +1,352 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+// Dynamically loads OpenAL
+
+#ifdef USE_OPENAL
+
+#include "qal.h"
+
+#ifdef USE_OPENAL_DLOPEN
+
+#include "../sys/sys_loadlib.h"
+
+LPALENABLE qalEnable;
+LPALDISABLE qalDisable;
+LPALISENABLED qalIsEnabled;
+LPALGETSTRING qalGetString;
+LPALGETBOOLEANV qalGetBooleanv;
+LPALGETINTEGERV qalGetIntegerv;
+LPALGETFLOATV qalGetFloatv;
+LPALGETDOUBLEV qalGetDoublev;
+LPALGETBOOLEAN qalGetBoolean;
+LPALGETINTEGER qalGetInteger;
+LPALGETFLOAT qalGetFloat;
+LPALGETDOUBLE qalGetDouble;
+LPALGETERROR qalGetError;
+LPALISEXTENSIONPRESENT qalIsExtensionPresent;
+LPALGETPROCADDRESS qalGetProcAddress;
+LPALGETENUMVALUE qalGetEnumValue;
+LPALLISTENERF qalListenerf;
+LPALLISTENER3F qalListener3f;
+LPALLISTENERFV qalListenerfv;
+LPALLISTENERI qalListeneri;
+LPALGETLISTENERF qalGetListenerf;
+LPALGETLISTENER3F qalGetListener3f;
+LPALGETLISTENERFV qalGetListenerfv;
+LPALGETLISTENERI qalGetListeneri;
+LPALGENSOURCES qalGenSources;
+LPALDELETESOURCES qalDeleteSources;
+LPALISSOURCE qalIsSource;
+LPALSOURCEF qalSourcef;
+LPALSOURCE3F qalSource3f;
+LPALSOURCEFV qalSourcefv;
+LPALSOURCEI qalSourcei;
+LPALGETSOURCEF qalGetSourcef;
+LPALGETSOURCE3F qalGetSource3f;
+LPALGETSOURCEFV qalGetSourcefv;
+LPALGETSOURCEI qalGetSourcei;
+LPALSOURCEPLAYV qalSourcePlayv;
+LPALSOURCESTOPV qalSourceStopv;
+LPALSOURCEREWINDV qalSourceRewindv;
+LPALSOURCEPAUSEV qalSourcePausev;
+LPALSOURCEPLAY qalSourcePlay;
+LPALSOURCESTOP qalSourceStop;
+LPALSOURCEREWIND qalSourceRewind;
+LPALSOURCEPAUSE qalSourcePause;
+LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers;
+LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers;
+LPALGENBUFFERS qalGenBuffers;
+LPALDELETEBUFFERS qalDeleteBuffers;
+LPALISBUFFER qalIsBuffer;
+LPALBUFFERDATA qalBufferData;
+LPALGETBUFFERF qalGetBufferf;
+LPALGETBUFFERI qalGetBufferi;
+LPALDOPPLERFACTOR qalDopplerFactor;
+LPALDOPPLERVELOCITY qalDopplerVelocity;
+LPALDISTANCEMODEL qalDistanceModel;
+
+LPALCCREATECONTEXT qalcCreateContext;
+LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent;
+LPALCPROCESSCONTEXT qalcProcessContext;
+LPALCSUSPENDCONTEXT qalcSuspendContext;
+LPALCDESTROYCONTEXT qalcDestroyContext;
+LPALCGETCURRENTCONTEXT qalcGetCurrentContext;
+LPALCGETCONTEXTSDEVICE qalcGetContextsDevice;
+LPALCOPENDEVICE qalcOpenDevice;
+LPALCCLOSEDEVICE qalcCloseDevice;
+LPALCGETERROR qalcGetError;
+LPALCISEXTENSIONPRESENT qalcIsExtensionPresent;
+LPALCGETPROCADDRESS qalcGetProcAddress;
+LPALCGETENUMVALUE qalcGetEnumValue;
+LPALCGETSTRING qalcGetString;
+LPALCGETINTEGERV qalcGetIntegerv;
+LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice;
+LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice;
+LPALCCAPTURESTART qalcCaptureStart;
+LPALCCAPTURESTOP qalcCaptureStop;
+LPALCCAPTURESAMPLES qalcCaptureSamples;
+
+static void *OpenALLib = NULL;
+
+static qboolean alinit_fail = qfalse;
+
+/*
+=================
+GPA
+=================
+*/
+static void *GPA(char *str)
+{
+	void *rv;
+
+	rv = Sys_LoadFunction(OpenALLib, str);
+	if(!rv)
+	{
+		Com_Printf( " Can't load symbol %s\n", str);
+		alinit_fail = qtrue;
+		return NULL;
+	}
+	else
+	{
+		Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv);
+        return rv;
+	}
+}
+
+/*
+=================
+QAL_Init
+=================
+*/
+qboolean QAL_Init(const char *libname)
+{
+	if(OpenALLib)
+		return qtrue;
+
+	Com_Printf( "Loading \"%s\"...\n", libname);
+	if( (OpenALLib = Sys_LoadLibrary(libname)) == 0 )
+	{
+#ifdef _WIN32
+		return qfalse;
+#else
+		char fn[1024];
+		Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) );
+		strncat(fn, "/", sizeof(fn) - strlen(fn) - 1);
+		strncat(fn, libname, sizeof(fn) - strlen(fn) - 1);
+
+		if( (OpenALLib = Sys_LoadLibrary(fn)) == 0 )
+		{
+			return qfalse;
+		}
+#endif
+	}
+
+	alinit_fail = qfalse;
+
+	qalEnable = GPA("alEnable");
+	qalDisable = GPA("alDisable");
+	qalIsEnabled = GPA("alIsEnabled");
+	qalGetString = GPA("alGetString");
+	qalGetBooleanv = GPA("alGetBooleanv");
+	qalGetIntegerv = GPA("alGetIntegerv");
+	qalGetFloatv = GPA("alGetFloatv");
+	qalGetDoublev = GPA("alGetDoublev");
+	qalGetBoolean = GPA("alGetBoolean");
+	qalGetInteger = GPA("alGetInteger");
+	qalGetFloat = GPA("alGetFloat");
+	qalGetDouble = GPA("alGetDouble");
+	qalGetError = GPA("alGetError");
+	qalIsExtensionPresent = GPA("alIsExtensionPresent");
+	qalGetProcAddress = GPA("alGetProcAddress");
+	qalGetEnumValue = GPA("alGetEnumValue");
+	qalListenerf = GPA("alListenerf");
+	qalListener3f = GPA("alListener3f");
+	qalListenerfv = GPA("alListenerfv");
+	qalListeneri = GPA("alListeneri");
+	qalGetListenerf = GPA("alGetListenerf");
+	qalGetListener3f = GPA("alGetListener3f");
+	qalGetListenerfv = GPA("alGetListenerfv");
+	qalGetListeneri = GPA("alGetListeneri");
+	qalGenSources = GPA("alGenSources");
+	qalDeleteSources = GPA("alDeleteSources");
+	qalIsSource = GPA("alIsSource");
+	qalSourcef = GPA("alSourcef");
+	qalSource3f = GPA("alSource3f");
+	qalSourcefv = GPA("alSourcefv");
+	qalSourcei = GPA("alSourcei");
+	qalGetSourcef = GPA("alGetSourcef");
+	qalGetSource3f = GPA("alGetSource3f");
+	qalGetSourcefv = GPA("alGetSourcefv");
+	qalGetSourcei = GPA("alGetSourcei");
+	qalSourcePlayv = GPA("alSourcePlayv");
+	qalSourceStopv = GPA("alSourceStopv");
+	qalSourceRewindv = GPA("alSourceRewindv");
+	qalSourcePausev = GPA("alSourcePausev");
+	qalSourcePlay = GPA("alSourcePlay");
+	qalSourceStop = GPA("alSourceStop");
+	qalSourceRewind = GPA("alSourceRewind");
+	qalSourcePause = GPA("alSourcePause");
+	qalSourceQueueBuffers = GPA("alSourceQueueBuffers");
+	qalSourceUnqueueBuffers = GPA("alSourceUnqueueBuffers");
+	qalGenBuffers = GPA("alGenBuffers");
+	qalDeleteBuffers = GPA("alDeleteBuffers");
+	qalIsBuffer = GPA("alIsBuffer");
+	qalBufferData = GPA("alBufferData");
+	qalGetBufferf = GPA("alGetBufferf");
+	qalGetBufferi = GPA("alGetBufferi");
+	qalDopplerFactor = GPA("alDopplerFactor");
+	qalDopplerVelocity = GPA("alDopplerVelocity");
+	qalDistanceModel = GPA("alDistanceModel");
+
+	qalcCreateContext = GPA("alcCreateContext");
+	qalcMakeContextCurrent = GPA("alcMakeContextCurrent");
+	qalcProcessContext = GPA("alcProcessContext");
+	qalcSuspendContext = GPA("alcSuspendContext");
+	qalcDestroyContext = GPA("alcDestroyContext");
+	qalcGetCurrentContext = GPA("alcGetCurrentContext");
+	qalcGetContextsDevice = GPA("alcGetContextsDevice");
+	qalcOpenDevice = GPA("alcOpenDevice");
+	qalcCloseDevice = GPA("alcCloseDevice");
+	qalcGetError = GPA("alcGetError");
+	qalcIsExtensionPresent = GPA("alcIsExtensionPresent");
+	qalcGetProcAddress = GPA("alcGetProcAddress");
+	qalcGetEnumValue = GPA("alcGetEnumValue");
+	qalcGetString = GPA("alcGetString");
+	qalcGetIntegerv = GPA("alcGetIntegerv");
+	qalcCaptureOpenDevice = GPA("alcCaptureOpenDevice");
+	qalcCaptureCloseDevice = GPA("alcCaptureCloseDevice");
+	qalcCaptureStart = GPA("alcCaptureStart");
+	qalcCaptureStop = GPA("alcCaptureStop");
+	qalcCaptureSamples = GPA("alcCaptureSamples");
+
+	if(alinit_fail)
+	{
+		QAL_Shutdown();
+		Com_Printf( " One or more symbols not found\n");
+		return qfalse;
+	}
+
+	return qtrue;
+}
+
+/*
+=================
+QAL_Shutdown
+=================
+*/
+void QAL_Shutdown( void )
+{
+	if(OpenALLib)
+	{
+		Sys_UnloadLibrary(OpenALLib);
+		OpenALLib = NULL;
+	}
+
+	qalEnable = NULL;
+	qalDisable = NULL;
+	qalIsEnabled = NULL;
+	qalGetString = NULL;
+	qalGetBooleanv = NULL;
+	qalGetIntegerv = NULL;
+	qalGetFloatv = NULL;
+	qalGetDoublev = NULL;
+	qalGetBoolean = NULL;
+	qalGetInteger = NULL;
+	qalGetFloat = NULL;
+	qalGetDouble = NULL;
+	qalGetError = NULL;
+	qalIsExtensionPresent = NULL;
+	qalGetProcAddress = NULL;
+	qalGetEnumValue = NULL;
+	qalListenerf = NULL;
+	qalListener3f = NULL;
+	qalListenerfv = NULL;
+	qalListeneri = NULL;
+	qalGetListenerf = NULL;
+	qalGetListener3f = NULL;
+	qalGetListenerfv = NULL;
+	qalGetListeneri = NULL;
+	qalGenSources = NULL;
+	qalDeleteSources = NULL;
+	qalIsSource = NULL;
+	qalSourcef = NULL;
+	qalSource3f = NULL;
+	qalSourcefv = NULL;
+	qalSourcei = NULL;
+	qalGetSourcef = NULL;
+	qalGetSource3f = NULL;
+	qalGetSourcefv = NULL;
+	qalGetSourcei = NULL;
+	qalSourcePlayv = NULL;
+	qalSourceStopv = NULL;
+	qalSourceRewindv = NULL;
+	qalSourcePausev = NULL;
+	qalSourcePlay = NULL;
+	qalSourceStop = NULL;
+	qalSourceRewind = NULL;
+	qalSourcePause = NULL;
+	qalSourceQueueBuffers = NULL;
+	qalSourceUnqueueBuffers = NULL;
+	qalGenBuffers = NULL;
+	qalDeleteBuffers = NULL;
+	qalIsBuffer = NULL;
+	qalBufferData = NULL;
+	qalGetBufferf = NULL;
+	qalGetBufferi = NULL;
+	qalDopplerFactor = NULL;
+	qalDopplerVelocity = NULL;
+	qalDistanceModel = NULL;
+
+	qalcCreateContext = NULL;
+	qalcMakeContextCurrent = NULL;
+	qalcProcessContext = NULL;
+	qalcSuspendContext = NULL;
+	qalcDestroyContext = NULL;
+	qalcGetCurrentContext = NULL;
+	qalcGetContextsDevice = NULL;
+	qalcOpenDevice = NULL;
+	qalcCloseDevice = NULL;
+	qalcGetError = NULL;
+	qalcIsExtensionPresent = NULL;
+	qalcGetProcAddress = NULL;
+	qalcGetEnumValue = NULL;
+	qalcGetString = NULL;
+	qalcGetIntegerv = NULL;
+	qalcCaptureOpenDevice = NULL;
+	qalcCaptureCloseDevice = NULL;
+	qalcCaptureStart = NULL;
+	qalcCaptureStop = NULL;
+	qalcCaptureSamples = NULL;
+}
+#else
+qboolean QAL_Init(const char *libname)
+{
+	return qtrue;
+}
+void QAL_Shutdown( void )
+{
+}
+#endif
+#endif
diff --git a/src/client/qal.h b/src/client/qal.h
new file mode 100644
index 0000000..7665e6e
--- /dev/null
+++ b/src/client/qal.h
@@ -0,0 +1,253 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+
+#ifndef __QAL_H__
+#define __QAL_H__
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef USE_OPENAL_DLOPEN
+#define AL_NO_PROTOTYPES
+#define ALC_NO_PROTOTYPES
+#endif
+
+#ifdef USE_LOCAL_HEADERS
+#include "../AL/al.h"
+#include "../AL/alc.h"
+#else
+#ifdef _MSC_VER
+  // MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme.
+  #include <al.h>
+  #include <alc.h>
+#else
+  #include <AL/al.h>
+  #include <AL/alc.h>
+#endif
+#endif
+
+/* Hack to enable compiling both on OpenAL SDK and OpenAL-soft. */
+#ifndef ALC_ENUMERATE_ALL_EXT
+#  define ALC_ENUMERATE_ALL_EXT 1
+#  define ALC_DEFAULT_ALL_DEVICES_SPECIFIER        0x1012
+#  define ALC_ALL_DEVICES_SPECIFIER                0x1013
+#endif
+
+#ifdef USE_OPENAL_DLOPEN
+extern LPALENABLE qalEnable;
+extern LPALDISABLE qalDisable;
+extern LPALISENABLED qalIsEnabled;
+extern LPALGETSTRING qalGetString;
+extern LPALGETBOOLEANV qalGetBooleanv;
+extern LPALGETINTEGERV qalGetIntegerv;
+extern LPALGETFLOATV qalGetFloatv;
+extern LPALGETDOUBLEV qalGetDoublev;
+extern LPALGETBOOLEAN qalGetBoolean;
+extern LPALGETINTEGER qalGetInteger;
+extern LPALGETFLOAT qalGetFloat;
+extern LPALGETDOUBLE qalGetDouble;
+extern LPALGETERROR qalGetError;
+extern LPALISEXTENSIONPRESENT qalIsExtensionPresent;
+extern LPALGETPROCADDRESS qalGetProcAddress;
+extern LPALGETENUMVALUE qalGetEnumValue;
+extern LPALLISTENERF qalListenerf;
+extern LPALLISTENER3F qalListener3f;
+extern LPALLISTENERFV qalListenerfv;
+extern LPALLISTENERI qalListeneri;
+extern LPALLISTENER3I qalListener3i;
+extern LPALLISTENERIV qalListeneriv;
+extern LPALGETLISTENERF qalGetListenerf;
+extern LPALGETLISTENER3F qalGetListener3f;
+extern LPALGETLISTENERFV qalGetListenerfv;
+extern LPALGETLISTENERI qalGetListeneri;
+extern LPALGETLISTENER3I qalGetListener3i;
+extern LPALGETLISTENERIV qalGetListeneriv;
+extern LPALGENSOURCES qalGenSources;
+extern LPALDELETESOURCES qalDeleteSources;
+extern LPALISSOURCE qalIsSource;
+extern LPALSOURCEF qalSourcef;
+extern LPALSOURCE3F qalSource3f;
+extern LPALSOURCEFV qalSourcefv;
+extern LPALSOURCEI qalSourcei;
+extern LPALSOURCE3I qalSource3i;
+extern LPALSOURCEIV qalSourceiv;
+extern LPALGETSOURCEF qalGetSourcef;
+extern LPALGETSOURCE3F qalGetSource3f;
+extern LPALGETSOURCEFV qalGetSourcefv;
+extern LPALGETSOURCEI qalGetSourcei;
+extern LPALGETSOURCE3I qalGetSource3i;
+extern LPALGETSOURCEIV qalGetSourceiv;
+extern LPALSOURCEPLAYV qalSourcePlayv;
+extern LPALSOURCESTOPV qalSourceStopv;
+extern LPALSOURCEREWINDV qalSourceRewindv;
+extern LPALSOURCEPAUSEV qalSourcePausev;
+extern LPALSOURCEPLAY qalSourcePlay;
+extern LPALSOURCESTOP qalSourceStop;
+extern LPALSOURCEREWIND qalSourceRewind;
+extern LPALSOURCEPAUSE qalSourcePause;
+extern LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers;
+extern LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers;
+extern LPALGENBUFFERS qalGenBuffers;
+extern LPALDELETEBUFFERS qalDeleteBuffers;
+extern LPALISBUFFER qalIsBuffer;
+extern LPALBUFFERDATA qalBufferData;
+extern LPALBUFFERF qalBufferf;
+extern LPALBUFFER3F qalBuffer3f;
+extern LPALBUFFERFV qalBufferfv;
+extern LPALBUFFERF qalBufferi;
+extern LPALBUFFER3F qalBuffer3i;
+extern LPALBUFFERFV qalBufferiv;
+extern LPALGETBUFFERF qalGetBufferf;
+extern LPALGETBUFFER3F qalGetBuffer3f;
+extern LPALGETBUFFERFV qalGetBufferfv;
+extern LPALGETBUFFERI qalGetBufferi;
+extern LPALGETBUFFER3I qalGetBuffer3i;
+extern LPALGETBUFFERIV qalGetBufferiv;
+extern LPALDOPPLERFACTOR qalDopplerFactor;
+extern LPALDOPPLERVELOCITY qalDopplerVelocity;
+extern LPALSPEEDOFSOUND qalSpeedOfSound;
+extern LPALDISTANCEMODEL qalDistanceModel;
+
+extern LPALCCREATECONTEXT qalcCreateContext;
+extern LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent;
+extern LPALCPROCESSCONTEXT qalcProcessContext;
+extern LPALCSUSPENDCONTEXT qalcSuspendContext;
+extern LPALCDESTROYCONTEXT qalcDestroyContext;
+extern LPALCGETCURRENTCONTEXT qalcGetCurrentContext;
+extern LPALCGETCONTEXTSDEVICE qalcGetContextsDevice;
+extern LPALCOPENDEVICE qalcOpenDevice;
+extern LPALCCLOSEDEVICE qalcCloseDevice;
+extern LPALCGETERROR qalcGetError;
+extern LPALCISEXTENSIONPRESENT qalcIsExtensionPresent;
+extern LPALCGETPROCADDRESS qalcGetProcAddress;
+extern LPALCGETENUMVALUE qalcGetEnumValue;
+extern LPALCGETSTRING qalcGetString;
+extern LPALCGETINTEGERV qalcGetIntegerv;
+extern LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice;
+extern LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice;
+extern LPALCCAPTURESTART qalcCaptureStart;
+extern LPALCCAPTURESTOP qalcCaptureStop;
+extern LPALCCAPTURESAMPLES qalcCaptureSamples;
+#else
+#define qalEnable alEnable
+#define qalDisable alDisable
+#define qalIsEnabled alIsEnabled
+#define qalGetString alGetString
+#define qalGetBooleanv alGetBooleanv
+#define qalGetIntegerv alGetIntegerv
+#define qalGetFloatv alGetFloatv
+#define qalGetDoublev alGetDoublev
+#define qalGetBoolean alGetBoolean
+#define qalGetInteger alGetInteger
+#define qalGetFloat alGetFloat
+#define qalGetDouble alGetDouble
+#define qalGetError alGetError
+#define qalIsExtensionPresent alIsExtensionPresent
+#define qalGetProcAddress alGetProcAddress
+#define qalGetEnumValue alGetEnumValue
+#define qalListenerf alListenerf
+#define qalListener3f alListener3f
+#define qalListenerfv alListenerfv
+#define qalListeneri alListeneri
+#define qalListener3i alListener3i
+#define qalListeneriv alListeneriv
+#define qalGetListenerf alGetListenerf
+#define qalGetListener3f alGetListener3f
+#define qalGetListenerfv alGetListenerfv
+#define qalGetListeneri alGetListeneri
+#define qalGetListener3i alGetListener3i
+#define qalGetListeneriv alGetListeneriv
+#define qalGenSources alGenSources
+#define qalDeleteSources alDeleteSources
+#define qalIsSource alIsSource
+#define qalSourcef alSourcef
+#define qalSource3f alSource3f
+#define qalSourcefv alSourcefv
+#define qalSourcei alSourcei
+#define qalSource3i alSource3i
+#define qalSourceiv alSourceiv
+#define qalGetSourcef alGetSourcef
+#define qalGetSource3f alGetSource3f
+#define qalGetSourcefv alGetSourcefv
+#define qalGetSourcei alGetSourcei
+#define qalGetSource3i alGetSource3i
+#define qalGetSourceiv alGetSourceiv
+#define qalSourcePlayv alSourcePlayv
+#define qalSourceStopv alSourceStopv
+#define qalSourceRewindv alSourceRewindv
+#define qalSourcePausev alSourcePausev
+#define qalSourcePlay alSourcePlay
+#define qalSourceStop alSourceStop
+#define qalSourceRewind alSourceRewind
+#define qalSourcePause alSourcePause
+#define qalSourceQueueBuffers alSourceQueueBuffers
+#define qalSourceUnqueueBuffers alSourceUnqueueBuffers
+#define qalGenBuffers alGenBuffers
+#define qalDeleteBuffers alDeleteBuffers
+#define qalIsBuffer alIsBuffer
+#define qalBufferData alBufferData
+#define qalBufferf alBufferf
+#define qalBuffer3f alBuffer3f
+#define qalBufferfv alBufferfv
+#define qalBufferi alBufferi
+#define qalBuffer3i alBuffer3i
+#define qalBufferiv alBufferiv
+#define qalGetBufferf alGetBufferf
+#define qalGetBuffer3f alGetBuffer3f
+#define qalGetBufferfv alGetBufferfv
+#define qalGetBufferi alGetBufferi
+#define qalGetBuffer3i alGetBuffer3i
+#define qalGetBufferiv alGetBufferiv
+#define qalDopplerFactor alDopplerFactor
+#define qalDopplerVelocity alDopplerVelocity
+#define qalSpeedOfSound alSpeedOfSound
+#define qalDistanceModel alDistanceModel
+
+#define qalcCreateContext alcCreateContext
+#define qalcMakeContextCurrent alcMakeContextCurrent
+#define qalcProcessContext alcProcessContext
+#define qalcSuspendContext alcSuspendContext
+#define qalcDestroyContext alcDestroyContext
+#define qalcGetCurrentContext alcGetCurrentContext
+#define qalcGetContextsDevice alcGetContextsDevice
+#define qalcOpenDevice alcOpenDevice
+#define qalcCloseDevice alcCloseDevice
+#define qalcGetError alcGetError
+#define qalcIsExtensionPresent alcIsExtensionPresent
+#define qalcGetProcAddress alcGetProcAddress
+#define qalcGetEnumValue alcGetEnumValue
+#define qalcGetString alcGetString
+#define qalcGetIntegerv alcGetIntegerv
+#define qalcCaptureOpenDevice alcCaptureOpenDevice
+#define qalcCaptureCloseDevice alcCaptureCloseDevice
+#define qalcCaptureStart alcCaptureStart
+#define qalcCaptureStop alcCaptureStop
+#define qalcCaptureSamples alcCaptureSamples
+#endif
+
+qboolean QAL_Init(const char *libname);
+void QAL_Shutdown( void );
+
+#endif	// __QAL_H__
diff --git a/src/client/snd_adpcm.c b/src/client/snd_adpcm.c
new file mode 100644
index 0000000..89e68f4
--- /dev/null
+++ b/src/client/snd_adpcm.c
@@ -0,0 +1,330 @@
+/***********************************************************
+Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
+Netherlands.
+
+                        All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its 
+documentation for any purpose and without fee is hereby granted, 
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in 
+supporting documentation, and that the names of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+******************************************************************/
+
+/*
+** Intel/DVI ADPCM coder/decoder.
+**
+** The algorithm for this coder was taken from the IMA Compatability Project
+** proceedings, Vol 2, Number 2; May 1992.
+**
+** Version 1.2, 18-Dec-92.
+*/
+
+#include "snd_local.h"
+
+
+/* Intel ADPCM step variation table */
+static int indexTable[16] = {
+    -1, -1, -1, -1, 2, 4, 6, 8,
+    -1, -1, -1, -1, 2, 4, 6, 8,
+};
+
+static int stepsizeTable[89] = {
+    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+   
+void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) {
+    short *inp;			/* Input buffer pointer */
+    signed char *outp;		/* output buffer pointer */
+    int val;			/* Current input sample value */
+    int sign;			/* Current adpcm sign bit */
+    int delta;			/* Current adpcm output value */
+    int diff;			/* Difference between val and sample */
+    int step;			/* Stepsize */
+    int valpred;		/* Predicted output value */
+    int vpdiff;			/* Current change to valpred */
+    int index;			/* Current step change index */
+    int outputbuffer;		/* place to keep previous 4-bit value */
+    int bufferstep;		/* toggle between outputbuffer/output */
+
+    outp = (signed char *)outdata;
+    inp = indata;
+
+    valpred = state->sample;
+    index = state->index;
+    step = stepsizeTable[index];
+    
+	outputbuffer = 0;	// quiet a compiler warning
+    bufferstep = 1;
+
+    for ( ; len > 0 ; len-- ) {
+		val = *inp++;
+
+		/* Step 1 - compute difference with previous value */
+		diff = val - valpred;
+		sign = (diff < 0) ? 8 : 0;
+		if ( sign ) diff = (-diff);
+
+		/* Step 2 - Divide and clamp */
+		/* Note:
+		** This code *approximately* computes:
+		**    delta = diff*4/step;
+		**    vpdiff = (delta+0.5)*step/4;
+		** but in shift step bits are dropped. The net result of this is
+		** that even if you have fast mul/div hardware you cannot put it to
+		** good use since the fixup would be too expensive.
+		*/
+		delta = 0;
+		vpdiff = (step >> 3);
+		
+		if ( diff >= step ) {
+			delta = 4;
+			diff -= step;
+			vpdiff += step;
+		}
+		step >>= 1;
+		if ( diff >= step  ) {
+			delta |= 2;
+			diff -= step;
+			vpdiff += step;
+		}
+		step >>= 1;
+		if ( diff >= step ) {
+			delta |= 1;
+			vpdiff += step;
+		}
+
+		/* Step 3 - Update previous value */
+		if ( sign )
+		  valpred -= vpdiff;
+		else
+		  valpred += vpdiff;
+
+		/* Step 4 - Clamp previous value to 16 bits */
+		if ( valpred > 32767 )
+		  valpred = 32767;
+		else if ( valpred < -32768 )
+		  valpred = -32768;
+
+		/* Step 5 - Assemble value, update index and step values */
+		delta |= sign;
+		
+		index += indexTable[delta];
+		if ( index < 0 ) index = 0;
+		if ( index > 88 ) index = 88;
+		step = stepsizeTable[index];
+
+		/* Step 6 - Output value */
+		if ( bufferstep ) {
+			outputbuffer = (delta << 4) & 0xf0;
+		} else {
+			*outp++ = (delta & 0x0f) | outputbuffer;
+		}
+		bufferstep = !bufferstep;
+    }
+
+    /* Output last step, if needed */
+    if ( !bufferstep )
+      *outp++ = outputbuffer;
+    
+    state->sample = valpred;
+    state->index = index;
+}
+
+
+/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) {
+    signed char *inp;		/* Input buffer pointer */
+    int outp;			/* output buffer pointer */
+    int sign;			/* Current adpcm sign bit */
+    int delta;			/* Current adpcm output value */
+    int step;			/* Stepsize */
+    int valpred;		/* Predicted value */
+    int vpdiff;			/* Current change to valpred */
+    int index;			/* Current step change index */
+    int inputbuffer;		/* place to keep next 4-bit value */
+    int bufferstep;		/* toggle between inputbuffer/input */
+
+    outp = 0;
+    inp = (signed char *)indata;
+
+    valpred = state->sample;
+    index = state->index;
+    step = stepsizeTable[index];
+
+    bufferstep = 0;
+    inputbuffer = 0;	// quiet a compiler warning
+    for ( ; len > 0 ; len-- ) {
+		
+		/* Step 1 - get the delta value */
+		if ( bufferstep ) {
+			delta = inputbuffer & 0xf;
+		} else {
+			inputbuffer = *inp++;
+			delta = (inputbuffer >> 4) & 0xf;
+		}
+		bufferstep = !bufferstep;
+
+		/* Step 2 - Find new index value (for later) */
+		index += indexTable[delta];
+		if ( index < 0 ) index = 0;
+		if ( index > 88 ) index = 88;
+
+		/* Step 3 - Separate sign and magnitude */
+		sign = delta & 8;
+		delta = delta & 7;
+
+		/* Step 4 - Compute difference and new predicted value */
+		/*
+		** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
+		** in adpcm_coder.
+		*/
+		vpdiff = step >> 3;
+		if ( delta & 4 ) vpdiff += step;
+		if ( delta & 2 ) vpdiff += step>>1;
+		if ( delta & 1 ) vpdiff += step>>2;
+
+		if ( sign )
+		  valpred -= vpdiff;
+		else
+		  valpred += vpdiff;
+
+		/* Step 5 - clamp output value */
+		if ( valpred > 32767 )
+		  valpred = 32767;
+		else if ( valpred < -32768 )
+		  valpred = -32768;
+
+		/* Step 6 - Update step value */
+		step = stepsizeTable[index];
+
+		/* Step 7 - Output value */
+		outdata[outp] = valpred;
+		outp++;
+    }
+
+    state->sample = valpred;
+    state->index = index;
+}
+
+
+/*
+====================
+S_AdpcmMemoryNeeded
+
+Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format
+====================
+*/
+int S_AdpcmMemoryNeeded( const wavinfo_t *info ) {
+	float	scale;
+	int		scaledSampleCount;
+	int		sampleMemory;
+	int		blockCount;
+	int		headerMemory;
+
+	// determine scale to convert from input sampling rate to desired sampling rate
+	scale = (float)info->rate / dma.speed;
+
+	// calc number of samples at playback sampling rate
+	scaledSampleCount = info->samples / scale;
+
+	// calc memory need to store those samples using ADPCM at 4 bits per sample
+	sampleMemory = scaledSampleCount / 2;
+
+	// calc number of sample blocks needed of PAINTBUFFER_SIZE
+	blockCount = scaledSampleCount / PAINTBUFFER_SIZE;
+	if( scaledSampleCount % PAINTBUFFER_SIZE ) {
+		blockCount++;
+	}
+
+	// calc memory needed to store the block headers
+	headerMemory = blockCount * sizeof(adpcm_state_t);
+
+	return sampleMemory + headerMemory;
+}
+
+
+/*
+====================
+S_AdpcmGetSamples
+====================
+*/
+void S_AdpcmGetSamples(sndBuffer *chunk, short *to) {
+	adpcm_state_t	state;
+	byte			*out;
+
+	// get the starting state from the block header
+	state.index = chunk->adpcm.index;
+	state.sample = chunk->adpcm.sample;
+
+	out = (byte *)chunk->sndChunk;
+	// get samples
+	S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state );
+}
+
+
+/*
+====================
+S_AdpcmEncodeSound
+====================
+*/
+void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) {
+	adpcm_state_t	state;
+	int				inOffset;
+	int				count;
+	int				n;
+	sndBuffer		*newchunk, *chunk;
+	byte			*out;
+
+	inOffset = 0;
+	count = sfx->soundLength;
+	state.index = 0;
+	state.sample = samples[0];
+
+	chunk = NULL;
+	while( count ) {
+		n = count;
+		if( n > SND_CHUNK_SIZE_BYTE*2 ) {
+			n = SND_CHUNK_SIZE_BYTE*2;
+		}
+
+		newchunk = SND_malloc();
+		if (sfx->soundData == NULL) {
+			sfx->soundData = newchunk;
+		} else {
+			chunk->next = newchunk;
+		}
+		chunk = newchunk;
+
+		// output the header
+		chunk->adpcm.index  = state.index;
+		chunk->adpcm.sample = state.sample;
+
+		out = (byte *)chunk->sndChunk;
+
+		// encode the samples
+		S_AdpcmEncode( samples + inOffset, (char *) out, n, &state );
+
+		inOffset += n;
+		count -= n;
+	}
+}
diff --git a/src/client/snd_codec.c b/src/client/snd_codec.c
new file mode 100644
index 0000000..b11e087
--- /dev/null
+++ b/src/client/snd_codec.c
@@ -0,0 +1,238 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+
+static snd_codec_t *codecs;
+
+/*
+=================
+S_FileExtension
+=================
+*/
+static char *S_FileExtension(const char *fni)
+{
+	// we should search from the ending to the last '/'
+
+	char *fn = (char *) fni + strlen(fni) - 1;
+	char *eptr = NULL;
+
+	while(*fn != '/' && fn != fni)
+	{
+		if(*fn == '.')
+		{
+			eptr = fn;
+			break;
+		}
+		fn--;
+	}
+
+	return eptr;
+}
+
+/*
+=================
+S_FindCodecForFile
+
+Select an appropriate codec for a file based on its extension
+=================
+*/
+static snd_codec_t *S_FindCodecForFile(const char *filename)
+{
+	char *ext = S_FileExtension(filename);
+	snd_codec_t *codec = codecs;
+
+	if(!ext)
+	{
+		// No extension - auto-detect
+		while(codec)
+		{
+			char fn[MAX_QPATH];
+			
+			// 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)
+				return codec;
+
+			// Nope. Next!
+			codec = codec->next;
+		}
+
+		// Nothin'
+		return NULL;
+	}
+
+	while(codec)
+	{
+		if(!Q_stricmp(ext, codec->ext))
+			return codec;
+		codec = codec->next;
+	}
+
+	return NULL;
+}
+
+/*
+=================
+S_CodecInit
+=================
+*/
+void S_CodecInit()
+{
+	codecs = NULL;
+	S_CodecRegister(&wav_codec);
+#ifdef USE_CODEC_VORBIS
+	S_CodecRegister(&ogg_codec);
+#endif
+}
+
+/*
+=================
+S_CodecShutdown
+=================
+*/
+void S_CodecShutdown()
+{
+	codecs = NULL;
+}
+
+/*
+=================
+S_CodecRegister
+=================
+*/
+void S_CodecRegister(snd_codec_t *codec)
+{
+	codec->next = codecs;
+	codecs = codec;
+}
+
+/*
+=================
+S_CodecLoad
+=================
+*/
+void *S_CodecLoad(const char *filename, snd_info_t *info)
+{
+	snd_codec_t *codec;
+	char fn[MAX_QPATH];
+
+	codec = S_FindCodecForFile(filename);
+	if(!codec)
+	{
+		Com_Printf("Unknown extension for %s\n", filename);
+		return NULL;
+	}
+
+	strncpy(fn, filename, sizeof(fn));
+	COM_DefaultExtension(fn, sizeof(fn), codec->ext);
+
+	return codec->load(fn, info);
+}
+
+/*
+=================
+S_CodecOpenStream
+=================
+*/
+snd_stream_t *S_CodecOpenStream(const char *filename)
+{
+	snd_codec_t *codec;
+	char fn[MAX_QPATH];
+
+	codec = S_FindCodecForFile(filename);
+	if(!codec)
+	{
+		Com_Printf("Unknown extension for %s\n", filename);
+		return NULL;
+	}
+
+	strncpy(fn, filename, sizeof(fn));
+	COM_DefaultExtension(fn, sizeof(fn), codec->ext);
+
+	return codec->open(fn);
+}
+
+void S_CodecCloseStream(snd_stream_t *stream)
+{
+	stream->codec->close(stream);
+}
+
+int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+	return stream->codec->read(stream, bytes, buffer);
+}
+
+//=======================================================================
+// Util functions (used by codecs)
+
+/*
+=================
+S_CodecUtilOpen
+=================
+*/
+snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec)
+{
+	snd_stream_t *stream;
+	fileHandle_t hnd;
+	int length;
+
+	// Try to open the file
+	length = FS_FOpenFileRead(filename, &hnd, qtrue);
+	if(!hnd)
+	{
+		Com_Printf("Can't read sound file %s\n", filename);
+		return NULL;
+	}
+
+	// Allocate a stream
+	stream = Z_Malloc(sizeof(snd_stream_t));
+	if(!stream)
+	{
+		FS_FCloseFile(hnd);
+		return NULL;
+	}
+
+	// Copy over, return
+	stream->codec = codec;
+	stream->file = hnd;
+	stream->length = length;
+	return stream;
+}
+
+/*
+=================
+S_CodecUtilClose
+=================
+*/
+void S_CodecUtilClose(snd_stream_t **stream)
+{
+	FS_FCloseFile((*stream)->file);
+	Z_Free(*stream);
+	*stream = NULL;
+}
diff --git a/src/client/snd_codec.h b/src/client/snd_codec.h
new file mode 100644
index 0000000..5497a7c
--- /dev/null
+++ b/src/client/snd_codec.h
@@ -0,0 +1,99 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#ifndef _SND_CODEC_H_
+#define _SND_CODEC_H_
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+
+typedef struct snd_info_s
+{
+	int rate;
+	int width;
+	int channels;
+	int samples;
+	int size;
+	int dataofs;
+} snd_info_t;
+
+typedef struct snd_codec_s snd_codec_t;
+
+typedef struct snd_stream_s
+{
+	snd_codec_t *codec;
+	fileHandle_t file;
+	snd_info_t info;
+	int length;
+	int pos;
+	void *ptr;
+} snd_stream_t;
+
+// Codec functions
+typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info);
+typedef snd_stream_t *(*CODEC_OPEN)(const char *filename);
+typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer);
+typedef void (*CODEC_CLOSE)(snd_stream_t *stream);
+
+// Codec data structure
+struct snd_codec_s
+{
+	char *ext;
+	CODEC_LOAD load;
+	CODEC_OPEN open;
+	CODEC_READ read;
+	CODEC_CLOSE close;
+	snd_codec_t *next;
+};
+
+// Codec management
+void S_CodecInit( void );
+void S_CodecShutdown( void );
+void S_CodecRegister(snd_codec_t *codec);
+void *S_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_CodecOpenStream(const char *filename);
+void S_CodecCloseStream(snd_stream_t *stream);
+int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+
+// Util functions (used by codecs)
+snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec);
+void S_CodecUtilClose(snd_stream_t **stream);
+
+// WAV Codec
+extern snd_codec_t wav_codec;
+void *S_WAV_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_WAV_CodecOpenStream(const char *filename);
+void S_WAV_CodecCloseStream(snd_stream_t *stream);
+int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+
+// Ogg Vorbis codec
+#ifdef USE_CODEC_VORBIS
+extern snd_codec_t ogg_codec;
+void *S_OGG_CodecLoad(const char *filename, snd_info_t *info);
+snd_stream_t *S_OGG_CodecOpenStream(const char *filename);
+void S_OGG_CodecCloseStream(snd_stream_t *stream);
+int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer);
+#endif // USE_CODEC_VORBIS
+
+#endif // !_SND_CODEC_H_
diff --git a/src/client/snd_codec_ogg.c b/src/client/snd_codec_ogg.c
new file mode 100644
index 0000000..0ca2f68
--- /dev/null
+++ b/src/client/snd_codec_ogg.c
@@ -0,0 +1,479 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+Copyright (C) 2005-2006 Joerg Dietrich <dietrich_joerg@gmx.de>
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+// OGG support is enabled by this define
+#ifdef USE_CODEC_VORBIS
+
+// includes for the Q3 sound system
+#include "client.h"
+#include "snd_codec.h"
+
+// includes for the OGG codec
+#include <errno.h>
+#define OV_EXCLUDE_STATIC_CALLBACKS
+#include <vorbis/vorbisfile.h>
+
+// The OGG codec can return the samples in a number of different formats,
+// we use the standard signed short format.
+#define OGG_SAMPLEWIDTH 2
+
+// Q3 OGG codec
+snd_codec_t ogg_codec =
+{
+	".ogg",
+	S_OGG_CodecLoad,
+	S_OGG_CodecOpenStream,
+	S_OGG_CodecReadStream,
+	S_OGG_CodecCloseStream,
+	NULL
+};
+
+// callbacks for vobisfile
+
+// fread() replacement
+size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+	snd_stream_t *stream;
+	int byteSize = 0;
+	int bytesRead = 0;
+	size_t nMembRead = 0;
+
+	// check if input is valid
+	if(!ptr)
+	{
+		errno = EFAULT; 
+		return 0;
+	}
+	
+	if(!(size && nmemb))
+	{
+		// It's not an error, caller just wants zero bytes!
+		errno = 0;
+		return 0;
+	}
+ 
+	if(!datasource)
+	{
+		errno = EBADF; 
+		return 0;
+	}
+	
+	// we use a snd_stream_t in the generic pointer to pass around
+	stream = (snd_stream_t *) datasource;
+
+	// FS_Read does not support multi-byte elements
+	byteSize = nmemb * size;
+
+	// read it with the Q3 function FS_Read()
+	bytesRead = FS_Read(ptr, byteSize, stream->file);
+
+	// update the file position
+	stream->pos += bytesRead;
+
+	// this function returns the number of elements read not the number of bytes
+	nMembRead = bytesRead / size;
+
+	// even if the last member is only read partially
+	// it is counted as a whole in the return value	
+	if(bytesRead % size)
+	{
+		nMembRead++;
+	}
+	
+	return nMembRead;
+}
+
+// fseek() replacement
+int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence)
+{
+	snd_stream_t *stream;
+	int retVal = 0;
+
+	// check if input is valid
+	if(!datasource)
+	{
+		errno = EBADF; 
+		return -1;
+	}
+
+	// snd_stream_t in the generic pointer
+	stream = (snd_stream_t *) datasource;
+
+	// we must map the whence to its Q3 counterpart
+	switch(whence)
+	{
+		case SEEK_SET :
+		{
+			// set the file position in the actual file with the Q3 function
+			retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET);
+
+			// something has gone wrong, so we return here
+			if(retVal < 0)
+			{
+			 return retVal;
+			}
+
+			// keep track of file position
+			stream->pos = (int) offset;
+			break;
+		}
+  
+		case SEEK_CUR :
+		{
+			// set the file position in the actual file with the Q3 function
+			retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR);
+
+			// something has gone wrong, so we return here
+			if(retVal < 0)
+			{
+			 return retVal;
+			}
+
+			// keep track of file position
+			stream->pos += (int) offset;
+			break;
+		}
+ 
+		case SEEK_END :
+		{
+			// Quake 3 seems to have trouble with FS_SEEK_END 
+			// so we use the file length and FS_SEEK_SET
+
+			// set the file position in the actual file with the Q3 function
+			retVal = FS_Seek(stream->file, (long) stream->length + (long) offset, FS_SEEK_SET);
+
+			// something has gone wrong, so we return here
+			if(retVal < 0)
+			{
+			 return retVal;
+			}
+
+			// keep track of file position
+			stream->pos = stream->length + (int) offset;
+			break;
+		}
+  
+		default :
+		{
+			// unknown whence, so we return an error
+			errno = EINVAL;
+			return -1;
+		}
+	}
+
+	// stream->pos shouldn't be smaller than zero or bigger than the filesize
+	stream->pos = (stream->pos < 0) ? 0 : stream->pos;
+	stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos;
+
+	return 0;
+}
+
+// fclose() replacement
+int S_OGG_Callback_close(void *datasource)
+{
+	// we do nothing here and close all things manually in S_OGG_CodecCloseStream()
+	return 0;
+}
+
+// ftell() replacement
+long S_OGG_Callback_tell(void *datasource)
+{
+	snd_stream_t   *stream;
+
+	// check if input is valid
+	if(!datasource)
+	{
+		errno = EBADF;
+		return -1;
+	}
+
+	// snd_stream_t in the generic pointer
+	stream = (snd_stream_t *) datasource;
+
+	return (long) FS_FTell(stream->file);
+}
+
+// the callback structure
+const ov_callbacks S_OGG_Callbacks =
+{
+ &S_OGG_Callback_read,
+ &S_OGG_Callback_seek,
+ &S_OGG_Callback_close,
+ &S_OGG_Callback_tell
+};
+
+/*
+=================
+S_OGG_CodecOpenStream
+=================
+*/
+snd_stream_t *S_OGG_CodecOpenStream(const char *filename)
+{
+	snd_stream_t *stream;
+
+	// OGG codec control structure
+	OggVorbis_File *vf;
+
+	// some variables used to get informations about the OGG 
+	vorbis_info *OGGInfo;
+	ogg_int64_t numSamples;
+
+	// check if input is valid
+	if(!filename)
+	{
+		return NULL;
+	}
+
+	// Open the stream
+	stream = S_CodecUtilOpen(filename, &ogg_codec);
+	if(!stream)
+	{
+		return NULL;
+	}
+
+	// alloctate the OggVorbis_File
+	vf = Z_Malloc(sizeof(OggVorbis_File));
+	if(!vf)
+	{
+		S_CodecUtilClose(&stream);
+
+		return NULL;
+	}
+
+	// open the codec with our callbacks and stream as the generic pointer
+	if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0)
+	{
+		Z_Free(vf);
+
+		S_CodecUtilClose(&stream);
+
+		return NULL;
+	}
+
+	// the stream must be seekable
+	if(!ov_seekable(vf))
+	{
+		ov_clear(vf);
+
+		Z_Free(vf);
+
+		S_CodecUtilClose(&stream);
+
+		return NULL;
+	}
+ 
+	// we only support OGGs with one substream
+	if(ov_streams(vf) != 1)
+	{
+		ov_clear(vf);
+
+		Z_Free(vf);
+
+		S_CodecUtilClose(&stream);
+
+		return NULL;  
+	}
+
+	// get the info about channels and rate
+	OGGInfo = ov_info(vf, 0);
+	if(!OGGInfo)
+	{
+		ov_clear(vf);
+
+		Z_Free(vf);
+
+		S_CodecUtilClose(&stream);
+
+		return NULL;  
+	}
+
+	// get the number of sample-frames in the OGG
+	numSamples = ov_pcm_total(vf, 0);
+
+	// fill in the info-structure in the stream
+	stream->info.rate = OGGInfo->rate;
+	stream->info.width = OGG_SAMPLEWIDTH;
+	stream->info.channels = OGGInfo->channels;
+	stream->info.samples = numSamples;
+	stream->info.size = stream->info.samples * stream->info.channels * stream->info.width;
+	stream->info.dataofs = 0;
+
+	// We use stream->pos for the file pointer in the compressed ogg file 
+	stream->pos = 0;
+	
+	// We use the generic pointer in stream for the OGG codec control structure
+	stream->ptr = vf;
+
+	return stream;
+}
+
+/*
+=================
+S_OGG_CodecCloseStream
+=================
+*/
+void S_OGG_CodecCloseStream(snd_stream_t *stream)
+{
+	// check if input is valid
+	if(!stream)
+	{
+		return;
+	}
+	
+	// let the OGG codec cleanup its stuff
+	ov_clear((OggVorbis_File *) stream->ptr);
+
+	// free the OGG codec control struct
+	Z_Free(stream->ptr);
+
+	// close the stream
+	S_CodecUtilClose(&stream);
+}
+
+/*
+=================
+S_OGG_CodecReadStream
+=================
+*/
+int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+	// buffer handling
+	int bytesRead, bytesLeft, c;
+	char *bufPtr;
+	
+	// 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))
+	{
+		return 0;
+	}
+
+	if(bytes <= 0)
+	{
+		return 0;
+	}
+
+	bytesRead = 0;
+	bytesLeft = bytes;
+	bufPtr = buffer;
+
+	// cycle until we have the requested or all available bytes read
+	while(-1)
+	{
+		// read some bytes from the OGG codec
+		c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS);
+		
+		// no more bytes are left
+		if(c <= 0)
+		{
+			break;
+		}
+
+		bytesRead += c;
+		bytesLeft -= c;
+		bufPtr += c;
+  
+		// we have enough bytes
+		if(bytesLeft <= 0)
+		{
+			break;
+		}
+	}
+
+	return bytesRead;
+}
+
+/*
+=====================================================================
+S_OGG_CodecLoad
+
+We handle S_OGG_CodecLoad as a special case of the streaming functions 
+where we read the whole stream at once.
+======================================================================
+*/
+void *S_OGG_CodecLoad(const char *filename, snd_info_t *info)
+{
+	snd_stream_t *stream;
+	byte *buffer;
+	int bytesRead;
+	
+	// check if input is valid
+	if(!(filename && info))
+	{
+		return NULL;
+	}
+	
+	// open the file as a stream
+	stream = S_OGG_CodecOpenStream(filename);
+	if(!stream)
+	{
+		return NULL;
+	}
+	
+	// copy over the info
+	info->rate = stream->info.rate;
+	info->width = stream->info.width;
+	info->channels = stream->info.channels;
+	info->samples = stream->info.samples;
+	info->size = stream->info.size;
+	info->dataofs = stream->info.dataofs;
+
+	// allocate a buffer
+	// this buffer must be free-ed by the caller of this function
+    	buffer = Z_Malloc(info->size);
+	if(!buffer)
+	{
+		S_OGG_CodecCloseStream(stream);
+	
+		return NULL;	
+	}
+
+	// fill the buffer
+	bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer);
+	
+	// we don't even have read a single byte
+	if(bytesRead <= 0)
+	{
+		Z_Free(buffer);
+		S_OGG_CodecCloseStream(stream);
+
+		return NULL;	
+	}
+
+	S_OGG_CodecCloseStream(stream);
+	
+	return buffer;
+}
+
+#endif // USE_CODEC_VORBIS
diff --git a/src/client/snd_codec_wav.c b/src/client/snd_codec_wav.c
new file mode 100644
index 0000000..dce561a
--- /dev/null
+++ b/src/client/snd_codec_wav.c
@@ -0,0 +1,295 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+
+/*
+=================
+FGetLittleLong
+=================
+*/
+static int FGetLittleLong( fileHandle_t f ) {
+	int		v;
+
+	FS_Read( &v, sizeof(v), f );
+
+	return LittleLong( v);
+}
+
+/*
+=================
+FGetLittleShort
+=================
+*/
+static short FGetLittleShort( fileHandle_t f ) {
+	short	v;
+
+	FS_Read( &v, sizeof(v), f );
+
+	return LittleShort( v);
+}
+
+/*
+=================
+S_ReadChunkInfo
+=================
+*/
+static int S_ReadChunkInfo(fileHandle_t f, char *name)
+{
+	int len, r;
+
+	name[4] = 0;
+
+	r = FS_Read(name, 4, f);
+	if(r != 4)
+		return -1;
+
+	len = FGetLittleLong(f);
+	if( len < 0 ) {
+		Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" );
+		return -1;
+	}
+
+	return len;
+}
+
+/*
+=================
+S_FindRIFFChunk
+
+Returns the length of the data in the chunk, or -1 if not found
+=================
+*/
+static int S_FindRIFFChunk( fileHandle_t f, char *chunk ) {
+	char	name[5];
+	int		len;
+
+	while( ( len = S_ReadChunkInfo(f, name) ) >= 0 )
+	{
+		// If this is the right chunk, return
+		if( !Q_strncmp( name, chunk, 4 ) )
+			return len;
+
+		len = PAD( len, 2 );
+
+		// Not the right chunk - skip it
+		FS_Seek( f, len, FS_SEEK_CUR );
+	}
+
+	return -1;
+}
+
+/*
+=================
+S_ByteSwapRawSamples
+=================
+*/
+static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
+	int		i;
+
+	if ( width != 2 ) {
+		return;
+	}
+	if ( LittleShort( 256 ) == 256 ) {
+		return;
+	}
+
+	if ( s_channels == 2 ) {
+		samples <<= 1;
+	}
+	for ( i = 0 ; i < samples ; i++ ) {
+		((short *)data)[i] = LittleShort( ((short *)data)[i] );
+	}
+}
+
+/*
+=================
+S_ReadRIFFHeader
+=================
+*/
+static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info)
+{
+	char dump[16];
+	int wav_format;
+	int bits;
+	int fmtlen = 0;
+
+	// skip the riff wav header
+	FS_Read(dump, 12, file);
+
+	// Scan for the format chunk
+	if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0)
+	{
+		Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n");
+		return qfalse;
+	}
+
+	// Save the parameters
+	wav_format = FGetLittleShort(file);
+	info->channels = FGetLittleShort(file);
+	info->rate = FGetLittleLong(file);
+	FGetLittleLong(file);
+	FGetLittleShort(file);
+	bits = FGetLittleShort(file);
+
+	if( bits < 8 )
+	{
+	  Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n");
+	  return qfalse;
+	}
+
+	info->width = bits / 8;
+	info->dataofs = 0;
+
+	// Skip the rest of the format chunk if required
+	if(fmtlen > 16)
+	{
+		fmtlen -= 16;
+		FS_Seek( file, fmtlen, FS_SEEK_CUR );
+	}
+
+	// Scan for the data chunk
+	if( (info->size = S_FindRIFFChunk(file, "data")) < 0)
+	{
+		Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n");
+		return qfalse;
+	}
+	info->samples = (info->size / info->width) / info->channels;
+
+	return qtrue;
+}
+
+// WAV codec
+snd_codec_t wav_codec =
+{
+	".wav",
+	S_WAV_CodecLoad,
+	S_WAV_CodecOpenStream,
+	S_WAV_CodecReadStream,
+	S_WAV_CodecCloseStream,
+	NULL
+};
+
+/*
+=================
+S_WAV_CodecLoad
+=================
+*/
+void *S_WAV_CodecLoad(const char *filename, snd_info_t *info)
+{
+	fileHandle_t file;
+	void *buffer;
+
+	// Try to open the file
+	FS_FOpenFileRead(filename, &file, qtrue);
+	if(!file)
+	{
+		Com_Printf( S_COLOR_RED "ERROR: Could not open \"%s\"\n",
+				filename);
+		return NULL;
+	}
+
+	// Read the RIFF header
+	if(!S_ReadRIFFHeader(file, info))
+	{
+		FS_FCloseFile(file);
+		Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n",
+				filename);
+		return NULL;
+	}
+
+	// Allocate some memory
+	buffer = Z_Malloc(info->size);
+	if(!buffer)
+	{
+		FS_FCloseFile(file);
+		Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n",
+				filename);
+		return NULL;
+	}
+
+	// Read, byteswap
+	FS_Read(buffer, info->size, file);
+	S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer);
+
+	// Close and return
+	FS_FCloseFile(file);
+	return buffer;
+}
+
+/*
+=================
+S_WAV_CodecOpenStream
+=================
+*/
+snd_stream_t *S_WAV_CodecOpenStream(const char *filename)
+{
+	snd_stream_t *rv;
+
+	// Open
+	rv = S_CodecUtilOpen(filename, &wav_codec);
+	if(!rv)
+		return NULL;
+
+	// Read the RIFF header
+	if(!S_ReadRIFFHeader(rv->file, &rv->info))
+	{
+		S_CodecUtilClose(&rv);
+		return NULL;
+	}
+
+	return rv;
+}
+
+/*
+=================
+S_WAV_CodecCloseStream
+=================
+*/
+void S_WAV_CodecCloseStream(snd_stream_t *stream)
+{
+	S_CodecUtilClose(&stream);
+}
+
+/*
+=================
+S_WAV_CodecReadStream
+=================
+*/
+int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
+{
+	int remaining = stream->info.size - stream->pos;
+	int samples;
+
+	if(remaining <= 0)
+		return 0;
+	if(bytes > remaining)
+		bytes = remaining;
+	stream->pos += bytes;
+	samples = (bytes / stream->info.width) / stream->info.channels;
+	FS_Read(buffer, bytes, stream->file);
+	S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, buffer);
+	return bytes;
+}
diff --git a/src/client/snd_dma.c b/src/client/snd_dma.c
new file mode 100644
index 0000000..5d53533
--- /dev/null
+++ b/src/client/snd_dma.c
@@ -0,0 +1,1558 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name:		snd_dma.c
+ *
+ * desc:		main control for any streaming sound output device
+ *
+ * $Archive: /MissionPack/code/client/snd_dma.c $
+ *
+ *****************************************************************************/
+
+#include "snd_local.h"
+#include "snd_codec.h"
+#include "client.h"
+
+void S_Update_( void );
+void S_Base_StopAllSounds(void);
+void S_Base_StopBackgroundTrack( void );
+
+snd_stream_t	*s_backgroundStream = NULL;
+static char		s_backgroundLoop[MAX_QPATH];
+//static char		s_backgroundMusic[MAX_QPATH]; //TTimo: unused
+
+
+// =======================================================================
+// Internal sound data & structures
+// =======================================================================
+
+// only begin attenuating sound volumes when outside the FULLVOLUME range
+#define		SOUND_FULLVOLUME	80
+
+#define		SOUND_ATTENUATE		0.0008f
+
+channel_t   s_channels[MAX_CHANNELS];
+channel_t   loop_channels[MAX_CHANNELS];
+int			numLoopChannels;
+
+static int	s_soundStarted;
+static		qboolean	s_soundMuted;
+
+dma_t		dma;
+
+static int			listener_number;
+static vec3_t		listener_origin;
+static vec3_t		listener_axis[3];
+
+int			s_soundtime;		// sample PAIRS
+int   		s_paintedtime; 		// sample PAIRS
+
+// MAX_SFX may be larger than MAX_SOUNDS because
+// of custom player sounds
+#define		MAX_SFX			4096
+sfx_t		s_knownSfx[MAX_SFX];
+int			s_numSfx = 0;
+
+#define		LOOP_HASH		128
+static	sfx_t		*sfxHash[LOOP_HASH];
+
+cvar_t		*s_testsound;
+cvar_t		*s_khz;
+cvar_t		*s_show;
+cvar_t		*s_mixahead;
+cvar_t		*s_mixPreStep;
+
+static loopSound_t		loopSounds[MAX_GENTITIES];
+static	channel_t		*freelist = NULL;
+
+int						s_rawend[MAX_RAW_STREAMS];
+portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
+
+
+// ====================================================================
+// User-setable variables
+// ====================================================================
+
+
+void S_Base_SoundInfo(void) {	
+	Com_Printf("----- Sound Info -----\n" );
+	if (!s_soundStarted) {
+		Com_Printf ("sound system not started\n");
+	} else {
+		Com_Printf("%5d stereo\n", dma.channels - 1);
+		Com_Printf("%5d samples\n", dma.samples);
+		Com_Printf("%5d samplebits\n", dma.samplebits);
+		Com_Printf("%5d submission_chunk\n", dma.submission_chunk);
+		Com_Printf("%5d speed\n", dma.speed);
+		Com_Printf("%p dma buffer\n", dma.buffer);
+		if ( s_backgroundStream ) {
+			Com_Printf("Background file: %s\n", s_backgroundLoop );
+		} else {
+			Com_Printf("No background file.\n" );
+		}
+
+	}
+	Com_Printf("----------------------\n" );
+}
+
+
+#ifdef USE_VOIP
+static
+void S_Base_StartCapture( void )
+{
+	// !!! FIXME: write me.
+}
+
+static
+int S_Base_AvailableCaptureSamples( void )
+{
+	// !!! FIXME: write me.
+	return 0;
+}
+
+static
+void S_Base_Capture( int samples, byte *data )
+{
+	// !!! FIXME: write me.
+}
+
+static
+void S_Base_StopCapture( void )
+{
+	// !!! FIXME: write me.
+}
+
+static
+void S_Base_MasterGain( float val )
+{
+	// !!! FIXME: write me.
+}
+#endif
+
+
+
+/*
+=================
+S_Base_SoundList
+=================
+*/
+void S_Base_SoundList( void ) {
+	int		i;
+	sfx_t	*sfx;
+	int		size, total;
+	char	type[4][16];
+	char	mem[2][16];
+
+	strcpy(type[0], "16bit");
+	strcpy(type[1], "adpcm");
+	strcpy(type[2], "daub4");
+	strcpy(type[3], "mulaw");
+	strcpy(mem[0], "paged out");
+	strcpy(mem[1], "resident ");
+	total = 0;
+	for (sfx=s_knownSfx, i=0 ; i<s_numSfx ; i++, sfx++) {
+		size = sfx->soundLength;
+		total += size;
+		Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod],
+				sfx->soundName, mem[sfx->inMemory] );
+	}
+	Com_Printf ("Total resident: %i\n", total);
+	S_DisplayFreeMemory();
+}
+
+
+
+void S_ChannelFree(channel_t *v) {
+	v->thesfx = NULL;
+	*(channel_t **)v = freelist;
+	freelist = (channel_t*)v;
+}
+
+channel_t*	S_ChannelMalloc( void ) {
+	channel_t *v;
+	if (freelist == NULL) {
+		return NULL;
+	}
+	v = freelist;
+	freelist = *(channel_t **)freelist;
+	v->allocTime = Com_Milliseconds();
+	return v;
+}
+
+void S_ChannelSetup( void ) {
+	channel_t *p, *q;
+
+	// clear all the sounds so they don't
+	Com_Memset( s_channels, 0, sizeof( s_channels ) );
+
+	p = s_channels;;
+	q = p + MAX_CHANNELS;
+	while (--q > p) {
+		*(channel_t **)q = q-1;
+	}
+	
+	*(channel_t **)q = NULL;
+	freelist = p + MAX_CHANNELS - 1;
+	Com_DPrintf("Channel memory manager started\n");
+}
+
+
+
+// =======================================================================
+// Load a sound
+// =======================================================================
+
+/*
+================
+return a hash value for the sfx name
+================
+*/
+static long S_HashSFXName(const char *name) {
+	int		i;
+	long	hash;
+	char	letter;
+
+	hash = 0;
+	i = 0;
+	while (name[i] != '\0') {
+		letter = tolower(name[i]);
+		if (letter =='.') break;				// don't include extension
+		if (letter =='\\') letter = '/';		// damn path names
+		hash+=(long)(letter)*(i+119);
+		i++;
+	}
+	hash &= (LOOP_HASH-1);
+	return hash;
+}
+
+/*
+==================
+S_FindName
+
+Will allocate a new sfx if it isn't found
+==================
+*/
+static sfx_t *S_FindName( const char *name ) {
+	int		i;
+	int		hash;
+
+	sfx_t	*sfx;
+
+	if (!name) {
+		Com_Error (ERR_FATAL, "S_FindName: NULL\n");
+	}
+	if (!name[0]) {
+		Com_Error (ERR_FATAL, "S_FindName: empty name\n");
+	}
+
+	if (strlen(name) >= MAX_QPATH) {
+		Com_Error (ERR_FATAL, "Sound name too long: %s", name);
+	}
+
+	hash = S_HashSFXName(name);
+
+	sfx = sfxHash[hash];
+	// see if already loaded
+	while (sfx) {
+		if (!Q_stricmp(sfx->soundName, name) ) {
+			return sfx;
+		}
+		sfx = sfx->next;
+	}
+
+	// find a free sfx
+	for (i=0 ; i < s_numSfx ; i++) {
+		if (!s_knownSfx[i].soundName[0]) {
+			break;
+		}
+	}
+
+	if (i == s_numSfx) {
+		if (s_numSfx == MAX_SFX) {
+			Com_Error (ERR_FATAL, "S_FindName: out of sfx_t");
+		}
+		s_numSfx++;
+	}
+	
+	sfx = &s_knownSfx[i];
+	Com_Memset (sfx, 0, sizeof(*sfx));
+	strcpy (sfx->soundName, name);
+
+	sfx->next = sfxHash[hash];
+	sfxHash[hash] = sfx;
+
+	return sfx;
+}
+
+/*
+=================
+S_DefaultSound
+=================
+*/
+void S_DefaultSound( sfx_t *sfx ) {
+	
+	int		i;
+
+	sfx->soundLength = 512;
+	sfx->soundData = SND_malloc();
+	sfx->soundData->next = NULL;
+
+
+	for ( i = 0 ; i < sfx->soundLength ; i++ ) {
+		sfx->soundData->sndChunk[i] = i;
+	}
+}
+
+/*
+===================
+S_DisableSounds
+
+Disables sounds until the next S_BeginRegistration.
+This is called when the hunk is cleared and the sounds
+are no longer valid.
+===================
+*/
+void S_Base_DisableSounds( void ) {
+	S_Base_StopAllSounds();
+	s_soundMuted = qtrue;
+}
+
+/*
+==================
+S_RegisterSound
+
+Creates a default buzz sound if the file can't be loaded
+==================
+*/
+sfxHandle_t	S_Base_RegisterSound( const char *name, qboolean compressed ) {
+	sfx_t	*sfx;
+
+	compressed = qfalse;
+	if (!s_soundStarted) {
+		return 0;
+	}
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		Com_Printf( "Sound name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+	sfx = S_FindName( name );
+	if ( sfx->soundData ) {
+		if ( sfx->defaultSound ) {
+			Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
+			return 0;
+		}
+		return sfx - s_knownSfx;
+	}
+
+	sfx->inMemory = qfalse;
+	sfx->soundCompressed = compressed;
+
+  S_memoryLoad(sfx);
+
+	if ( sfx->defaultSound ) {
+		Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
+		return 0;
+	}
+
+	return sfx - s_knownSfx;
+}
+
+/*
+==================
+S_Base_SoundDuration
+==================
+*/
+static int S_Base_SoundDuration( sfxHandle_t handle ) {
+	if ( handle < 0 || handle >= s_numSfx ) {
+		Com_Printf( S_COLOR_YELLOW "S_Base_SoundDuration: handle %i out of range\n", handle );
+		return 0;
+	}
+	return s_knownSfx[ handle ].duration;
+}
+
+
+
+/*
+=====================
+S_BeginRegistration
+
+=====================
+*/
+void S_Base_BeginRegistration( void ) {
+	s_soundMuted = qfalse;		// we can play again
+
+	if (s_numSfx == 0) {
+		SND_setup();
+
+		s_numSfx = 0;
+		Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) );
+		Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
+
+		S_Base_RegisterSound("sound/feedback/hit.wav", qfalse);		// changed to a sound in baseq3
+	}
+}
+
+void S_memoryLoad(sfx_t	*sfx) {
+	// load the sound file
+	if ( !S_LoadSound ( sfx ) ) {
+//		Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName );
+		sfx->defaultSound = qtrue;
+	}
+	sfx->inMemory = qtrue;
+}
+
+//=============================================================================
+
+/*
+=================
+S_SpatializeOrigin
+
+Used for spatializing s_channels
+=================
+*/
+void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol)
+{
+    vec_t		dot;
+    vec_t		dist;
+    vec_t		lscale, rscale, scale;
+    vec3_t		source_vec;
+    vec3_t		vec;
+
+	const float dist_mult = SOUND_ATTENUATE;
+	
+	// calculate stereo seperation and distance attenuation
+	VectorSubtract(origin, listener_origin, source_vec);
+
+	dist = VectorNormalize(source_vec);
+	dist -= SOUND_FULLVOLUME;
+	if (dist < 0)
+		dist = 0;			// close enough to be at full volume
+	dist *= dist_mult;		// different attenuation levels
+	
+	VectorRotate( source_vec, listener_axis, vec );
+
+	dot = -vec[1];
+
+	if (dma.channels == 1)
+	{ // no attenuation = no spatialization
+		rscale = 1.0;
+		lscale = 1.0;
+	}
+	else
+	{
+		rscale = 0.5 * (1.0 + dot);
+		lscale = 0.5 * (1.0 - dot);
+		if ( rscale < 0 ) {
+			rscale = 0;
+		}
+		if ( lscale < 0 ) {
+			lscale = 0;
+		}
+	}
+
+	// add in distance effect
+	scale = (1.0 - dist) * rscale;
+	*right_vol = (master_vol * scale);
+	if (*right_vol < 0)
+		*right_vol = 0;
+
+	scale = (1.0 - dist) * lscale;
+	*left_vol = (master_vol * scale);
+	if (*left_vol < 0)
+		*left_vol = 0;
+}
+
+// =======================================================================
+// Start a sound effect
+// =======================================================================
+
+/*
+====================
+S_StartSound
+
+Validates the parms and ques the sound up
+if pos is NULL, the sound will be dynamically sourced from the entity
+Entchannel 0 will never override a playing sound
+====================
+*/
+void S_Base_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
+	channel_t	*ch;
+	sfx_t		*sfx;
+  int i, oldest, chosen, time;
+  int	inplay, allowed;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) {
+		Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum );
+	}
+
+	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+		Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle );
+		return;
+	}
+
+	sfx = &s_knownSfx[ sfxHandle ];
+
+	if (sfx->inMemory == qfalse) {
+		S_memoryLoad(sfx);
+	}
+
+	if ( s_show->integer == 1 ) {
+		Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName );
+	}
+
+	time = Com_Milliseconds();
+
+//	Com_Printf("playing %s\n", sfx->soundName);
+	// pick a channel to play on
+
+	allowed = 4;
+	if (entityNum == listener_number) {
+		allowed = 8;
+	}
+
+	ch = s_channels;
+	inplay = 0;
+	for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {		
+		if (ch->entnum == entityNum && ch->thesfx == sfx) {
+			if (time - ch->allocTime < 50) {
+//				if (Cvar_VariableValue( "cg_showmiss" )) {
+//					Com_Printf("double sound start\n");
+//				}
+				return;
+			}
+			inplay++;
+		}
+	}
+
+	if (inplay>allowed) {
+		return;
+	}
+
+	sfx->lastTimeUsed = time;
+
+	ch = S_ChannelMalloc();	// entityNum, entchannel);
+	if (!ch) {
+		ch = s_channels;
+
+		oldest = sfx->lastTimeUsed;
+		chosen = -1;
+		for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+			if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
+				oldest = ch->allocTime;
+				chosen = i;
+			}
+		}
+		if (chosen == -1) {
+			ch = s_channels;
+			for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+				if (ch->entnum != listener_number && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
+					oldest = ch->allocTime;
+					chosen = i;
+				}
+			}
+			if (chosen == -1) {
+				ch = s_channels;
+				if (ch->entnum == listener_number) {
+					for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+						if (ch->allocTime<oldest) {
+							oldest = ch->allocTime;
+							chosen = i;
+						}
+					}
+				}
+				if (chosen == -1) {
+					Com_DPrintf("dropping sound\n");
+					return;
+				}
+			}
+		}
+		ch = &s_channels[chosen];
+		ch->allocTime = sfx->lastTimeUsed;
+	}
+
+	if (origin) {
+		VectorCopy (origin, ch->origin);
+		ch->fixed_origin = qtrue;
+	} else {
+		ch->fixed_origin = qfalse;
+	}
+
+	ch->master_vol = 127;
+	ch->entnum = entityNum;
+	ch->thesfx = sfx;
+	ch->startSample = START_SAMPLE_IMMEDIATE;
+	ch->entchannel = entchannel;
+	ch->leftvol = ch->master_vol;		// these will get calced at next spatialize
+	ch->rightvol = ch->master_vol;		// unless the game isn't running
+	ch->doppler = qfalse;
+}
+
+
+/*
+==================
+S_StartLocalSound
+==================
+*/
+void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+		Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle );
+		return;
+	}
+
+	S_Base_StartSound (NULL, listener_number, channelNum, sfxHandle );
+}
+
+
+/*
+==================
+S_ClearSoundBuffer
+
+If we are about to perform file access, clear the buffer
+so sound doesn't stutter.
+==================
+*/
+void S_Base_ClearSoundBuffer( void ) {
+	int		clear;
+		
+	if (!s_soundStarted)
+		return;
+
+	// stop looping sounds
+	Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t));
+	Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t));
+	numLoopChannels = 0;
+
+	S_ChannelSetup();
+
+	Com_Memset(s_rawend, '\0', sizeof (s_rawend));
+
+	if (dma.samplebits == 8)
+		clear = 0x80;
+	else
+		clear = 0;
+
+	SNDDMA_BeginPainting ();
+	if (dma.buffer)
+		Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
+	SNDDMA_Submit ();
+}
+
+/*
+==================
+S_StopAllSounds
+==================
+*/
+void S_Base_StopAllSounds(void) {
+	if ( !s_soundStarted ) {
+		return;
+	}
+
+	// stop the background music
+	S_Base_StopBackgroundTrack();
+
+	S_Base_ClearSoundBuffer ();
+}
+
+/*
+==============================================================
+
+continuous looping sounds are added each frame
+
+==============================================================
+*/
+
+void S_Base_StopLoopingSound(int entityNum) {
+	loopSounds[entityNum].active = qfalse;
+//	loopSounds[entityNum].sfx = 0;
+	loopSounds[entityNum].kill = qfalse;
+}
+
+/*
+==================
+S_ClearLoopingSounds
+
+==================
+*/
+void S_Base_ClearLoopingSounds( qboolean killall ) {
+	int i;
+	for ( i = 0 ; i < MAX_GENTITIES ; i++) {
+		if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) {
+			loopSounds[i].kill = qfalse;
+			S_Base_StopLoopingSound(i);
+		}
+	}
+	numLoopChannels = 0;
+}
+
+/*
+==================
+S_AddLoopingSound
+
+Called during entity generation for a frame
+Include velocity in case I get around to doing doppler...
+==================
+*/
+void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
+	sfx_t *sfx;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+		Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle );
+		return;
+	}
+
+	sfx = &s_knownSfx[ sfxHandle ];
+
+	if (sfx->inMemory == qfalse) {
+		S_memoryLoad(sfx);
+	}
+
+	if ( !sfx->soundLength ) {
+		Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
+	}
+
+	VectorCopy( origin, loopSounds[entityNum].origin );
+	VectorCopy( velocity, loopSounds[entityNum].velocity );
+	loopSounds[entityNum].active = qtrue;
+	loopSounds[entityNum].kill = qtrue;
+	loopSounds[entityNum].doppler = qfalse;
+	loopSounds[entityNum].oldDopplerScale = 1.0;
+	loopSounds[entityNum].dopplerScale = 1.0;
+	loopSounds[entityNum].sfx = sfx;
+
+	if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) {
+		vec3_t	out;
+		float	lena, lenb;
+
+		loopSounds[entityNum].doppler = qtrue;
+		lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin);
+		VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out);
+		lenb = DistanceSquared(loopSounds[listener_number].origin, out);
+		if ((loopSounds[entityNum].framenum+1) != cls.framecount) {
+			loopSounds[entityNum].oldDopplerScale = 1.0;
+		} else {
+			loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale;
+		}
+		loopSounds[entityNum].dopplerScale = lenb/(lena*100);
+		if (loopSounds[entityNum].dopplerScale<=1.0) {
+			loopSounds[entityNum].doppler = qfalse;			// don't bother doing the math
+		} else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) {
+			loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE;
+		}
+	}
+
+	loopSounds[entityNum].framenum = cls.framecount;
+}
+
+/*
+==================
+S_AddLoopingSound
+
+Called during entity generation for a frame
+Include velocity in case I get around to doing doppler...
+==================
+*/
+void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
+	sfx_t *sfx;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
+		Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle );
+		return;
+	}
+
+	sfx = &s_knownSfx[ sfxHandle ];
+
+	if (sfx->inMemory == qfalse) {
+		S_memoryLoad(sfx);
+	}
+
+	if ( !sfx->soundLength ) {
+		Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
+	}
+	VectorCopy( origin, loopSounds[entityNum].origin );
+	VectorCopy( velocity, loopSounds[entityNum].velocity );
+	loopSounds[entityNum].sfx = sfx;
+	loopSounds[entityNum].active = qtrue;
+	loopSounds[entityNum].kill = qfalse;
+	loopSounds[entityNum].doppler = qfalse;
+}
+
+
+
+/*
+==================
+S_AddLoopSounds
+
+Spatialize all of the looping sounds.
+All sounds are on the same cycle, so any duplicates can just
+sum up the channel multipliers.
+==================
+*/
+void S_AddLoopSounds (void) {
+	int			i, j, time;
+	int			left_total, right_total, left, right;
+	channel_t	*ch;
+	loopSound_t	*loop, *loop2;
+	static int	loopFrame;
+
+
+	numLoopChannels = 0;
+
+	time = Com_Milliseconds();
+
+	loopFrame++;
+	for ( i = 0 ; i < MAX_GENTITIES ; i++) {
+		loop = &loopSounds[i];
+		if ( !loop->active || loop->mergeFrame == loopFrame ) {
+			continue;	// already merged into an earlier sound
+		}
+
+		if (loop->kill) {
+			S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total);			// 3d
+		} else {
+			S_SpatializeOrigin( loop->origin, 90,  &left_total, &right_total);			// sphere
+		}
+
+		loop->sfx->lastTimeUsed = time;
+
+		for (j=(i+1); j< MAX_GENTITIES ; j++) {
+			loop2 = &loopSounds[j];
+			if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) {
+				continue;
+			}
+			loop2->mergeFrame = loopFrame;
+
+			if (loop2->kill) {
+				S_SpatializeOrigin( loop2->origin, 127, &left, &right);				// 3d
+			} else {
+				S_SpatializeOrigin( loop2->origin, 90,  &left, &right);				// sphere
+			}
+
+			loop2->sfx->lastTimeUsed = time;
+			left_total += left;
+			right_total += right;
+		}
+		if (left_total == 0 && right_total == 0) {
+			continue;		// not audible
+		}
+
+		// allocate a channel
+		ch = &loop_channels[numLoopChannels];
+		
+		if (left_total > 255) {
+			left_total = 255;
+		}
+		if (right_total > 255) {
+			right_total = 255;
+		}
+		
+		ch->master_vol = 127;
+		ch->leftvol = left_total;
+		ch->rightvol = right_total;
+		ch->thesfx = loop->sfx;
+		ch->doppler = loop->doppler;
+		ch->dopplerScale = loop->dopplerScale;
+		ch->oldDopplerScale = loop->oldDopplerScale;
+		numLoopChannels++;
+		if (numLoopChannels == MAX_CHANNELS) {
+			return;
+		}
+	}
+}
+
+//=============================================================================
+
+/*
+=================
+S_ByteSwapRawSamples
+
+If raw data has been loaded in little endien binary form, this must be done.
+If raw data was calculated, as with ADPCM, this should not be called.
+=================
+*/
+void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
+	int		i;
+
+	if ( width != 2 ) {
+		return;
+	}
+	if ( LittleShort( 256 ) == 256 ) {
+		return;
+	}
+
+	if ( s_channels == 2 ) {
+		samples <<= 1;
+	}
+	for ( i = 0 ; i < samples ; i++ ) {
+		((short *)data)[i] = LittleShort( ((short *)data)[i] );
+	}
+}
+
+/*
+============
+S_Base_RawSamples
+
+Music streaming
+============
+*/
+void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume ) {
+	int		i;
+	int		src, dst;
+	float	scale;
+	int		intVolume;
+	portable_samplepair_t *rawsamples;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) {
+		return;
+	}
+	rawsamples = s_rawsamples[stream];
+
+	if(s_muted->integer)
+		intVolume = 0;
+	else
+		intVolume = 256 * volume * s_volume->value;
+
+	if ( s_rawend[stream] < s_soundtime ) {
+		Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime );
+		s_rawend[stream] = s_soundtime;
+	}
+
+	scale = (float)rate / dma.speed;
+
+//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
+	if (s_channels == 2 && width == 2)
+	{
+		if (scale == 1.0)
+		{	// optimized case
+			for (i=0 ; i<samples ; i++)
+			{
+				dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+				s_rawend[stream]++;
+				rawsamples[dst].left = ((short *)data)[i*2] * intVolume;
+				rawsamples[dst].right = ((short *)data)[i*2+1] * intVolume;
+			}
+		}
+		else
+		{
+			for (i=0 ; ; i++)
+			{
+				src = i*scale;
+				if (src >= samples)
+					break;
+				dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+				s_rawend[stream]++;
+				rawsamples[dst].left = ((short *)data)[src*2] * intVolume;
+				rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume;
+			}
+		}
+	}
+	else if (s_channels == 1 && width == 2)
+	{
+		for (i=0 ; ; i++)
+		{
+			src = i*scale;
+			if (src >= samples)
+				break;
+			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+			s_rawend[stream]++;
+			rawsamples[dst].left = ((short *)data)[src] * intVolume;
+			rawsamples[dst].right = ((short *)data)[src] * intVolume;
+		}
+	}
+	else if (s_channels == 2 && width == 1)
+	{
+		intVolume *= 256;
+
+		for (i=0 ; ; i++)
+		{
+			src = i*scale;
+			if (src >= samples)
+				break;
+			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+			s_rawend[stream]++;
+			rawsamples[dst].left = ((char *)data)[src*2] * intVolume;
+			rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume;
+		}
+	}
+	else if (s_channels == 1 && width == 1)
+	{
+		intVolume *= 256;
+
+		for (i=0 ; ; i++)
+		{
+			src = i*scale;
+			if (src >= samples)
+				break;
+			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+			s_rawend[stream]++;
+			rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume;
+			rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume;
+		}
+	}
+
+	if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
+		Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
+	}
+}
+
+//=============================================================================
+
+/*
+=====================
+S_UpdateEntityPosition
+
+let the sound system know where an entity currently is
+======================
+*/
+void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) {
+	if ( entityNum < 0 || entityNum > MAX_GENTITIES ) {
+		Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
+	}
+	VectorCopy( origin, loopSounds[entityNum].origin );
+}
+
+
+/*
+============
+S_Respatialize
+
+Change the volumes of all the playing sounds for changes in their positions
+============
+*/
+void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) {
+	int			i;
+	channel_t	*ch;
+	vec3_t		origin;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	listener_number = entityNum;
+	VectorCopy(head, listener_origin);
+	VectorCopy(axis[0], listener_axis[0]);
+	VectorCopy(axis[1], listener_axis[1]);
+	VectorCopy(axis[2], listener_axis[2]);
+
+	// update spatialization for dynamic sounds	
+	ch = s_channels;
+	for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
+		if ( !ch->thesfx ) {
+			continue;
+		}
+		// anything coming from the view entity will always be full volume
+		if (ch->entnum == listener_number) {
+			ch->leftvol = ch->master_vol;
+			ch->rightvol = ch->master_vol;
+		} else {
+			if (ch->fixed_origin) {
+				VectorCopy( ch->origin, origin );
+			} else {
+				VectorCopy( loopSounds[ ch->entnum ].origin, origin );
+			}
+
+			S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol);
+		}
+	}
+
+	// add loopsounds
+	S_AddLoopSounds ();
+}
+
+
+/*
+========================
+S_ScanChannelStarts
+
+Returns qtrue if any new sounds were started since the last mix
+========================
+*/
+qboolean S_ScanChannelStarts( void ) {
+	channel_t		*ch;
+	int				i;
+	qboolean		newSamples;
+
+	newSamples = qfalse;
+	ch = s_channels;
+
+	for (i=0; i<MAX_CHANNELS ; i++, ch++) {
+		if ( !ch->thesfx ) {
+			continue;
+		}
+		// if this channel was just started this frame,
+		// set the sample count to it begins mixing
+		// into the very first sample
+		if ( ch->startSample == START_SAMPLE_IMMEDIATE ) {
+			ch->startSample = s_paintedtime;
+			newSamples = qtrue;
+			continue;
+		}
+
+		// if it is completely finished by now, clear it
+		if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) {
+			S_ChannelFree(ch);
+		}
+	}
+
+	return newSamples;
+}
+
+/*
+============
+S_Update
+
+Called once each time through the main loop
+============
+*/
+void S_Base_Update( void ) {
+	int			i;
+	int			total;
+	channel_t	*ch;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+//		Com_DPrintf ("not started or muted\n");
+		return;
+	}
+
+	//
+	// debugging output
+	//
+	if ( s_show->integer == 2 ) {
+		total = 0;
+		ch = s_channels;
+		for (i=0 ; i<MAX_CHANNELS; i++, ch++) {
+			if (ch->thesfx && (ch->leftvol || ch->rightvol) ) {
+				Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName);
+				total++;
+			}
+		}
+		
+		Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime);
+	}
+
+	// add raw data from streamed samples
+	S_UpdateBackgroundTrack();
+
+	// mix some sound
+	S_Update_();
+}
+
+void S_GetSoundtime(void)
+{
+	int		samplepos;
+	static	int		buffers;
+	static	int		oldsamplepos;
+	int		fullsamples;
+	
+	fullsamples = dma.samples / dma.channels;
+
+	if( CL_VideoRecording( ) )
+	{
+		s_soundtime += (int)ceil( dma.speed / cl_aviFrameRate->value );
+		return;
+	}
+
+	// it is possible to miscount buffers if it has wrapped twice between
+	// calls to S_Update.  Oh well.
+	samplepos = SNDDMA_GetDMAPos();
+	if (samplepos < oldsamplepos)
+	{
+		buffers++;					// buffer wrapped
+		
+		if (s_paintedtime > 0x40000000)
+		{	// time to chop things off to avoid 32 bit limits
+			buffers = 0;
+			s_paintedtime = fullsamples;
+			S_Base_StopAllSounds ();
+		}
+	}
+	oldsamplepos = samplepos;
+
+	s_soundtime = buffers*fullsamples + samplepos/dma.channels;
+
+#if 0
+// check to make sure that we haven't overshot
+	if (s_paintedtime < s_soundtime)
+	{
+		Com_DPrintf ("S_Update_ : overflow\n");
+		s_paintedtime = s_soundtime;
+	}
+#endif
+
+	if ( dma.submission_chunk < 256 ) {
+		s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed;
+	} else {
+		s_paintedtime = s_soundtime + dma.submission_chunk;
+	}
+}
+
+
+void S_Update_(void) {
+	unsigned        endtime;
+	int				samps;
+	static			float	lastTime = 0.0f;
+	float			ma, op;
+	float			thisTime, sane;
+	static			int ot = -1;
+
+	if ( !s_soundStarted || s_soundMuted ) {
+		return;
+	}
+
+	thisTime = Com_Milliseconds();
+
+	// Updates s_soundtime
+	S_GetSoundtime();
+
+	if (s_soundtime == ot) {
+		return;
+	}
+	ot = s_soundtime;
+
+	// clear any sound effects that end before the current time,
+	// and start any new sounds
+	S_ScanChannelStarts();
+
+	sane = thisTime - lastTime;
+	if (sane<11) {
+		sane = 11;			// 85hz
+	}
+
+	ma = s_mixahead->value * dma.speed;
+	op = s_mixPreStep->value + sane*dma.speed*0.01;
+
+	if (op < ma) {
+		ma = op;
+	}
+
+	// mix ahead of current position
+	endtime = s_soundtime + ma;
+
+	// mix to an even submission block size
+	endtime = (endtime + dma.submission_chunk-1)
+		& ~(dma.submission_chunk-1);
+
+	// never mix more than the complete buffer
+	samps = dma.samples >> (dma.channels-1);
+	if (endtime - s_soundtime > samps)
+		endtime = s_soundtime + samps;
+
+
+
+	SNDDMA_BeginPainting ();
+
+	S_PaintChannels (endtime);
+
+	SNDDMA_Submit ();
+
+	lastTime = thisTime;
+}
+
+
+
+/*
+===============================================================================
+
+background music functions
+
+===============================================================================
+*/
+
+/*
+======================
+S_StopBackgroundTrack
+======================
+*/
+void S_Base_StopBackgroundTrack( void ) {
+	if(!s_backgroundStream)
+		return;
+	S_CodecCloseStream(s_backgroundStream);
+	s_backgroundStream = NULL;
+	s_rawend[0] = 0;
+}
+
+/*
+======================
+S_StartBackgroundTrack
+======================
+*/
+void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){
+	if ( !intro ) {
+		intro = "";
+	}
+	if ( !loop || !loop[0] ) {
+		loop = intro;
+	}
+	Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );
+
+	if(!*intro)
+	{
+		S_Base_StopBackgroundTrack();
+		return;
+	}
+
+	if( !loop ) {
+		s_backgroundLoop[0] = 0;
+	} else {
+		Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
+	}
+
+	// close the background track, but DON'T reset s_rawend
+	// if restarting the same back ground track
+	if(s_backgroundStream)
+	{
+		S_CodecCloseStream(s_backgroundStream);
+		s_backgroundStream = NULL;
+	}
+
+	// Open stream
+	s_backgroundStream = S_CodecOpenStream(intro);
+	if(!s_backgroundStream) {
+		Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", intro );
+		return;
+	}
+
+	if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) {
+		Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", intro );
+	}
+}
+
+/*
+======================
+S_UpdateBackgroundTrack
+======================
+*/
+void S_UpdateBackgroundTrack( void ) {
+	int		bufferSamples;
+	int		fileSamples;
+	byte	raw[30000];		// just enough to fit in a mac stack frame
+	int		fileBytes;
+	int		r;
+
+	if(!s_backgroundStream) {
+		return;
+	}
+
+	// don't bother playing anything if musicvolume is 0
+	if ( s_musicVolume->value <= 0 ) {
+		return;
+	}
+
+	// see how many samples should be copied into the raw buffer
+	if ( s_rawend[0] < s_soundtime ) {
+		s_rawend[0] = s_soundtime;
+	}
+
+	while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
+		bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);
+
+		// decide how much data needs to be read from the file
+		fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;
+
+		if (!fileSamples)
+			return;
+
+		// our max buffer size
+		fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+		if ( fileBytes > sizeof(raw) ) {
+			fileBytes = sizeof(raw);
+			fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+		}
+
+		// Read
+		r = S_CodecReadStream(s_backgroundStream, fileBytes, raw);
+		if(r < fileBytes)
+		{
+			fileBytes = r;
+			fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
+		}
+
+		if(r > 0)
+		{
+			// add to raw buffer
+			S_Base_RawSamples( 0, fileSamples, s_backgroundStream->info.rate,
+				s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value );
+		}
+		else
+		{
+			// loop
+			if(s_backgroundLoop[0])
+			{
+				S_CodecCloseStream(s_backgroundStream);
+				s_backgroundStream = NULL;
+				S_Base_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop );
+				if(!s_backgroundStream)
+					return;
+			}
+			else
+			{
+				S_Base_StopBackgroundTrack();
+				return;
+			}
+		}
+
+	}
+}
+
+
+/*
+======================
+S_FreeOldestSound
+======================
+*/
+
+void S_FreeOldestSound( void ) {
+	int	i, oldest, used;
+	sfx_t	*sfx;
+	sndBuffer	*buffer, *nbuffer;
+
+	oldest = Com_Milliseconds();
+	used = 0;
+
+	for (i=1 ; i < s_numSfx ; i++) {
+		sfx = &s_knownSfx[i];
+		if (sfx->inMemory && sfx->lastTimeUsed<oldest) {
+			used = i;
+			oldest = sfx->lastTimeUsed;
+		}
+	}
+
+	sfx = &s_knownSfx[used];
+
+	Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName);
+
+	buffer = sfx->soundData;
+	while(buffer != NULL) {
+		nbuffer = buffer->next;
+		SND_free(buffer);
+		buffer = nbuffer;
+	}
+	sfx->inMemory = qfalse;
+	sfx->soundData = NULL;
+}
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+
+void S_Base_Shutdown( void ) {
+	if ( !s_soundStarted ) {
+		return;
+	}
+
+	SNDDMA_Shutdown();
+
+	s_soundStarted = 0;
+
+	Cmd_RemoveCommand("s_info");
+}
+
+/*
+================
+S_Init
+================
+*/
+qboolean S_Base_Init( soundInterface_t *si ) {
+	qboolean	r;
+
+	if( !si ) {
+		return qfalse;
+	}
+
+	s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE);
+	s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
+	s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE);
+	s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT);
+	s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT);
+
+	r = SNDDMA_Init();
+
+	if ( r ) {
+		s_soundStarted = 1;
+		s_soundMuted = 1;
+//		s_numSfx = 0;
+
+		Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
+
+		s_soundtime = 0;
+		s_paintedtime = 0;
+
+		S_Base_StopAllSounds( );
+	} else {
+		return qfalse;
+	}
+
+	si->Shutdown = S_Base_Shutdown;
+	si->StartSound = S_Base_StartSound;
+	si->StartLocalSound = S_Base_StartLocalSound;
+	si->StartBackgroundTrack = S_Base_StartBackgroundTrack;
+	si->StopBackgroundTrack = S_Base_StopBackgroundTrack;
+	si->RawSamples = S_Base_RawSamples;
+	si->StopAllSounds = S_Base_StopAllSounds;
+	si->ClearLoopingSounds = S_Base_ClearLoopingSounds;
+	si->AddLoopingSound = S_Base_AddLoopingSound;
+	si->AddRealLoopingSound = S_Base_AddRealLoopingSound;
+	si->StopLoopingSound = S_Base_StopLoopingSound;
+	si->Respatialize = S_Base_Respatialize;
+	si->UpdateEntityPosition = S_Base_UpdateEntityPosition;
+	si->Update = S_Base_Update;
+	si->DisableSounds = S_Base_DisableSounds;
+	si->BeginRegistration = S_Base_BeginRegistration;
+	si->RegisterSound = S_Base_RegisterSound;
+	si->SoundDuration = S_Base_SoundDuration;
+	si->ClearSoundBuffer = S_Base_ClearSoundBuffer;
+	si->SoundInfo = S_Base_SoundInfo;
+	si->SoundList = S_Base_SoundList;
+
+#ifdef USE_VOIP
+	si->StartCapture = S_Base_StartCapture;
+	si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples;
+	si->Capture = S_Base_Capture;
+	si->StopCapture = S_Base_StopCapture;
+	si->MasterGain = S_Base_MasterGain;
+#endif
+
+	return qtrue;
+}
diff --git a/src/client/snd_local.h b/src/client/snd_local.h
new file mode 100644
index 0000000..53ddbca
--- /dev/null
+++ b/src/client/snd_local.h
@@ -0,0 +1,254 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// snd_local.h -- private sound definations
+
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qcommon.h"
+#include "snd_public.h"
+
+#define	PAINTBUFFER_SIZE		4096					// this is in samples
+
+#define SND_CHUNK_SIZE			1024					// samples
+#define SND_CHUNK_SIZE_FLOAT	(SND_CHUNK_SIZE/2)		// floats
+#define SND_CHUNK_SIZE_BYTE		(SND_CHUNK_SIZE*2)		// floats
+
+typedef struct {
+	int			left;	// the final values will be clamped to +/- 0x00ffff00 and shifted down
+	int			right;
+} portable_samplepair_t;
+
+typedef struct adpcm_state {
+    short	sample;		/* Previous output value */
+    char	index;		/* Index into stepsize table */
+} adpcm_state_t;
+
+typedef	struct sndBuffer_s {
+	short					sndChunk[SND_CHUNK_SIZE];
+	struct sndBuffer_s		*next;
+    int						size;
+	adpcm_state_t			adpcm;
+} sndBuffer;
+
+typedef struct sfx_s {
+	sndBuffer		*soundData;
+	qboolean		defaultSound;			// couldn't be loaded, so use buzz
+	qboolean		inMemory;				// not in Memory
+	qboolean		soundCompressed;		// not in Memory
+	int				soundCompressionMethod;	
+	int 			soundLength;
+	char 			soundName[MAX_QPATH];
+	int				lastTimeUsed;
+	int				duration;
+	struct sfx_s	*next;
+} sfx_t;
+
+typedef struct {
+	int			channels;
+	int			samples;				// mono samples in buffer
+	int			submission_chunk;		// don't mix less than this #
+	int			samplebits;
+	int			speed;
+	byte		*buffer;
+} dma_t;
+
+#define START_SAMPLE_IMMEDIATE	0x7fffffff
+
+#define MAX_DOPPLER_SCALE 50.0f //arbitrary
+
+typedef struct loopSound_s {
+	vec3_t		origin;
+	vec3_t		velocity;
+	sfx_t		*sfx;
+	int			mergeFrame;
+	qboolean	active;
+	qboolean	kill;
+	qboolean	doppler;
+	float		dopplerScale;
+	float		oldDopplerScale;
+	int			framenum;
+} loopSound_t;
+
+typedef struct
+{
+	int			allocTime;
+	int			startSample;	// START_SAMPLE_IMMEDIATE = set immediately on next mix
+	int			entnum;			// to allow overriding a specific sound
+	int			entchannel;		// to allow overriding a specific sound
+	int			leftvol;		// 0-255 volume after spatialization
+	int			rightvol;		// 0-255 volume after spatialization
+	int			master_vol;		// 0-255 volume before spatialization
+	float		dopplerScale;
+	float		oldDopplerScale;
+	vec3_t		origin;			// only use if fixed_origin is set
+	qboolean	fixed_origin;	// use origin instead of fetching entnum's origin
+	sfx_t		*thesfx;		// sfx structure
+	qboolean	doppler;
+} channel_t;
+
+
+#define	WAV_FORMAT_PCM		1
+
+
+typedef struct {
+	int			format;
+	int			rate;
+	int			width;
+	int			channels;
+	int			samples;
+	int			dataofs;		// chunk starts this many bytes from file start
+} wavinfo_t;
+
+// Interface between Q3 sound "api" and the sound backend
+typedef struct
+{
+	void (*Shutdown)(void);
+	void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx );
+	void (*StartLocalSound)( sfxHandle_t sfx, int channelNum );
+	void (*StartBackgroundTrack)( const char *intro, const char *loop );
+	void (*StopBackgroundTrack)( void );
+	void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume);
+	void (*StopAllSounds)( void );
+	void (*ClearLoopingSounds)( qboolean killall );
+	void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+	void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+	void (*StopLoopingSound)(int entityNum );
+	void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+	void (*UpdateEntityPosition)( int entityNum, const vec3_t origin );
+	void (*Update)( void );
+	void (*DisableSounds)( void );
+	void (*BeginRegistration)( void );
+	sfxHandle_t (*RegisterSound)( const char *sample, qboolean compressed );
+	int  (*SoundDuration)( sfxHandle_t handle );
+	void (*ClearSoundBuffer)( void );
+	void (*SoundInfo)( void );
+	void (*SoundList)( void );
+#ifdef USE_VOIP
+	void (*StartCapture)( void );
+	int (*AvailableCaptureSamples)( void );
+	void (*Capture)( int samples, byte *data );
+	void (*StopCapture)( void );
+	void (*MasterGain)( float gain );
+#endif
+} soundInterface_t;
+
+
+/*
+====================================================================
+
+  SYSTEM SPECIFIC FUNCTIONS
+
+====================================================================
+*/
+
+// initializes cycling through a DMA buffer and returns information on it
+qboolean SNDDMA_Init(void);
+
+// gets the current DMA position
+int		SNDDMA_GetDMAPos(void);
+
+// shutdown the DMA xfer.
+void	SNDDMA_Shutdown(void);
+
+void	SNDDMA_BeginPainting (void);
+
+void	SNDDMA_Submit(void);
+
+//====================================================================
+
+#define	MAX_CHANNELS			96
+
+extern	channel_t   s_channels[MAX_CHANNELS];
+extern	channel_t   loop_channels[MAX_CHANNELS];
+extern	int		numLoopChannels;
+
+extern	int		s_paintedtime;
+extern	vec3_t	listener_forward;
+extern	vec3_t	listener_right;
+extern	vec3_t	listener_up;
+extern	dma_t	dma;
+
+#define	MAX_RAW_SAMPLES	16384
+#define MAX_RAW_STREAMS 128
+extern	portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
+extern	int		s_rawend[MAX_RAW_STREAMS];
+
+extern cvar_t *s_volume;
+extern cvar_t *s_musicVolume;
+extern cvar_t *s_muted;
+extern cvar_t *s_doppler;
+
+extern cvar_t *s_testsound;
+
+qboolean S_LoadSound( sfx_t *sfx );
+
+void		SND_free(sndBuffer *v);
+sndBuffer*	SND_malloc( void );
+void		SND_setup( void );
+
+void S_PaintChannels(int endtime);
+
+void S_memoryLoad(sfx_t *sfx);
+
+// spatializes a channel
+void S_Spatialize(channel_t *ch);
+
+// adpcm functions
+int  S_AdpcmMemoryNeeded( const wavinfo_t *info );
+void S_AdpcmEncodeSound( sfx_t *sfx, short *samples );
+void S_AdpcmGetSamples(sndBuffer *chunk, short *to);
+
+// wavelet function
+
+#define SENTINEL_MULAW_ZERO_RUN 127
+#define SENTINEL_MULAW_FOUR_BIT_RUN 126
+
+void S_FreeOldestSound( void );
+
+#define	NXStream byte
+
+void encodeWavelet(sfx_t *sfx, short *packets);
+void decodeWavelet( sndBuffer *stream, short *packets);
+
+void encodeMuLaw( sfx_t *sfx, short *packets);
+extern short mulawToShort[256];
+
+extern short *sfxScratchBuffer;
+extern sfx_t *sfxScratchPointer;
+extern int	   sfxScratchIndex;
+
+qboolean S_Base_Init( soundInterface_t *si );
+
+// OpenAL stuff
+typedef enum
+{
+	SRCPRI_AMBIENT = 0,	// Ambient sound effects
+	SRCPRI_ENTITY,			// Entity sound effects
+	SRCPRI_ONESHOT,			// One-shot sounds
+	SRCPRI_LOCAL,				// Local sounds
+	SRCPRI_STREAM				// Streams (music, cutscenes)
+} alSrcPriority_t;
+
+typedef int srcHandle_t;
+
+qboolean S_AL_Init( soundInterface_t *si );
diff --git a/src/client/snd_main.c b/src/client/snd_main.c
new file mode 100644
index 0000000..22213cc
--- /dev/null
+++ b/src/client/snd_main.c
@@ -0,0 +1,562 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "client.h"
+#include "snd_codec.h"
+#include "snd_local.h"
+#include "snd_public.h"
+
+cvar_t *s_volume;
+cvar_t *s_muted;
+cvar_t *s_musicVolume;
+cvar_t *s_doppler;
+cvar_t *s_backend;
+cvar_t *s_muteWhenMinimized;
+cvar_t *s_muteWhenUnfocused;
+
+static soundInterface_t si;
+
+/*
+=================
+S_ValidateInterface
+=================
+*/
+static qboolean S_ValidSoundInterface( soundInterface_t *si )
+{
+	if( !si->Shutdown ) return qfalse;
+	if( !si->StartSound ) return qfalse;
+	if( !si->StartLocalSound ) return qfalse;
+	if( !si->StartBackgroundTrack ) return qfalse;
+	if( !si->StopBackgroundTrack ) return qfalse;
+	if( !si->RawSamples ) return qfalse;
+	if( !si->StopAllSounds ) return qfalse;
+	if( !si->ClearLoopingSounds ) return qfalse;
+	if( !si->AddLoopingSound ) return qfalse;
+	if( !si->AddRealLoopingSound ) return qfalse;
+	if( !si->StopLoopingSound ) return qfalse;
+	if( !si->Respatialize ) return qfalse;
+	if( !si->UpdateEntityPosition ) return qfalse;
+	if( !si->Update ) return qfalse;
+	if( !si->DisableSounds ) return qfalse;
+	if( !si->BeginRegistration ) return qfalse;
+	if( !si->RegisterSound ) return qfalse;
+	if( !si->SoundDuration ) return qfalse;
+	if( !si->ClearSoundBuffer ) return qfalse;
+	if( !si->SoundInfo ) return qfalse;
+	if( !si->SoundList ) return qfalse;
+
+#ifdef USE_VOIP
+	if( !si->StartCapture ) return qfalse;
+	if( !si->AvailableCaptureSamples ) return qfalse;
+	if( !si->Capture ) return qfalse;
+	if( !si->StopCapture ) return qfalse;
+	if( !si->MasterGain ) return qfalse;
+#endif
+
+	return qtrue;
+}
+
+/*
+=================
+S_StartSound
+=================
+*/
+void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx )
+{
+	if( si.StartSound ) {
+		si.StartSound( origin, entnum, entchannel, sfx );
+	}
+}
+
+/*
+=================
+S_StartLocalSound
+=================
+*/
+void S_StartLocalSound( sfxHandle_t sfx, int channelNum )
+{
+	if( si.StartLocalSound ) {
+		si.StartLocalSound( sfx, channelNum );
+	}
+}
+
+/*
+=================
+S_StartBackgroundTrack
+=================
+*/
+void S_StartBackgroundTrack( const char *intro, const char *loop )
+{
+	if( si.StartBackgroundTrack ) {
+		si.StartBackgroundTrack( intro, loop );
+	}
+}
+
+/*
+=================
+S_StopBackgroundTrack
+=================
+*/
+void S_StopBackgroundTrack( void )
+{
+	if( si.StopBackgroundTrack ) {
+		si.StopBackgroundTrack( );
+	}
+}
+
+/*
+=================
+S_RawSamples
+=================
+*/
+void S_RawSamples (int stream, int samples, int rate, int width, int channels,
+		   const byte *data, float volume)
+{
+	if( si.RawSamples ) {
+		si.RawSamples( stream, samples, rate, width, channels, data, volume );
+	}
+}
+
+/*
+=================
+S_StopAllSounds
+=================
+*/
+void S_StopAllSounds( void )
+{
+	if( si.StopAllSounds ) {
+		si.StopAllSounds( );
+	}
+}
+
+/*
+=================
+S_ClearLoopingSounds
+=================
+*/
+void S_ClearLoopingSounds( qboolean killall )
+{
+	if( si.ClearLoopingSounds ) {
+		si.ClearLoopingSounds( killall );
+	}
+}
+
+/*
+=================
+S_AddLoopingSound
+=================
+*/
+void S_AddLoopingSound( int entityNum, const vec3_t origin,
+		const vec3_t velocity, sfxHandle_t sfx )
+{
+	if( si.AddLoopingSound ) {
+		si.AddLoopingSound( entityNum, origin, velocity, sfx );
+	}
+}
+
+/*
+=================
+S_AddRealLoopingSound
+=================
+*/
+void S_AddRealLoopingSound( int entityNum, const vec3_t origin,
+		const vec3_t velocity, sfxHandle_t sfx )
+{
+	if( si.AddRealLoopingSound ) {
+		si.AddRealLoopingSound( entityNum, origin, velocity, sfx );
+	}
+}
+
+/*
+=================
+S_StopLoopingSound
+=================
+*/
+void S_StopLoopingSound( int entityNum )
+{
+	if( si.StopLoopingSound ) {
+		si.StopLoopingSound( entityNum );
+	}
+}
+
+/*
+=================
+S_Respatialize
+=================
+*/
+void S_Respatialize( int entityNum, const vec3_t origin,
+		vec3_t axis[3], int inwater )
+{
+	if( si.Respatialize ) {
+		si.Respatialize( entityNum, origin, axis, inwater );
+	}
+}
+
+/*
+=================
+S_UpdateEntityPosition
+=================
+*/
+void S_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+	if( si.UpdateEntityPosition ) {
+		si.UpdateEntityPosition( entityNum, origin );
+	}
+}
+
+/*
+=================
+S_Update
+=================
+*/
+void S_Update( void )
+{
+	if(s_muted->integer)
+	{
+		if(!(s_muteWhenMinimized->integer && com_minimized->integer) &&
+		   !(s_muteWhenUnfocused->integer && com_unfocused->integer))
+		{
+			s_muted->integer = qfalse;
+			s_muted->modified = qtrue;
+		}
+	}
+	else
+	{
+		if((s_muteWhenMinimized->integer && com_minimized->integer) ||
+		   (s_muteWhenUnfocused->integer && com_unfocused->integer))
+		{
+			s_muted->integer = qtrue;
+			s_muted->modified = qtrue;
+		}
+	}
+	
+	if( si.Update ) {
+		si.Update( );
+	}
+}
+
+/*
+=================
+S_DisableSounds
+=================
+*/
+void S_DisableSounds( void )
+{
+	if( si.DisableSounds ) {
+		si.DisableSounds( );
+	}
+}
+
+/*
+=================
+S_BeginRegistration
+=================
+*/
+void S_BeginRegistration( void )
+{
+	if( si.BeginRegistration ) {
+		si.BeginRegistration( );
+	}
+}
+
+/*
+=================
+S_RegisterSound
+=================
+*/
+sfxHandle_t	S_RegisterSound( const char *sample, qboolean compressed )
+{
+	if( si.RegisterSound ) {
+		return si.RegisterSound( sample, compressed );
+	} else {
+		return 0;
+	}
+}
+
+/*
+=================
+S_SoundDuration
+=================
+*/
+int S_SoundDuration( sfxHandle_t handle )
+{
+	if( si.SoundDuration )
+		return si.SoundDuration( handle );
+	else
+		return 0;
+}
+
+/*
+=================
+S_ClearSoundBuffer
+=================
+*/
+void S_ClearSoundBuffer( void )
+{
+	if( si.ClearSoundBuffer ) {
+		si.ClearSoundBuffer( );
+	}
+}
+
+/*
+=================
+S_SoundInfo
+=================
+*/
+void S_SoundInfo( void )
+{
+	if( si.SoundInfo ) {
+		si.SoundInfo( );
+	}
+}
+
+/*
+=================
+S_SoundList
+=================
+*/
+void S_SoundList( void )
+{
+	if( si.SoundList ) {
+		si.SoundList( );
+	}
+}
+
+
+#ifdef USE_VOIP
+/*
+=================
+S_StartCapture
+=================
+*/
+void S_StartCapture( void )
+{
+	if( si.StartCapture ) {
+		si.StartCapture( );
+	}
+}
+
+/*
+=================
+S_AvailableCaptureSamples
+=================
+*/
+int S_AvailableCaptureSamples( void )
+{
+	if( si.AvailableCaptureSamples ) {
+		return si.AvailableCaptureSamples( );
+	}
+	return 0;
+}
+
+/*
+=================
+S_Capture
+=================
+*/
+void S_Capture( int samples, byte *data )
+{
+	if( si.Capture ) {
+		si.Capture( samples, data );
+	}
+}
+
+/*
+=================
+S_StopCapture
+=================
+*/
+void S_StopCapture( void )
+{
+	if( si.StopCapture ) {
+		si.StopCapture( );
+	}
+}
+
+/*
+=================
+S_MasterGain
+=================
+*/
+void S_MasterGain( float gain )
+{
+	if( si.MasterGain ) {
+		si.MasterGain( gain );
+	}
+}
+#endif
+
+//=============================================================================
+
+/*
+=================
+S_Play_f
+=================
+*/
+void S_Play_f( void ) {
+	int 		i;
+	sfxHandle_t	h;
+	char		name[256];
+
+	if( !si.RegisterSound || !si.StartLocalSound ) {
+		return;
+	}
+
+	i = 1;
+	while ( i<Cmd_Argc() ) {
+		Q_strncpyz( name, Cmd_Argv(i), sizeof(name) );
+		h = si.RegisterSound( name, qfalse );
+
+		if( h ) {
+			si.StartLocalSound( h, CHAN_LOCAL_SOUND );
+		}
+		i++;
+	}
+}
+
+/*
+=================
+S_Music_f
+=================
+*/
+void S_Music_f( void ) {
+	int		c;
+
+	if( !si.StartBackgroundTrack ) {
+		return;
+	}
+
+	c = Cmd_Argc();
+
+	if ( c == 2 ) {
+		si.StartBackgroundTrack( Cmd_Argv(1), NULL );
+	} else if ( c == 3 ) {
+		si.StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2) );
+	} else {
+		Com_Printf ("music <musicfile> [loopfile]\n");
+		return;
+	}
+
+}
+
+/*
+=================
+S_Music_f
+=================
+*/
+void S_StopMusic_f( void )
+{
+	if(!si.StopBackgroundTrack)
+		return;
+
+	si.StopBackgroundTrack();
+}
+
+
+//=============================================================================
+
+/*
+=================
+S_Init
+=================
+*/
+void S_Init( void )
+{
+	cvar_t		*cv;
+	qboolean	started = qfalse;
+
+	Com_Printf( "------ Initializing Sound ------\n" );
+
+	s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE );
+	s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE );
+	s_muted = Cvar_Get("s_muted", "0", CVAR_ROM);
+	s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE );
+	s_backend = Cvar_Get( "s_backend", "", CVAR_ROM );
+	s_muteWhenMinimized = Cvar_Get( "s_muteWhenMinimized", "0", CVAR_ARCHIVE );
+	s_muteWhenUnfocused = Cvar_Get( "s_muteWhenUnfocused", "0", CVAR_ARCHIVE );
+
+	cv = Cvar_Get( "s_initsound", "1", 0 );
+	if( !cv->integer ) {
+		Com_Printf( "Sound disabled.\n" );
+	} else {
+
+		S_CodecInit( );
+
+		Cmd_AddCommand( "play", S_Play_f );
+		Cmd_AddCommand( "music", S_Music_f );
+		Cmd_AddCommand( "stopmusic", S_StopMusic_f );
+		Cmd_AddCommand( "s_list", S_SoundList );
+		Cmd_AddCommand( "s_stop", S_StopAllSounds );
+		Cmd_AddCommand( "s_info", S_SoundInfo );
+
+		cv = Cvar_Get( "s_useOpenAL", "0", CVAR_ARCHIVE );
+		if( cv->integer ) {
+			//OpenAL
+			started = S_AL_Init( &si );
+			Cvar_Set( "s_backend", "OpenAL" );
+		}
+
+		if( !started ) {
+			started = S_Base_Init( &si );
+			Cvar_Set( "s_backend", "base" );
+		}
+
+		if( started ) {
+			if( !S_ValidSoundInterface( &si ) ) {
+				Com_Error( ERR_FATAL, "Sound interface invalid." );
+			}
+
+			S_SoundInfo( );
+			Com_Printf( "Sound initialization successful.\n" );
+		} else {
+			Com_Printf( "Sound initialization failed.\n" );
+		}
+	}
+
+	Com_Printf( "--------------------------------\n");
+}
+
+/*
+=================
+S_Shutdown
+=================
+*/
+void S_Shutdown( void )
+{
+	if( si.Shutdown ) {
+		si.Shutdown( );
+	}
+
+	Com_Memset( &si, 0, sizeof( soundInterface_t ) );
+
+	Cmd_RemoveCommand( "play" );
+	Cmd_RemoveCommand( "music");
+	Cmd_RemoveCommand( "stopmusic");
+	Cmd_RemoveCommand( "s_list" );
+	Cmd_RemoveCommand( "s_stop" );
+	Cmd_RemoveCommand( "s_info" );
+
+	S_CodecShutdown( );
+}
+
diff --git a/src/client/snd_mem.c b/src/client/snd_mem.c
new file mode 100644
index 0000000..f3d90dd
--- /dev/null
+++ b/src/client/snd_mem.c
@@ -0,0 +1,270 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name:		snd_mem.c
+ *
+ * desc:		sound caching
+ *
+ * $Archive: /MissionPack/code/client/snd_mem.c $
+ *
+ *****************************************************************************/
+
+#include "snd_local.h"
+#include "snd_codec.h"
+
+#define DEF_COMSOUNDMEGS "8"
+
+/*
+===============================================================================
+
+memory management
+
+===============================================================================
+*/
+
+static	sndBuffer	*buffer = NULL;
+static	sndBuffer	*freelist = NULL;
+static	int inUse = 0;
+static	int totalInUse = 0;
+
+short *sfxScratchBuffer = NULL;
+sfx_t *sfxScratchPointer = NULL;
+int	   sfxScratchIndex = 0;
+
+void	SND_free(sndBuffer *v) {
+	*(sndBuffer **)v = freelist;
+	freelist = (sndBuffer*)v;
+	inUse += sizeof(sndBuffer);
+}
+
+sndBuffer*	SND_malloc(void) {
+	sndBuffer *v;
+redo:
+	if (freelist == NULL) {
+		S_FreeOldestSound();
+		goto redo;
+	}
+
+	inUse -= sizeof(sndBuffer);
+	totalInUse += sizeof(sndBuffer);
+
+	v = freelist;
+	freelist = *(sndBuffer **)freelist;
+	v->next = NULL;
+	return v;
+}
+
+void SND_setup(void) {
+	sndBuffer *p, *q;
+	cvar_t	*cv;
+	int scs;
+
+	cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE );
+
+	scs = (cv->integer*1536);
+
+	buffer = malloc(scs*sizeof(sndBuffer) );
+	// allocate the stack based hunk allocator
+	sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4);	//Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4);
+	sfxScratchPointer = NULL;
+
+	inUse = scs*sizeof(sndBuffer);
+	p = buffer;;
+	q = p + scs;
+	while (--q > p)
+		*(sndBuffer **)q = q-1;
+	
+	*(sndBuffer **)q = NULL;
+	freelist = p + scs - 1;
+
+	Com_Printf("Sound memory manager started\n");
+}
+
+/*
+================
+ResampleSfx
+
+resample / decimate to the current source rate
+================
+*/
+static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) {
+	int		outcount;
+	int		srcsample;
+	float	stepscale;
+	int		i;
+	int		sample, samplefrac, fracstep;
+	int			part;
+	sndBuffer	*chunk;
+	
+	stepscale = (float)inrate / dma.speed;	// this is usually 0.5, 1, or 2
+
+	outcount = sfx->soundLength / stepscale;
+	sfx->soundLength = outcount;
+
+	samplefrac = 0;
+	fracstep = stepscale * 256;
+	chunk = sfx->soundData;
+
+	for (i=0 ; i<outcount ; i++)
+	{
+		srcsample = samplefrac >> 8;
+		samplefrac += fracstep;
+		if( inwidth == 2 ) {
+			sample = ( ((short *)data)[srcsample] );
+		} else {
+			sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
+		}
+		part  = (i&(SND_CHUNK_SIZE-1));
+		if (part == 0) {
+			sndBuffer	*newchunk;
+			newchunk = SND_malloc();
+			if (chunk == NULL) {
+				sfx->soundData = newchunk;
+			} else {
+				chunk->next = newchunk;
+			}
+			chunk = newchunk;
+		}
+
+		chunk->sndChunk[part] = sample;
+	}
+}
+
+/*
+================
+ResampleSfx
+
+resample / decimate to the current source rate
+================
+*/
+static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) {
+	int			outcount;
+	int			srcsample;
+	float		stepscale;
+	int			i;
+	int			sample, samplefrac, fracstep;
+	
+	stepscale = (float)inrate / dma.speed;	// this is usually 0.5, 1, or 2
+
+	outcount = samples / stepscale;
+
+	samplefrac = 0;
+	fracstep = stepscale * 256;
+
+	for (i=0 ; i<outcount ; i++)
+	{
+		srcsample = samplefrac >> 8;
+		samplefrac += fracstep;
+		if( inwidth == 2 ) {
+			sample = LittleShort ( ((short *)data)[srcsample] );
+		} else {
+			sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
+		}
+		sfx[i] = sample;
+	}
+	return outcount;
+}
+
+//=============================================================================
+
+/*
+==============
+S_LoadSound
+
+The filename may be different than sfx->name in the case
+of a forced fallback of a player specific sound
+==============
+*/
+qboolean S_LoadSound( sfx_t *sfx )
+{
+	byte	*data;
+	short	*samples;
+	snd_info_t	info;
+	int		size_per_sec;
+
+	// player specific sounds are never directly loaded
+	if ( sfx->soundName[0] == '*') {
+		return qfalse;
+	}
+
+	// load it in
+	data = S_CodecLoad(sfx->soundName, &info);
+	if(!data)
+		return qfalse;
+
+	size_per_sec = info.rate * info.channels * info.width;
+	if( size_per_sec > 0 )
+		sfx->duration = (int)(1000.0f * ((double)info.size / size_per_sec));
+
+	if ( info.width == 1 ) {
+		Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName);
+	}
+
+	if ( info.rate != 22050 ) {
+		Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName);
+	}
+
+	samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2);
+
+	sfx->lastTimeUsed = Com_Milliseconds()+1;
+
+	// each of these compression schemes works just fine
+	// but the 16bit quality is much nicer and with a local
+	// install assured we can rely upon the sound memory
+	// manager to do the right thing for us and page
+	// sound in as needed
+
+	if( sfx->soundCompressed == qtrue) {
+		sfx->soundCompressionMethod = 1;
+		sfx->soundData = NULL;
+		sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, data + info.dataofs );
+		S_AdpcmEncodeSound(sfx, samples);
+#if 0
+	} else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) {
+		sfx->soundCompressionMethod = 3;
+		sfx->soundData = NULL;
+		sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
+		encodeMuLaw( sfx, samples);
+	} else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) {
+		sfx->soundCompressionMethod = 2;
+		sfx->soundData = NULL;
+		sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
+		encodeWavelet( sfx, samples);
+#endif
+	} else {
+		sfx->soundCompressionMethod = 0;
+		sfx->soundLength = info.samples;
+		sfx->soundData = NULL;
+		ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse );
+	}
+	
+	Hunk_FreeTempMemory(samples);
+	Z_Free(data);
+
+	return qtrue;
+}
+
+void S_DisplayFreeMemory(void) {
+	Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse);
+}
diff --git a/src/client/snd_mix.c b/src/client/snd_mix.c
new file mode 100644
index 0000000..2dd4428
--- /dev/null
+++ b/src/client/snd_mix.c
@@ -0,0 +1,742 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// snd_mix.c -- portable code to mix sounds for snd_dma.c
+
+#include "client.h"
+#include "snd_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
+static int snd_vol;
+
+int*     snd_p;  
+int      snd_linear_count;
+short*   snd_out;
+
+#if	!id386                                        // if configured not to use asm
+
+void S_WriteLinearBlastStereo16 (void)
+{
+	int		i;
+	int		val;
+
+	for (i=0 ; i<snd_linear_count ; i+=2)
+	{
+		val = snd_p[i]>>8;
+		if (val > 0x7fff)
+			snd_out[i] = 0x7fff;
+		else if (val < -32768)
+			snd_out[i] = -32768;
+		else
+			snd_out[i] = val;
+
+		val = snd_p[i+1]>>8;
+		if (val > 0x7fff)
+			snd_out[i+1] = 0x7fff;
+		else if (val < -32768)
+			snd_out[i+1] = -32768;
+		else
+			snd_out[i+1] = val;
+	}
+}
+#elif defined(__GNUC__)
+// uses snd_mixa.s
+void S_WriteLinearBlastStereo16 (void);
+#else
+
+__declspec( naked ) void S_WriteLinearBlastStereo16 (void)
+{
+	__asm {
+
+ push edi
+ push ebx
+ mov ecx,ds:dword ptr[snd_linear_count]
+ mov ebx,ds:dword ptr[snd_p]
+ mov edi,ds:dword ptr[snd_out]
+LWLBLoopTop:
+ mov eax,ds:dword ptr[-8+ebx+ecx*4]
+ sar eax,8
+ cmp eax,07FFFh
+ jg LClampHigh
+ cmp eax,0FFFF8000h
+ jnl LClampDone
+ mov eax,0FFFF8000h
+ jmp LClampDone
+LClampHigh:
+ mov eax,07FFFh
+LClampDone:
+ mov edx,ds:dword ptr[-4+ebx+ecx*4]
+ sar edx,8
+ cmp edx,07FFFh
+ jg LClampHigh2
+ cmp edx,0FFFF8000h
+ jnl LClampDone2
+ mov edx,0FFFF8000h
+ jmp LClampDone2
+LClampHigh2:
+ mov edx,07FFFh
+LClampDone2:
+ shl edx,16
+ and eax,0FFFFh
+ or edx,eax
+ mov ds:dword ptr[-4+edi+ecx*2],edx
+ sub ecx,2
+ jnz LWLBLoopTop
+ pop ebx
+ pop edi
+ ret
+	}
+}
+
+#endif
+
+void S_TransferStereo16 (unsigned long *pbuf, int endtime)
+{
+	int		lpos;
+	int		ls_paintedtime;
+	
+	snd_p = (int *) paintbuffer;
+	ls_paintedtime = s_paintedtime;
+
+	while (ls_paintedtime < endtime)
+	{
+	// handle recirculating buffer issues
+		lpos = ls_paintedtime & ((dma.samples>>1)-1);
+
+		snd_out = (short *) pbuf + (lpos<<1);
+
+		snd_linear_count = (dma.samples>>1) - lpos;
+		if (ls_paintedtime + snd_linear_count > endtime)
+			snd_linear_count = endtime - ls_paintedtime;
+
+		snd_linear_count <<= 1;
+
+	// write a linear blast of samples
+		S_WriteLinearBlastStereo16 ();
+
+		snd_p += snd_linear_count;
+		ls_paintedtime += (snd_linear_count>>1);
+
+		if( CL_VideoRecording( ) )
+			CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 );
+	}
+}
+
+/*
+===================
+S_TransferPaintBuffer
+
+===================
+*/
+void S_TransferPaintBuffer(int endtime)
+{
+	int 	out_idx;
+	int 	count;
+	int 	out_mask;
+	int 	*p;
+	int 	step;
+	int		val;
+	unsigned long *pbuf;
+
+	pbuf = (unsigned long *)dma.buffer;
+
+
+	if ( s_testsound->integer ) {
+		int		i;
+		int		count;
+
+		// write a fixed sine wave
+		count = (endtime - s_paintedtime);
+		for (i=0 ; i<count ; i++)
+			paintbuffer[i].left = paintbuffer[i].right = sin((s_paintedtime+i)*0.1)*20000*256;
+	}
+
+
+	if (dma.samplebits == 16 && dma.channels == 2)
+	{	// optimized case
+		S_TransferStereo16 (pbuf, endtime);
+	}
+	else
+	{	// general case
+		p = (int *) paintbuffer;
+		count = (endtime - s_paintedtime) * dma.channels;
+		out_mask = dma.samples - 1; 
+		out_idx = s_paintedtime * dma.channels & out_mask;
+		step = 3 - dma.channels;
+
+		if (dma.samplebits == 16)
+		{
+			short *out = (short *) pbuf;
+			while (count--)
+			{
+				val = *p >> 8;
+				p+= step;
+				if (val > 0x7fff)
+					val = 0x7fff;
+				else if (val < -32768)
+					val = -32768;
+				out[out_idx] = val;
+				out_idx = (out_idx + 1) & out_mask;
+			}
+		}
+		else if (dma.samplebits == 8)
+		{
+			unsigned char *out = (unsigned char *) pbuf;
+			while (count--)
+			{
+				val = *p >> 8;
+				p+= step;
+				if (val > 0x7fff)
+					val = 0x7fff;
+				else if (val < -32768)
+					val = -32768;
+				out[out_idx] = (val>>8) + 128;
+				out_idx = (out_idx + 1) & out_mask;
+			}
+		}
+	}
+}
+
+
+/*
+===============================================================================
+
+CHANNEL MIXING
+
+===============================================================================
+*/
+
+#if idppc_altivec
+static void S_PaintChannelFrom16_altivec( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+	int						data, aoff, boff;
+	int						leftvol, rightvol;
+	int						i, j;
+	portable_samplepair_t	*samp;
+	sndBuffer				*chunk;
+	short					*samples;
+	float					ooff, fdata, fdiv, fleftvol, frightvol;
+
+	samp = &paintbuffer[ bufferOffset ];
+
+	if (ch->doppler) {
+		sampleOffset = sampleOffset*ch->oldDopplerScale;
+	}
+
+	chunk = sc->soundData;
+	while (sampleOffset>=SND_CHUNK_SIZE) {
+		chunk = chunk->next;
+		sampleOffset -= SND_CHUNK_SIZE;
+		if (!chunk) {
+			chunk = sc->soundData;
+		}
+	}
+
+	if (!ch->doppler || ch->dopplerScale==1.0f) {
+		vector signed short volume_vec;
+		vector unsigned int volume_shift;
+		int vectorCount, samplesLeft, chunkSamplesLeft;
+		leftvol = ch->leftvol*snd_vol;
+		rightvol = ch->rightvol*snd_vol;
+		samples = chunk->sndChunk;
+		((short *)&volume_vec)[0] = leftvol;
+		((short *)&volume_vec)[1] = leftvol;
+		((short *)&volume_vec)[4] = leftvol;
+		((short *)&volume_vec)[5] = leftvol;
+		((short *)&volume_vec)[2] = rightvol;
+		((short *)&volume_vec)[3] = rightvol;
+		((short *)&volume_vec)[6] = rightvol;
+		((short *)&volume_vec)[7] = rightvol;
+		volume_shift = vec_splat_u32(8);
+		i = 0;
+
+		while(i < count) {
+			/* Try to align destination to 16-byte boundary */
+			while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) {
+				data  = samples[sampleOffset++];
+				samp[i].left += (data * leftvol)>>8;
+				samp[i].right += (data * rightvol)>>8;
+	
+				if (sampleOffset == SND_CHUNK_SIZE) {
+					chunk = chunk->next;
+					samples = chunk->sndChunk;
+					sampleOffset = 0;
+				}
+				i++;
+			}
+			/* Destination is now aligned.  Process as many 8-sample 
+			   chunks as we can before we run out of room from the current
+			   sound chunk.  We do 8 per loop to avoid extra source data reads. */
+			samplesLeft = count - i;
+			chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset;
+			if(samplesLeft > chunkSamplesLeft)
+				samplesLeft = chunkSamplesLeft;
+			
+			vectorCount = samplesLeft / 8;
+			
+			if(vectorCount)
+			{
+				vector unsigned char tmp;
+				vector short s0, s1, sampleData0, sampleData1;
+				vector signed int merge0, merge1;
+				vector signed int d0, d1, d2, d3;				
+				vector unsigned char samplePermute0 =
+					VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7);
+				vector unsigned char samplePermute1 = 
+					VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15);
+				vector unsigned char loadPermute0, loadPermute1;
+				
+				// Rather than permute the vectors after we load them to do the sample
+				// replication and rearrangement, we permute the alignment vector so
+				// we do everything in one step below and avoid data shuffling.
+				tmp = vec_lvsl(0,&samples[sampleOffset]);								
+				loadPermute0 = vec_perm(tmp,tmp,samplePermute0);
+				loadPermute1 = vec_perm(tmp,tmp,samplePermute1);
+				
+				s0 = *(vector short *)&samples[sampleOffset];
+				while(vectorCount)
+				{
+					/* Load up source (16-bit) sample data */
+					s1 = *(vector short *)&samples[sampleOffset+7];
+					
+					/* Load up destination sample data */
+					d0 = *(vector signed int *)&samp[i];
+					d1 = *(vector signed int *)&samp[i+2];
+					d2 = *(vector signed int *)&samp[i+4];
+					d3 = *(vector signed int *)&samp[i+6];
+
+					sampleData0 = vec_perm(s0,s1,loadPermute0);
+					sampleData1 = vec_perm(s0,s1,loadPermute1);
+					
+					merge0 = vec_mule(sampleData0,volume_vec);
+					merge0 = vec_sra(merge0,volume_shift);	/* Shift down to proper range */
+					
+					merge1 = vec_mulo(sampleData0,volume_vec);
+					merge1 = vec_sra(merge1,volume_shift);
+					
+					d0 = vec_add(merge0,d0);
+					d1 = vec_add(merge1,d1);
+					
+					merge0 = vec_mule(sampleData1,volume_vec);
+					merge0 = vec_sra(merge0,volume_shift);	/* Shift down to proper range */
+					
+					merge1 = vec_mulo(sampleData1,volume_vec);
+					merge1 = vec_sra(merge1,volume_shift);					
+
+					d2 = vec_add(merge0,d2);
+					d3 = vec_add(merge1,d3);
+
+					/* Store destination sample data */
+					*(vector signed int *)&samp[i] = d0;
+					*(vector signed int *)&samp[i+2] = d1;
+					*(vector signed int *)&samp[i+4] = d2;
+					*(vector signed int *)&samp[i+6] = d3;
+
+					i += 8;
+					vectorCount--;
+					s0 = s1;
+					sampleOffset += 8;
+				}
+				if (sampleOffset == SND_CHUNK_SIZE) {
+					chunk = chunk->next;
+					samples = chunk->sndChunk;
+					sampleOffset = 0;
+				}
+			}
+		}
+	} else {
+		fleftvol = ch->leftvol*snd_vol;
+		frightvol = ch->rightvol*snd_vol;
+
+		ooff = sampleOffset;
+		samples = chunk->sndChunk;
+		
+		for ( i=0 ; i<count ; i++ ) {
+
+			aoff = ooff;
+			ooff = ooff + ch->dopplerScale;
+			boff = ooff;
+			fdata = 0;
+			for (j=aoff; j<boff; j++) {
+				if (j == SND_CHUNK_SIZE) {
+					chunk = chunk->next;
+					if (!chunk) {
+						chunk = sc->soundData;
+					}
+					samples = chunk->sndChunk;
+					ooff -= SND_CHUNK_SIZE;
+				}
+				fdata  += samples[j&(SND_CHUNK_SIZE-1)];
+			}
+			fdiv = 256 * (boff-aoff);
+			samp[i].left += (fdata * fleftvol)/fdiv;
+			samp[i].right += (fdata * frightvol)/fdiv;
+		}
+	}
+}
+#endif
+
+static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+	int						data, aoff, boff;
+	int						leftvol, rightvol;
+	int						i, j;
+	portable_samplepair_t	*samp;
+	sndBuffer				*chunk;
+	short					*samples;
+	float					ooff, fdata, fdiv, fleftvol, frightvol;
+
+	samp = &paintbuffer[ bufferOffset ];
+
+	if (ch->doppler) {
+		sampleOffset = sampleOffset*ch->oldDopplerScale;
+	}
+
+	chunk = sc->soundData;
+	while (sampleOffset>=SND_CHUNK_SIZE) {
+		chunk = chunk->next;
+		sampleOffset -= SND_CHUNK_SIZE;
+		if (!chunk) {
+			chunk = sc->soundData;
+		}
+	}
+
+	if (!ch->doppler || ch->dopplerScale==1.0f) {
+		leftvol = ch->leftvol*snd_vol;
+		rightvol = ch->rightvol*snd_vol;
+		samples = chunk->sndChunk;
+		for ( i=0 ; i<count ; i++ ) {
+			data  = samples[sampleOffset++];
+			samp[i].left += (data * leftvol)>>8;
+			samp[i].right += (data * rightvol)>>8;
+
+			if (sampleOffset == SND_CHUNK_SIZE) {
+				chunk = chunk->next;
+				samples = chunk->sndChunk;
+				sampleOffset = 0;
+			}
+		}
+	} else {
+		fleftvol = ch->leftvol*snd_vol;
+		frightvol = ch->rightvol*snd_vol;
+
+		ooff = sampleOffset;
+		samples = chunk->sndChunk;
+		
+
+
+
+		for ( i=0 ; i<count ; i++ ) {
+
+			aoff = ooff;
+			ooff = ooff + ch->dopplerScale;
+			boff = ooff;
+			fdata = 0;
+			for (j=aoff; j<boff; j++) {
+				if (j == SND_CHUNK_SIZE) {
+					chunk = chunk->next;
+					if (!chunk) {
+						chunk = sc->soundData;
+					}
+					samples = chunk->sndChunk;
+					ooff -= SND_CHUNK_SIZE;
+				}
+				fdata  += samples[j&(SND_CHUNK_SIZE-1)];
+			}
+			fdiv = 256 * (boff-aoff);
+			samp[i].left += (fdata * fleftvol)/fdiv;
+			samp[i].right += (fdata * frightvol)/fdiv;
+		}
+	}
+}
+
+static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+#if idppc_altivec
+	if (com_altivec->integer) {
+		// must be in a seperate function or G3 systems will crash.
+		S_PaintChannelFrom16_altivec( ch, sc, count, sampleOffset, bufferOffset );
+		return;
+	}
+#endif
+	S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset );
+}
+
+void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+	int						data;
+	int						leftvol, rightvol;
+	int						i;
+	portable_samplepair_t	*samp;
+	sndBuffer				*chunk;
+	short					*samples;
+
+	leftvol = ch->leftvol*snd_vol;
+	rightvol = ch->rightvol*snd_vol;
+
+	i = 0;
+	samp = &paintbuffer[ bufferOffset ];
+	chunk = sc->soundData;
+	while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) {
+		chunk = chunk->next;
+		sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4);
+		i++;
+	}
+
+	if (i!=sfxScratchIndex || sfxScratchPointer != sc) {
+		S_AdpcmGetSamples( chunk, sfxScratchBuffer );
+		sfxScratchIndex = i;
+		sfxScratchPointer = sc;
+	}
+
+	samples = sfxScratchBuffer;
+
+	for ( i=0 ; i<count ; i++ ) {
+		data  = samples[sampleOffset++];
+		samp[i].left += (data * leftvol)>>8;
+		samp[i].right += (data * rightvol)>>8;
+
+		if (sampleOffset == SND_CHUNK_SIZE*2) {
+			chunk = chunk->next;
+			decodeWavelet(chunk, sfxScratchBuffer);
+			sfxScratchIndex++;
+			sampleOffset = 0;
+		}
+	}
+}
+
+void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+	int						data;
+	int						leftvol, rightvol;
+	int						i;
+	portable_samplepair_t	*samp;
+	sndBuffer				*chunk;
+	short					*samples;
+
+	leftvol = ch->leftvol*snd_vol;
+	rightvol = ch->rightvol*snd_vol;
+
+	i = 0;
+	samp = &paintbuffer[ bufferOffset ];
+	chunk = sc->soundData;
+
+	if (ch->doppler) {
+		sampleOffset = sampleOffset*ch->oldDopplerScale;
+	}
+
+	while (sampleOffset>=(SND_CHUNK_SIZE*4)) {
+		chunk = chunk->next;
+		sampleOffset -= (SND_CHUNK_SIZE*4);
+		i++;
+	}
+
+	if (i!=sfxScratchIndex || sfxScratchPointer != sc) {
+		S_AdpcmGetSamples( chunk, sfxScratchBuffer );
+		sfxScratchIndex = i;
+		sfxScratchPointer = sc;
+	}
+
+	samples = sfxScratchBuffer;
+
+	for ( i=0 ; i<count ; i++ ) {
+		data  = samples[sampleOffset++];
+		samp[i].left += (data * leftvol)>>8;
+		samp[i].right += (data * rightvol)>>8;
+
+		if (sampleOffset == SND_CHUNK_SIZE*4) {
+			chunk = chunk->next;
+			S_AdpcmGetSamples( chunk, sfxScratchBuffer);
+			sampleOffset = 0;
+			sfxScratchIndex++;
+		}
+	}
+}
+
+void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) {
+	int						data;
+	int						leftvol, rightvol;
+	int						i;
+	portable_samplepair_t	*samp;
+	sndBuffer				*chunk;
+	byte					*samples;
+	float					ooff;
+
+	leftvol = ch->leftvol*snd_vol;
+	rightvol = ch->rightvol*snd_vol;
+
+	samp = &paintbuffer[ bufferOffset ];
+	chunk = sc->soundData;
+	while (sampleOffset>=(SND_CHUNK_SIZE*2)) {
+		chunk = chunk->next;
+		sampleOffset -= (SND_CHUNK_SIZE*2);
+		if (!chunk) {
+			chunk = sc->soundData;
+		}
+	}
+
+	if (!ch->doppler) {
+		samples = (byte *)chunk->sndChunk + sampleOffset;
+		for ( i=0 ; i<count ; i++ ) {
+			data  = mulawToShort[*samples];
+			samp[i].left += (data * leftvol)>>8;
+			samp[i].right += (data * rightvol)>>8;
+			samples++;
+			if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) {
+				chunk = chunk->next;
+				samples = (byte *)chunk->sndChunk;
+			}
+		}
+	} else {
+		ooff = sampleOffset;
+		samples = (byte *)chunk->sndChunk;
+		for ( i=0 ; i<count ; i++ ) {
+			data  = mulawToShort[samples[(int)(ooff)]];
+			ooff = ooff + ch->dopplerScale;
+			samp[i].left += (data * leftvol)>>8;
+			samp[i].right += (data * rightvol)>>8;
+			if (ooff >= SND_CHUNK_SIZE*2) {
+				chunk = chunk->next;
+				if (!chunk) {
+					chunk = sc->soundData;
+				}
+				samples = (byte *)chunk->sndChunk;
+				ooff = 0.0;
+			}
+		}
+	}
+}
+
+/*
+===================
+S_PaintChannels
+===================
+*/
+void S_PaintChannels( int endtime ) {
+	int 	i;
+	int 	end;
+	int 	stream;
+	channel_t *ch;
+	sfx_t	*sc;
+	int		ltime, count;
+	int		sampleOffset;
+
+	if(s_muted->integer)
+		snd_vol = 0;
+	else
+		snd_vol = s_volume->value*255;
+
+//Com_Printf ("%i to %i\n", s_paintedtime, endtime);
+	while ( s_paintedtime < endtime ) {
+		// if paintbuffer is smaller than DMA buffer
+		// we may need to fill it multiple times
+		end = endtime;
+		if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) {
+			end = s_paintedtime + PAINTBUFFER_SIZE;
+		}
+
+		// clear the paint buffer and mix any raw samples...
+		Com_Memset(paintbuffer, 0, sizeof (paintbuffer));
+		for (stream = 0; stream < MAX_RAW_STREAMS; stream++) {
+			if ( s_rawend[stream] >= s_paintedtime ) {
+				// copy from the streaming sound source
+				const portable_samplepair_t *rawsamples = s_rawsamples[stream];
+				const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream];
+				for ( i = s_paintedtime ; i < stop ; i++ ) {
+					const int s = i&(MAX_RAW_SAMPLES-1);
+					paintbuffer[i-s_paintedtime].left += rawsamples[s].left;
+					paintbuffer[i-s_paintedtime].right += rawsamples[s].right;
+				}
+			}
+		}
+
+		// paint in the channels.
+		ch = s_channels;
+		for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {		
+			if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) {
+				continue;
+			}
+
+			ltime = s_paintedtime;
+			sc = ch->thesfx;
+
+			sampleOffset = ltime - ch->startSample;
+			count = end - ltime;
+			if ( sampleOffset + count > sc->soundLength ) {
+				count = sc->soundLength - sampleOffset;
+			}
+
+			if ( count > 0 ) {	
+				if( sc->soundCompressionMethod == 1) {
+					S_PaintChannelFromADPCM		(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+				} else if( sc->soundCompressionMethod == 2) {
+					S_PaintChannelFromWavelet	(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+				} else if( sc->soundCompressionMethod == 3) {
+					S_PaintChannelFromMuLaw	(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+				} else {
+					S_PaintChannelFrom16		(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+				}
+			}
+		}
+
+		// paint in the looped channels.
+		ch = loop_channels;
+		for ( i = 0; i < numLoopChannels ; i++, ch++ ) {		
+			if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) {
+				continue;
+			}
+
+			ltime = s_paintedtime;
+			sc = ch->thesfx;
+
+			if (sc->soundData==NULL || sc->soundLength==0) {
+				continue;
+			}
+			// we might have to make two passes if it
+			// is a looping sound effect and the end of
+			// the sample is hit
+			do {
+				sampleOffset = (ltime % sc->soundLength);
+
+				count = end - ltime;
+				if ( sampleOffset + count > sc->soundLength ) {
+					count = sc->soundLength - sampleOffset;
+				}
+
+				if ( count > 0 ) {	
+					if( sc->soundCompressionMethod == 1) {
+						S_PaintChannelFromADPCM		(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+					} else if( sc->soundCompressionMethod == 2) {
+						S_PaintChannelFromWavelet	(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+					} else if( sc->soundCompressionMethod == 3) {
+						S_PaintChannelFromMuLaw		(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+					} else {
+						S_PaintChannelFrom16		(ch, sc, count, sampleOffset, ltime - s_paintedtime);
+					}
+					ltime += count;
+				}
+			} while ( ltime < end);
+		}
+
+		// transfer out according to DMA format
+		S_TransferPaintBuffer( end );
+		s_paintedtime = end;
+	}
+}
diff --git a/src/client/snd_openal.c b/src/client/snd_openal.c
new file mode 100644
index 0000000..9b60aa7
--- /dev/null
+++ b/src/client/snd_openal.c
@@ -0,0 +1,2601 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com)
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "snd_local.h"
+#include "snd_codec.h"
+#include "client.h"
+
+#ifdef USE_OPENAL
+
+#include "qal.h"
+
+// Console variables specific to OpenAL
+cvar_t *s_alPrecache;
+cvar_t *s_alGain;
+cvar_t *s_alSources;
+cvar_t *s_alDopplerFactor;
+cvar_t *s_alDopplerSpeed;
+cvar_t *s_alMinDistance;
+cvar_t *s_alMaxDistance;
+cvar_t *s_alRolloff;
+cvar_t *s_alGraceDistance;
+cvar_t *s_alDriver;
+cvar_t *s_alDevice;
+cvar_t *s_alInputDevice;
+cvar_t *s_alAvailableDevices;
+cvar_t *s_alAvailableInputDevices;
+
+static qboolean enumeration_ext = qfalse;
+static qboolean enumeration_all_ext = qfalse;
+static qboolean capture_ext = qfalse;
+
+/*
+=================
+S_AL_Format
+=================
+*/
+static
+ALuint S_AL_Format(int width, int channels)
+{
+	ALuint format = AL_FORMAT_MONO16;
+
+	// Work out format
+	if(width == 1)
+	{
+		if(channels == 1)
+			format = AL_FORMAT_MONO8;
+		else if(channels == 2)
+			format = AL_FORMAT_STEREO8;
+	}
+	else if(width == 2)
+	{
+		if(channels == 1)
+			format = AL_FORMAT_MONO16;
+		else if(channels == 2)
+			format = AL_FORMAT_STEREO16;
+	}
+
+	return format;
+}
+
+/*
+=================
+S_AL_ErrorMsg
+=================
+*/
+static const char *S_AL_ErrorMsg(ALenum error)
+{
+	switch(error)
+	{
+		case AL_NO_ERROR:
+			return "No error";
+		case AL_INVALID_NAME:
+			return "Invalid name";
+		case AL_INVALID_ENUM:
+			return "Invalid enumerator";
+		case AL_INVALID_VALUE:
+			return "Invalid value";
+		case AL_INVALID_OPERATION:
+			return "Invalid operation";
+		case AL_OUT_OF_MEMORY:
+			return "Out of memory";
+		default:
+			return "Unknown error";
+	}
+}
+
+/*
+=================
+S_AL_ClearError
+=================
+*/
+static void S_AL_ClearError( qboolean quiet )
+{
+	int error = qalGetError();
+
+	if( quiet )
+		return;
+	if(error != AL_NO_ERROR)
+	{
+		Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n",
+			S_AL_ErrorMsg(error));
+	}
+}
+
+
+//===========================================================================
+
+
+typedef struct alSfx_s
+{
+	char			filename[MAX_QPATH];
+	ALuint		buffer;					// OpenAL buffer
+	snd_info_t	info;					// information for this sound like rate, sample count..
+
+	qboolean	isDefault;				// Couldn't be loaded - use default FX
+	qboolean	isDefaultChecked;		// Sound has been check if it isDefault
+	qboolean	inMemory;				// Sound is stored in memory
+	qboolean	isLocked;				// Sound is locked (can not be unloaded)
+	int				lastUsedTime;		// Time last used
+
+	int				duration;				// Milliseconds
+
+	int				loopCnt;		// number of loops using this sfx
+	int				loopActiveCnt;		// number of playing loops using this sfx
+	int				masterLoopSrc;		// All other sources looping this buffer are synced to this master src
+} alSfx_t;
+
+static qboolean alBuffersInitialised = qfalse;
+
+// Sound effect storage, data structures
+#define MAX_SFX 4096
+static alSfx_t knownSfx[MAX_SFX];
+static int numSfx = 0;
+
+static sfxHandle_t default_sfx;
+
+/*
+=================
+S_AL_BufferFindFree
+
+Find a free handle
+=================
+*/
+static sfxHandle_t S_AL_BufferFindFree( void )
+{
+	int i;
+
+	for(i = 0; i < MAX_SFX; i++)
+	{
+		// Got one
+		if(knownSfx[i].filename[0] == '\0')
+		{
+			if(i >= numSfx)
+				numSfx = i + 1;
+			return i;
+		}
+	}
+
+	// Shit...
+	Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles");
+	return -1;
+}
+
+/*
+=================
+S_AL_BufferFind
+
+Find a sound effect if loaded, set up a handle otherwise
+=================
+*/
+static sfxHandle_t S_AL_BufferFind(const char *filename)
+{
+	// Look it up in the table
+	sfxHandle_t sfx = -1;
+	int i;
+
+	for(i = 0; i < numSfx; i++)
+	{
+		if(!Q_stricmp(knownSfx[i].filename, filename))
+		{
+			sfx = i;
+			break;
+		}
+	}
+
+	// Not found in table?
+	if(sfx == -1)
+	{
+		alSfx_t *ptr;
+
+		sfx = S_AL_BufferFindFree();
+
+		// Clear and copy the filename over
+		ptr = &knownSfx[sfx];
+		memset(ptr, 0, sizeof(*ptr));
+		ptr->masterLoopSrc = -1;
+		strcpy(ptr->filename, filename);
+	}
+
+	// Return the handle
+	return sfx;
+}
+
+/*
+=================
+S_AL_BufferUseDefault
+=================
+*/
+static void S_AL_BufferUseDefault(sfxHandle_t sfx)
+{
+	if(sfx == default_sfx)
+		Com_Error(ERR_FATAL, "Can't load default sound effect %s\n", knownSfx[sfx].filename);
+
+	Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename);
+	knownSfx[sfx].isDefault = qtrue;
+	knownSfx[sfx].buffer = knownSfx[default_sfx].buffer;
+}
+
+/*
+=================
+S_AL_BufferUnload
+=================
+*/
+static void S_AL_BufferUnload(sfxHandle_t sfx)
+{
+	ALenum error;
+
+	if(knownSfx[sfx].filename[0] == '\0')
+		return;
+
+	if(!knownSfx[sfx].inMemory)
+		return;
+
+	// Delete it 
+	S_AL_ClearError( qfalse );
+	qalDeleteBuffers(1, &knownSfx[sfx].buffer);
+	if((error = qalGetError()) != AL_NO_ERROR)
+		Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n",
+				knownSfx[sfx].filename);
+
+	knownSfx[sfx].inMemory = qfalse;
+}
+
+/*
+=================
+S_AL_BufferEvict
+=================
+*/
+static qboolean S_AL_BufferEvict( void )
+{
+	int	i, oldestBuffer = -1;
+	int	oldestTime = Sys_Milliseconds( );
+
+	for( i = 0; i < numSfx; i++ )
+	{
+		if( !knownSfx[ i ].filename[ 0 ] )
+			continue;
+
+		if( !knownSfx[ i ].inMemory )
+			continue;
+
+		if( knownSfx[ i ].lastUsedTime < oldestTime )
+		{
+			oldestTime = knownSfx[ i ].lastUsedTime;
+			oldestBuffer = i;
+		}
+	}
+
+	if( oldestBuffer >= 0 )
+	{
+		S_AL_BufferUnload( oldestBuffer );
+		return qtrue;
+	}
+	else
+		return qfalse;
+}
+
+/*
+=================
+S_AL_BufferLoad
+=================
+*/
+static void S_AL_BufferLoad(sfxHandle_t sfx, qboolean cache)
+{
+	ALenum error;
+	ALuint format;
+
+	void *data;
+	snd_info_t info;
+	alSfx_t *curSfx = &knownSfx[sfx];
+
+	// Nothing?
+	if(curSfx->filename[0] == '\0')
+		return;
+
+	// Player SFX
+	if(curSfx->filename[0] == '*')
+		return;
+
+	// Already done?
+	if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked))
+		return;
+
+	// Try to load
+	data = S_CodecLoad(curSfx->filename, &info);
+	if(!data)
+	{
+		S_AL_BufferUseDefault(sfx);
+		return;
+	}
+
+	curSfx->isDefaultChecked = qtrue;
+
+	if (!cache)
+	{
+		// Don't create AL cache
+		Z_Free(data);
+		return;
+	}
+
+	format = S_AL_Format(info.width, info.channels);
+
+	// Create a buffer
+	S_AL_ClearError( qfalse );
+	qalGenBuffers(1, &curSfx->buffer);
+	if((error = qalGetError()) != AL_NO_ERROR)
+	{
+		S_AL_BufferUseDefault(sfx);
+		Z_Free(data);
+		Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n",
+				curSfx->filename, S_AL_ErrorMsg(error));
+		return;
+	}
+
+	// Fill the buffer
+	if( info.size == 0 )
+	{
+		// We have no data to buffer, so buffer silence
+		byte dummyData[ 2 ] = { 0 };
+
+		qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050);
+	}
+	else
+		qalBufferData(curSfx->buffer, format, data, info.size, info.rate);
+
+	error = qalGetError();
+
+	// If we ran out of memory, start evicting the least recently used sounds
+	while(error == AL_OUT_OF_MEMORY)
+	{
+		if( !S_AL_BufferEvict( ) )
+		{
+			S_AL_BufferUseDefault(sfx);
+			Z_Free(data);
+			Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename);
+			return;
+		}
+
+		// Try load it again
+		qalBufferData(curSfx->buffer, format, data, info.size, info.rate);
+		error = qalGetError();
+	}
+
+	// Some other error condition
+	if(error != AL_NO_ERROR)
+	{
+		S_AL_BufferUseDefault(sfx);
+		Z_Free(data);
+		Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n",
+				curSfx->filename, S_AL_ErrorMsg(error));
+		return;
+	}
+
+	curSfx->info = info;
+	
+	// Free the memory
+	Z_Free(data);
+
+	// Woo!
+	curSfx->inMemory = qtrue;
+}
+
+/*
+=================
+S_AL_BufferUse
+=================
+*/
+static
+void S_AL_BufferUse(sfxHandle_t sfx)
+{
+	if(knownSfx[sfx].filename[0] == '\0')
+		return;
+
+	if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
+		S_AL_BufferLoad(sfx, qtrue);
+	knownSfx[sfx].lastUsedTime = Sys_Milliseconds();
+}
+
+/*
+=================
+S_AL_BufferInit
+=================
+*/
+static
+qboolean S_AL_BufferInit( void )
+{
+	if(alBuffersInitialised)
+		return qtrue;
+
+	// Clear the hash table, and SFX table
+	memset(knownSfx, 0, sizeof(knownSfx));
+	numSfx = 0;
+
+	// Load the default sound, and lock it
+	default_sfx = S_AL_BufferFind("sound/feedback/hit.wav");
+	S_AL_BufferUse(default_sfx);
+	knownSfx[default_sfx].isLocked = qtrue;
+
+	// All done
+	alBuffersInitialised = qtrue;
+	return qtrue;
+}
+
+/*
+=================
+S_AL_BufferShutdown
+=================
+*/
+static
+void S_AL_BufferShutdown( void )
+{
+	int i;
+
+	if(!alBuffersInitialised)
+		return;
+
+	// Unlock the default sound effect
+	knownSfx[default_sfx].isLocked = qfalse;
+
+	// Free all used effects
+	for(i = 0; i < numSfx; i++)
+		S_AL_BufferUnload(i);
+
+	// Clear the tables
+	memset(knownSfx, 0, sizeof(knownSfx));
+
+	// All undone
+	alBuffersInitialised = qfalse;
+}
+
+/*
+=================
+S_AL_RegisterSound
+=================
+*/
+static
+sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed )
+{
+	sfxHandle_t sfx = S_AL_BufferFind(sample);
+
+	if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
+		S_AL_BufferLoad(sfx, s_alPrecache->integer);
+	knownSfx[sfx].lastUsedTime = Com_Milliseconds();
+
+	if (knownSfx[sfx].isDefault) {
+		return 0;
+	}
+
+	return sfx;
+}
+
+/*
+=================
+S_AL_SoundDuration
+=================
+*/
+static
+int S_AL_SoundDuration( sfxHandle_t sfx )
+{
+	if (sfx < 0 || sfx >= numSfx)
+	{
+		Com_Printf(S_COLOR_RED "ERROR: S_AL_SoundDuration: handle %i out of range\n", sfx);
+		return 0;
+	}
+	return knownSfx[sfx].duration;
+}
+
+/*
+=================
+S_AL_BufferGet
+
+Return's an sfx's buffer
+=================
+*/
+static
+ALuint S_AL_BufferGet(sfxHandle_t sfx)
+{
+	return knownSfx[sfx].buffer;
+}
+
+
+//===========================================================================
+
+
+typedef struct src_s
+{
+	ALuint		alSource;		// OpenAL source object
+	sfxHandle_t	sfx;			// Sound effect in use
+
+	int		lastUsedTime;		// Last time used
+	alSrcPriority_t	priority;		// Priority
+	int		entity;			// Owning entity (-1 if none)
+	int		channel;		// Associated channel (-1 if none)
+
+	qboolean	isActive;		// Is this source currently in use?
+	qboolean	isPlaying;		// Is this source currently playing, or stopped?
+	qboolean	isLocked;		// This is locked (un-allocatable)
+	qboolean	isLooping;		// Is this a looping effect (attached to an entity)
+	qboolean	isTracking;		// Is this object tracking its owner
+
+	float		curGain;		// gain employed if source is within maxdistance.
+	float		scaleGain;		// Last gain value for this source. 0 if muted.
+	
+	float		lastTimePos;		// On stopped loops, the last position in the buffer
+	int		lastSampleTime;		// Time when this was stopped
+	vec3_t		loopSpeakerPos;		// Origin of the loop speaker
+	
+	qboolean	local;			// Is this local (relative to the cam)
+} src_t;
+
+#ifdef MACOS_X
+	#define MAX_SRC 64
+#else
+	#define MAX_SRC 128
+#endif
+static src_t srcList[MAX_SRC];
+static int srcCount = 0;
+static int srcActiveCnt = 0;
+static qboolean alSourcesInitialised = qfalse;
+static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f };
+
+typedef struct sentity_s
+{
+	vec3_t					origin;
+
+	qboolean						srcAllocated; // If a src_t has been allocated to this entity
+	int							srcIndex;
+
+	qboolean				loopAddedThisFrame;
+	alSrcPriority_t	loopPriority;
+	sfxHandle_t			loopSfx;
+	qboolean				startLoopingSound;
+} sentity_t;
+
+static sentity_t entityList[MAX_GENTITIES];
+
+/*
+=================
+S_AL_SanitiseVector
+=================
+*/
+#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__)
+static void _S_AL_SanitiseVector( vec3_t v, int line )
+{
+	if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) )
+	{
+		Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components "
+				"being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line );
+		VectorClear( v );
+	}
+}
+
+
+#define AL_THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f)
+
+/*
+=================
+S_AL_Gain
+Set gain to 0 if muted, otherwise set it to given value.
+=================
+*/
+
+static void S_AL_Gain(ALuint source, float gainval)
+{
+	if(s_muted->integer)
+		qalSourcef(source, AL_GAIN, 0.0f);
+	else
+		qalSourcef(source, AL_GAIN, gainval);
+}
+
+/*
+=================
+S_AL_ScaleGain
+Adapt the gain if necessary to get a quicker fadeout when the source is too far away.
+=================
+*/
+
+static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin)
+{
+	float distance;
+	
+	if(!chksrc->local)
+		distance = Distance(origin, lastListenerOrigin);
+		
+	// If we exceed a certain distance, scale the gain linearly until the sound
+	// vanishes into nothingness.
+	if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0)
+	{
+		float scaleFactor;
+
+		if(distance >= s_alGraceDistance->value)
+			scaleFactor = 0.0f;
+		else
+			scaleFactor = 1.0f - distance / s_alGraceDistance->value;
+		
+		scaleFactor *= chksrc->curGain;
+		
+		if(chksrc->scaleGain != scaleFactor)
+		{
+			chksrc->scaleGain = scaleFactor;
+			S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
+		}
+	}
+	else if(chksrc->scaleGain != chksrc->curGain)
+	{
+		chksrc->scaleGain = chksrc->curGain;
+		S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
+	}
+}
+
+/*
+=================
+S_AL_HearingThroughEntity
+=================
+*/
+static qboolean S_AL_HearingThroughEntity( int entityNum )
+{
+	float	distanceSq;
+
+	if( clc.clientNum == entityNum )
+	{
+		// FIXME: <tim@ngus.net> 28/02/06 This is an outrageous hack to detect
+		// whether or not the player is rendering in third person or not. We can't
+		// ask the renderer because the renderer has no notion of entities and we
+		// can't ask cgame since that would involve changing the API and hence mod
+		// compatibility. I don't think there is any way around this, but I'll leave
+		// the FIXME just in case anyone has a bright idea.
+		distanceSq = DistanceSquared(
+				entityList[ entityNum ].origin,
+				lastListenerOrigin );
+
+		if( distanceSq > AL_THIRD_PERSON_THRESHOLD_SQ )
+			return qfalse; //we're the player, but third person
+		else
+			return qtrue;  //we're the player
+	}
+	else
+		return qfalse; //not the player
+}
+
+/*
+=================
+S_AL_SrcInit
+=================
+*/
+static
+qboolean S_AL_SrcInit( void )
+{
+	int i;
+	int limit;
+	ALenum error;
+
+	// Clear the sources data structure
+	memset(srcList, 0, sizeof(srcList));
+	srcCount = 0;
+	srcActiveCnt = 0;
+
+	// Cap s_alSources to MAX_SRC
+	limit = s_alSources->integer;
+	if(limit > MAX_SRC)
+		limit = MAX_SRC;
+	else if(limit < 16)
+		limit = 16;
+ 
+	S_AL_ClearError( qfalse );
+	// Allocate as many sources as possible
+	for(i = 0; i < limit; i++)
+	{
+		qalGenSources(1, &srcList[i].alSource);
+		if((error = qalGetError()) != AL_NO_ERROR)
+			break;
+		srcCount++;
+	}
+
+	// All done. Print this for informational purposes
+	Com_Printf( "Allocated %d sources.\n", srcCount);
+	alSourcesInitialised = qtrue;
+	return qtrue;
+}
+
+/*
+=================
+S_AL_SrcShutdown
+=================
+*/
+static
+void S_AL_SrcShutdown( void )
+{
+	int i;
+	src_t *curSource;
+
+	if(!alSourcesInitialised)
+		return;
+
+	// Destroy all the sources
+	for(i = 0; i < srcCount; i++)
+	{
+		curSource = &srcList[i];
+		
+		if(curSource->isLocked)
+			Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i);
+
+		if(curSource->entity > 0)
+			entityList[curSource->entity].srcAllocated = qfalse;
+
+		qalSourceStop(srcList[i].alSource);
+		qalDeleteSources(1, &srcList[i].alSource);
+	}
+
+	memset(srcList, 0, sizeof(srcList));
+
+	alSourcesInitialised = qfalse;
+}
+
+/*
+=================
+S_AL_SrcSetup
+=================
+*/
+static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority,
+		int entity, int channel, qboolean local)
+{
+	ALuint buffer;
+	src_t *curSource;
+
+	// Mark the SFX as used, and grab the raw AL buffer
+	S_AL_BufferUse(sfx);
+	buffer = S_AL_BufferGet(sfx);
+
+	// Set up src struct
+	curSource = &srcList[src];
+	
+	curSource->lastUsedTime = Sys_Milliseconds();
+	curSource->sfx = sfx;
+	curSource->priority = priority;
+	curSource->entity = entity;
+	curSource->channel = channel;
+	curSource->isPlaying = qfalse;
+	curSource->isLocked = qfalse;
+	curSource->isLooping = qfalse;
+	curSource->isTracking = qfalse;
+	curSource->curGain = s_alGain->value * s_volume->value;
+	curSource->scaleGain = curSource->curGain;
+	curSource->local = local;
+
+	// Set up OpenAL source
+	qalSourcei(curSource->alSource, AL_BUFFER, buffer);
+	qalSourcef(curSource->alSource, AL_PITCH, 1.0f);
+	S_AL_Gain(curSource->alSource, curSource->curGain);
+	qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin);
+	qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin);
+	qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE);
+	qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
+
+	if(local)
+	{
+		qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
+		qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
+	}
+	else
+	{
+		qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
+		qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+	}
+}
+
+/*
+=================
+S_AL_NewLoopMaster
+Remove given source as loop master if it is the master and hand off master status to another source in this case.
+=================
+*/
+
+static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource)
+{
+	int error;
+	
+	S_AL_ClearError(qfalse);
+	
+	qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos);
+	if((error = qalGetError()) != AL_NO_ERROR)
+	{
+		// Old OpenAL implementations don't support AL_SEC_OFFSET
+
+		if(error != AL_INVALID_ENUM)
+		{
+			Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n",
+				   alSource, S_AL_ErrorMsg(error));
+		}
+		
+		dest->lastTimePos = -1;
+	}
+	else
+		dest->lastSampleTime = Sys_Milliseconds();
+}
+
+/*
+=================
+S_AL_NewLoopMaster
+Remove given source as loop master if it is the master and hand off master status to another source in this case.
+=================
+*/
+
+static void S_AL_NewLoopMaster(src_t *rmSource, qboolean iskilled)
+{
+	int index;
+	src_t *curSource = NULL;
+	alSfx_t *curSfx;
+	
+	curSfx = &knownSfx[rmSource->sfx];
+
+	if(rmSource->isPlaying)
+		curSfx->loopActiveCnt--;
+	if(iskilled)
+		curSfx->loopCnt--;
+	
+	if(curSfx->loopCnt)
+	{
+		if(rmSource->priority == SRCPRI_ENTITY)
+		{
+			if(!iskilled && rmSource->isPlaying)
+			{
+				// only sync ambient loops...
+				// It makes more sense to have sounds for weapons/projectiles unsynced
+				S_AL_SaveLoopPos(rmSource, rmSource->alSource);
+			}
+		}
+		else if(rmSource == &srcList[curSfx->masterLoopSrc])
+		{
+			int firstInactive = -1;
+
+			// Only if rmSource was the master and if there are still playing loops for
+			// this sound will we need to find a new master.
+	
+			if(iskilled || curSfx->loopActiveCnt)
+			{
+				for(index = 0; index < srcCount; index++)
+				{
+					curSource = &srcList[index];
+	
+					if(curSource->sfx == rmSource->sfx && curSource != rmSource &&
+					   curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT)
+					{
+						if(curSource->isPlaying)
+						{
+							curSfx->masterLoopSrc = index;
+							break;
+						}
+						else if(firstInactive < 0)
+							firstInactive = index;
+					}
+				}
+			}
+		
+			if(!curSfx->loopActiveCnt)
+			{
+				if(firstInactive < 0)
+				{
+					if(iskilled)
+					{
+						curSfx->masterLoopSrc = -1;
+						return;
+					}
+					else
+						curSource = rmSource;
+				}
+				else
+					curSource = &srcList[firstInactive];
+
+				if(rmSource->isPlaying)
+				{
+					// this was the last not stopped source, save last sample position + time
+					S_AL_SaveLoopPos(curSource, rmSource->alSource);
+				}
+				else
+				{
+					// second case: all loops using this sound have stopped due to listener being of of range,
+					// and now the inactive master gets deleted. Just move over the soundpos settings to the
+					// new master.
+					curSource->lastTimePos = rmSource->lastTimePos;
+					curSource->lastSampleTime = rmSource->lastSampleTime;
+				}
+			}
+		}
+	}
+	else
+		curSfx->masterLoopSrc = -1;
+}
+
+/*
+=================
+S_AL_SrcKill
+=================
+*/
+static void S_AL_SrcKill(srcHandle_t src)
+{
+	src_t *curSource = &srcList[src];
+	
+	// I'm not touching it. Unlock it first.
+	if(curSource->isLocked)
+		return;
+
+	// Remove the entity association and loop master status
+	if(curSource->isLooping)
+	{
+		curSource->isLooping = qfalse;
+
+		if(curSource->entity != -1)
+		{
+			sentity_t *curEnt = &entityList[curSource->entity];
+			
+			curEnt->srcAllocated = qfalse;
+			curEnt->srcIndex = -1;
+			curEnt->loopAddedThisFrame = qfalse;
+			curEnt->startLoopingSound = qfalse;
+		}
+		
+		S_AL_NewLoopMaster(curSource, qtrue);
+	}
+
+	// Stop it if it's playing
+	if(curSource->isPlaying)
+	{
+		qalSourceStop(curSource->alSource);
+		curSource->isPlaying = qfalse;
+	}
+
+	// Remove the buffer
+	qalSourcei(curSource->alSource, AL_BUFFER, 0);
+
+	curSource->sfx = 0;
+	curSource->lastUsedTime = 0;
+	curSource->priority = 0;
+	curSource->entity = -1;
+	curSource->channel = -1;
+	if(curSource->isActive)
+	{
+		curSource->isActive = qfalse;
+		srcActiveCnt--;
+	}
+	curSource->isLocked = qfalse;
+	curSource->isTracking = qfalse;
+	curSource->local = qfalse;
+}
+
+/*
+=================
+S_AL_SrcAlloc
+=================
+*/
+static
+srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel )
+{
+	int i;
+	int empty = -1;
+	int weakest = -1;
+	int weakest_time = Sys_Milliseconds();
+	int weakest_pri = 999;
+	float weakest_gain = 1000.0;
+	qboolean weakest_isplaying = qtrue;
+	int weakest_numloops = 0;
+	src_t *curSource;
+
+	for(i = 0; i < srcCount; i++)
+	{
+		curSource = &srcList[i];
+		
+		// If it's locked, we aren't even going to look at it
+		if(curSource->isLocked)
+			continue;
+
+		// Is it empty or not?
+		if(!curSource->isActive)
+		{
+			empty = i;
+			break;
+		}
+
+		if(curSource->isPlaying)
+		{
+			if(weakest_isplaying && curSource->priority < priority &&
+			   (curSource->priority < weakest_pri ||
+			   (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time))))
+			{
+				// If it has lower priority, is fainter or older, flag it as weak
+				// the last two values are only compared if it's not a looping sound, because we want to prevent two
+				// loops (loops are added EVERY frame) fighting for a slot
+				weakest_pri = curSource->priority;
+				weakest_time = curSource->lastUsedTime;
+				weakest_gain = curSource->scaleGain;
+				weakest = i;
+			}
+		}
+		else
+		{
+			weakest_isplaying = qfalse;
+			
+			if(weakest < 0 ||
+			   knownSfx[curSource->sfx].loopCnt > weakest_numloops ||
+			   curSource->priority < weakest_pri ||
+			   curSource->lastUsedTime < weakest_time)
+			{
+				// Sources currently not playing of course have lowest priority
+				// also try to always keep at least one loop master for every loop sound
+				weakest_pri = curSource->priority;
+				weakest_time = curSource->lastUsedTime;
+				weakest_numloops = knownSfx[curSource->sfx].loopCnt;
+				weakest = i;
+			}
+		}
+
+		// The channel system is not actually adhered to by baseq3, and not
+		// implemented in snd_dma.c, so while the following is strictly correct, it
+		// causes incorrect behaviour versus defacto baseq3
+#if 0
+		// Is it an exact match, and not on channel 0?
+		if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0))
+		{
+			S_AL_SrcKill(i);
+			return i;
+		}
+#endif
+	}
+
+	if(empty == -1)
+		empty = weakest;
+	
+	if(empty >= 0)
+	{
+		S_AL_SrcKill(empty);
+		srcList[empty].isActive = qtrue;
+		srcActiveCnt++;
+	}
+
+	return empty;
+}
+
+/*
+=================
+S_AL_SrcFind
+
+Finds an active source with matching entity and channel numbers
+Returns -1 if there isn't one
+=================
+*/
+#if 0
+static
+srcHandle_t S_AL_SrcFind(int entnum, int channel)
+{
+	int i;
+	for(i = 0; i < srcCount; i++)
+	{
+		if(!srcList[i].isActive)
+			continue;
+		if((srcList[i].entity == entnum) && (srcList[i].channel == channel))
+			return i;
+	}
+	return -1;
+}
+#endif
+
+/*
+=================
+S_AL_SrcLock
+
+Locked sources will not be automatically reallocated or managed
+=================
+*/
+static
+void S_AL_SrcLock(srcHandle_t src)
+{
+	srcList[src].isLocked = qtrue;
+}
+
+/*
+=================
+S_AL_SrcUnlock
+
+Once unlocked, the source may be reallocated again
+=================
+*/
+static
+void S_AL_SrcUnlock(srcHandle_t src)
+{
+	srcList[src].isLocked = qfalse;
+}
+
+/*
+=================
+S_AL_UpdateEntityPosition
+=================
+*/
+static
+void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+	vec3_t sanOrigin;
+
+	VectorCopy( origin, sanOrigin );
+	S_AL_SanitiseVector( sanOrigin );
+	if ( entityNum < 0 || entityNum > MAX_GENTITIES )
+		Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
+	VectorCopy( sanOrigin, entityList[entityNum].origin );
+}
+
+/*
+=================
+S_AL_CheckInput
+Check whether input values from mods are out of range.
+Necessary for i.g. Western Quake3 mod which is buggy.
+=================
+*/
+static qboolean S_AL_CheckInput(int entityNum, sfxHandle_t sfx)
+{
+	if (entityNum < 0 || entityNum > MAX_GENTITIES)
+		Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum);
+
+	if (sfx < 0 || sfx >= numSfx)
+	{
+		Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx);
+		return qtrue;
+	}
+
+	return qfalse;
+}
+
+/*
+=================
+S_AL_StartLocalSound
+
+Play a local (non-spatialized) sound effect
+=================
+*/
+static
+void S_AL_StartLocalSound(sfxHandle_t sfx, int channel)
+{
+	srcHandle_t src;
+	
+	if(S_AL_CheckInput(0, sfx))
+		return;
+
+	// Try to grab a source
+	src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel);
+	
+	if(src == -1)
+		return;
+
+	// Set up the effect
+	S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, qtrue);
+
+	// Start it playing
+	srcList[src].isPlaying = qtrue;
+	qalSourcePlay(srcList[src].alSource);
+}
+
+/*
+=================
+S_AL_StartSound
+
+Play a one-shot sound effect
+=================
+*/
+static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx )
+{
+	vec3_t sorigin;
+	srcHandle_t src;
+	src_t *curSource;
+
+	if(origin)
+	{
+		if(S_AL_CheckInput(0, sfx))
+			return;
+		
+		VectorCopy(origin, sorigin);
+	}
+	else
+	{
+		if(S_AL_CheckInput(entnum, sfx))
+			return;
+
+		if(S_AL_HearingThroughEntity(entnum))
+		{
+			S_AL_StartLocalSound(sfx, entchannel);
+			return;
+		}
+		
+		VectorCopy(entityList[entnum].origin, sorigin);
+	}
+	
+	S_AL_SanitiseVector(sorigin);
+	
+	if((srcActiveCnt > 5 * srcCount / 3) &&
+		(DistanceSquared(sorigin, lastListenerOrigin) >=
+		(s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value)))
+	{
+		// We're getting tight on sources and source is not within hearing distance so don't add it
+		return;
+	}
+
+	// Try to grab a source
+	src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel);
+	if(src == -1)
+		return;
+
+	S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse);
+	
+	curSource = &srcList[src];
+
+	if(!origin)
+		curSource->isTracking = qtrue;
+		
+	qalSourcefv(curSource->alSource, AL_POSITION, sorigin );
+	S_AL_ScaleGain(curSource, sorigin);
+
+	// Start it playing
+	curSource->isPlaying = qtrue;
+	qalSourcePlay(curSource->alSource);
+}
+
+/*
+=================
+S_AL_ClearLoopingSounds
+=================
+*/
+static
+void S_AL_ClearLoopingSounds( qboolean killall )
+{
+	int i;
+	for(i = 0; i < srcCount; i++)
+	{
+		if((srcList[i].isLooping) && (srcList[i].entity != -1))
+			entityList[srcList[i].entity].loopAddedThisFrame = qfalse;
+	}
+}
+
+/*
+=================
+S_AL_SrcLoop
+=================
+*/
+static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx,
+		const vec3_t origin, const vec3_t velocity, int entityNum )
+{
+	int				src;
+	sentity_t	*sent = &entityList[ entityNum ];
+	src_t		*curSource;
+	vec3_t		sorigin, svelocity;
+
+	if(S_AL_CheckInput(entityNum, sfx))
+		return;
+
+	// Do we need to allocate a new source for this entity
+	if( !sent->srcAllocated )
+	{
+		// Try to get a channel
+		src = S_AL_SrcAlloc( priority, entityNum, -1 );
+		if( src == -1 )
+		{
+			Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source "
+					"for loop sfx %d on entity %d\n", sfx, entityNum );
+			return;
+		}
+
+		curSource = &srcList[src];
+
+		sent->startLoopingSound = qtrue;
+
+		curSource->lastTimePos = -1.0;
+		curSource->lastSampleTime = Sys_Milliseconds();
+	}
+	else
+	{
+		src = sent->srcIndex;
+		curSource = &srcList[src];
+	}
+
+	sent->srcAllocated = qtrue;
+	sent->srcIndex = src;
+
+	sent->loopPriority = priority;
+	sent->loopSfx = sfx;
+
+	// If this is not set then the looping sound is stopped.
+	sent->loopAddedThisFrame = qtrue;
+
+	// UGH
+	// These lines should be called via S_AL_SrcSetup, but we
+	// can't call that yet as it buffers sfxes that may change
+	// with subsequent calls to S_AL_SrcLoop
+	curSource->entity = entityNum;
+	curSource->isLooping = qtrue;
+
+	if( S_AL_HearingThroughEntity( entityNum ) )
+	{
+		curSource->local = qtrue;
+
+		VectorClear(sorigin);
+
+		qalSourcefv(curSource->alSource, AL_POSITION, sorigin);
+		qalSourcefv(curSource->alSource, AL_VELOCITY, sorigin);
+	}
+	else
+	{
+		curSource->local = qfalse;
+
+		if(origin)
+			VectorCopy(origin, sorigin);
+		else
+			VectorCopy(sent->origin, sorigin);
+
+		S_AL_SanitiseVector(sorigin);
+		
+		VectorCopy(sorigin, curSource->loopSpeakerPos);
+		
+		if(velocity)
+		{
+			VectorCopy(velocity, svelocity);
+			S_AL_SanitiseVector(svelocity);
+		}
+		else
+			VectorClear(svelocity);
+
+		qalSourcefv( curSource->alSource, AL_POSITION, (ALfloat *)sorigin );
+		qalSourcefv( curSource->alSource, AL_VELOCITY, (ALfloat *)velocity );
+	}
+}
+
+/*
+=================
+S_AL_AddLoopingSound
+=================
+*/
+static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx)
+{
+	S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum);
+}
+
+/*
+=================
+S_AL_AddRealLoopingSound
+=================
+*/
+static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx)
+{
+	S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum);
+}
+
+/*
+=================
+S_AL_StopLoopingSound
+=================
+*/
+static
+void S_AL_StopLoopingSound(int entityNum )
+{
+	if(entityList[entityNum].srcAllocated)
+		S_AL_SrcKill(entityList[entityNum].srcIndex);
+}
+
+/*
+=================
+S_AL_SrcUpdate
+
+Update state (move things around, manage sources, and so on)
+=================
+*/
+static
+void S_AL_SrcUpdate( void )
+{
+	int i;
+	int entityNum;
+	ALint state;
+	src_t *curSource;
+	
+	for(i = 0; i < srcCount; i++)
+	{
+		entityNum = srcList[i].entity;
+		curSource = &srcList[i];
+
+		if(curSource->isLocked)
+			continue;
+
+		if(!curSource->isActive)
+			continue;
+
+		// Update source parameters
+		if((s_alGain->modified) || (s_volume->modified))
+			curSource->curGain = s_alGain->value * s_volume->value;
+		if((s_alRolloff->modified) && (!curSource->local))
+			qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+		if(s_alMinDistance->modified)
+			qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
+
+		if(curSource->isLooping)
+		{
+			sentity_t *sent = &entityList[ entityNum ];
+
+			// If a looping effect hasn't been touched this frame, pause or kill it
+			if(sent->loopAddedThisFrame)
+			{
+				alSfx_t *curSfx;
+			
+				// The sound has changed without an intervening removal
+				if(curSource->isActive && !sent->startLoopingSound &&
+						curSource->sfx != sent->loopSfx)
+				{
+					S_AL_NewLoopMaster(curSource, qtrue);
+
+					curSource->isPlaying = qfalse;
+					qalSourceStop(curSource->alSource);
+					qalSourcei(curSource->alSource, AL_BUFFER, 0);
+					sent->startLoopingSound = qtrue;
+				}
+
+				// The sound hasn't been started yet
+				if(sent->startLoopingSound)
+				{
+					S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority,
+							entityNum, -1, curSource->local);
+					curSource->isLooping = qtrue;
+					
+					knownSfx[curSource->sfx].loopCnt++;
+					sent->startLoopingSound = qfalse;
+				}
+				
+				curSfx = &knownSfx[curSource->sfx];
+
+				S_AL_ScaleGain(curSource, curSource->loopSpeakerPos);
+				if(!curSource->scaleGain)
+				{
+					if(curSource->isPlaying)
+					{
+						// Sound is mute, stop playback until we are in range again
+						S_AL_NewLoopMaster(curSource, qfalse);
+						qalSourceStop(curSource->alSource);
+						curSource->isPlaying = qfalse;
+					}
+					else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0)
+						curSfx->masterLoopSrc = i;
+					
+					continue;
+				}
+
+				if(!curSource->isPlaying)
+				{
+					if(curSource->priority == SRCPRI_AMBIENT)
+					{
+						// If there are other ambient looping sources with the same sound,
+						// make sure the sound of these sources are in sync.
+
+						if(curSfx->loopActiveCnt)
+						{
+							int offset, error;
+						
+							// we already have a master loop playing, get buffer position.
+							S_AL_ClearError(qfalse);
+							qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset);
+							if((error = qalGetError()) != AL_NO_ERROR)
+							{
+								if(error != AL_INVALID_ENUM)
+								{
+									Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: "
+										   "%s\n", i, S_AL_ErrorMsg(error));
+								}
+							}
+							else
+								qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset);
+						}
+						else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0)
+						{
+							float secofs;
+						
+							src_t *master = &srcList[curSfx->masterLoopSrc];
+							// This loop sound used to be played, but all sources are stopped. Use last sample position/time
+							// to calculate offset so the player thinks the sources continued playing while they were inaudible.
+						
+							if(master->lastTimePos >= 0)
+							{
+								secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f;
+								secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
+						
+								qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
+							}
+
+							// I be the master now
+							curSfx->masterLoopSrc = i;
+						}
+						else
+							curSfx->masterLoopSrc = i;
+					}
+					else if(curSource->lastTimePos >= 0)
+					{
+						float secofs;
+						
+						// For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped
+						
+						secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f;
+						secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
+						qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
+					}
+						
+					curSfx->loopActiveCnt++;
+					
+					qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE);
+					curSource->isPlaying = qtrue;
+					qalSourcePlay(curSource->alSource);
+				}
+
+				// Update locality
+				if(curSource->local)
+				{
+					qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
+					qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
+				}
+				else
+				{
+					qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
+					qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
+				}
+				
+			}
+			else if(curSource->priority == SRCPRI_AMBIENT)
+			{
+				if(curSource->isPlaying)
+				{
+					S_AL_NewLoopMaster(curSource, qfalse);
+					qalSourceStop(curSource->alSource);
+					curSource->isPlaying = qfalse;
+				}
+			}
+			else
+				S_AL_SrcKill(i);
+
+			continue;
+		}
+
+		// Check if it's done, and flag it
+		qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state);
+		if(state == AL_STOPPED)
+		{
+			curSource->isPlaying = qfalse;
+			S_AL_SrcKill(i);
+			continue;
+		}
+
+		// Query relativity of source, don't move if it's true
+		qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state);
+
+		// See if it needs to be moved
+		if(curSource->isTracking && !state)
+		{
+			qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin);
+ 			S_AL_ScaleGain(curSource, entityList[entityNum].origin);
+		}
+	}
+}
+
+/*
+=================
+S_AL_SrcShutup
+=================
+*/
+static
+void S_AL_SrcShutup( void )
+{
+	int i;
+	for(i = 0; i < srcCount; i++)
+		S_AL_SrcKill(i);
+}
+
+/*
+=================
+S_AL_SrcGet
+=================
+*/
+static
+ALuint S_AL_SrcGet(srcHandle_t src)
+{
+	return srcList[src].alSource;
+}
+
+
+//===========================================================================
+
+static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
+static qboolean streamPlaying[MAX_RAW_STREAMS];
+static ALuint streamSources[MAX_RAW_STREAMS];
+
+/*
+=================
+S_AL_AllocateStreamChannel
+=================
+*/
+static void S_AL_AllocateStreamChannel( int stream )
+{
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+		return;
+
+	// Allocate a streamSource at high priority
+	streamSourceHandles[stream] = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+	if(streamSourceHandles[stream] == -1)
+		return;
+
+	// Lock the streamSource so nobody else can use it, and get the raw streamSource
+	S_AL_SrcLock(streamSourceHandles[stream]);
+	streamSources[stream] = S_AL_SrcGet(streamSourceHandles[stream]);
+
+	// make sure that after unmuting the S_AL_Gain in S_Update() does not turn
+	// volume up prematurely for this source
+	srcList[streamSourceHandles[stream]].scaleGain = 0.0f;
+
+	// Set some streamSource parameters
+	qalSourcei (streamSources[stream], AL_BUFFER,          0            );
+	qalSourcei (streamSources[stream], AL_LOOPING,         AL_FALSE     );
+	qalSource3f(streamSources[stream], AL_POSITION,        0.0, 0.0, 0.0);
+	qalSource3f(streamSources[stream], AL_VELOCITY,        0.0, 0.0, 0.0);
+	qalSource3f(streamSources[stream], AL_DIRECTION,       0.0, 0.0, 0.0);
+	qalSourcef (streamSources[stream], AL_ROLLOFF_FACTOR,  0.0          );
+	qalSourcei (streamSources[stream], AL_SOURCE_RELATIVE, AL_TRUE      );
+}
+
+/*
+=================
+S_AL_FreeStreamChannel
+=================
+*/
+static void S_AL_FreeStreamChannel( int stream )
+{
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+		return;
+
+	// Release the output streamSource
+	S_AL_SrcUnlock(streamSourceHandles[stream]);
+	streamSources[stream] = 0;
+	streamSourceHandles[stream] = -1;
+}
+
+/*
+=================
+S_AL_RawSamples
+=================
+*/
+static
+void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume)
+{
+	ALuint buffer;
+	ALuint format;
+
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+		return;
+
+	format = S_AL_Format( width, channels );
+
+	// Create the streamSource if necessary
+	if(streamSourceHandles[stream] == -1)
+	{
+		S_AL_AllocateStreamChannel(stream);
+	
+		// Failed?
+		if(streamSourceHandles[stream] == -1)
+		{
+			Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n");
+			return;
+		}
+	}
+
+	// Create a buffer, and stuff the data into it
+	qalGenBuffers(1, &buffer);
+	qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate);
+
+	// Shove the data onto the streamSource
+	qalSourceQueueBuffers(streamSources[stream], 1, &buffer);
+
+	// Volume
+	S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value);
+}
+
+/*
+=================
+S_AL_StreamUpdate
+=================
+*/
+static
+void S_AL_StreamUpdate( int stream )
+{
+	int		numBuffers;
+	ALint	state;
+
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+		return;
+
+	if(streamSourceHandles[stream] == -1)
+		return;
+
+	// Un-queue any buffers, and delete them
+	qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
+	while( numBuffers-- )
+	{
+		ALuint buffer;
+		qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
+		qalDeleteBuffers(1, &buffer);
+	}
+
+	// Start the streamSource playing if necessary
+	qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers );
+
+	qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state);
+	if(state == AL_STOPPED)
+	{
+		streamPlaying[stream] = qfalse;
+
+		// If there are no buffers queued up, release the streamSource
+		if( !numBuffers )
+			S_AL_FreeStreamChannel( stream );
+	}
+
+	if( !streamPlaying[stream] && numBuffers )
+	{
+		qalSourcePlay( streamSources[stream] );
+		streamPlaying[stream] = qtrue;
+	}
+}
+
+/*
+=================
+S_AL_StreamDie
+=================
+*/
+static
+void S_AL_StreamDie( int stream )
+{
+	int		numBuffers;
+
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+		return;
+
+	if(streamSourceHandles[stream] == -1)
+		return;
+
+	streamPlaying[stream] = qfalse;
+	qalSourceStop(streamSources[stream]);
+
+	// Un-queue any buffers, and delete them
+	qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
+	while( numBuffers-- )
+	{
+		ALuint buffer;
+		qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
+		qalDeleteBuffers(1, &buffer);
+	}
+
+	S_AL_FreeStreamChannel(stream);
+}
+
+
+//===========================================================================
+
+
+#define NUM_MUSIC_BUFFERS	4
+#define	MUSIC_BUFFER_SIZE 4096
+
+static qboolean musicPlaying = qfalse;
+static srcHandle_t musicSourceHandle = -1;
+static ALuint musicSource;
+static ALuint musicBuffers[NUM_MUSIC_BUFFERS];
+
+static snd_stream_t *mus_stream;
+static snd_stream_t *intro_stream;
+static char s_backgroundLoop[MAX_QPATH];
+
+static byte decode_buffer[MUSIC_BUFFER_SIZE];
+
+/*
+=================
+S_AL_MusicSourceGet
+=================
+*/
+static void S_AL_MusicSourceGet( void )
+{
+	// Allocate a musicSource at high priority
+	musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+	if(musicSourceHandle == -1)
+		return;
+
+	// Lock the musicSource so nobody else can use it, and get the raw musicSource
+	S_AL_SrcLock(musicSourceHandle);
+	musicSource = S_AL_SrcGet(musicSourceHandle);
+
+	// make sure that after unmuting the S_AL_Gain in S_Update() does not turn
+	// volume up prematurely for this source
+	srcList[musicSourceHandle].scaleGain = 0.0f;
+
+	// Set some musicSource parameters
+	qalSource3f(musicSource, AL_POSITION,        0.0, 0.0, 0.0);
+	qalSource3f(musicSource, AL_VELOCITY,        0.0, 0.0, 0.0);
+	qalSource3f(musicSource, AL_DIRECTION,       0.0, 0.0, 0.0);
+	qalSourcef (musicSource, AL_ROLLOFF_FACTOR,  0.0          );
+	qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE      );
+}
+
+/*
+=================
+S_AL_MusicSourceFree
+=================
+*/
+static void S_AL_MusicSourceFree( void )
+{
+	// Release the output musicSource
+	S_AL_SrcUnlock(musicSourceHandle);
+	musicSource = 0;
+	musicSourceHandle = -1;
+}
+
+/*
+=================
+S_AL_CloseMusicFiles
+=================
+*/
+static void S_AL_CloseMusicFiles(void)
+{
+	if(intro_stream)
+	{
+		S_CodecCloseStream(intro_stream);
+		intro_stream = NULL;
+	}
+	
+	if(mus_stream)
+	{
+		S_CodecCloseStream(mus_stream);
+		mus_stream = NULL;
+	}
+}
+
+/*
+=================
+S_AL_StopBackgroundTrack
+=================
+*/
+static
+void S_AL_StopBackgroundTrack( void )
+{
+	if(!musicPlaying)
+		return;
+
+	// Stop playing
+	qalSourceStop(musicSource);
+
+	// De-queue the musicBuffers
+	qalSourceUnqueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers);
+
+	// Destroy the musicBuffers
+	qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
+
+	// Free the musicSource
+	S_AL_MusicSourceFree();
+
+	// Unload the stream
+	S_AL_CloseMusicFiles();
+
+	musicPlaying = qfalse;
+}
+
+/*
+=================
+S_AL_MusicProcess
+=================
+*/
+static
+void S_AL_MusicProcess(ALuint b)
+{
+	ALenum error;
+	int l;
+	ALuint format;
+	snd_stream_t *curstream;
+
+	S_AL_ClearError( qfalse );
+
+	if(intro_stream)
+		curstream = intro_stream;
+	else
+		curstream = mus_stream;
+
+	if(!curstream)
+		return;
+
+	l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
+
+	// Run out data to read, start at the beginning again
+	if(l == 0)
+	{
+		S_CodecCloseStream(curstream);
+
+		// the intro stream just finished playing so we don't need to reopen
+		// the music stream.
+		if(intro_stream)
+			intro_stream = NULL;
+		else
+			mus_stream = S_CodecOpenStream(s_backgroundLoop);
+		
+		curstream = mus_stream;
+
+		if(!curstream)
+		{
+			S_AL_StopBackgroundTrack();
+			return;
+		}
+
+		l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
+	}
+
+	format = S_AL_Format(curstream->info.width, curstream->info.channels);
+
+	if( l == 0 )
+	{
+		// We have no data to buffer, so buffer silence
+		byte dummyData[ 2 ] = { 0 };
+
+		qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 );
+	}
+	else
+		qalBufferData(b, format, decode_buffer, l, curstream->info.rate);
+
+	if( ( error = qalGetError( ) ) != AL_NO_ERROR )
+	{
+		S_AL_StopBackgroundTrack( );
+		Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n",
+				S_AL_ErrorMsg( error ) );
+		return;
+	}
+}
+
+/*
+=================
+S_AL_StartBackgroundTrack
+=================
+*/
+static
+void S_AL_StartBackgroundTrack( const char *intro, const char *loop )
+{
+	int i;
+	qboolean issame;
+
+	// Stop any existing music that might be playing
+	S_AL_StopBackgroundTrack();
+
+	if((!intro || !*intro) && (!loop || !*loop))
+		return;
+
+	// Allocate a musicSource
+	S_AL_MusicSourceGet();
+	if(musicSourceHandle == -1)
+		return;
+
+	if (!loop || !*loop)
+	{
+		loop = intro;
+		issame = qtrue;
+	}
+	else if(intro && *intro && !strcmp(intro, loop))
+		issame = qtrue;
+	else
+		issame = qfalse;
+
+	// Copy the loop over
+	strncpy( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
+
+	if(!issame)
+	{
+		// Open the intro and don't mind whether it succeeds.
+		// The important part is the loop.
+		intro_stream = S_CodecOpenStream(intro);
+	}
+	else
+		intro_stream = NULL;
+
+	mus_stream = S_CodecOpenStream(s_backgroundLoop);
+	if(!mus_stream)
+	{
+		S_AL_CloseMusicFiles();
+		S_AL_MusicSourceFree();
+		return;
+	}
+
+	// Generate the musicBuffers
+	qalGenBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
+	
+	// Queue the musicBuffers up
+	for(i = 0; i < NUM_MUSIC_BUFFERS; i++)
+	{
+		S_AL_MusicProcess(musicBuffers[i]);
+	}
+
+	qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers);
+
+	// Set the initial gain property
+	S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value);
+	
+	// Start playing
+	qalSourcePlay(musicSource);
+
+	musicPlaying = qtrue;
+}
+
+/*
+=================
+S_AL_MusicUpdate
+=================
+*/
+static
+void S_AL_MusicUpdate( void )
+{
+	int		numBuffers;
+	ALint	state;
+
+	if(!musicPlaying)
+		return;
+
+	qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers );
+	while( numBuffers-- )
+	{
+		ALuint b;
+		qalSourceUnqueueBuffers(musicSource, 1, &b);
+		S_AL_MusicProcess(b);
+		qalSourceQueueBuffers(musicSource, 1, &b);
+	}
+
+	// Hitches can cause OpenAL to be starved of buffers when streaming.
+	// If this happens, it will stop playback. This restarts the source if
+	// it is no longer playing, and if there are buffers available
+	qalGetSourcei( musicSource, AL_SOURCE_STATE, &state );
+	qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers );
+	if( state == AL_STOPPED && numBuffers )
+	{
+		Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" );
+		qalSourcePlay(musicSource);
+	}
+
+	// Set the gain property
+	S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value);
+}
+
+
+//===========================================================================
+
+
+// Local state variables
+static ALCdevice *alDevice;
+static ALCcontext *alContext;
+
+#ifdef USE_VOIP
+static ALCdevice *alCaptureDevice;
+static cvar_t *s_alCapture;
+#endif
+
+#ifdef _WIN32
+#define ALDRIVER_DEFAULT "OpenAL32.dll"
+#elif defined(MACOS_X)
+#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL"
+#else
+#define ALDRIVER_DEFAULT "libopenal.so.1"
+#endif
+
+/*
+=================
+S_AL_StopAllSounds
+=================
+*/
+static
+void S_AL_StopAllSounds( void )
+{
+	int i;
+	S_AL_SrcShutup();
+	S_AL_StopBackgroundTrack();
+	for (i = 0; i < MAX_RAW_STREAMS; i++)
+		S_AL_StreamDie(i);
+}
+
+/*
+=================
+S_AL_Respatialize
+=================
+*/
+static
+void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater )
+{
+	float		velocity[3] = {0.0f, 0.0f, 0.0f};
+	float		orientation[6];
+	vec3_t	sorigin;
+
+	VectorCopy( origin, sorigin );
+	S_AL_SanitiseVector( sorigin );
+
+	S_AL_SanitiseVector( axis[ 0 ] );
+	S_AL_SanitiseVector( axis[ 1 ] );
+	S_AL_SanitiseVector( axis[ 2 ] );
+
+	orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2];
+	orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2];
+
+	VectorCopy( sorigin, lastListenerOrigin );
+
+	// Set OpenAL listener paramaters
+	qalListenerfv(AL_POSITION, (ALfloat *)sorigin);
+	qalListenerfv(AL_VELOCITY, velocity);
+	qalListenerfv(AL_ORIENTATION, orientation);
+}
+
+/*
+=================
+S_AL_Update
+=================
+*/
+static
+void S_AL_Update( void )
+{
+	int i;
+
+	if(s_muted->modified)
+	{
+		// muted state changed. Let S_AL_Gain turn up all sources again.
+		for(i = 0; i < srcCount; i++)
+		{
+			if(srcList[i].isActive)
+				S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain);
+		}
+		
+		s_muted->modified = qfalse;
+	}
+
+	// Update SFX channels
+	S_AL_SrcUpdate();
+
+	// Update streams
+	for (i = 0; i < MAX_RAW_STREAMS; i++)
+		S_AL_StreamUpdate(i);
+	S_AL_MusicUpdate();
+
+	// Doppler
+	if(s_doppler->modified)
+	{
+		s_alDopplerFactor->modified = qtrue;
+		s_doppler->modified = qfalse;
+	}
+
+	// Doppler parameters
+	if(s_alDopplerFactor->modified)
+	{
+		if(s_doppler->integer)
+			qalDopplerFactor(s_alDopplerFactor->value);
+		else
+			qalDopplerFactor(0.0f);
+		s_alDopplerFactor->modified = qfalse;
+	}
+	if(s_alDopplerSpeed->modified)
+	{
+		qalDopplerVelocity(s_alDopplerSpeed->value);
+		s_alDopplerSpeed->modified = qfalse;
+	}
+
+	// Clear the modified flags on the other cvars
+	s_alGain->modified = qfalse;
+	s_volume->modified = qfalse;
+	s_musicVolume->modified = qfalse;
+	s_alMinDistance->modified = qfalse;
+	s_alRolloff->modified = qfalse;
+}
+
+/*
+=================
+S_AL_DisableSounds
+=================
+*/
+static
+void S_AL_DisableSounds( void )
+{
+	S_AL_StopAllSounds();
+}
+
+/*
+=================
+S_AL_BeginRegistration
+=================
+*/
+static
+void S_AL_BeginRegistration( void )
+{
+}
+
+/*
+=================
+S_AL_ClearSoundBuffer
+=================
+*/
+static
+void S_AL_ClearSoundBuffer( void )
+{
+}
+
+/*
+=================
+S_AL_SoundList
+=================
+*/
+static
+void S_AL_SoundList( void )
+{
+}
+
+#ifdef USE_VOIP
+static
+void S_AL_StartCapture( void )
+{
+	if (alCaptureDevice != NULL)
+		qalcCaptureStart(alCaptureDevice);
+}
+
+static
+int S_AL_AvailableCaptureSamples( void )
+{
+	int retval = 0;
+	if (alCaptureDevice != NULL)
+	{
+		ALint samples = 0;
+		qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples);
+		retval = (int) samples;
+	}
+	return retval;
+}
+
+static
+void S_AL_Capture( int samples, byte *data )
+{
+	if (alCaptureDevice != NULL)
+		qalcCaptureSamples(alCaptureDevice, data, samples);
+}
+
+void S_AL_StopCapture( void )
+{
+	if (alCaptureDevice != NULL)
+		qalcCaptureStop(alCaptureDevice);
+}
+
+void S_AL_MasterGain( float gain )
+{
+	qalListenerf(AL_GAIN, gain);
+}
+#endif
+
+
+/*
+=================
+S_AL_SoundInfo
+=================
+*/
+static void S_AL_SoundInfo(void)
+{
+	Com_Printf( "OpenAL info:\n" );
+	Com_Printf( "  Vendor:         %s\n", qalGetString( AL_VENDOR ) );
+	Com_Printf( "  Version:        %s\n", qalGetString( AL_VERSION ) );
+	Com_Printf( "  Renderer:       %s\n", qalGetString( AL_RENDERER ) );
+	Com_Printf( "  AL Extensions:  %s\n", qalGetString( AL_EXTENSIONS ) );
+	Com_Printf( "  ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) );
+
+	if(enumeration_all_ext)
+		Com_Printf("  Device:         %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER));
+	else if(enumeration_ext)
+		Com_Printf("  Device:         %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER));
+
+	if(enumeration_all_ext || enumeration_ext)
+		Com_Printf("  Available Devices:\n%s", s_alAvailableDevices->string);
+
+#ifdef USE_VOIP
+	if(capture_ext)
+	{
+		Com_Printf("  Input Device:   %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER));
+		Com_Printf("  Available Input Devices:\n%s", s_alAvailableInputDevices->string);
+	}
+#endif
+}
+
+
+
+/*
+=================
+S_AL_Shutdown
+=================
+*/
+static
+void S_AL_Shutdown( void )
+{
+	// Shut down everything
+	int i;
+	for (i = 0; i < MAX_RAW_STREAMS; i++)
+		S_AL_StreamDie(i);
+	S_AL_StopBackgroundTrack( );
+	S_AL_SrcShutdown( );
+	S_AL_BufferShutdown( );
+
+	qalcDestroyContext(alContext);
+	qalcCloseDevice(alDevice);
+
+#ifdef USE_VOIP
+	if (alCaptureDevice != NULL) {
+		qalcCaptureStop(alCaptureDevice);
+		qalcCaptureCloseDevice(alCaptureDevice);
+		alCaptureDevice = NULL;
+		Com_Printf( "OpenAL capture device closed.\n" );
+	}
+#endif
+
+	for (i = 0; i < MAX_RAW_STREAMS; i++) {
+		streamSourceHandles[i] = -1;
+		streamPlaying[i] = qfalse;
+		streamSources[i] = 0;
+	}
+
+	QAL_Shutdown();
+}
+
+#endif
+
+/*
+=================
+S_AL_Init
+=================
+*/
+qboolean S_AL_Init( soundInterface_t *si )
+{
+#ifdef USE_OPENAL
+	const char* device = NULL;
+	const char* inputdevice = NULL;
+	int i;
+
+	if( !si ) {
+		return qfalse;
+	}
+
+	for (i = 0; i < MAX_RAW_STREAMS; i++) {
+		streamSourceHandles[i] = -1;
+		streamPlaying[i] = qfalse;
+		streamSources[i] = 0;
+	}
+
+	// New console variables
+	s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
+	s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE );
+	s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE );
+	s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE );
+	s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "2200", CVAR_ARCHIVE );
+	s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT );
+	s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT);
+	s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT);
+	s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT);
+
+	s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH );
+
+	s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH );
+	s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);
+
+	// Load QAL
+	if( !QAL_Init( s_alDriver->string ) )
+	{
+		Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
+		return qfalse;
+	}
+
+	device = s_alDevice->string;
+	if(device && !*device)
+		device = NULL;
+
+	inputdevice = s_alInputDevice->string;
+	if(inputdevice && !*inputdevice)
+		inputdevice = NULL;
+
+
+	// Device enumeration support
+	enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
+	enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
+
+	if(enumeration_ext || enumeration_all_ext)
+	{
+		char devicenames[16384] = "";
+		const char *devicelist;
+		const char *defaultdevice;
+		int curlen;
+
+		// get all available devices + the default device name.
+		if(enumeration_ext)
+		{
+			devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
+		}
+		else
+		{
+			// We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
+			devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
+			defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+			enumeration_ext = qtrue;
+		}
+
+#ifdef _WIN32
+		// check whether the default device is generic hardware. If it is, change to
+		// Generic Software as that one works more reliably with various sound systems.
+		// If it's not, use OpenAL's default selection as we don't want to ignore
+		// native hardware acceleration.
+		if(!device && !strcmp(defaultdevice, "Generic Hardware"))
+			device = "Generic Software";
+#endif
+
+		// dump a list of available devices to a cvar for the user to see.
+		while((curlen = strlen(devicelist)))
+		{
+			Q_strcat(devicenames, sizeof(devicenames), devicelist);
+			Q_strcat(devicenames, sizeof(devicenames), "\n");
+
+			devicelist += curlen + 1;
+		}
+
+		s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
+	}
+
+	alDevice = qalcOpenDevice(device);
+	if( !alDevice && device )
+	{
+		Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device );
+		alDevice = qalcOpenDevice(NULL);
+	}
+
+	if( !alDevice )
+	{
+		QAL_Shutdown( );
+		Com_Printf( "Failed to open OpenAL device.\n" );
+		return qfalse;
+	}
+
+	// Create OpenAL context
+	alContext = qalcCreateContext( alDevice, NULL );
+	if( !alContext )
+	{
+		QAL_Shutdown( );
+		qalcCloseDevice( alDevice );
+		Com_Printf( "Failed to create OpenAL context.\n" );
+		return qfalse;
+	}
+	qalcMakeContextCurrent( alContext );
+
+	// Initialize sources, buffers, music
+	S_AL_BufferInit( );
+	S_AL_SrcInit( );
+
+	// Set up OpenAL parameters (doppler, etc)
+	qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
+	qalDopplerFactor( s_alDopplerFactor->value );
+	qalDopplerVelocity( s_alDopplerSpeed->value );
+
+#ifdef USE_VOIP
+	// !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
+	// !!! FIXME: add support for capture device enumeration.
+	// !!! FIXME: add some better error reporting.
+	s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	if (!s_alCapture->integer)
+	{
+		Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
+	}
+#if USE_MUMBLE
+	else if (cl_useMumble->integer)
+	{
+		Com_Printf("OpenAL capture support disabled for Mumble support\n");
+	}
+#endif
+	else
+	{
+#ifdef MACOS_X
+		// !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
+		// !!! FIXME:  capture support, but they don't list it in the
+		// !!! FIXME:  extension string. We need to check the version string,
+		// !!! FIXME:  then the extension string, but that's too much trouble,
+		// !!! FIXME:  so we'll just check the function pointer for now.
+		if (qalcCaptureOpenDevice == NULL)
+#else
+		if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture"))
+#endif
+		{
+			Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
+		}
+		else
+		{
+			char inputdevicenames[16384] = "";
+			const char *inputdevicelist;
+			const char *defaultinputdevice;
+			int curlen;
+
+			capture_ext = qtrue;
+
+			// get all available input devices + the default input device name.
+			inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
+			defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
+
+			// dump a list of available devices to a cvar for the user to see.
+			while((curlen = strlen(inputdevicelist)))
+			{
+				Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist);
+				Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n");
+				inputdevicelist += curlen + 1;
+			}
+
+			s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);
+
+			// !!! FIXME: 8000Hz is what Speex narrowband mode needs, but we
+			// !!! FIXME:  should probably open the capture device after
+			// !!! FIXME:  initializing Speex so we can change to wideband
+			// !!! FIXME:  if we like.
+			Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice);
+			alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 8000, AL_FORMAT_MONO16, 4096);
+			if( !alCaptureDevice && inputdevice )
+			{
+				Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
+				alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096);
+			}
+			Com_Printf( "OpenAL capture device %s.\n",
+				    (alCaptureDevice == NULL) ? "failed to open" : "opened");
+		}
+	}
+#endif
+
+	si->Shutdown = S_AL_Shutdown;
+	si->StartSound = S_AL_StartSound;
+	si->StartLocalSound = S_AL_StartLocalSound;
+	si->StartBackgroundTrack = S_AL_StartBackgroundTrack;
+	si->StopBackgroundTrack = S_AL_StopBackgroundTrack;
+	si->RawSamples = S_AL_RawSamples;
+	si->StopAllSounds = S_AL_StopAllSounds;
+	si->ClearLoopingSounds = S_AL_ClearLoopingSounds;
+	si->AddLoopingSound = S_AL_AddLoopingSound;
+	si->AddRealLoopingSound = S_AL_AddRealLoopingSound;
+	si->StopLoopingSound = S_AL_StopLoopingSound;
+	si->Respatialize = S_AL_Respatialize;
+	si->UpdateEntityPosition = S_AL_UpdateEntityPosition;
+	si->Update = S_AL_Update;
+	si->DisableSounds = S_AL_DisableSounds;
+	si->BeginRegistration = S_AL_BeginRegistration;
+	si->RegisterSound = S_AL_RegisterSound;
+	si->SoundDuration = S_AL_SoundDuration;
+	si->ClearSoundBuffer = S_AL_ClearSoundBuffer;
+	si->SoundInfo = S_AL_SoundInfo;
+	si->SoundList = S_AL_SoundList;
+
+#ifdef USE_VOIP
+	si->StartCapture = S_AL_StartCapture;
+	si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
+	si->Capture = S_AL_Capture;
+	si->StopCapture = S_AL_StopCapture;
+	si->MasterGain = S_AL_MasterGain;
+#endif
+
+	return qtrue;
+#else
+	return qfalse;
+#endif
+}
+
diff --git a/src/client/snd_public.h b/src/client/snd_public.h
new file mode 100644
index 0000000..20c1c7f
--- /dev/null
+++ b/src/client/snd_public.h
@@ -0,0 +1,85 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+
+void S_Init( void );
+void S_Shutdown( void );
+
+// if origin is NULL, the sound will be dynamically sourced from the entity
+void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx );
+void S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+
+void S_StartBackgroundTrack( const char *intro, const char *loop );
+void S_StopBackgroundTrack( void );
+
+// cinematics and voice-over-network will send raw samples
+// 1.0 volume will be direct output of source samples
+void S_RawSamples (int stream, int samples, int rate, int width, int channels,
+				   const byte *data, float volume);
+
+// stop all sounds and the background track
+void S_StopAllSounds( void );
+
+// all continuous looping sounds must be added before calling S_Update
+void S_ClearLoopingSounds( qboolean killall );
+void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void S_StopLoopingSound(int entityNum );
+
+// recompute the reletive volumes for all running sounds
+// reletive to the given entityNum / orientation
+void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+
+// let the sound system know where an entity currently is
+void S_UpdateEntityPosition( int entityNum, const vec3_t origin );
+
+void S_Update( void );
+
+void S_DisableSounds( void );
+
+void S_BeginRegistration( void );
+
+// RegisterSound will allways return a valid sample, even if it
+// has to create a placeholder.  This prevents continuous filesystem
+// checks for missing files
+sfxHandle_t	S_RegisterSound( const char *sample, qboolean compressed );
+
+int S_SoundDuration( sfxHandle_t handle );
+
+void S_DisplayFreeMemory(void);
+
+void S_ClearSoundBuffer( void );
+
+void SNDDMA_Activate( void );
+
+void S_UpdateBackgroundTrack( void );
+
+
+#ifdef USE_VOIP
+void S_StartCapture( void );
+int S_AvailableCaptureSamples( void );
+void S_Capture( int samples, byte *data );
+void S_StopCapture( void );
+void S_MasterGain( float gain );
+#endif
+
diff --git a/src/client/snd_wavelet.c b/src/client/snd_wavelet.c
new file mode 100644
index 0000000..e75323c
--- /dev/null
+++ b/src/client/snd_wavelet.c
@@ -0,0 +1,254 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "snd_local.h"
+
+long myftol( float f );
+
+#define C0 0.4829629131445341
+#define C1 0.8365163037378079
+#define C2 0.2241438680420134
+#define C3 -0.1294095225512604
+
+void daub4(float b[], unsigned long n, int isign)
+{
+	float wksp[4097];
+	float	*a=b-1;						// numerical recipies so a[1] = b[0]
+
+	unsigned long nh,nh1,i,j;
+
+	if (n < 4) return;
+
+	nh1=(nh=n >> 1)+1;
+	if (isign >= 0) {
+		for (i=1,j=1;j<=n-3;j+=2,i++) {
+			wksp[i]	   = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3];
+			wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3];
+		}
+		wksp[i   ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2];
+		wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2];
+	} else {
+		wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1];
+		wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1];
+		for (i=1,j=3;i<nh;i++) {
+			wksp[j++] = C2*a[i]+C1*a[i+nh]+C0*a[i+1]+C3*a[i+nh1];
+			wksp[j++] = C3*a[i]-C0*a[i+nh]+C1*a[i+1]-C2*a[i+nh1];
+		}
+	}
+	for (i=1;i<=n;i++) {
+		a[i]=wksp[i];
+	}
+}
+
+void wt1(float a[], unsigned long n, int isign)
+{
+	unsigned long nn;
+	int inverseStartLength = n/4;
+	if (n < inverseStartLength) return;
+	if (isign >= 0) {
+		for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign);
+	} else {
+		for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign);
+	}
+}
+
+/* The number of bits required by each value */
+static unsigned char numBits[] = {
+   0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+};
+
+byte MuLawEncode(short s) {
+	unsigned long adjusted;
+	byte sign, exponent, mantissa;
+
+	sign = (s<0)?0:0x80;
+
+	if (s<0) s=-s;
+	adjusted = (long)s << (16-sizeof(short)*8);
+	adjusted += 128L + 4L;
+	if (adjusted > 32767) adjusted = 32767;
+	exponent = numBits[(adjusted>>7)&0xff] - 1;
+	mantissa = (adjusted>>(exponent+3))&0xf;
+	return ~(sign | (exponent<<4) | mantissa);
+}
+
+short MuLawDecode(byte uLaw) {
+	signed long adjusted;
+	byte exponent, mantissa;
+
+	uLaw = ~uLaw;
+	exponent = (uLaw>>4) & 0x7;
+	mantissa = (uLaw&0xf) + 16;
+	adjusted = (mantissa << (exponent +3)) - 128 - 4;
+
+	return (uLaw & 0x80)? adjusted : -adjusted;
+}
+
+short mulawToShort[256];
+static qboolean madeTable = qfalse;
+
+static	int	NXStreamCount;
+
+void NXPutc(NXStream *stream, char out) {
+	stream[NXStreamCount++] = out;
+}
+
+
+void encodeWavelet( sfx_t *sfx, short *packets) {
+	float	wksp[4097], temp;
+	int		i, samples, size;
+	sndBuffer		*newchunk, *chunk;
+	byte			*out;
+
+	if (!madeTable) {
+		for (i=0;i<256;i++) {
+			mulawToShort[i] = (float)MuLawDecode((byte)i);
+		}
+		madeTable = qtrue;
+	}
+	chunk = NULL;
+
+	samples = sfx->soundLength;
+	while(samples>0) {
+		size = samples;
+		if (size>(SND_CHUNK_SIZE*2)) {
+			size = (SND_CHUNK_SIZE*2);
+		}
+
+		if (size<4) {
+			size = 4;
+		}
+
+		newchunk = SND_malloc();
+		if (sfx->soundData == NULL) {
+			sfx->soundData = newchunk;
+		} else {
+			chunk->next = newchunk;
+		}
+		chunk = newchunk;
+		for(i=0; i<size; i++) {
+			wksp[i] = *packets;
+			packets++;
+		}
+		wt1(wksp, size, 1);
+		out = (byte *)chunk->sndChunk;
+
+		for(i=0;i<size;i++) {
+			temp = wksp[i];
+			if (temp > 32767) temp = 32767; else if (temp<-32768) temp = -32768;
+			out[i] = MuLawEncode((short)temp);
+		}
+
+		chunk->size = size;
+		samples -= size;
+	}
+}
+
+void decodeWavelet(sndBuffer *chunk, short *to) {
+	float			wksp[4097];
+	int				i;
+	byte			*out;
+
+	int size = chunk->size;
+	
+	out = (byte *)chunk->sndChunk;
+	for(i=0;i<size;i++) {
+		wksp[i] = mulawToShort[out[i]];
+	}
+
+	wt1(wksp, size, -1);
+	
+	if (!to) return;
+
+	for(i=0; i<size; i++) {
+		to[i] = wksp[i];
+	}
+}
+
+
+void encodeMuLaw( sfx_t *sfx, short *packets) {
+	int		i, samples, size, grade, poop;
+	sndBuffer		*newchunk, *chunk;
+	byte			*out;
+
+	if (!madeTable) {
+		for (i=0;i<256;i++) {
+			mulawToShort[i] = (float)MuLawDecode((byte)i);
+		}
+		madeTable = qtrue;
+	}
+
+	chunk = NULL;
+	samples = sfx->soundLength;
+	grade = 0;
+
+	while(samples>0) {
+		size = samples;
+		if (size>(SND_CHUNK_SIZE*2)) {
+			size = (SND_CHUNK_SIZE*2);
+		}
+
+		newchunk = SND_malloc();
+		if (sfx->soundData == NULL) {
+			sfx->soundData = newchunk;
+		} else {
+			chunk->next = newchunk;
+		}
+		chunk = newchunk;
+		out = (byte *)chunk->sndChunk;
+		for(i=0; i<size; i++) {
+			poop = packets[0]+grade;
+			if (poop>32767) {
+				poop = 32767;
+			} else if (poop<-32768) {
+				poop = -32768;
+			}
+			out[i] = MuLawEncode((short)poop);
+			grade = poop - mulawToShort[out[i]];
+			packets++;
+		}
+		chunk->size = size;
+		samples -= size;
+	}
+}
+
+void decodeMuLaw(sndBuffer *chunk, short *to) {
+	int				i;
+	byte			*out;
+
+	int size = chunk->size;
+	
+	out = (byte *)chunk->sndChunk;
+	for(i=0;i<size;i++) {
+		to[i] = mulawToShort[out[i]];
+	}
+}
+
+
-- 
cgit