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