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(), &currentFile);
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, &currentFile) == 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