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