]> git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Blub's "deflate" extension to the download system.
authordivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Thu, 8 Jan 2009 18:58:13 +0000 (18:58 +0000)
committerdivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Thu, 8 Jan 2009 18:58:13 +0000 (18:58 +0000)
Currently only provides csprogs.dat in deflated form.
Compatible in both directions, compression is only done if both client and server use this new code.

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8638 d7cf8633-e32d-0410-b094-e92efae38249

cl_parse.c
client.h
fs.c
fs.h
server.h
sv_main.c

index 4aa25c5a4fc4e2a396fc89a41af0a0b93df47098..55b832efcba4228898091fb2a947d9ef183cf66c 100644 (file)
@@ -995,7 +995,10 @@ void CL_BeginDownloads(qboolean aborteddownload)
                 && !FS_FileExists(va("dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer)))
                {
                        Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer);
-                       Cmd_ForwardStringToServer(va("download %s", csqc_progname.string));
+                       if(cl_serverextension_download.integer >= 2 && FS_HasZlib())
+                               Cmd_ForwardStringToServer(va("download %s deflate", csqc_progname.string));
+                       else
+                               Cmd_ForwardStringToServer(va("download %s", csqc_progname.string));
                        return;
                }
        }
@@ -1184,37 +1187,57 @@ void CL_StopDownload(int size, int crc)
                size_t existingsize;
                const char *extension;
 
-               // finished file
-               // save to disk only if we don't already have it
-               // (this is mainly for playing back demos)
-               existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
-               if (existingsize || gamemode == GAME_NEXUIZ || !strcmp(cls.qw_downloadname, csqc_progname.string))
-                       // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz
+               if(cls.qw_download_deflate)
+               {
+                       unsigned char *out;
+                       size_t inflated_size;
+                       out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool);
+                       Mem_Free(cls.qw_downloadmemory);
+                       Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size));
+                       cls.qw_downloadmemory = out;
+                       cls.qw_downloadmemorycursize = inflated_size;
+               }
+
+               if(!cls.qw_downloadmemory)
                {
-                       if ((int)existingsize != size || existingcrc != crc)
+                       Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname);
+               }
+               else
+               {
+                       crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+                       size = cls.qw_downloadmemorycursize;
+                       // finished file
+                       // save to disk only if we don't already have it
+                       // (this is mainly for playing back demos)
+                       existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
+                       if (existingsize || gamemode == GAME_NEXUIZ || !strcmp(cls.qw_downloadname, csqc_progname.string))
+                               // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz
                        {
-                               // we have a mismatching file, pick another name for it
-                               char name[MAX_QPATH*2];
-                               dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
-                               if (!FS_FileExists(name))
+                               if ((int)existingsize != size || existingcrc != crc)
                                {
-                                       Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
-                                       FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+                                       // we have a mismatching file, pick another name for it
+                                       char name[MAX_QPATH*2];
+                                       dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
+                                       if (!FS_FileExists(name))
+                                       {
+                                               Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
+                                               FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+                                       }
                                }
                        }
-               }
-               else
-               {
-                       // we either don't have it or have a mismatching file...
-                       // so it's time to accept the file
-                       // but if we already have a mismatching file we need to rename
-                       // this new one, and if we already have this file in renamed form,
-                       // we do nothing
-                       Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
-                       FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
-                       extension = FS_FileExtension(cls.qw_downloadname);
-                       if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
-                               FS_Rescan();
+                       else
+                       {
+                               // we either don't have it or have a mismatching file...
+                               // so it's time to accept the file
+                               // but if we already have a mismatching file we need to rename
+                               // this new one, and if we already have this file in renamed form,
+                               // we do nothing
+                               Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
+                               FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+                               extension = FS_FileExtension(cls.qw_downloadname);
+                               if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
+                                       FS_Rescan();
+                       }
                }
        }
        else if (cls.qw_downloadmemory && size)
@@ -1293,6 +1316,14 @@ void CL_DownloadBegin_f(void)
        cls.qw_downloadmemory = Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
        cls.qw_downloadnumber++;
 
+       cls.qw_download_deflate = false;
+       if(Cmd_Argc() >= 4)
+       {
+               if(!strcmp(Cmd_Argv(3), "deflate"))
+                       cls.qw_download_deflate = true;
+               // check further encodings here
+       }
+
        Cmd_ForwardStringToServer("sv_startdownload");
 }
 
index 889f8e739f93651707c6f293cd76a5c0097f8410..6c3f46ac2b7ccc59dc4e360f136556cc4e229d33 100644 (file)
--- a/client.h
+++ b/client.h
@@ -589,6 +589,7 @@ typedef struct client_static_s
        double qw_downloadspeedtime;
        int qw_downloadspeedcount;
        int qw_downloadspeedrate;
+       qboolean qw_download_deflate;
 
        // current file upload buffer (for uploading screenshots to server)
        unsigned char *qw_uploaddata;
diff --git a/fs.c b/fs.c
index 4f7baf9d2a6f41e6c00e4097c4efbca26e4dac1f..119499f9b445d62c5723aac1a8bca8590863bd4a 100644 (file)
--- a/fs.c
+++ b/fs.c
@@ -111,8 +111,23 @@ CONSTANTS
 #define MAX_WBITS              15
 #define Z_OK                   0
 #define Z_STREAM_END   1
+#define Z_STREAM_ERROR  (-2)
+#define Z_DATA_ERROR    (-3)
+#define Z_MEM_ERROR     (-4)
+#define Z_BUF_ERROR     (-5)
 #define ZLIB_VERSION   "1.2.3"
 
+#define Z_BINARY 0
+#define Z_DEFLATED 8
+#define Z_MEMLEVEL_DEFAULT 8
+
+#define Z_NULL 0
+#define Z_DEFAULT_COMPRESSION (-1)
+#define Z_NO_FLUSH 0
+#define Z_SYNC_FLUSH 2
+#define Z_FULL_FLUSH 3
+#define Z_FINISH 4
+
 // Uncomment the following line if the zlib DLL you have still uses
 // the 1.1.x series calling convention on Win32 (WINAPI)
 //#define ZLIB_USES_WINAPI
@@ -156,6 +171,8 @@ typedef struct
 #define QFILE_FLAG_PACKED (1 << 0)
 // file is compressed using the deflate algorithm (PK3 only)
 #define QFILE_FLAG_DEFLATED (1 << 1)
+// file is actually already loaded data
+#define QFILE_FLAG_DATA (1 << 2)
 
 #define FILE_BUFF_SIZE 2048
 typedef struct
@@ -182,6 +199,9 @@ struct qfile_s
 
        // For zipped files
        ztoolkit_t*             ztk;
+
+       // for data files
+       const unsigned char *data;
 };
 
 
@@ -316,9 +336,16 @@ static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
+static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
+static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
+static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
 
 #define qz_inflateInit2(strm, windowBits) \
         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
+#define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+        qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
+
+//        qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
 
 static dllfunction_t zlibfuncs[] =
 {
@@ -326,6 +353,9 @@ static dllfunction_t zlibfuncs[] =
        {"inflateEnd",          (void **) &qz_inflateEnd},
        {"inflateInit2_",       (void **) &qz_inflateInit2_},
        {"inflateReset",        (void **) &qz_inflateReset},
+       {"deflateInit2_",   (void **) &qz_deflateInit2_},
+       {"deflateEnd",      (void **) &qz_deflateEnd},
+       {"deflate",         (void **) &qz_deflate},
        {NULL, NULL}
 };
 
@@ -392,6 +422,18 @@ qboolean PK3_OpenLibrary (void)
        return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
 }
 
+/*
+====================
+FS_HasZlib
+
+See if zlib is available
+====================
+*/
+qboolean FS_HasZlib(void)
+{
+       PK3_OpenLibrary(); // to be safe
+       return (zlib_dll != 0);
+}
 
 /*
 ====================
@@ -2036,6 +2078,25 @@ qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
 }
 
 
+/*
+====================
+FS_FileFromData
+
+Open a file. The syntax is the same as fopen
+====================
+*/
+qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
+{
+       qfile_t* file;
+       file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
+       memset (file, 0, sizeof (*file));
+       file->flags = QFILE_FLAG_DATA;
+       file->ungetc = EOF;
+       file->real_length = size;
+       file->data = data;
+       return file;
+}
+
 /*
 ====================
 FS_Close
@@ -2045,6 +2106,12 @@ Close a file
 */
 int FS_Close (qfile_t* file)
 {
+       if(file->flags & QFILE_FLAG_DATA)
+       {
+               Mem_Free(file);
+               return 0;
+       }
+
        if (close (file->handle))
                return EOF;
 
@@ -2115,6 +2182,16 @@ fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
        else
                done = 0;
 
+       if(file->flags & QFILE_FLAG_DATA)
+       {
+               size_t left = file->real_length - file->position;
+               if(buffersize > left)
+                       buffersize = left;
+               memcpy(buffer, file->data + file->position, buffersize);
+               file->position += buffersize;
+               return buffersize;
+       }
+
        // First, we copy as many bytes as we can from "buff"
        if (file->buff_ind < file->buff_len)
        {
@@ -2396,6 +2473,12 @@ int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
        if (offset < 0 || offset > file->real_length)
                return -1;
 
+       if(file->flags & QFILE_FLAG_DATA)
+       {
+               file->position = offset;
+               return 0;
+       }
+
        // If we have the data in our read buffer, we don't need to actually seek
        if (file->position - file->buff_len <= offset && offset <= file->position)
        {
@@ -3102,3 +3185,181 @@ int FS_CRCFile(const char *filename, size_t *filesizepointer)
        return crc;
 }
 
+unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
+{
+       z_stream strm;
+       unsigned char *out = NULL;
+       unsigned char *tmp;
+
+       memset(&strm, 0, sizeof(strm));
+       strm.zalloc = Z_NULL;
+       strm.zfree = Z_NULL;
+       strm.opaque = Z_NULL;
+
+       if(level < 0)
+               level = Z_DEFAULT_COMPRESSION;
+
+       if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
+       {
+               Con_Printf("FS_Deflate: deflate init error!\n");
+               return NULL;
+       }
+
+       strm.next_in = (unsigned char*)data;
+       strm.avail_in = size;
+
+       tmp = Mem_Alloc(tempmempool, size);
+       if(!tmp)
+       {
+               Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
+               qz_deflateEnd(&strm);
+               return NULL;
+       }
+
+       strm.next_out = tmp;
+       strm.avail_out = size;
+
+       if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
+       {
+               Con_Printf("FS_Deflate: deflate failed!\n");
+               qz_deflateEnd(&strm);
+               Mem_Free(tmp);
+               return NULL;
+       }
+       
+       if(qz_deflateEnd(&strm) != Z_OK)
+       {
+               Con_Printf("FS_Deflate: deflateEnd failed\n");
+               Mem_Free(tmp);
+               return NULL;
+       }
+
+       if(strm.total_out >= size)
+       {
+               Con_Printf("FS_Deflate: deflate is useless on this data!\n");
+               Mem_Free(tmp);
+               return NULL;
+       }
+
+       out = Mem_Alloc(mempool, strm.total_out);
+       if(!out)
+       {
+               Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
+               Mem_Free(tmp);
+               return NULL;
+       }
+
+       if(deflated_size)
+               *deflated_size = (size_t)strm.total_out;
+
+       memcpy(out, tmp, strm.total_out);
+       Mem_Free(tmp);
+       
+       return out;
+}
+
+static void AssertBufsize(sizebuf_t *buf, int length)
+{
+       if(buf->cursize + length > buf->maxsize)
+       {
+               int oldsize = buf->maxsize;
+               unsigned char *olddata;
+               olddata = buf->data;
+               buf->maxsize += length;
+               buf->data = Mem_Alloc(tempmempool, buf->maxsize);
+               if(olddata)
+               {
+                       memcpy(buf->data, olddata, oldsize);
+                       Mem_Free(olddata);
+               }
+       }
+}
+
+unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
+{
+       int ret;
+       z_stream strm;
+       unsigned char *out = NULL;
+       unsigned char tmp[2048];
+       unsigned int have;
+       sizebuf_t outbuf;
+
+       memset(&outbuf, 0, sizeof(outbuf));
+       outbuf.data = Mem_Alloc(tempmempool, sizeof(tmp));
+       outbuf.maxsize = sizeof(tmp);
+
+       memset(&strm, 0, sizeof(strm));
+       strm.zalloc = Z_NULL;
+       strm.zfree = Z_NULL;
+       strm.opaque = Z_NULL;
+
+       if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
+       {
+               Con_Printf("FS_Inflate: inflate init error!\n");
+               Mem_Free(outbuf.data);
+               return NULL;
+       }
+
+       strm.next_in = (unsigned char*)data;
+       strm.avail_in = size;
+
+       do
+       {
+               strm.next_out = tmp;
+               strm.avail_out = sizeof(tmp);
+               ret = qz_inflate(&strm, Z_NO_FLUSH);
+               // it either returns Z_OK on progress, Z_STREAM_END on end
+               // or an error code
+               switch(ret)
+               {
+                       case Z_STREAM_END:
+                       case Z_OK:
+                               break;
+                               
+                       case Z_STREAM_ERROR:
+                               Con_Print("FS_Inflate: stream error!\n");
+                               break;
+                       case Z_DATA_ERROR:
+                               Con_Print("FS_Inflate: data error!\n");
+                               break;
+                       case Z_MEM_ERROR:
+                               Con_Print("FS_Inflate: mem error!\n");
+                               break;
+                       case Z_BUF_ERROR:
+                               Con_Print("FS_Inflate: buf error!\n");
+                               break;
+                       default:
+                               Con_Print("FS_Inflate: unknown error!\n");
+                               break;
+                               
+               }
+               if(ret != Z_OK && ret != Z_STREAM_END)
+               {
+                       Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
+                       Mem_Free(outbuf.data);
+                       qz_inflateEnd(&strm);
+                       return NULL;
+               }
+               have = sizeof(tmp) - strm.avail_out;
+               AssertBufsize(&outbuf, max(have, sizeof(tmp)));
+               SZ_Write(&outbuf, tmp, have);
+       } while(ret != Z_STREAM_END);
+
+       qz_inflateEnd(&strm);
+
+       out = Mem_Alloc(mempool, outbuf.cursize);
+       if(!out)
+       {
+               Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
+               Mem_Free(outbuf.data);
+               return NULL;
+       }
+
+       memcpy(out, outbuf.data, outbuf.cursize);
+       Mem_Free(outbuf.data);
+
+       if(inflated_size)
+               *inflated_size = (size_t)outbuf.cursize;
+       
+       return out;
+}
diff --git a/fs.h b/fs.h
index cf2290fa87e70688eebd10dcb7663d3032aa4efd..9f01461d7a4b783a89400e7b4199b820e02ab9ec 100644 (file)
--- a/fs.h
+++ b/fs.h
@@ -59,6 +59,7 @@ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep
 const char *FS_WhichPack(const char *filename);
 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet);
 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet);
+qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet);
 int FS_Close (qfile_t* file);
 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize);
 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize);
@@ -112,5 +113,9 @@ qboolean FS_SysFileExists (const char *filename);   // only look for files outside
 
 void FS_mkdir (const char *path);
 
+unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool);
+unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool);
+
+qboolean FS_HasZlib(void);
 
 #endif
index fe11c2660fb8b3c6b841fea218cbf7034e7af058..0307bb520106f286ca32e46d72c95afb7ea32c0c 100644 (file)
--- a/server.h
+++ b/server.h
@@ -86,6 +86,9 @@ typedef struct server_s
        int csqc_progcrc; // -1 = no progs
        int csqc_progsize; // -1 = no progs
        char csqc_progname[MAX_QPATH]; // copied from csqc_progname at level start
+       unsigned char *csqc_progdata;
+       size_t csqc_progsize_deflated;
+       unsigned char *csqc_progdata_deflated;
 
        // collision culling data
        world_t world;
@@ -270,6 +273,7 @@ typedef struct client_s
        int download_expectedposition; // next position the client should ack
        qboolean download_started;
        char download_name[MAX_QPATH];
+       qboolean download_deflate;
 
        // fixangle data
        qboolean fixangle_angles_set;
index 9ca62e2c4008020a9a023ef33148b49c74f59443..e0fd7e764d53ddfbfaf74007b7bce692e672b552 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -788,24 +788,15 @@ void SV_SendServerinfo (client_t *client)
 
                if(client->sv_demo_file != NULL)
                {
-                       void *csqcbuf;
-                       fs_offset_t csqclen;
-                       int csqccrc;
                        int i;
                        char buf[NET_MAXMESSAGE];
                        sizebuf_t sb;
 
-                       csqcbuf = FS_LoadFile(sv.csqc_progname, tempmempool, true, &csqclen);
-                       if(csqcbuf)
-                       {
-                               csqccrc = CRC_Block(csqcbuf, csqclen);
-                               sb.data = (void *) buf;
-                               sb.maxsize = sizeof(buf);
-                               i = 0;
-                               while(MakeDownloadPacket(sv.csqc_progname, csqcbuf, csqclen, csqccrc, i++, &sb, sv.protocol))
-                                       SV_WriteDemoMessage(client, &sb, false);
-                               Mem_Free(csqcbuf);
-                       }
+                       sb.data = (void *) buf;
+                       sb.maxsize = sizeof(buf);
+                       i = 0;
+                       while(MakeDownloadPacket(sv.csqc_progname, sv.csqc_progdata, sv.csqc_progsize, sv.csqc_progcrc, i++, &sb, sv.protocol))
+                               SV_WriteDemoMessage(client, &sb, false);
                }
 
                //[515]: init stufftext string (it is sent before svc_serverinfo)
@@ -817,10 +808,12 @@ void SV_SendServerinfo (client_t *client)
                }
        }
 
-       if (sv_allowdownloads.integer)
+       //if (sv_allowdownloads.integer)
+       // always send the info that the server supports the protocol, even if downloads are forbidden
+       // only because of that, the CSQC exception can work
        {
                MSG_WriteByte (&client->netconnection->message, svc_stufftext);
-               MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 1\n");
+               MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 2\n");
        }
 
        // send at this time so it's guaranteed to get executed at the right time
@@ -2128,13 +2121,33 @@ static void SV_StartDownload_f(void)
                host_client->download_started = true;
 }
 
+static void Download_CheckExtensions(void)
+{
+       int i;
+       int argc = Cmd_Argc();
+
+       // first reset them all
+       host_client->download_deflate = false;
+       
+       for(i = 2; i < argc; ++i)
+       {
+               if(!strcmp(Cmd_Argv(i), "deflate"))
+               {
+                       host_client->download_deflate = true;
+                       break;
+               }
+       }
+}
+
 static void SV_Download_f(void)
 {
        const char *whichpack, *whichpack2, *extension;
+       qboolean is_csqc; // so we need to check only once
 
-       if (Cmd_Argc() != 2)
+       if (Cmd_Argc() < 2)
        {
-               SV_ClientPrintf("usage: download <filename>\n");
+               SV_ClientPrintf("usage: download <filename> {<extensions>}*\n");
+               SV_ClientPrintf("       supported extensions: deflate\n");
                return;
        }
 
@@ -2158,13 +2171,17 @@ static void SV_Download_f(void)
                host_client->download_started = false;
        }
 
-       if (!sv_allowdownloads.integer)
+       is_csqc = (sv.csqc_progname[0] && strcmp(Cmd_Argv(1), sv.csqc_progname) == 0);
+       
+       if (!sv_allowdownloads.integer && !is_csqc)
        {
                SV_ClientPrintf("Downloads are disabled on this server\n");
                Host_ClientCommands("\nstopdownload\n");
                return;
        }
 
+       Download_CheckExtensions();
+
        strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name));
        extension = FS_FileExtension(host_client->download_name);
 
@@ -2172,6 +2189,30 @@ static void SV_Download_f(void)
        if (developer.integer >= 100)
                Con_Printf("Download request for %s by %s\n", host_client->download_name, host_client->name);
 
+       if(is_csqc)
+       {
+               char extensions[MAX_QPATH]; // make sure this can hold all extensions
+               extensions[0] = '\0';
+               
+               if(host_client->download_deflate)
+                       strlcat(extensions, " deflate", sizeof(extensions));
+               
+               Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name);
+
+               if(host_client->download_deflate)
+                       host_client->download_file = FS_FileFromData(sv.csqc_progdata_deflated, sv.csqc_progsize_deflated, true);
+               else
+                       host_client->download_file = FS_FileFromData(sv.csqc_progdata, sv.csqc_progsize, true);
+               
+               // no, no space is needed between %s and %s :P
+               Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions);
+
+               host_client->download_expectedposition = 0;
+               host_client->download_started = false;
+               host_client->sendsignon = true; // make sure this message is sent
+               return;
+       }
+
        if (!FS_FileExists(host_client->download_name))
        {
                SV_ClientPrintf("Download rejected: server does not have the file \"%s\"\nYou may need to separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name);
@@ -2259,6 +2300,20 @@ static void SV_Download_f(void)
 
        Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name);
 
+       /*
+        * we can only do this if we would actually deflate on the fly
+        * which we do not (yet)!
+       {
+               char extensions[MAX_QPATH]; // make sure this can hold all extensions
+               extensions[0] = '\0';
+               
+               if(host_client->download_deflate)
+                       strlcat(extensions, " deflate", sizeof(extensions));
+
+               // no, no space is needed between %s and %s :P
+               Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions);
+       }
+       */
        Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name);
 
        host_client->download_expectedposition = 0;
@@ -2537,6 +2592,51 @@ static void SV_CreateBaseline (void)
        }
 }
 
+/*
+================
+SV_Prepare_CSQC
+
+Load csprogs.dat and comperss it so it doesn't need to be
+reloaded on request.
+================
+*/
+void SV_Prepare_CSQC(void)
+{
+       fs_offset_t progsize;
+
+       if(sv.csqc_progdata)
+       {
+               Con_DPrintf("Unloading old CSQC data.\n");
+               Mem_Free(sv.csqc_progdata);
+               if(sv.csqc_progdata_deflated)
+                       Mem_Free(sv.csqc_progdata_deflated);
+       }
+
+       sv.csqc_progdata = NULL;
+       sv.csqc_progdata_deflated = NULL;
+       
+       Con_Print("Loading csprogs.dat\n");
+
+       sv.csqc_progname[0] = 0;
+       sv.csqc_progdata = FS_LoadFile(csqc_progname.string, sv_mempool, false, &progsize);
+
+       if(progsize > 0)
+       {
+               size_t deflated_size;
+               
+               sv.csqc_progsize = (int)progsize;
+               sv.csqc_progcrc = CRC_Block(sv.csqc_progdata, progsize);
+               strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname));
+               Con_Printf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc);
+
+               Con_Print("Compressing csprogs.dat\n");
+               //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool);
+               sv.csqc_progdata_deflated = FS_Deflate(sv.csqc_progdata, progsize, &deflated_size, -1, sv_mempool);
+               sv.csqc_progsize_deflated = (int)deflated_size;
+               Con_Printf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize));
+               Con_DPrintf("Uncompressed: %u\nCompressed:   %u\n", (unsigned)sv.csqc_progsize, (unsigned)sv.csqc_progsize_deflated);
+       }
+}
 
 /*
 ================
@@ -2980,7 +3080,6 @@ static qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent)
 static void SV_VM_Setup(void)
 {
        extern cvar_t csqc_progname;    //[515]: csqc crc check and right csprogs name according to progs.dat
-       size_t csprogsdatasize;
        PRVM_Begin;
        PRVM_InitProg( PRVM_SERVERPROG );
 
@@ -3054,15 +3153,7 @@ static void SV_VM_Setup(void)
 
        PRVM_End;
 
-       // see if there is a csprogs.dat installed, and if so, set the csqc_progcrc accordingly, this will be sent to connecting clients to tell them to only load a matching csprogs.dat file
-       sv.csqc_progname[0] = 0;
-       sv.csqc_progcrc = FS_CRCFile(csqc_progname.string, &csprogsdatasize);
-       sv.csqc_progsize = csprogsdatasize;
-       if (sv.csqc_progsize > 0)
-       {
-               strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname));
-               Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc);
-       }
+       SV_Prepare_CSQC();
 }
 
 void SV_VM_Begin(void)