1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: cl_download.cpp 4473 2014-01-06 22:46:03Z russellrice $
5 //
6 // Copyright (C) 1998-2006 by Randy Heit (ZDoom).
7 // Copyright (C) 2000-2006 by Sergey Makovkin (CSDoom .62).
8 // Copyright (C) 2006-2014 by The Odamex Team.
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // DESCRIPTION:
21 // File downloads
22 //
23 //-----------------------------------------------------------------------------
24
25 #include <sstream>
26 #include <iomanip>
27
28 #include "c_dispatch.h"
29 #include "cmdlib.h"
30 #include "cl_main.h"
31 #include "d_main.h"
32 #include "i_net.h"
33 #include "md5.h"
34 #include "m_argv.h"
35 #include "m_fileio.h"
36
37 #ifdef _XBOX
38 #include "i_xbox.h"
39 #endif
40
41 EXTERN_CVAR (waddirs)
42
43 void CL_Reconnect(void);
44
45 // GhostlyDeath <October 26, 2008> -- VC6 Compiler Error
46 // C2552: 'identifier' : non-aggregates cannot be initialized with initializer list
47 // What does this mean? VC6 considers std::string non-static (that it changes every time?)
48 // So it complains because std::string has a constructor!
49
50 /*struct download_s
51 {
52 public:
53 std::string filename;
54 std::string md5;
55 buf_t *buf;
56 unsigned int got_bytes;
57 } download = { "", "", NULL, 0 };*/
58
59 // this works though!
60 struct download_s
61 {
62 public:
63 std::string filename;
64 std::string md5;
65 buf_t *buf;
66 unsigned int got_bytes;
67
download_sdownload_s68 download_s()
69 {
70 buf = NULL;
71 this->clear();
72 }
73
~download_sdownload_s74 ~download_s()
75 {
76 }
77
cleardownload_s78 void clear()
79 {
80 filename = "";
81 md5 = "";
82 got_bytes = 0;
83
84 if (buf != NULL)
85 {
86 delete buf;
87 buf = NULL;
88 }
89 }
90 } download;
91
92
93 extern std::string DownloadStr;
94
95 // Pretty prints supplied byte amount into a string
FormatNBytes(float b)96 std::string FormatNBytes(float b)
97 {
98 std::ostringstream osstr;
99 float amt;
100
101 if (b >= 1048576)
102 {
103 amt = b / 1048576;
104
105 osstr << std::setprecision(4) << amt << 'M';
106 }
107 else
108 if (b >= 1024)
109 {
110 amt = b / 1024;
111
112 osstr << std::setprecision(4) << amt << 'K';
113 }
114 else
115 {
116 osstr << std::setprecision(4) << b << 'B';
117 }
118
119 return osstr.str();
120 }
121
SetDownloadPercentage(size_t pc)122 void SetDownloadPercentage(size_t pc)
123 {
124 std::stringstream ss;
125
126 ss << "Downloading " << download.filename << ": " << pc << "%";
127
128 DownloadStr = ss.str();
129 }
130
ClearDownloadProgressBar()131 void ClearDownloadProgressBar()
132 {
133 DownloadStr = "";
134 }
135
136
IntDownloadComplete(void)137 void IntDownloadComplete(void)
138 {
139 std::string actual_md5 = MD5SUM(download.buf->ptr(), download.buf->maxsize());
140
141 Printf(PRINT_HIGH, "\nDownload complete, got %u bytes\n", download.buf->maxsize());
142 Printf(PRINT_HIGH, "%s\n %s\n", download.filename.c_str(), actual_md5.c_str());
143
144 if(download.md5 == "")
145 {
146 Printf(PRINT_HIGH, "Server gave no checksum, assuming valid\n", (int)download.buf->maxsize());
147 }
148 else if(actual_md5 != download.md5)
149 {
150 Printf(PRINT_HIGH, " %s on server\n", download.md5.c_str());
151 Printf(PRINT_HIGH, "Download failed: bad checksum\n");
152
153 download.clear();
154 CL_QuitNetGame();
155
156 ClearDownloadProgressBar();
157
158 return;
159 }
160
161 // got the wad! save it!
162 std::vector<std::string> dirs;
163 std::string filename;
164 size_t i;
165 #ifdef _WIN32
166 const char separator = ';';
167 #else
168 const char separator = ':';
169 #endif
170
171 // Try to save to the wad paths in this order -- Hyper_Eye
172 D_AddSearchDir(dirs, Args.CheckValue("-waddir"), separator);
173 D_AddSearchDir(dirs, "%%DATADIR%%", separator);
174 D_AddSearchDir(dirs, "%%DMDIR%%", separator);
175 D_AddSearchDir(dirs, waddirs.cstring(), separator);
176 dirs.push_back(startdir);
177 dirs.push_back(progdir);
178
179 dirs.erase(std::unique(dirs.begin(), dirs.end()), dirs.end());
180
181 for(i = 0; i < dirs.size(); i++)
182 {
183 filename.clear();
184 filename = dirs[i];
185 if(filename[filename.length() - 1] != PATHSEPCHAR)
186 filename += PATHSEP;
187 filename += download.filename;
188
189 // check for existing file
190 if(M_FileExists(filename))
191 {
192 // there is an existing file, so use a new file whose name includes the checksum
193 filename += ".";
194 filename += actual_md5;
195 }
196
197 if (M_WriteFile(filename, download.buf->ptr(), download.buf->maxsize()))
198 break;
199 }
200
201 // Unable to write
202 if(i == dirs.size())
203 {
204 download.clear();
205 CL_QuitNetGame();
206 return;
207 }
208
209 Printf(PRINT_HIGH, "Saved download as \"%s\"\n", filename.c_str());
210
211 download.clear();
212 CL_QuitNetGame();
213 CL_Reconnect();
214
215 ClearDownloadProgressBar();
216 }
217
218 //
219 // CL_RequestDownload
220 // please sir, can i have some more?
221 //
CL_RequestDownload(std::string filename,std::string filehash)222 void CL_RequestDownload(std::string filename, std::string filehash)
223 {
224 // [Russell] - Allow resumeable downloads
225 if ((download.filename != filename) ||
226 (download.md5 != filehash))
227 {
228 download.filename = filename;
229 download.md5 = filehash;
230 download.got_bytes = 0;
231 }
232
233 // denis todo clear previous downloads
234 MSG_WriteMarker(&net_buffer, clc_wantwad);
235 MSG_WriteString(&net_buffer, filename.c_str());
236 MSG_WriteString(&net_buffer, filehash.c_str());
237 MSG_WriteLong(&net_buffer, download.got_bytes);
238
239 NET_SendPacket(net_buffer, serveraddr);
240
241 Printf(PRINT_HIGH, "Requesting download...\n");
242
243 // check for completion
244 // [Russell] - We go over the boundary, because sometimes the download will
245 // pause at 100% if the server disconnected you previously, you can
246 // reconnect a couple of times and this will let the checksum system do its
247 // work
248
249 if ((download.buf != NULL) &&
250 (download.got_bytes >= download.buf->maxsize()))
251 {
252 IntDownloadComplete();
253 }
254 }
255
256 //
257 // CL_DownloadStart
258 // server tells us the size of the file we are about to download
259 //
CL_DownloadStart()260 void CL_DownloadStart()
261 {
262 DWORD file_len = MSG_ReadLong();
263
264 if(gamestate != GS_DOWNLOAD)
265 {
266 Printf(PRINT_HIGH, "Server initiated download failed\n");
267 return;
268 }
269
270 // don't go for more than 100 megs
271 if(file_len > 100*1024*1024)
272 {
273 Printf(PRINT_HIGH, "Download is over 100MiB, aborting!\n");
274 CL_QuitNetGame();
275 return;
276 }
277
278 // [Russell] - Allow resumeable downloads
279 if (download.got_bytes == 0)
280 {
281 if (download.buf != NULL)
282 {
283 delete download.buf;
284 download.buf = NULL;
285 }
286
287 download.buf = new buf_t ((size_t)file_len);
288
289 memset(download.buf->ptr(), 0, file_len);
290 }
291 else
292 Printf(PRINT_HIGH, "Resuming download of %s...\n", download.filename.c_str());
293
294
295
296 Printf(PRINT_HIGH, "Downloading %s bytes...\n",
297 FormatNBytes(file_len).c_str());
298
299 // Make initial 0% show
300 SetDownloadPercentage(0);
301 }
302
303 //
304 // CL_Download
305 // denis - get a little chunk of the file and store it, much like a hampster. Well, hamster; but hampsters can dance and sing. Also much like Scraps, the Ice Age squirrel thing, stores his acorn. Only with a bit more success. Actually, quite a bit more success, specifically as in that the world doesn't crack apart when we store our chunk and it does when Scraps stores his (or her?) acorn. But when Scraps does it, it is funnier. The rest of Ice Age mostly sucks.
306 //
CL_Download()307 void CL_Download()
308 {
309 DWORD offset = MSG_ReadLong();
310 size_t len = MSG_ReadShort();
311 size_t left = MSG_BytesLeft();
312 void *p = MSG_ReadChunk(len);
313
314 if(gamestate != GS_DOWNLOAD)
315 return;
316
317 if (download.buf == NULL)
318 {
319 // We must have not received the svc_wadinfo message
320 Printf(PRINT_HIGH, "Unable to start download, aborting\n");
321 download.clear();
322 CL_QuitNetGame();
323 return;
324 }
325
326 // check ranges
327 if(offset + len > download.buf->maxsize() || len > left || p == NULL)
328 {
329 Printf(PRINT_HIGH, "Bad download packet (%d, %d) encountered (%d), aborting\n", (int)offset, (int)left, (int)download.buf->size());
330
331 download.clear();
332 CL_QuitNetGame();
333 return;
334 }
335
336 // check for missing packet, re-request
337 if(offset < download.got_bytes || offset > download.got_bytes)
338 {
339 DPrintf("Missed a packet after/before %d bytes (got %d), re-requesting\n", download.got_bytes, offset);
340 MSG_WriteMarker(&net_buffer, clc_wantwad);
341 MSG_WriteString(&net_buffer, download.filename.c_str());
342 MSG_WriteString(&net_buffer, download.md5.c_str());
343 MSG_WriteLong(&net_buffer, download.got_bytes);
344 NET_SendPacket(net_buffer, serveraddr);
345 return;
346 }
347
348 // send keepalive
349 NET_SendPacket(net_buffer, serveraddr);
350
351 // copy into downloaded buffer
352 memcpy(download.buf->ptr() + offset, p, len);
353 download.got_bytes += len;
354
355 // calculate percentage for the user
356 static int old_percent = 0;
357 int percent = (download.got_bytes*100)/download.buf->maxsize();
358 if(percent != old_percent)
359 {
360 SetDownloadPercentage(percent);
361
362 old_percent = percent;
363 }
364
365 // check for completion
366 // [Russell] - We go over the boundary, because sometimes the download will
367 // pause at 100% if the server disconnected you previously, you can
368 // reconnect a couple of times and this will let the checksum system do its
369 // work
370 if(download.got_bytes >= download.buf->maxsize())
371 {
372 IntDownloadComplete();
373 }
374 }
375
376 VERSION_CONTROL (cl_download_cpp, "$Id: cl_download.cpp 4473 2014-01-06 22:46:03Z russellrice $")
377