1 /***************************************************************************
2  *      Mechanized Assault and Exploration Reloaded Projectfile            *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19 
20 /* Author: Paul Grathwohl */
21 
22 #include "main.h"
23 #include "mapdownload.h"
24 
25 #include <iostream>
26 #include <fstream>
27 #include <sstream>
28 #include <cassert>
29 
30 #include "defines.h"
31 #include "utility/files.h"
32 #include "utility/log.h"
33 #include "menuevents.h"
34 #include "netmessage.h"
35 #include "settings.h"
36 #include "utility/string/tolower.h"
37 
38 using namespace std;
39 
40 //------------------------------------------------------------------------------
isMapOriginal(const std::string & mapName,int32_t checksum)41 bool MapDownload::isMapOriginal (const std::string& mapName, int32_t checksum)
42 {
43 	std::string lowerMapName (to_lower_copy (mapName));
44 
45 	const struct
46 	{
47 		const char* filename;
48 		int32_t checksum;
49 	} maps[] =
50 	{
51 		{ "bottleneck.wrl"        , 344087468},
52 		{ "flash point.wrl"       , 1702427970},
53 		{ "freckles.wrl"          , 1401869069},
54 		{ "frigia.wrl"            , 1612651246},
55 		{ "great circle.wrl"      , 1041139234},
56 		{ "great divide.wrl"      , 117739146},
57 		{ "hammerhead.wrl"        , 1969035068},
58 		{ "high impact.wrl"       , 268073155},
59 		{ "ice berg.wrl"          , 1382754034},
60 		{ "iron cross.wrl"        , 1704409466},
61 		{ "islandia.wrl"          , 1893077128},
62 		{ "long floes.wrl"        , 289119678},
63 		{ "long passage.wrl"      , 231873358},
64 		{ "middle sea.wrl"        , 959897984},
65 		{ "new luzon.wrl"         , 1422663356},
66 		{ "peak-a-boo.wrl"        , 2072925938},
67 		{ "sanctuary.wrl"         , 1286420600},
68 		{ "sandspit.wrl"          , 2040193020},
69 		{ "snowcrab.wrl"          , 10554807},
70 		{ "splatterscape.wrl"     , 486474018},
71 		{ "the cooler.wrl"        , 451439582},
72 		{ "three rings.wrl"       , 1682525072},
73 		{ "ultima thule.wrl"      , 1397392934},
74 		{ "valentine's planet.wrl", 280492815}
75 	};
76 
77 	for (int i = 0; i != sizeof (maps) / sizeof (*maps); ++i)
78 	{
79 		if (lowerMapName.compare (maps[i].filename) == 0)
80 		{
81 			return true;
82 		}
83 	}
84 	if (checksum == 0)
85 		checksum = calculateCheckSum (lowerMapName);
86 	for (int i = 0; i != sizeof (maps) / sizeof (*maps); ++i)
87 	{
88 		if (maps[i].checksum == checksum)
89 		{
90 			return true;
91 		}
92 	}
93 	return false;
94 }
95 
96 //------------------------------------------------------------------------------
getExistingMapFilePath(const std::string & mapName)97 std::string MapDownload::getExistingMapFilePath (const std::string& mapName)
98 {
99 	string filenameFactory = cSettings::getInstance().getMapsPath() + PATH_DELIMITER + mapName;
100 	if (FileExists (filenameFactory.c_str()))
101 		return filenameFactory;
102 	if (!getUserMapsDir().empty())
103 	{
104 		string filenameUser = getUserMapsDir() + mapName;
105 		if (FileExists (filenameUser.c_str()))
106 			return filenameUser;
107 	}
108 	return "";
109 }
110 
111 //------------------------------------------------------------------------------
calculateCheckSum(const std::string & mapName)112 int32_t MapDownload::calculateCheckSum (const std::string& mapName)
113 {
114 	int32_t result = 0;
115 	string filename = cSettings::getInstance().getMapsPath() + PATH_DELIMITER + mapName;
116 	ifstream file (filename.c_str(), ios::in | ios::binary | ios::ate);
117 	if (!file.is_open() && !getUserMapsDir().empty())
118 	{
119 		// try to open the map from the user's maps dir
120 		filename = getUserMapsDir() + mapName.c_str();
121 		file.open (filename.c_str(), ios::in | ios::binary | ios::ate);
122 	}
123 	if (file.is_open())
124 	{
125 		const int mapSize = (int) file.tellg();
126 		std::vector<char> data (mapSize);
127 		file.seekg (0, ios::beg);
128 
129 		file.read (data.data(), 9);  // read only header
130 		const int width = data[5] + data[6] * 256;
131 		const int height = data[7] + data[8] * 256;
132 		// the information after this is only for graphic stuff
133 		// and not necessary for comparing two maps
134 		const int relevantMapDataSize = width * height * 3;
135 
136 		if (relevantMapDataSize + 9 <= mapSize)
137 		{
138 			file.read (data.data() + 9, relevantMapDataSize);
139 			if (!file.bad() && !file.eof())
140 				result = calcCheckSum (data.data(), relevantMapDataSize + 9);
141 		}
142 	}
143 	return result;
144 }
145 
146 //------------------------------------------------------------------------------
147 // cMapReceiver implementation
148 //------------------------------------------------------------------------------
149 
150 //------------------------------------------------------------------------------
cMapReceiver(const std::string & mapName,int mapSize)151 cMapReceiver::cMapReceiver (const std::string& mapName, int mapSize) :
152 	mapName (mapName),
153 	bytesReceived (0),
154 	readBuffer (mapSize)
155 {
156 }
157 
158 //------------------------------------------------------------------------------
receiveData(cNetMessage & message)159 bool cMapReceiver::receiveData (cNetMessage& message)
160 {
161 	assert (message.iType == MU_MSG_MAP_DOWNLOAD_DATA);
162 
163 	const int bytesInMsg = message.popInt32();
164 	if (bytesInMsg <= 0 || bytesReceived + bytesInMsg > readBuffer.size())
165 		return false;
166 
167 	for (int i = bytesInMsg - 1; i >= 0; i--)
168 		readBuffer[bytesReceived + i] = message.popChar();
169 
170 	bytesReceived += bytesInMsg;
171 	std::ostringstream os;
172 	os << "MapReceiver: Received Data for map " << mapName << lngPack.i18n ("Text~Punctuation~Colon")
173 	   << bytesReceived << "/" << readBuffer.size();
174 	Log.write (os.str(), cLog::eLOG_TYPE_DEBUG);
175 	return true;
176 }
177 
178 //------------------------------------------------------------------------------
finished()179 bool cMapReceiver::finished()
180 {
181 	Log.write ("MapReceiver: Received complete map", cLog::eLOG_TYPE_DEBUG);
182 
183 	if (bytesReceived != readBuffer.size())
184 		return false;
185 	std::string mapsFolder = getUserMapsDir();
186 	if (mapsFolder.empty())
187 		mapsFolder = cSettings::getInstance().getMapsPath() + PATH_DELIMITER;
188 	const std::string filename = mapsFolder + mapName;
189 	std::ofstream newMapFile (filename.c_str(), ios::out | ios::binary);
190 	if (newMapFile.bad())
191 		return false;
192 	newMapFile.write (readBuffer.data(), readBuffer.size());
193 	if (newMapFile.bad())
194 		return false;
195 	newMapFile.close();
196 	if (newMapFile.bad())
197 		return false;
198 
199 	return true;
200 }
201 
202 //------------------------------------------------------------------------------
203 // cMapSender implementation
204 //------------------------------------------------------------------------------
205 
206 //------------------------------------------------------------------------------
mapSenderThreadFunction(void * data)207 int mapSenderThreadFunction (void* data)
208 {
209 	cMapSender* mapSender = reinterpret_cast<cMapSender*> (data);
210 	mapSender->run();
211 	return 0;
212 }
213 
214 //------------------------------------------------------------------------------
cMapSender(cTCP & network_,int toSocket,const std::string & mapName,const std::string & receivingPlayerName)215 cMapSender::cMapSender (cTCP& network_, int toSocket,
216 						const std::string& mapName,
217 						const std::string& receivingPlayerName) :
218 	network (&network_),
219 	toSocket (toSocket),
220 	receivingPlayerName (receivingPlayerName),
221 	mapName (mapName),
222 	bytesSent (0),
223 	sendBuffer(),
224 	thread (nullptr),
225 	canceled (false)
226 {
227 }
228 
229 //------------------------------------------------------------------------------
~cMapSender()230 cMapSender::~cMapSender()
231 {
232 	if (thread != nullptr)
233 	{
234 		canceled = true;
235 		SDL_WaitThread (thread, nullptr);
236 		thread = nullptr;
237 	}
238 	if (!sendBuffer.empty())
239 	{
240 		// the thread was not finished yet
241 		// (else it would have deleted sendBuffer already)
242 		// send a canceled msg to the client
243 		cNetMessage msg (MU_MSG_CANCELED_MAP_DOWNLOAD);
244 		sendMsg (msg);
245 		Log.write ("MapSender: Canceling an unfinished upload thread", cLog::eLOG_TYPE_DEBUG);
246 	}
247 }
248 
249 //------------------------------------------------------------------------------
runInThread()250 void cMapSender::runInThread()
251 {
252 	// the thread will quit, when it finished uploading the map
253 	thread = SDL_CreateThread (mapSenderThreadFunction, "mapSender", this);
254 }
255 
256 //------------------------------------------------------------------------------
getMapFileContent()257 bool cMapSender::getMapFileContent()
258 {
259 	// read map file in memory
260 	string filename = cSettings::getInstance().getMapsPath() + PATH_DELIMITER + mapName.c_str();
261 	ifstream file (filename.c_str(), ios::in | ios::binary | ios::ate);
262 	if (!file.is_open() && !getUserMapsDir().empty())
263 	{
264 		// try to open the map from the user's maps dir
265 		filename = getUserMapsDir() + mapName.c_str();
266 		file.open (filename.c_str(), ios::in | ios::binary | ios::ate);
267 	}
268 	if (!file.is_open())
269 	{
270 		Log.write (string ("MapSender: could not read the map \"") + filename + "\" into memory.", cLog::eLOG_TYPE_WARNING);
271 		return false;
272 	}
273 	const std::size_t mapSize = file.tellg();
274 	sendBuffer.resize (mapSize);
275 	file.seekg (0, ios::beg);
276 	file.read (sendBuffer.data(), mapSize);
277 	file.close();
278 	Log.write (string ("MapSender: read the map \"") + filename + "\" into memory.", cLog::eLOG_TYPE_DEBUG);
279 	return true;
280 }
281 
282 //------------------------------------------------------------------------------
run()283 void cMapSender::run()
284 {
285 	if (canceled) return;
286 	getMapFileContent();
287 	if (canceled) return;
288 
289 	{
290 		cNetMessage msg (MU_MSG_START_MAP_DOWNLOAD);
291 		msg.pushString (mapName);
292 		msg.pushInt32 (sendBuffer.size());
293 		sendMsg (msg);
294 	}
295 	int msgCount = 0;
296 	while (bytesSent < sendBuffer.size())
297 	{
298 		if (canceled) return;
299 
300 		cNetMessage msg (MU_MSG_MAP_DOWNLOAD_DATA);
301 		int bytesToSend = sendBuffer.size() - bytesSent;
302 		if (bytesToSend + msg.iLength + 4 > PACKAGE_LENGTH)
303 			bytesToSend = PACKAGE_LENGTH - msg.iLength - 4;
304 		for (int i = 0; i < bytesToSend; i++)
305 			msg.pushChar (sendBuffer[bytesSent + i]);
306 		bytesSent += bytesToSend;
307 		msg.pushInt32 (bytesToSend);
308 		sendMsg (msg);
309 
310 		msgCount++;
311 		if (msgCount % 10 == 0)
312 			SDL_Delay (100);
313 	}
314 
315 	// finished
316 	sendBuffer.clear();
317 
318 	cNetMessage msg (MU_MSG_FINISHED_MAP_DOWNLOAD);
319 	msg.pushString (receivingPlayerName);
320 	sendMsg (msg);
321 
322 	// Push message also to client, that belongs to the host,
323 	// to give feedback about the finished upload state.
324 	// The EventHandler mechanism is used,
325 	// because this code runs in another thread than the code,
326 	// that must display the msg.
327 
328 	// FIXME: may use a signal here? (and protect it by mutexes?!)
329 
330 	//if (eventHandling)
331 	//{
332 	//	cNetMessage* message = new cNetMessage (MU_MSG_FINISHED_MAP_DOWNLOAD);
333 	//	message->pushString (receivingPlayerName);
334 	//	eventHandling->pushEvent (message);
335 	//}
336 }
337 
338 //------------------------------------------------------------------------------
sendMsg(cNetMessage & msg)339 void cMapSender::sendMsg (cNetMessage& msg)
340 {
341 	msg.iPlayerNr = -1;
342 	network->sendTo (toSocket, msg.iLength, msg.serialize());
343 
344 	Log.write ("MapSender: --> " + msg.getTypeAsString() + ", Hexdump: " + msg.getHexDump(), cLog::eLOG_TYPE_NET_DEBUG);
345 }
346