summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2013-02-03 21:58:32 +0400
committerAndrey Nazarov <skuller@skuller.net>2013-02-03 22:12:21 +0400
commit8b05e7f3a9972da520eff04e0af36e19ed0eb6d4 (patch)
treeadbfad4ff528d46be6f6e7204ae1be0bdfbe0293
parent66a7677c2911793c4d9380f13044205fbd795947 (diff)
Add client support for compressed UDP downloads.
Support both continuous deflate stream for entire download and chunked per-packet streams. Add new minor Q2PRO protocol version 1021.
-rw-r--r--inc/common/protocol.h3
-rw-r--r--src/client/client.h5
-rw-r--r--src/client/download.c182
-rw-r--r--src/client/parse.c28
4 files changed, 169 insertions, 49 deletions
diff --git a/inc/common/protocol.h b/inc/common/protocol.h
index 5d752ef..6ab42fc 100644
--- a/inc/common/protocol.h
+++ b/inc/common/protocol.h
@@ -46,7 +46,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PROTOCOL_VERSION_Q2PRO_SHORT_ANGLES 1018 // r1037-44
#define PROTOCOL_VERSION_Q2PRO_SERVER_STATE 1019 // r1302
#define PROTOCOL_VERSION_Q2PRO_EXTENDED_LAYOUT 1020 // r1354
-#define PROTOCOL_VERSION_Q2PRO_CURRENT 1020 // r1354
+#define PROTOCOL_VERSION_Q2PRO_ZLIB_DOWNLOADS 1021 // r1358
+#define PROTOCOL_VERSION_Q2PRO_CURRENT 1021 // r1358
#define PROTOCOL_VERSION_MVD_MINIMUM 2009 // r168
#define PROTOCOL_VERSION_MVD_CURRENT 2010 // r177
diff --git a/src/client/client.h b/src/client/client.h
index e46d888..6960e48 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -411,6 +411,9 @@ typedef struct client_static_s {
int percent; // how much downloaded
qhandle_t file; // UDP file transfer from server
char temp[MAX_QPATH + 4];// account 4 bytes for .tmp suffix
+#if USE_ZLIB
+ z_stream z; // UDP download zlib stream
+#endif
string_entry_t *ignores; // list of ignored paths
} download;
@@ -588,7 +591,7 @@ qboolean CL_IgnoreDownload(const char *path);
void CL_FinishDownload(dlqueue_t *q);
void CL_CleanupDownloads(void);
void CL_LoadDownloadIgnores(void);
-void CL_HandleDownload(const byte *data, int size, int percent);
+void CL_HandleDownload(byte *data, int size, int percent, int compressed);
qboolean CL_CheckDownloadExtension(const char *ext);
void CL_StartNextDownload(void);
void CL_RequestNextDownload(void);
diff --git a/src/client/download.c b/src/client/download.c
index fd9df7c..c2d8a82 100644
--- a/src/client/download.c
+++ b/src/client/download.c
@@ -155,6 +155,10 @@ void CL_CleanupDownloads(void)
}
cls.download.temp[0] = 0;
+
+#if USE_ZLIB
+ inflateEnd(&cls.download.z);
+#endif
}
/*
@@ -228,8 +232,7 @@ void CL_LoadDownloadIgnores(void)
FS_FreeFile(raw);
}
-// start legacy UDP download
-static qboolean start_download(dlqueue_t *q)
+static qboolean start_udp_download(dlqueue_t *q)
{
size_t len;
qhandle_t f;
@@ -246,7 +249,6 @@ static qboolean start_download(dlqueue_t *q)
memcpy(cls.download.temp, q->path, len);
memcpy(cls.download.temp + len, ".tmp", 5);
-//ZOID
// check to see if we already have a tmp for this file, if so, try to resume
// open the file if not opened yet
ret = FS_FOpenFile(cls.download.temp, &f, FS_MODE_RDWR);
@@ -254,10 +256,20 @@ static qboolean start_download(dlqueue_t *q)
cls.download.file = f;
// give the server an offset to start the download
Com_DPrintf("[UDP] Resuming %s\n", q->path);
- CL_ClientCommand(va("download \"%s\" %"PRIz, q->path, ret));
+#if USE_ZLIB
+ if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2)
+ CL_ClientCommand(va("download \"%s\" %"PRIz" udp-zlib", q->path, ret));
+ else
+#endif
+ CL_ClientCommand(va("download \"%s\" %"PRIz, q->path, ret));
} else if (ret == Q_ERR_NOENT) { // it doesn't exist
Com_DPrintf("[UDP] Downloading %s\n", q->path);
- CL_ClientCommand(va("download \"%s\"", q->path));
+#if USE_ZLIB
+ if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2)
+ CL_ClientCommand(va("download \"%s\" %"PRIz" udp-zlib", q->path, (size_t)0));
+ else
+#endif
+ CL_ClientCommand(va("download \"%s\"", q->path));
} else { // error happened
Com_EPrintf("[UDP] Couldn't open %s for appending: %s\n",
cls.download.temp, Q_ErrorString(ret));
@@ -287,24 +299,127 @@ void CL_StartNextDownload(void)
FOR_EACH_DLQ(q) {
if (q->state == DL_PENDING) {
- if (start_download(q)) {
+ if (start_udp_download(q)) {
break;
}
}
}
}
+static void finish_udp_download(const char *msg)
+{
+ dlqueue_t *q = cls.download.current;
+
+ // finished with current path
+ CL_FinishDownload(q);
+
+ cls.download.current = NULL;
+ cls.download.percent = 0;
+
+ if (cls.download.file) {
+ FS_FCloseFile(cls.download.file);
+ cls.download.file = 0;
+ }
+
+ cls.download.temp[0] = 0;
+
+#if USE_ZLIB
+ inflateReset(&cls.download.z);
+#endif
+
+ if (msg) {
+ Com_Printf("[UDP] %s [%s] [%d remaining file%s]\n",
+ q->path, msg, cls.download.pending,
+ cls.download.pending == 1 ? "" : "s");
+ }
+
+ // get another file if needed
+ CL_RequestNextDownload();
+ CL_StartNextDownload();
+}
+
+static int write_udp_download(byte *data, int size)
+{
+ ssize_t ret;
+
+ ret = FS_Write(data, size, cls.download.file);
+ if (ret != size) {
+ Com_EPrintf("[UDP] Couldn't write %s: %s\n",
+ cls.download.temp, Q_ErrorString(ret));
+ finish_udp_download(NULL);
+ return -1;
+ }
+
+ return 0;
+}
+
+// handles both continuous deflate stream for entire download and chunked
+// per-packet streams for compatibility.
+static int inflate_udp_download(byte *data, int inlen, int outlen)
+{
+#if USE_ZLIB
+
+#define CHUNK 0x10000
+
+ z_streamp z = &cls.download.z;
+ byte buffer[CHUNK];
+ int ret;
+
+ z->next_in = data;
+ z->avail_in = inlen;
+
+ // initialize stream if not done yet
+ if (z->state == NULL && inflateInit2(z, -MAX_WBITS) != Z_OK)
+ Com_Error(ERR_FATAL, "%s: inflateInit2() failed", __func__);
+
+ // run inflate() until output buffer not full
+ do {
+ z->next_out = buffer;
+ z->avail_out = CHUNK;
+
+ ret = inflate(z, Z_SYNC_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END) {
+ Com_EPrintf("[UDP] inflate() failed: %s\n", z->msg);
+ finish_udp_download(NULL);
+ return -1;
+ }
+
+ Com_DDPrintf("%s: %u --> %u [%d]\n",
+ __func__,
+ inlen - z->avail_in,
+ CHUNK - z->avail_out,
+ ret);
+
+ if (write_udp_download(buffer, CHUNK - z->avail_out))
+ return -1;
+ } while (z->avail_out == 0);
+
+ // check uncompressed length if known
+ if (outlen > 0 && outlen != z->total_out)
+ Com_WPrintf("[UDP] Decompressed length mismatch: %d != %lu\n", outlen, z->total_out);
+
+ // prepare for the next stream if done
+ if (ret == Z_STREAM_END)
+ inflateReset(z);
+
+ return 0;
+#else
+ // should never happen
+ Com_Error(ERR_DROP, "Compressed server packet received, "
+ "but no zlib support linked in.");
+#endif
+}
+
/*
=====================
CL_HandleDownload
-A download data has been received from the server
+An UDP download data has been received from the server.
=====================
*/
-void CL_HandleDownload(const byte *data, int size, int percent)
+void CL_HandleDownload(byte *data, int size, int percent, int compressed)
{
dlqueue_t *q = cls.download.current;
- const char *msg = NULL;
qerror_t ret;
if (!q) {
@@ -313,15 +428,11 @@ void CL_HandleDownload(const byte *data, int size, int percent)
if (size == -1) {
if (!percent) {
- msg = "FAIL";
+ finish_udp_download("FAIL");
} else {
- msg = "STOP";
- }
- if (cls.download.file) {
- // if here, we tried to resume a file but the server said no
- FS_FCloseFile(cls.download.file);
+ finish_udp_download("STOP");
}
- goto another;
+ return;
}
// open the file if not opened yet
@@ -330,16 +441,17 @@ void CL_HandleDownload(const byte *data, int size, int percent)
if (!cls.download.file) {
Com_EPrintf("[UDP] Couldn't open %s for writing: %s\n",
cls.download.temp, Q_ErrorString(ret));
- goto another;
+ finish_udp_download(NULL);
+ return;
}
}
- ret = FS_Write(data, size, cls.download.file);
- if (ret != size) {
- Com_EPrintf("[UDP] Couldn't write %s: %s\n",
- cls.download.temp, Q_ErrorString(ret));
- FS_FCloseFile(cls.download.file);
- goto another;
+ if (compressed) {
+ if (inflate_udp_download(data, size, compressed))
+ return;
+ } else {
+ if (write_udp_download(data, size))
+ return;
}
if (percent != 100) {
@@ -349,33 +461,19 @@ void CL_HandleDownload(const byte *data, int size, int percent)
CL_ClientCommand("nextdl");
} else {
+ // close the file before renaming
FS_FCloseFile(cls.download.file);
+ cls.download.file = 0;
- // rename the temp file to it's final name
+ // rename the temp file to its final name
ret = FS_RenameFile(cls.download.temp, q->path);
if (ret) {
Com_EPrintf("[UDP] Couldn't rename %s to %s: %s\n",
cls.download.temp, q->path, Q_ErrorString(ret));
+ finish_udp_download(NULL);
} else {
- msg = "DONE";
- }
-
-another:
- // finished with current path
- CL_FinishDownload(q);
- cls.download.current = NULL;
- cls.download.percent = 0;
- cls.download.file = 0;
- cls.download.temp[0] = 0;
-
- if (msg) {
- Com_Printf("[UDP] %s [%s] [%d remaining file%s]\n",
- q->path, msg, cls.download.pending, cls.download.pending == 1 ? "" : "s");
+ finish_udp_download("DONE");
}
-
- // get another file if needed
- CL_RequestNextDownload();
- CL_StartNextDownload();
}
}
diff --git a/src/client/parse.c b/src/client/parse.c
index df2c21b..699e896 100644
--- a/src/client/parse.c
+++ b/src/client/parse.c
@@ -1022,9 +1022,9 @@ static void CL_ParseInventory(void)
}
}
-static void CL_ParseDownload(void)
+static void CL_ParseDownload(int cmd)
{
- int size, percent;
+ int size, percent, compressed;
byte *data;
if (!cls.download.temp[0]) {
@@ -1035,10 +1035,21 @@ static void CL_ParseDownload(void)
size = MSG_ReadShort();
percent = MSG_ReadByte();
if (size == -1) {
- CL_HandleDownload(NULL, size, percent);
+ CL_HandleDownload(NULL, size, percent, qfalse);
return;
}
+ // read optional uncompressed packet size
+ if (cmd == svc_zdownload) {
+ if (cls.serverProtocol == PROTOCOL_VERSION_R1Q2) {
+ compressed = MSG_ReadShort();
+ } else {
+ compressed = -1;
+ }
+ } else {
+ compressed = 0;
+ }
+
if (size < 0) {
Com_Error(ERR_DROP, "%s: bad size: %d", __func__, size);
}
@@ -1050,7 +1061,7 @@ static void CL_ParseDownload(void)
data = msg_read.data + msg_read.readcount;
msg_read.readcount += size;
- CL_HandleDownload(data, size, percent);
+ CL_HandleDownload(data, size, percent, compressed);
}
static void CL_ParseZPacket(void)
@@ -1249,7 +1260,7 @@ badbyte:
break;
case svc_download:
- CL_ParseDownload();
+ CL_ParseDownload(cmd);
continue;
case svc_frame:
@@ -1271,6 +1282,13 @@ badbyte:
CL_ParseZPacket();
continue;
+ case svc_zdownload:
+ if (cls.serverProtocol < PROTOCOL_VERSION_R1Q2) {
+ goto badbyte;
+ }
+ CL_ParseDownload(cmd);
+ continue;
+
case svc_gamestate:
if (cls.serverProtocol != PROTOCOL_VERSION_Q2PRO) {
goto badbyte;