diff options
author | Andrey Nazarov <skuller@skuller.net> | 2013-02-03 21:58:32 +0400 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2013-02-03 22:12:21 +0400 |
commit | 8b05e7f3a9972da520eff04e0af36e19ed0eb6d4 (patch) | |
tree | adbfad4ff528d46be6f6e7204ae1be0bdfbe0293 | |
parent | 66a7677c2911793c4d9380f13044205fbd795947 (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.h | 3 | ||||
-rw-r--r-- | src/client/client.h | 5 | ||||
-rw-r--r-- | src/client/download.c | 182 | ||||
-rw-r--r-- | src/client/parse.c | 28 |
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; |