1 #include "Client.h"
2
3 #include <cstdlib>
4 #include <vector>
5 #include <map>
6 #include <iostream>
7 #include <iomanip>
8 #include <ctime>
9 #include <cstdio>
10 #include <fstream>
11 #include <dirent.h>
12
13 #ifdef MACOSX
14 #include <mach-o/dyld.h>
15 #include <ApplicationServices/ApplicationServices.h>
16 #endif
17
18 #ifdef WIN
19 #define NOMINMAX
20 #include <shlobj.h>
21 #include <shlwapi.h>
22 #include <windows.h>
23 #include <direct.h>
24 #else
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #endif
28
29 #include "common/String.h"
30 #include "Config.h"
31 #include "Format.h"
32 #include "MD5.h"
33 #include "Platform.h"
34 #include "Update.h"
35
36 #include "ClientListener.h"
37
38 #include "graphics/Graphics.h"
39
40 #include "gui/preview/Comment.h"
41
42 #include "client/SaveInfo.h"
43 #include "client/SaveFile.h"
44 #include "client/GameSave.h"
45 #include "client/UserInfo.h"
46 #include "client/http/Request.h"
47 #include "client/http/RequestManager.h"
48
49
50 extern "C"
51 {
52 #if defined(WIN) && !defined(__GNUC__)
53 #include <io.h>
54 #else
55 #include <dirent.h>
56 #endif
57 }
58
59
Client()60 Client::Client():
61 messageOfTheDay("Fetching the message of the day..."),
62 versionCheckRequest(nullptr),
63 alternateVersionCheckRequest(nullptr),
64 usingAltUpdateServer(false),
65 updateAvailable(false),
66 authUser(0, "")
67 {
68 //Read config
69 std::ifstream configFile;
70 configFile.open("powder.pref", std::ios::binary);
71 if (configFile)
72 {
73 try
74 {
75 preferences.clear();
76 configFile >> preferences;
77 int ID = preferences["User"]["ID"].asInt();
78 ByteString Username = preferences["User"]["Username"].asString();
79 ByteString SessionID = preferences["User"]["SessionID"].asString();
80 ByteString SessionKey = preferences["User"]["SessionKey"].asString();
81 ByteString Elevation = preferences["User"]["Elevation"].asString();
82
83 authUser.UserID = ID;
84 authUser.Username = Username;
85 authUser.SessionID = SessionID;
86 authUser.SessionKey = SessionKey;
87 if (Elevation == "Admin")
88 authUser.UserElevation = User::ElevationAdmin;
89 else if (Elevation == "Mod")
90 authUser.UserElevation = User::ElevationModerator;
91 else
92 authUser.UserElevation = User::ElevationNone;
93 }
94 catch (std::exception &e)
95 {
96
97 }
98 configFile.close();
99 firstRun = false;
100 }
101 else
102 firstRun = true;
103 }
104
Initialise(ByteString proxyString,bool disableNetwork)105 void Client::Initialise(ByteString proxyString, bool disableNetwork)
106 {
107 if (GetPrefBool("version.update", false))
108 {
109 SetPref("version.update", false);
110 update_finish();
111 }
112
113 #ifndef NOHTTP
114 if (!disableNetwork)
115 http::RequestManager::Ref().Initialise(proxyString);
116 #endif
117
118 //Read stamps library
119 std::ifstream stampsLib;
120 stampsLib.open(STAMPS_DIR PATH_SEP "stamps.def", std::ios::binary);
121 while (!stampsLib.eof())
122 {
123 char data[11];
124 memset(data, 0, 11);
125 stampsLib.read(data, 10);
126 if(!data[0])
127 break;
128 stampIDs.push_back(data);
129 }
130 stampsLib.close();
131
132 //Begin version check
133 versionCheckRequest = new http::Request(SCHEME SERVER "/Startup.json");
134
135 if (authUser.UserID)
136 {
137 versionCheckRequest->AuthHeaders(ByteString::Build(authUser.UserID), authUser.SessionID);
138 }
139 versionCheckRequest->Start();
140
141 #ifdef UPDATESERVER
142 // use an alternate update server
143 alternateVersionCheckRequest = new http::Request(SCHEME UPDATESERVER "/Startup.json");
144 usingAltUpdateServer = true;
145 if (authUser.UserID)
146 {
147 alternateVersionCheckRequest->AuthHeaders(authUser.Username, "");
148 }
149 alternateVersionCheckRequest->Start();
150 #endif
151 }
152
IsFirstRun()153 bool Client::IsFirstRun()
154 {
155 return firstRun;
156 }
157
DoInstallation()158 bool Client::DoInstallation()
159 {
160 #if defined(WIN)
161 int returnval;
162 LONG rresult;
163 HKEY newkey;
164 ByteString currentfilename2 = Platform::ExecutableName();
165 // this isn't necessary but I don't feel like c++ifying this code right now
166 const char *currentfilename = currentfilename2.c_str();
167 char *iconname = NULL;
168 char *opencommand = NULL;
169 char *protocolcommand = NULL;
170 //char AppDataPath[MAX_PATH];
171 char *AppDataPath = NULL;
172 iconname = (char*)malloc(strlen(currentfilename)+6);
173 sprintf(iconname, "%s,-102", currentfilename);
174
175 //Create Roaming application data folder
176 /*if(!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, AppDataPath)))
177 {
178 returnval = 0;
179 goto finalise;
180 }*/
181
182 AppDataPath = _getcwd(NULL, 0);
183
184 //Move Game executable into application data folder
185 //TODO: Implement
186
187 opencommand = (char*)malloc(strlen(currentfilename)+53+strlen(AppDataPath));
188 protocolcommand = (char*)malloc(strlen(currentfilename)+53+strlen(AppDataPath));
189 /*if((strlen(AppDataPath)+strlen(APPDATA_SUBDIR "\\Powder Toy"))<MAX_PATH)
190 {
191 strappend(AppDataPath, APPDATA_SUBDIR);
192 _mkdir(AppDataPath);
193 strappend(AppDataPath, "\\Powder Toy");
194 _mkdir(AppDataPath);
195 } else {
196 returnval = 0;
197 goto finalise;
198 }*/
199 sprintf(opencommand, "\"%s\" open \"%%1\" ddir \"%s\"", currentfilename, AppDataPath);
200 sprintf(protocolcommand, "\"%s\" ddir \"%s\" ptsave \"%%1\"", currentfilename, AppDataPath);
201
202 //Create protocol entry
203 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\ptsave", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
204 if (rresult != ERROR_SUCCESS) {
205 returnval = 0;
206 goto finalise;
207 }
208 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"Powder Toy Save", strlen("Powder Toy Save")+1);
209 if (rresult != ERROR_SUCCESS) {
210 RegCloseKey(newkey);
211 returnval = 0;
212 goto finalise;
213 }
214 rresult = RegSetValueEx(newkey, (LPCSTR)"URL Protocol", 0, REG_SZ, (LPBYTE)"", strlen("")+1);
215 if (rresult != ERROR_SUCCESS) {
216 RegCloseKey(newkey);
217 returnval = 0;
218 goto finalise;
219 }
220 RegCloseKey(newkey);
221
222 //Set Protocol DefaultIcon
223 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\ptsave\\DefaultIcon", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
224 if (rresult != ERROR_SUCCESS) {
225 returnval = 0;
226 goto finalise;
227 }
228 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)iconname, strlen(iconname)+1);
229 if (rresult != ERROR_SUCCESS) {
230 RegCloseKey(newkey);
231 returnval = 0;
232 goto finalise;
233 }
234 RegCloseKey(newkey);
235
236 //Set Protocol Launch command
237 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\ptsave\\shell\\open\\command", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
238 if (rresult != ERROR_SUCCESS) {
239 returnval = 0;
240 goto finalise;
241 }
242 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)protocolcommand, strlen(protocolcommand)+1);
243 if (rresult != ERROR_SUCCESS) {
244 RegCloseKey(newkey);
245 returnval = 0;
246 goto finalise;
247 }
248 RegCloseKey(newkey);
249
250 //Create extension entry
251 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\.cps", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
252 if (rresult != ERROR_SUCCESS) {
253 returnval = 0;
254 goto finalise;
255 }
256 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"PowderToySave", strlen("PowderToySave")+1);
257 if (rresult != ERROR_SUCCESS) {
258 RegCloseKey(newkey);
259 returnval = 0;
260 goto finalise;
261 }
262 RegCloseKey(newkey);
263
264 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\.stm", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
265 if (rresult != ERROR_SUCCESS) {
266 returnval = 0;
267 goto finalise;
268 }
269 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"PowderToySave", strlen("PowderToySave")+1);
270 if (rresult != ERROR_SUCCESS) {
271 RegCloseKey(newkey);
272 returnval = 0;
273 goto finalise;
274 }
275 RegCloseKey(newkey);
276
277 //Create program entry
278 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
279 if (rresult != ERROR_SUCCESS) {
280 returnval = 0;
281 goto finalise;
282 }
283 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"Powder Toy Save", strlen("Powder Toy Save")+1);
284 if (rresult != ERROR_SUCCESS) {
285 RegCloseKey(newkey);
286 returnval = 0;
287 goto finalise;
288 }
289 RegCloseKey(newkey);
290
291 //Set DefaultIcon
292 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave\\DefaultIcon", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
293 if (rresult != ERROR_SUCCESS) {
294 returnval = 0;
295 goto finalise;
296 }
297 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)iconname, strlen(iconname)+1);
298 if (rresult != ERROR_SUCCESS) {
299 RegCloseKey(newkey);
300 returnval = 0;
301 goto finalise;
302 }
303 RegCloseKey(newkey);
304
305 //Set Launch command
306 rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave\\shell\\open\\command", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
307 if (rresult != ERROR_SUCCESS) {
308 returnval = 0;
309 goto finalise;
310 }
311 rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)opencommand, strlen(opencommand)+1);
312 if (rresult != ERROR_SUCCESS) {
313 RegCloseKey(newkey);
314 returnval = 0;
315 goto finalise;
316 }
317 RegCloseKey(newkey);
318
319 returnval = 1;
320 finalise:
321
322 free(iconname);
323 free(opencommand);
324 free(protocolcommand);
325
326 return returnval;
327 #elif defined(LIN)
328 #include "icondoc.h"
329
330 int success = 1;
331 ByteString filename = Platform::ExecutableName(), pathname = filename.SplitFromEndBy('/').Before();
332 filename.Substitute('\'', "'\\''");
333 filename = '\'' + filename + '\'';
334
335 FILE *f;
336 const char *mimedata =
337 "<?xml version=\"1.0\"?>\n"
338 " <mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>\n"
339 " <mime-type type=\"application/vnd.powdertoy.save\">\n"
340 " <comment>Powder Toy save</comment>\n"
341 " <glob pattern=\"*.cps\"/>\n"
342 " <glob pattern=\"*.stm\"/>\n"
343 " </mime-type>\n"
344 "</mime-info>\n";
345 f = fopen("powdertoy-save.xml", "wb");
346 if (!f)
347 return 0;
348 fwrite(mimedata, 1, strlen(mimedata), f);
349 fclose(f);
350
351 const char *protocolfiledata_tmp =
352 "[Desktop Entry]\n"
353 "Type=Application\n"
354 "Name=Powder Toy\n"
355 "Comment=Physics sandbox game\n"
356 "MimeType=x-scheme-handler/ptsave;\n"
357 "NoDisplay=true\n"
358 "Categories=Game;Simulation\n"
359 "Icon=powdertoy.png\n";
360 ByteString protocolfiledata = ByteString::Build(protocolfiledata_tmp, "Exec=", filename, " ptsave %u\nPath=", pathname, "\n");
361 f = fopen("powdertoy-tpt-ptsave.desktop", "wb");
362 if (!f)
363 return 0;
364 fwrite(protocolfiledata.c_str(), 1, protocolfiledata.size(), f);
365 fclose(f);
366 success = system("xdg-desktop-menu install powdertoy-tpt-ptsave.desktop");
367
368 const char *desktopopenfiledata_tmp =
369 "[Desktop Entry]\n"
370 "Type=Application\n"
371 "Name=Powder Toy\n"
372 "Comment=Physics sandbox game\n"
373 "MimeType=application/vnd.powdertoy.save;\n"
374 "NoDisplay=true\n"
375 "Categories=Game;Simulation\n"
376 "Icon=powdertoy.png\n";
377 ByteString desktopopenfiledata = ByteString::Build(desktopopenfiledata_tmp, "Exec=", filename, " open %f\nPath=", pathname, "\n");
378 f = fopen("powdertoy-tpt-open.desktop", "wb");
379 if (!f)
380 return 0;
381 fwrite(desktopopenfiledata.c_str(), 1, desktopopenfiledata.size(), f);
382 fclose(f);
383 success = system("xdg-mime install powdertoy-save.xml") && success;
384 success = system("xdg-desktop-menu install powdertoy-tpt-open.desktop") && success;
385
386 const char *desktopfiledata_tmp =
387 "[Desktop Entry]\n"
388 "Version=1.0\n"
389 "Encoding=UTF-8\n"
390 "Name=Powder Toy\n"
391 "Type=Application\n"
392 "Comment=Physics sandbox game\n"
393 "Categories=Game;Simulation\n"
394 "Icon=powdertoy.png\n";
395 ByteString desktopfiledata = ByteString::Build(desktopfiledata_tmp, "Exec=", filename, "\nPath=", pathname, "\n");
396 f = fopen("powdertoy-tpt.desktop", "wb");
397 if (!f)
398 return 0;
399 fwrite(desktopfiledata.c_str(), 1, desktopfiledata.size(), f);
400 fclose(f);
401 success = system("xdg-desktop-menu install powdertoy-tpt.desktop") && success;
402
403 f = fopen("powdertoy-save-32.png", "wb");
404 if (!f)
405 return 0;
406 fwrite(icon_doc_32_png, 1, sizeof(icon_doc_32_png), f);
407 fclose(f);
408 f = fopen("powdertoy-save-16.png", "wb");
409 if (!f)
410 return 0;
411 fwrite(icon_doc_16_png, 1, sizeof(icon_doc_16_png), f);
412 fclose(f);
413 f = fopen("powdertoy.png", "wb");
414 if (!f)
415 return 0;
416 fwrite(icon_desktop_48_png, 1, sizeof(icon_desktop_48_png), f);
417 fclose(f);
418 success = system("xdg-icon-resource install --noupdate --context mimetypes --size 32 powdertoy-save-32.png application-vnd.powdertoy.save") && success;
419 success = system("xdg-icon-resource install --noupdate --context mimetypes --size 16 powdertoy-save-16.png application-vnd.powdertoy.save") && success;
420 success = system("xdg-icon-resource install --noupdate --novendor --size 48 powdertoy.png") && success;
421 success = system("xdg-icon-resource forceupdate") && success;
422 success = system("xdg-mime default powdertoy-tpt-open.desktop application/vnd.powdertoy.save") && success;
423 success = system("xdg-mime default powdertoy-tpt-ptsave.desktop x-scheme-handler/ptsave") && success;
424 unlink("powdertoy.png");
425 unlink("powdertoy-save-32.png");
426 unlink("powdertoy-save-16.png");
427 unlink("powdertoy-save.xml");
428 unlink("powdertoy-tpt.desktop");
429 unlink("powdertoy-tpt-open.desktop");
430 unlink("powdertoy-tpt-ptsave.desktop");
431 return !success;
432 #elif defined MACOSX
433 return false;
434 #endif
435 }
436
DirectorySearch(ByteString directory,ByteString search,ByteString extension)437 std::vector<ByteString> Client::DirectorySearch(ByteString directory, ByteString search, ByteString extension)
438 {
439 std::vector<ByteString> extensions;
440 extensions.push_back(extension);
441 return DirectorySearch(directory, search.ToUpper(), extensions);
442 }
443
DirectorySearch(ByteString directory,ByteString search,std::vector<ByteString> extensions)444 std::vector<ByteString> Client::DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions)
445 {
446 //Get full file listing
447 //Normalise directory string, ensure / or \ is present
448 if(*directory.rbegin() != '/' && *directory.rbegin() != '\\')
449 directory += PATH_SEP;
450 std::vector<ByteString> directoryList;
451 #if defined(WIN) && !defined(__GNUC__)
452 //Windows
453 struct _finddata_t currentFile;
454 intptr_t findFileHandle;
455 ByteString fileMatch = directory + "*.*";
456 findFileHandle = _findfirst(fileMatch.c_str(), ¤tFile);
457 if (findFileHandle == -1L)
458 {
459 #ifdef DEBUG
460 printf("Unable to open directory: %s\n", directory.c_str());
461 #endif
462 return std::vector<ByteString>();
463 }
464 do
465 {
466 ByteString currentFileName = ByteString(currentFile.name);
467 if(currentFileName.length()>4)
468 directoryList.push_back(directory+currentFileName);
469 }
470 while (_findnext(findFileHandle, ¤tFile) == 0);
471 _findclose(findFileHandle);
472 #else
473 //Linux or MinGW
474 struct dirent * directoryEntry;
475 DIR *directoryHandle = opendir(directory.c_str());
476 if(!directoryHandle)
477 {
478 #ifdef DEBUG
479 printf("Unable to open directory: %s\n", directory.c_str());
480 #endif
481 return std::vector<ByteString>();
482 }
483 while ((directoryEntry = readdir(directoryHandle)))
484 {
485 ByteString currentFileName = ByteString(directoryEntry->d_name);
486 if(currentFileName.length()>4)
487 directoryList.push_back(directory+currentFileName);
488 }
489 closedir(directoryHandle);
490 #endif
491
492 std::vector<ByteString> searchResults;
493 for(std::vector<ByteString>::iterator iter = directoryList.begin(), end = directoryList.end(); iter != end; ++iter)
494 {
495 ByteString filename = *iter, tempfilename = *iter;
496 bool extensionMatch = !extensions.size();
497 for(std::vector<ByteString>::iterator extIter = extensions.begin(), extEnd = extensions.end(); extIter != extEnd; ++extIter)
498 {
499 if(filename.EndsWith(*extIter))
500 {
501 extensionMatch = true;
502 tempfilename = filename.SubstrFromEnd(0, (*extIter).size()).ToUpper();
503 break;
504 }
505 }
506 bool searchMatch = !search.size();
507 if(search.size() && tempfilename.Contains(search))
508 searchMatch = true;
509
510 if(searchMatch && extensionMatch)
511 searchResults.push_back(filename);
512 }
513
514 //Filter results
515 return searchResults;
516 }
517
MakeDirectory(const char * dirName)518 int Client::MakeDirectory(const char * dirName)
519 {
520 #ifdef WIN
521 return _mkdir(dirName);
522 #else
523 return mkdir(dirName, 0755);
524 #endif
525 }
526
WriteFile(std::vector<unsigned char> fileData,ByteString filename)527 bool Client::WriteFile(std::vector<unsigned char> fileData, ByteString filename)
528 {
529 bool saveError = false;
530 try
531 {
532 std::ofstream fileStream;
533 fileStream.open(filename, std::ios::binary);
534 if(fileStream.is_open())
535 {
536 fileStream.write((char*)&fileData[0], fileData.size());
537 fileStream.close();
538 }
539 else
540 saveError = true;
541 }
542 catch (std::exception & e)
543 {
544 std::cerr << "WriteFile:" << e.what() << std::endl;
545 saveError = true;
546 }
547 return saveError;
548 }
549
FileExists(ByteString filename)550 bool Client::FileExists(ByteString filename)
551 {
552 bool exists = false;
553 try
554 {
555 std::ifstream fileStream;
556 fileStream.open(filename, std::ios::binary);
557 if(fileStream.is_open())
558 {
559 exists = true;
560 fileStream.close();
561 }
562 }
563 catch (std::exception & e)
564 {
565 exists = false;
566 }
567 return exists;
568 }
569
WriteFile(std::vector<char> fileData,ByteString filename)570 bool Client::WriteFile(std::vector<char> fileData, ByteString filename)
571 {
572 bool saveError = false;
573 try
574 {
575 std::ofstream fileStream;
576 fileStream.open(filename, std::ios::binary);
577 if(fileStream.is_open())
578 {
579 fileStream.write(&fileData[0], fileData.size());
580 fileStream.close();
581 }
582 else
583 saveError = true;
584 }
585 catch (std::exception & e)
586 {
587 std::cerr << "WriteFile:" << e.what() << std::endl;
588 saveError = true;
589 }
590 return saveError;
591 }
592
ReadFile(ByteString filename)593 std::vector<unsigned char> Client::ReadFile(ByteString filename)
594 {
595 try
596 {
597 std::ifstream fileStream;
598 fileStream.open(filename, std::ios::binary);
599 if(fileStream.is_open())
600 {
601 fileStream.seekg(0, std::ios::end);
602 size_t fileSize = fileStream.tellg();
603 fileStream.seekg(0);
604
605 unsigned char * tempData = new unsigned char[fileSize];
606 fileStream.read((char *)tempData, fileSize);
607 fileStream.close();
608
609 std::vector<unsigned char> fileData;
610 fileData.insert(fileData.end(), tempData, tempData+fileSize);
611 delete[] tempData;
612
613 return fileData;
614 }
615 else
616 {
617 return std::vector<unsigned char>();
618 }
619 }
620 catch(std::exception & e)
621 {
622 std::cerr << "Readfile: " << e.what() << std::endl;
623 throw;
624 }
625 }
626
SetMessageOfTheDay(String message)627 void Client::SetMessageOfTheDay(String message)
628 {
629 messageOfTheDay = message;
630 notifyMessageOfTheDay();
631 }
632
GetMessageOfTheDay()633 String Client::GetMessageOfTheDay()
634 {
635 return messageOfTheDay;
636 }
637
AddServerNotification(std::pair<String,ByteString> notification)638 void Client::AddServerNotification(std::pair<String, ByteString> notification)
639 {
640 serverNotifications.push_back(notification);
641 notifyNewNotification(notification);
642 }
643
GetServerNotifications()644 std::vector<std::pair<String, ByteString> > Client::GetServerNotifications()
645 {
646 return serverNotifications;
647 }
648
ParseServerReturn(ByteString & result,int status,bool json)649 RequestStatus Client::ParseServerReturn(ByteString &result, int status, bool json)
650 {
651 lastError = "";
652 // no server response, return "Malformed Response"
653 if (status == 200 && !result.size())
654 {
655 status = 603;
656 }
657 if (status == 302)
658 return RequestOkay;
659 if (status != 200)
660 {
661 lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
662 return RequestFailure;
663 }
664
665 if (json)
666 {
667 std::istringstream datastream(result);
668 Json::Value root;
669
670 try
671 {
672 datastream >> root;
673 // assume everything is fine if an empty [] is returned
674 if (root.size() == 0)
675 {
676 return RequestOkay;
677 }
678 int status = root.get("Status", 1).asInt();
679 if (status != 1)
680 {
681 lastError = ByteString(root.get("Error", "Unspecified Error").asString()).FromUtf8();
682 return RequestFailure;
683 }
684 }
685 catch (std::exception &e)
686 {
687 // sometimes the server returns a 200 with the text "Error: 401"
688 if (!strncmp(result.c_str(), "Error: ", 7))
689 {
690 status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
691 lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
692 return RequestFailure;
693 }
694 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
695 return RequestFailure;
696 }
697 }
698 else
699 {
700 if (strncmp(result.c_str(), "OK", 2))
701 {
702 lastError = result.FromUtf8();
703 return RequestFailure;
704 }
705 }
706 return RequestOkay;
707 }
708
Tick()709 void Client::Tick()
710 {
711 if (versionCheckRequest)
712 {
713 if (CheckUpdate(versionCheckRequest, true))
714 versionCheckRequest = nullptr;
715 }
716 if (alternateVersionCheckRequest)
717 {
718 if (CheckUpdate(alternateVersionCheckRequest, false))
719 alternateVersionCheckRequest = nullptr;
720 }
721 }
722
CheckUpdate(http::Request * updateRequest,bool checkSession)723 bool Client::CheckUpdate(http::Request *updateRequest, bool checkSession)
724 {
725 //Check status on version check request
726 if (updateRequest->CheckDone())
727 {
728 int status;
729 ByteString data = updateRequest->Finish(&status);
730
731 if (status != 200)
732 {
733 //free(data);
734 if (usingAltUpdateServer && !checkSession)
735 this->messageOfTheDay = String::Build("HTTP Error ", status, " while checking for updates: ", http::StatusText(status));
736 }
737 else if(data.size())
738 {
739 std::istringstream dataStream(data);
740
741 try
742 {
743 Json::Value objDocument;
744 dataStream >> objDocument;
745
746 //Check session
747 if (checkSession)
748 {
749 if (!objDocument["Session"].asBool())
750 {
751 SetAuthUser(User(0, ""));
752 }
753 }
754
755 //Notifications from server
756 Json::Value notificationsArray = objDocument["Notifications"];
757 for (Json::UInt j = 0; j < notificationsArray.size(); j++)
758 {
759 ByteString notificationLink = notificationsArray[j]["Link"].asString();
760 String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
761
762 std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
763 AddServerNotification(item);
764 }
765
766
767 //MOTD
768 if (!usingAltUpdateServer || !checkSession)
769 {
770 this->messageOfTheDay = ByteString(objDocument["MessageOfTheDay"].asString()).FromUtf8();
771 notifyMessageOfTheDay();
772
773 #ifndef IGNORE_UPDATES
774 //Check for updates
775 Json::Value versions = objDocument["Updates"];
776 #ifndef SNAPSHOT
777 Json::Value stableVersion = versions["Stable"];
778 int stableMajor = stableVersion["Major"].asInt();
779 int stableMinor = stableVersion["Minor"].asInt();
780 int stableBuild = stableVersion["Build"].asInt();
781 ByteString stableFile = stableVersion["File"].asString();
782 String stableChangelog = ByteString(stableVersion["Changelog"].asString()).FromUtf8();
783 if (stableBuild > BUILD_NUM)
784 {
785 updateAvailable = true;
786 updateInfo = UpdateInfo(stableMajor, stableMinor, stableBuild, stableFile, stableChangelog, UpdateInfo::Stable);
787 }
788 #endif
789
790 if (!updateAvailable)
791 {
792 Json::Value betaVersion = versions["Beta"];
793 int betaMajor = betaVersion["Major"].asInt();
794 int betaMinor = betaVersion["Minor"].asInt();
795 int betaBuild = betaVersion["Build"].asInt();
796 ByteString betaFile = betaVersion["File"].asString();
797 String betaChangelog = ByteString(betaVersion["Changelog"].asString()).FromUtf8();
798 if (betaBuild > BUILD_NUM)
799 {
800 updateAvailable = true;
801 updateInfo = UpdateInfo(betaMajor, betaMinor, betaBuild, betaFile, betaChangelog, UpdateInfo::Beta);
802 }
803 }
804
805 #if defined(SNAPSHOT) || MOD_ID > 0
806 Json::Value snapshotVersion = versions["Snapshot"];
807 int snapshotSnapshot = snapshotVersion["Snapshot"].asInt();
808 ByteString snapshotFile = snapshotVersion["File"].asString();
809 String snapshotChangelog = ByteString(snapshotVersion["Changelog"].asString()).FromUtf8();
810 if (snapshotSnapshot > SNAPSHOT_ID)
811 {
812 updateAvailable = true;
813 updateInfo = UpdateInfo(snapshotSnapshot, snapshotFile, snapshotChangelog, UpdateInfo::Snapshot);
814 }
815 #endif
816
817 if(updateAvailable)
818 {
819 notifyUpdateAvailable();
820 }
821 #endif
822 }
823 }
824 catch (std::exception & e)
825 {
826 //Do nothing
827 }
828 }
829 return true;
830 }
831 return false;
832 }
833
GetUpdateInfo()834 UpdateInfo Client::GetUpdateInfo()
835 {
836 return updateInfo;
837 }
838
notifyUpdateAvailable()839 void Client::notifyUpdateAvailable()
840 {
841 for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
842 {
843 (*iterator)->NotifyUpdateAvailable(this);
844 }
845 }
846
notifyMessageOfTheDay()847 void Client::notifyMessageOfTheDay()
848 {
849 for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
850 {
851 (*iterator)->NotifyMessageOfTheDay(this);
852 }
853 }
854
notifyAuthUserChanged()855 void Client::notifyAuthUserChanged()
856 {
857 for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
858 {
859 (*iterator)->NotifyAuthUserChanged(this);
860 }
861 }
862
notifyNewNotification(std::pair<String,ByteString> notification)863 void Client::notifyNewNotification(std::pair<String, ByteString> notification)
864 {
865 for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
866 {
867 (*iterator)->NotifyNewNotification(this, notification);
868 }
869 }
870
AddListener(ClientListener * listener)871 void Client::AddListener(ClientListener * listener)
872 {
873 listeners.push_back(listener);
874 }
875
RemoveListener(ClientListener * listener)876 void Client::RemoveListener(ClientListener * listener)
877 {
878 for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
879 {
880 if((*iterator) == listener)
881 {
882 listeners.erase(iterator);
883 return;
884 }
885 }
886 }
887
WritePrefs()888 void Client::WritePrefs()
889 {
890 std::ofstream configFile;
891 configFile.open("powder.pref", std::ios::trunc);
892
893 if (configFile)
894 {
895 if (authUser.UserID)
896 {
897 preferences["User"]["ID"] = authUser.UserID;
898 preferences["User"]["SessionID"] = authUser.SessionID;
899 preferences["User"]["SessionKey"] = authUser.SessionKey;
900 preferences["User"]["Username"] = authUser.Username;
901 if (authUser.UserElevation == User::ElevationAdmin)
902 preferences["User"]["Elevation"] = "Admin";
903 else if (authUser.UserElevation == User::ElevationModerator)
904 preferences["User"]["Elevation"] = "Mod";
905 else
906 preferences["User"]["Elevation"] = "None";
907 }
908 else
909 {
910 preferences["User"] = Json::nullValue;
911 }
912 configFile << preferences;
913
914 configFile.close();
915 }
916 }
917
Shutdown()918 void Client::Shutdown()
919 {
920 if (versionCheckRequest)
921 {
922 versionCheckRequest->Cancel();
923 }
924 if (alternateVersionCheckRequest)
925 {
926 alternateVersionCheckRequest->Cancel();
927 }
928
929 #ifndef NOHTTP
930 http::RequestManager::Ref().Shutdown();
931 #endif
932
933 //Save config
934 WritePrefs();
935 }
936
~Client()937 Client::~Client()
938 {
939 }
940
941
SetAuthUser(User user)942 void Client::SetAuthUser(User user)
943 {
944 authUser = user;
945 notifyAuthUserChanged();
946 }
947
GetAuthUser()948 User Client::GetAuthUser()
949 {
950 return authUser;
951 }
952
UploadSave(SaveInfo & save)953 RequestStatus Client::UploadSave(SaveInfo & save)
954 {
955 lastError = "";
956 unsigned int gameDataLength;
957 char * gameData = NULL;
958 int dataStatus;
959 ByteString data;
960 ByteString userID = ByteString::Build(authUser.UserID);
961 if (authUser.UserID)
962 {
963 if (!save.GetGameSave())
964 {
965 lastError = "Empty game save";
966 return RequestFailure;
967 }
968
969 save.SetID(0);
970
971 gameData = save.GetGameSave()->Serialise(gameDataLength);
972
973 if (!gameData)
974 {
975 lastError = "Cannot serialize game save";
976 return RequestFailure;
977 }
978 #if defined(SNAPSHOT) || defined(BETA) || defined(DEBUG)
979 else if (save.gameSave->fromNewerVersion)
980 {
981 lastError = "Cannot upload save, incompatible with latest release version";
982 return RequestFailure;
983 }
984 #endif
985
986 data = http::Request::SimpleAuth(SCHEME SERVER "/Save.api", &dataStatus, userID, authUser.SessionID, {
987 { "Name", save.GetName().ToUtf8() },
988 { "Description", save.GetDescription().ToUtf8() },
989 { "Data:save.bin", ByteString(gameData, gameData + gameDataLength) },
990 { "Publish", save.GetPublished() ? "Public" : "Private" },
991 });
992 }
993 else
994 {
995 lastError = "Not authenticated";
996 return RequestFailure;
997 }
998
999 RequestStatus ret = ParseServerReturn(data, dataStatus, false);
1000 if (ret == RequestOkay)
1001 {
1002 int saveID = ByteString(data.begin() + 3, data.end()).ToNumber<int>();
1003 if (!saveID)
1004 {
1005 lastError = "Server did not return Save ID";
1006 ret = RequestFailure;
1007 }
1008 else
1009 save.SetID(saveID);
1010 }
1011 delete[] gameData;
1012 return ret;
1013 }
1014
MoveStampToFront(ByteString stampID)1015 void Client::MoveStampToFront(ByteString stampID)
1016 {
1017 for (std::list<ByteString>::iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator)
1018 {
1019 if((*iterator) == stampID)
1020 {
1021 stampIDs.erase(iterator);
1022 break;
1023 }
1024 }
1025 stampIDs.push_front(stampID);
1026 updateStamps();
1027 }
1028
GetStamp(ByteString stampID)1029 SaveFile * Client::GetStamp(ByteString stampID)
1030 {
1031 ByteString stampFile = ByteString(STAMPS_DIR PATH_SEP + stampID + ".stm");
1032 SaveFile *saveFile = LoadSaveFile(stampFile);
1033 if (!saveFile)
1034 saveFile = LoadSaveFile(stampID);
1035 else
1036 saveFile->SetDisplayName(stampID.FromUtf8());
1037 return saveFile;
1038 }
1039
DeleteStamp(ByteString stampID)1040 void Client::DeleteStamp(ByteString stampID)
1041 {
1042 for (std::list<ByteString>::iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator)
1043 {
1044 if ((*iterator) == stampID)
1045 {
1046 ByteString stampFilename = ByteString::Build(STAMPS_DIR, PATH_SEP, stampID, ".stm");
1047 remove(stampFilename.c_str());
1048 stampIDs.erase(iterator);
1049 break;
1050 }
1051 }
1052
1053 updateStamps();
1054 }
1055
AddStamp(GameSave * saveData)1056 ByteString Client::AddStamp(GameSave * saveData)
1057 {
1058 unsigned t=(unsigned)time(NULL);
1059 if (lastStampTime!=t)
1060 {
1061 lastStampTime=t;
1062 lastStampName=0;
1063 }
1064 else
1065 lastStampName++;
1066 ByteString saveID = ByteString::Build(Format::Hex(Format::Width(lastStampTime, 8)), Format::Hex(Format::Width(lastStampName, 2)));
1067 ByteString filename = STAMPS_DIR PATH_SEP + saveID + ".stm";
1068
1069 MakeDirectory(STAMPS_DIR);
1070
1071 Json::Value stampInfo;
1072 stampInfo["type"] = "stamp";
1073 stampInfo["username"] = authUser.Username;
1074 stampInfo["name"] = filename;
1075 stampInfo["date"] = (Json::Value::UInt64)time(NULL);
1076 if (authors.size() != 0)
1077 {
1078 // This is a stamp, always append full authorship info (even if same user)
1079 stampInfo["links"].append(Client::Ref().authors);
1080 }
1081 saveData->authors = stampInfo;
1082
1083 unsigned int gameDataLength;
1084 char * gameData = saveData->Serialise(gameDataLength);
1085 if (gameData == NULL)
1086 return "";
1087
1088 std::ofstream stampStream;
1089 stampStream.open(filename.c_str(), std::ios::binary);
1090 stampStream.write((const char *)gameData, gameDataLength);
1091 stampStream.close();
1092
1093 delete[] gameData;
1094
1095 stampIDs.push_front(saveID);
1096
1097 updateStamps();
1098
1099 return saveID;
1100 }
1101
updateStamps()1102 void Client::updateStamps()
1103 {
1104 MakeDirectory(STAMPS_DIR);
1105
1106 std::ofstream stampsStream;
1107 stampsStream.open(ByteString(STAMPS_DIR PATH_SEP "stamps.def").c_str(), std::ios::binary);
1108 for (std::list<ByteString>::const_iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator)
1109 {
1110 stampsStream.write((*iterator).c_str(), 10);
1111 }
1112 stampsStream.write("\0", 1);
1113 stampsStream.close();
1114 return;
1115 }
1116
RescanStamps()1117 void Client::RescanStamps()
1118 {
1119 DIR * directory;
1120 struct dirent * entry;
1121 directory = opendir("stamps");
1122 if (directory != NULL)
1123 {
1124 stampIDs.clear();
1125 while ((entry = readdir(directory)))
1126 {
1127 ByteString name = entry->d_name;
1128 if(name != ".." && name != "." && name.EndsWith(".stm") && name.size() == 14)
1129 stampIDs.push_front(name.Substr(0, 10));
1130 }
1131 closedir(directory);
1132 stampIDs.sort(std::greater<ByteString>());
1133 updateStamps();
1134 }
1135 }
1136
GetStampsCount()1137 int Client::GetStampsCount()
1138 {
1139 return stampIDs.size();
1140 }
1141
GetStamps(int start,int count)1142 std::vector<ByteString> Client::GetStamps(int start, int count)
1143 {
1144 int size = (int)stampIDs.size();
1145 if (start+count > size)
1146 {
1147 if(start > size)
1148 return std::vector<ByteString>();
1149 count = size-start;
1150 }
1151
1152 std::vector<ByteString> stampRange;
1153 int index = 0;
1154 for (std::list<ByteString>::const_iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator, ++index)
1155 {
1156 if(index>=start && index < start+count)
1157 stampRange.push_back(*iterator);
1158 }
1159 return stampRange;
1160 }
1161
ExecVote(int saveID,int direction)1162 RequestStatus Client::ExecVote(int saveID, int direction)
1163 {
1164 lastError = "";
1165 int dataStatus;
1166 ByteString data;
1167
1168 if (authUser.UserID)
1169 {
1170 ByteString saveIDText = ByteString::Build(saveID);
1171 ByteString userIDText = ByteString::Build(authUser.UserID);
1172 data = http::Request::SimpleAuth(SCHEME SERVER "/Vote.api", &dataStatus, userIDText, authUser.SessionID, {
1173 { "ID", saveIDText },
1174 { "Action", direction == 1 ? "Up" : "Down" },
1175 });
1176 }
1177 else
1178 {
1179 lastError = "Not authenticated";
1180 return RequestFailure;
1181 }
1182 RequestStatus ret = ParseServerReturn(data, dataStatus, false);
1183 return ret;
1184 }
1185
GetSaveData(int saveID,int saveDate)1186 std::vector<unsigned char> Client::GetSaveData(int saveID, int saveDate)
1187 {
1188 lastError = "";
1189 int dataStatus;
1190 ByteString data;
1191 ByteString urlStr;
1192 if (saveDate)
1193 urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
1194 else
1195 urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
1196
1197 data = http::Request::Simple(urlStr, &dataStatus);
1198
1199 // will always return failure
1200 ParseServerReturn(data, dataStatus, false);
1201 if (data.size() && dataStatus == 200)
1202 {
1203 return std::vector<unsigned char>(data.begin(), data.end());
1204 }
1205 return std::vector<unsigned char>();
1206 }
1207
Login(ByteString username,ByteString password,User & user)1208 LoginStatus Client::Login(ByteString username, ByteString password, User & user)
1209 {
1210 lastError = "";
1211 char passwordHash[33];
1212 char totalHash[33];
1213
1214 user.UserID = 0;
1215 user.Username = "";
1216 user.SessionID = "";
1217 user.SessionKey = "";
1218
1219 //Doop
1220 md5_ascii(passwordHash, (const unsigned char *)password.c_str(), password.length());
1221 passwordHash[32] = 0;
1222 ByteString total = ByteString::Build(username, "-", passwordHash);
1223 md5_ascii(totalHash, (const unsigned char *)(total.c_str()), total.size());
1224 totalHash[32] = 0;
1225
1226 ByteString data;
1227 int dataStatus;
1228 data = http::Request::Simple(SCHEME SERVER "/Login.json", &dataStatus, {
1229 { "Username", username },
1230 { "Hash", totalHash },
1231 });
1232
1233 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1234 if (ret == RequestOkay)
1235 {
1236 try
1237 {
1238 std::istringstream dataStream(data);
1239 Json::Value objDocument;
1240 dataStream >> objDocument;
1241
1242 int userIDTemp = objDocument["UserID"].asInt();
1243 ByteString sessionIDTemp = objDocument["SessionID"].asString();
1244 ByteString sessionKeyTemp = objDocument["SessionKey"].asString();
1245 ByteString userElevationTemp = objDocument["Elevation"].asString();
1246
1247 Json::Value notificationsArray = objDocument["Notifications"];
1248 for (Json::UInt j = 0; j < notificationsArray.size(); j++)
1249 {
1250 ByteString notificationLink = notificationsArray[j]["Link"].asString();
1251 String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
1252
1253 std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
1254 AddServerNotification(item);
1255 }
1256
1257 user.Username = username;
1258 user.UserID = userIDTemp;
1259 user.SessionID = sessionIDTemp;
1260 user.SessionKey = sessionKeyTemp;
1261 ByteString userElevation = userElevationTemp;
1262 if(userElevation == "Admin")
1263 user.UserElevation = User::ElevationAdmin;
1264 else if(userElevation == "Mod")
1265 user.UserElevation = User::ElevationModerator;
1266 else
1267 user.UserElevation= User::ElevationNone;
1268 return LoginOkay;
1269 }
1270 catch (std::exception &e)
1271 {
1272 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1273 return LoginError;
1274 }
1275 }
1276 return LoginError;
1277 }
1278
DeleteSave(int saveID)1279 RequestStatus Client::DeleteSave(int saveID)
1280 {
1281 lastError = "";
1282 ByteString data;
1283 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete&Key=", authUser.SessionKey);
1284 int dataStatus;
1285 if(authUser.UserID)
1286 {
1287 ByteString userID = ByteString::Build(authUser.UserID);
1288 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
1289 }
1290 else
1291 {
1292 lastError = "Not authenticated";
1293 return RequestFailure;
1294 }
1295 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1296 return ret;
1297 }
1298
AddComment(int saveID,String comment)1299 RequestStatus Client::AddComment(int saveID, String comment)
1300 {
1301 lastError = "";
1302 ByteString data;
1303 int dataStatus;
1304 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID);
1305 if(authUser.UserID)
1306 {
1307 ByteString userID = ByteString::Build(authUser.UserID);
1308 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
1309 { "Comment", comment.ToUtf8() },
1310 });
1311 }
1312 else
1313 {
1314 lastError = "Not authenticated";
1315 return RequestFailure;
1316 }
1317 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1318 return ret;
1319 }
1320
FavouriteSave(int saveID,bool favourite)1321 RequestStatus Client::FavouriteSave(int saveID, bool favourite)
1322 {
1323 lastError = "";
1324 ByteStringBuilder urlStream;
1325 ByteString data;
1326 int dataStatus;
1327 urlStream << SCHEME << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
1328 if(!favourite)
1329 urlStream << "&Mode=Remove";
1330 if(authUser.UserID)
1331 {
1332 ByteString userID = ByteString::Build(authUser.UserID);
1333 data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
1334 }
1335 else
1336 {
1337 lastError = "Not authenticated";
1338 return RequestFailure;
1339 }
1340 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1341 return ret;
1342 }
1343
ReportSave(int saveID,String message)1344 RequestStatus Client::ReportSave(int saveID, String message)
1345 {
1346 lastError = "";
1347 ByteString data;
1348 int dataStatus;
1349 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Report.json?ID=", saveID, "&Key=", authUser.SessionKey);
1350 if(authUser.UserID)
1351 {
1352 ByteString userID = ByteString::Build(authUser.UserID);
1353 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
1354 { "Reason", message.ToUtf8() },
1355 });
1356 }
1357 else
1358 {
1359 lastError = "Not authenticated";
1360 return RequestFailure;
1361 }
1362 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1363 return ret;
1364 }
1365
UnpublishSave(int saveID)1366 RequestStatus Client::UnpublishSave(int saveID)
1367 {
1368 lastError = "";
1369 ByteString data;
1370 int dataStatus;
1371 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Unpublish&Key=", authUser.SessionKey);
1372 if(authUser.UserID)
1373 {
1374 ByteString userID = ByteString::Build(authUser.UserID);
1375 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
1376 }
1377 else
1378 {
1379 lastError = "Not authenticated";
1380 return RequestFailure;
1381 }
1382 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1383 return ret;
1384 }
1385
PublishSave(int saveID)1386 RequestStatus Client::PublishSave(int saveID)
1387 {
1388 lastError = "";
1389 ByteString data;
1390 int dataStatus;
1391 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/View.json?ID=", saveID, "&Key=", authUser.SessionKey);
1392 if (authUser.UserID)
1393 {
1394 ByteString userID = ByteString::Build(authUser.UserID);
1395 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
1396 { "ActionPublish", "bagels" },
1397 });
1398 }
1399 else
1400 {
1401 lastError = "Not authenticated";
1402 return RequestFailure;
1403 }
1404 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1405 return ret;
1406 }
1407
GetSave(int saveID,int saveDate)1408 SaveInfo * Client::GetSave(int saveID, int saveDate)
1409 {
1410 lastError = "";
1411 ByteStringBuilder urlStream;
1412 urlStream << SCHEME << SERVER << "/Browse/View.json?ID=" << saveID;
1413 if(saveDate)
1414 {
1415 urlStream << "&Date=" << saveDate;
1416 }
1417 ByteString data;
1418 int dataStatus;
1419 if(authUser.UserID)
1420 {
1421 ByteString userID = ByteString::Build(authUser.UserID);
1422
1423 data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
1424 }
1425 else
1426 {
1427 data = http::Request::Simple(urlStream.Build(), &dataStatus);
1428 }
1429 if(dataStatus == 200 && data.size())
1430 {
1431 try
1432 {
1433 std::istringstream dataStream(data);
1434 Json::Value objDocument;
1435 dataStream >> objDocument;
1436
1437 int tempID = objDocument["ID"].asInt();
1438 int tempScoreUp = objDocument["ScoreUp"].asInt();
1439 int tempScoreDown = objDocument["ScoreDown"].asInt();
1440 int tempMyScore = objDocument["ScoreMine"].asInt();
1441 ByteString tempUsername = objDocument["Username"].asString();
1442 String tempName = ByteString(objDocument["Name"].asString()).FromUtf8();
1443 String tempDescription = ByteString(objDocument["Description"].asString()).FromUtf8();
1444 int tempCreatedDate = objDocument["DateCreated"].asInt();
1445 int tempUpdatedDate = objDocument["Date"].asInt();
1446 bool tempPublished = objDocument["Published"].asBool();
1447 bool tempFavourite = objDocument["Favourite"].asBool();
1448 int tempComments = objDocument["Comments"].asInt();
1449 int tempViews = objDocument["Views"].asInt();
1450 int tempVersion = objDocument["Version"].asInt();
1451
1452 Json::Value tagsArray = objDocument["Tags"];
1453 std::list<ByteString> tempTags;
1454 for (Json::UInt j = 0; j < tagsArray.size(); j++)
1455 tempTags.push_back(tagsArray[j].asString());
1456
1457 SaveInfo * tempSave = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp,
1458 tempScoreDown, tempMyScore, tempUsername, tempName,
1459 tempDescription, tempPublished, tempTags);
1460 tempSave->Comments = tempComments;
1461 tempSave->Favourite = tempFavourite;
1462 tempSave->Views = tempViews;
1463 tempSave->Version = tempVersion;
1464 return tempSave;
1465 }
1466 catch (std::exception & e)
1467 {
1468 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1469 return NULL;
1470 }
1471 }
1472 else
1473 {
1474 lastError = http::StatusText(dataStatus);
1475 }
1476 return NULL;
1477 }
1478
LoadSaveFile(ByteString filename)1479 SaveFile * Client::LoadSaveFile(ByteString filename)
1480 {
1481 if (!FileExists(filename))
1482 return nullptr;
1483 SaveFile * file = new SaveFile(filename);
1484 try
1485 {
1486 GameSave * tempSave = new GameSave(ReadFile(filename));
1487 file->SetGameSave(tempSave);
1488 }
1489 catch (ParseException & e)
1490 {
1491 std::cerr << "Client: Invalid save file '" << filename << "': " << e.what() << std::endl;
1492 file->SetLoadingError(ByteString(e.what()).FromUtf8());
1493 }
1494 return file;
1495 }
1496
GetTags(int start,int count,String query,int & resultCount)1497 std::vector<std::pair<ByteString, int> > * Client::GetTags(int start, int count, String query, int & resultCount)
1498 {
1499 lastError = "";
1500 resultCount = 0;
1501 std::vector<std::pair<ByteString, int> > * tagArray = new std::vector<std::pair<ByteString, int> >();
1502 ByteStringBuilder urlStream;
1503 ByteString data;
1504 int dataStatus;
1505 urlStream << SCHEME << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
1506 if(query.length())
1507 {
1508 urlStream << "&Search_Query=";
1509 if(query.length())
1510 urlStream << format::URLEncode(query.ToUtf8());
1511 }
1512
1513 data = http::Request::Simple(urlStream.Build(), &dataStatus);
1514 if(dataStatus == 200 && data.size())
1515 {
1516 try
1517 {
1518 std::istringstream dataStream(data);
1519 Json::Value objDocument;
1520 dataStream >> objDocument;
1521
1522 resultCount = objDocument["TagTotal"].asInt();
1523 Json::Value tagsArray = objDocument["Tags"];
1524 for (Json::UInt j = 0; j < tagsArray.size(); j++)
1525 {
1526 int tagCount = tagsArray[j]["Count"].asInt();
1527 ByteString tag = tagsArray[j]["Tag"].asString();
1528 tagArray->push_back(std::pair<ByteString, int>(tag, tagCount));
1529 }
1530 }
1531 catch (std::exception & e)
1532 {
1533 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1534 }
1535 }
1536 else
1537 {
1538 lastError = http::StatusText(dataStatus);
1539 }
1540 return tagArray;
1541 }
1542
SearchSaves(int start,int count,String query,ByteString sort,ByteString category,int & resultCount)1543 std::vector<SaveInfo*> * Client::SearchSaves(int start, int count, String query, ByteString sort, ByteString category, int & resultCount)
1544 {
1545 lastError = "";
1546 resultCount = 0;
1547 std::vector<SaveInfo*> * saveArray = new std::vector<SaveInfo*>();
1548 ByteStringBuilder urlStream;
1549 ByteString data;
1550 int dataStatus;
1551 urlStream << SCHEME << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
1552 if(query.length() || sort.length())
1553 {
1554 urlStream << "&Search_Query=";
1555 if(query.length())
1556 urlStream << format::URLEncode(query.ToUtf8());
1557 if(sort == "date")
1558 {
1559 if(query.length())
1560 urlStream << format::URLEncode(" ");
1561 urlStream << format::URLEncode("sort:") << format::URLEncode(sort);
1562 }
1563 }
1564 if(category.length())
1565 {
1566 urlStream << "&Category=" << format::URLEncode(category);
1567 }
1568 if(authUser.UserID)
1569 {
1570 ByteString userID = ByteString::Build(authUser.UserID);
1571 data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
1572 }
1573 else
1574 {
1575 data = http::Request::Simple(urlStream.Build(), &dataStatus);
1576 }
1577 ParseServerReturn(data, dataStatus, true);
1578 if (dataStatus == 200 && data.size())
1579 {
1580 try
1581 {
1582 std::istringstream dataStream(data);
1583 Json::Value objDocument;
1584 dataStream >> objDocument;
1585
1586 resultCount = objDocument["Count"].asInt();
1587 Json::Value savesArray = objDocument["Saves"];
1588 for (Json::UInt j = 0; j < savesArray.size(); j++)
1589 {
1590 int tempID = savesArray[j]["ID"].asInt();
1591 int tempCreatedDate = savesArray[j]["Created"].asInt();
1592 int tempUpdatedDate = savesArray[j]["Updated"].asInt();
1593 int tempScoreUp = savesArray[j]["ScoreUp"].asInt();
1594 int tempScoreDown = savesArray[j]["ScoreDown"].asInt();
1595 ByteString tempUsername = savesArray[j]["Username"].asString();
1596 String tempName = ByteString(savesArray[j]["Name"].asString()).FromUtf8();
1597 int tempVersion = savesArray[j]["Version"].asInt();
1598 bool tempPublished = savesArray[j]["Published"].asBool();
1599 SaveInfo * tempSaveInfo = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp, tempScoreDown, tempUsername, tempName);
1600 tempSaveInfo->Version = tempVersion;
1601 tempSaveInfo->SetPublished(tempPublished);
1602 saveArray->push_back(tempSaveInfo);
1603 }
1604 }
1605 catch (std::exception &e)
1606 {
1607 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1608 }
1609 }
1610 return saveArray;
1611 }
1612
RemoveTag(int saveID,ByteString tag)1613 std::list<ByteString> * Client::RemoveTag(int saveID, ByteString tag)
1614 {
1615 lastError = "";
1616 std::list<ByteString> * tags = NULL;
1617 ByteString data;
1618 int dataStatus;
1619 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
1620 if(authUser.UserID)
1621 {
1622 ByteString userID = ByteString::Build(authUser.UserID);
1623 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
1624 }
1625 else
1626 {
1627 lastError = "Not authenticated";
1628 return NULL;
1629 }
1630 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1631 if (ret == RequestOkay)
1632 {
1633 try
1634 {
1635 std::istringstream dataStream(data);
1636 Json::Value responseObject;
1637 dataStream >> responseObject;
1638
1639 Json::Value tagsArray = responseObject["Tags"];
1640 tags = new std::list<ByteString>();
1641 for (Json::UInt j = 0; j < tagsArray.size(); j++)
1642 tags->push_back(tagsArray[j].asString());
1643 }
1644 catch (std::exception &e)
1645 {
1646 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1647 }
1648 }
1649 return tags;
1650 }
1651
AddTag(int saveID,ByteString tag)1652 std::list<ByteString> * Client::AddTag(int saveID, ByteString tag)
1653 {
1654 lastError = "";
1655 std::list<ByteString> * tags = NULL;
1656 ByteString data;
1657 int dataStatus;
1658 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
1659 if(authUser.UserID)
1660 {
1661 ByteString userID = ByteString::Build(authUser.UserID);
1662 data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
1663 }
1664 else
1665 {
1666 lastError = "Not authenticated";
1667 return NULL;
1668 }
1669 RequestStatus ret = ParseServerReturn(data, dataStatus, true);
1670 if (ret == RequestOkay)
1671 {
1672 try
1673 {
1674 std::istringstream dataStream(data);
1675 Json::Value responseObject;
1676 dataStream >> responseObject;
1677
1678 Json::Value tagsArray = responseObject["Tags"];
1679 tags = new std::list<ByteString>();
1680 for (Json::UInt j = 0; j < tagsArray.size(); j++)
1681 tags->push_back(tagsArray[j].asString());
1682 }
1683 catch (std::exception & e)
1684 {
1685 lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
1686 }
1687 }
1688 return tags;
1689 }
1690
1691 // stamp-specific wrapper for MergeAuthorInfo
1692 // also used for clipboard and lua stamps
MergeStampAuthorInfo(Json::Value stampAuthors)1693 void Client::MergeStampAuthorInfo(Json::Value stampAuthors)
1694 {
1695 if (stampAuthors.size())
1696 {
1697 // when loading stamp/clipboard, only append info to authorship info (since we aren't replacing the save)
1698 // unless there is nothing loaded currently, then set authors directly
1699 if (authors.size())
1700 {
1701 // Don't add if it's exactly the same
1702 if (stampAuthors["links"].size() == 1 && stampAuthors["links"][0] == Client::Ref().authors)
1703 return;
1704 if (authors["username"] != stampAuthors["username"])
1705 {
1706 // 2nd arg of MergeAuthorInfo needs to be an array
1707 Json::Value toAdd;
1708 toAdd.append(stampAuthors);
1709 MergeAuthorInfo(toAdd);
1710 }
1711 else if (stampAuthors["links"].size())
1712 {
1713 MergeAuthorInfo(stampAuthors["links"]);
1714 }
1715 }
1716 else
1717 authors = stampAuthors;
1718 }
1719 }
1720
1721 // linksToAdd is an array (NOT an object) of links to add to authors["links"]
MergeAuthorInfo(Json::Value linksToAdd)1722 void Client::MergeAuthorInfo(Json::Value linksToAdd)
1723 {
1724 for (Json::Value::ArrayIndex i = 0; i < linksToAdd.size(); i++)
1725 {
1726 // link is the same exact json we have open, don't do anything
1727 if (linksToAdd[i] == authors)
1728 return;
1729
1730 bool hasLink = false;
1731 for (Json::Value::ArrayIndex j = 0; j < authors["links"].size(); j++)
1732 {
1733 // check everything in authors["links"] to see if it's the same json as what we are already adding
1734 if (authors["links"][j] == linksToAdd[i])
1735 hasLink = true;
1736 }
1737 if (!hasLink)
1738 authors["links"].append(linksToAdd[i]);
1739 }
1740 }
1741
1742 // load current authors information into a json value (when saving everything: stamps, clipboard, local saves, and online saves)
SaveAuthorInfo(Json::Value * saveInto)1743 void Client::SaveAuthorInfo(Json::Value *saveInto)
1744 {
1745 if (authors.size() != 0)
1746 {
1747 // Different username? Save full original save info
1748 if (authors["username"] != (*saveInto)["username"])
1749 (*saveInto)["links"].append(authors);
1750 // This is probalby the same save
1751 // Don't append another layer of links, just keep existing links
1752 else if (authors["links"].size())
1753 (*saveInto)["links"] = authors["links"];
1754 }
1755 }
1756
1757 // powder.pref preference getting / setting functions
1758
1759 // Recursively go down the json to get the setting we want
GetPref(Json::Value root,ByteString prop,Json::Value defaultValue)1760 Json::Value Client::GetPref(Json::Value root, ByteString prop, Json::Value defaultValue)
1761 {
1762 try
1763 {
1764 if(ByteString::Split split = prop.SplitBy('.'))
1765 return GetPref(root[split.Before()], split.After(), defaultValue);
1766 else
1767 return root.get(prop, defaultValue);
1768 }
1769 catch (std::exception & e)
1770 {
1771 return defaultValue;
1772 }
1773 }
1774
GetPrefByteString(ByteString prop,ByteString defaultValue)1775 ByteString Client::GetPrefByteString(ByteString prop, ByteString defaultValue)
1776 {
1777 try
1778 {
1779 return GetPref(preferences, prop, defaultValue).asString();
1780 }
1781 catch (std::exception & e)
1782 {
1783 return defaultValue;
1784 }
1785 }
1786
GetPrefString(ByteString prop,String defaultValue)1787 String Client::GetPrefString(ByteString prop, String defaultValue)
1788 {
1789 try
1790 {
1791 return ByteString(GetPref(preferences, prop, defaultValue.ToUtf8()).asString()).FromUtf8(false);
1792 }
1793 catch (std::exception & e)
1794 {
1795 return defaultValue;
1796 }
1797 }
1798
GetPrefNumber(ByteString prop,double defaultValue)1799 double Client::GetPrefNumber(ByteString prop, double defaultValue)
1800 {
1801 try
1802 {
1803 return GetPref(preferences, prop, defaultValue).asDouble();
1804 }
1805 catch (std::exception & e)
1806 {
1807 return defaultValue;
1808 }
1809 }
1810
GetPrefInteger(ByteString prop,int defaultValue)1811 int Client::GetPrefInteger(ByteString prop, int defaultValue)
1812 {
1813 try
1814 {
1815 return GetPref(preferences, prop, defaultValue).asInt();
1816 }
1817 catch (std::exception & e)
1818 {
1819 return defaultValue;
1820 }
1821 }
1822
GetPrefUInteger(ByteString prop,unsigned int defaultValue)1823 unsigned int Client::GetPrefUInteger(ByteString prop, unsigned int defaultValue)
1824 {
1825 try
1826 {
1827 return GetPref(preferences, prop, defaultValue).asUInt();
1828 }
1829 catch (std::exception & e)
1830 {
1831 return defaultValue;
1832 }
1833 }
1834
GetPrefBool(ByteString prop,bool defaultValue)1835 bool Client::GetPrefBool(ByteString prop, bool defaultValue)
1836 {
1837 try
1838 {
1839 return GetPref(preferences, prop, defaultValue).asBool();
1840 }
1841 catch (std::exception & e)
1842 {
1843 return defaultValue;
1844 }
1845 }
1846
GetPrefByteStringArray(ByteString prop)1847 std::vector<ByteString> Client::GetPrefByteStringArray(ByteString prop)
1848 {
1849 try
1850 {
1851 std::vector<ByteString> ret;
1852 Json::Value arr = GetPref(preferences, prop);
1853 for (int i = 0; i < (int)arr.size(); i++)
1854 ret.push_back(arr[i].asString());
1855 return ret;
1856 }
1857 catch (std::exception & e)
1858 {
1859
1860 }
1861 return std::vector<ByteString>();
1862 }
1863
GetPrefStringArray(ByteString prop)1864 std::vector<String> Client::GetPrefStringArray(ByteString prop)
1865 {
1866 try
1867 {
1868 std::vector<String> ret;
1869 Json::Value arr = GetPref(preferences, prop);
1870 for (int i = 0; i < (int)arr.size(); i++)
1871 ret.push_back(ByteString(arr[i].asString()).FromUtf8(false));
1872 return ret;
1873 }
1874 catch (std::exception & e)
1875 {
1876
1877 }
1878 return std::vector<String>();
1879 }
1880
GetPrefNumberArray(ByteString prop)1881 std::vector<double> Client::GetPrefNumberArray(ByteString prop)
1882 {
1883 try
1884 {
1885 std::vector<double> ret;
1886 Json::Value arr = GetPref(preferences, prop);
1887 for (int i = 0; i < (int)arr.size(); i++)
1888 ret.push_back(arr[i].asDouble());
1889 return ret;
1890 }
1891 catch (std::exception & e)
1892 {
1893
1894 }
1895 return std::vector<double>();
1896 }
1897
GetPrefIntegerArray(ByteString prop)1898 std::vector<int> Client::GetPrefIntegerArray(ByteString prop)
1899 {
1900 try
1901 {
1902 std::vector<int> ret;
1903 Json::Value arr = GetPref(preferences, prop);
1904 for (int i = 0; i < (int)arr.size(); i++)
1905 ret.push_back(arr[i].asInt());
1906 return ret;
1907 }
1908 catch (std::exception & e)
1909 {
1910
1911 }
1912 return std::vector<int>();
1913 }
1914
GetPrefUIntegerArray(ByteString prop)1915 std::vector<unsigned int> Client::GetPrefUIntegerArray(ByteString prop)
1916 {
1917 try
1918 {
1919 std::vector<unsigned int> ret;
1920 Json::Value arr = GetPref(preferences, prop);
1921 for (int i = 0; i < (int)arr.size(); i++)
1922 ret.push_back(arr[i].asUInt());
1923 return ret;
1924 }
1925 catch (std::exception & e)
1926 {
1927
1928 }
1929 return std::vector<unsigned int>();
1930 }
1931
GetPrefBoolArray(ByteString prop)1932 std::vector<bool> Client::GetPrefBoolArray(ByteString prop)
1933 {
1934 try
1935 {
1936 std::vector<bool> ret;
1937 Json::Value arr = GetPref(preferences, prop);
1938 for (int i = 0; i < (int)arr.size(); i++)
1939 ret.push_back(arr[i].asBool());
1940 return ret;
1941 }
1942 catch (std::exception & e)
1943 {
1944
1945 }
1946 return std::vector<bool>();
1947 }
1948
1949 // Helper preference setting function.
1950 // To actually save any changes to preferences, we need to directly do preferences[property] = thing
1951 // any other way will set the value of a copy of preferences, not the original
1952 // This function will recursively go through and create an object with the property we wanted set,
1953 // and return it to SetPref to do the actual setting
SetPrefHelper(Json::Value root,ByteString prop,Json::Value value)1954 Json::Value Client::SetPrefHelper(Json::Value root, ByteString prop, Json::Value value)
1955 {
1956 if(ByteString::Split split = prop.SplitBy('.'))
1957 {
1958 Json::Value toSet = GetPref(root, split.Before());
1959 toSet = SetPrefHelper(toSet, split.After(), value);
1960 root[split.Before()] = toSet;
1961 }
1962 else
1963 root[prop] = value;
1964 return root;
1965 }
1966
SetPref(ByteString prop,Json::Value value)1967 void Client::SetPref(ByteString prop, Json::Value value)
1968 {
1969 try
1970 {
1971 if(ByteString::Split split = prop.SplitBy('.'))
1972 preferences[split.Before()] = SetPrefHelper(preferences[split.Before()], split.After(), value);
1973 else
1974 preferences[prop] = value;
1975 }
1976 catch (std::exception & e)
1977 {
1978
1979 }
1980 }
1981
SetPref(ByteString prop,std::vector<Json::Value> value)1982 void Client::SetPref(ByteString prop, std::vector<Json::Value> value)
1983 {
1984 try
1985 {
1986 Json::Value arr;
1987 for (int i = 0; i < (int)value.size(); i++)
1988 {
1989 arr.append(value[i]);
1990 }
1991 SetPref(prop, arr);
1992 }
1993 catch (std::exception & e)
1994 {
1995
1996 }
1997 }
1998
SetPrefUnicode(ByteString prop,String value)1999 void Client::SetPrefUnicode(ByteString prop, String value)
2000 {
2001 SetPref(prop, value.ToUtf8());
2002 }
2003