summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorSimon Robertshaw <simon@hardwired.org.uk>2012-11-17 19:44:09 (GMT)
committer Simon Robertshaw <simon@hardwired.org.uk>2012-11-17 19:44:09 (GMT)
commit058a2edd75debbd0297f92572316daa704bd379f (patch)
treead303f091f9a08b209b91eb34a9fcad996a3de69 /src/client
parente3594aba9e05c6865d396418c028049cda92c2f3 (diff)
parent7a21ae192fe19868539956f3fe28e62b2c7c4429 (diff)
downloadpowder-058a2edd75debbd0297f92572316daa704bd379f.zip
powder-058a2edd75debbd0297f92572316daa704bd379f.tar.gz
Merge branch 'master' of github.com:FacialTurd/PowderToypp
Diffstat (limited to 'src/client')
-rw-r--r--src/client/Client.cpp2351
-rw-r--r--src/client/Client.h176
-rw-r--r--src/client/ClientListener.h24
-rw-r--r--src/client/GameSave.cpp2092
-rw-r--r--src/client/GameSave.h112
-rw-r--r--src/client/HTTP.cpp1104
-rw-r--r--src/client/HTTP.h45
-rw-r--r--src/client/MD5.cpp231
-rw-r--r--src/client/MD5.h18
-rw-r--r--src/client/SaveFile.cpp80
-rw-r--r--src/client/SaveFile.h38
-rw-r--r--src/client/SaveInfo.cpp128
-rw-r--r--src/client/SaveInfo.h78
-rw-r--r--src/client/ThumbnailBroker.cpp347
-rw-r--r--src/client/ThumbnailBroker.h108
-rw-r--r--src/client/ThumbnailListener.h12
-rw-r--r--src/client/User.h38
17 files changed, 6982 insertions, 0 deletions
diff --git a/src/client/Client.cpp b/src/client/Client.cpp
new file mode 100644
index 0000000..763814a
--- /dev/null
+++ b/src/client/Client.cpp
@@ -0,0 +1,2351 @@
+#include <stdlib.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <iomanip>
+#include <time.h>
+#include <stdio.h>
+#include <deque>
+#include <fstream>
+#include <dirent.h>
+
+#ifdef MACOSX
+#include <mach-o/dyld.h>
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef WIN
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <windows.h>
+#include <direct.h>
+#else
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "Config.h"
+#include "Format.h"
+#include "Client.h"
+#include "MD5.h"
+#include "graphics/Graphics.h"
+#include "Misc.h"
+#include "Update.h"
+#include "HTTP.h"
+
+#include "simulation/SaveRenderer.h"
+#include "interface/Point.h"
+#include "client/SaveInfo.h"
+#include "client/SaveFile.h"
+#include "client/GameSave.h"
+#include "search/Thumbnail.h"
+#include "preview/Comment.h"
+#include "ClientListener.h"
+#include "ThumbnailBroker.h"
+
+#include "cajun/reader.h"
+#include "cajun/writer.h"
+
+extern "C"
+{
+#if defined(WIN) && !defined(__GNUC__)
+#include <io.h>
+#else
+#include <dirent.h>
+#endif
+}
+
+Client::Client():
+ authUser(0, ""),
+ updateAvailable(false),
+ versionCheckRequest(NULL),
+ messageOfTheDay("")
+{
+ int i = 0;
+ for(i = 0; i < THUMB_CACHE_SIZE; i++)
+ {
+ thumbnailCache[i] = NULL;
+ }
+ for(i = 0; i < IMGCONNS; i++)
+ {
+ activeThumbRequests[i] = NULL;
+ activeThumbRequestTimes[i] = 0;
+ activeThumbRequestCompleteTimes[i] = 0;
+ }
+
+ //Read config
+ std::ifstream configFile;
+ configFile.open("powder.pref", std::ios::binary);
+ if(configFile)
+ {
+ int fsize = configFile.tellg();
+ configFile.seekg(0, std::ios::end);
+ fsize = configFile.tellg() - (std::streampos)fsize;
+ configFile.seekg(0, std::ios::beg);
+ if(fsize)
+ {
+ try
+ {
+ json::Reader::Read(configDocument, configFile);
+ authUser.ID = ((json::Number)(configDocument["User"]["ID"])).Value();
+ authUser.SessionID = ((json::String)(configDocument["User"]["SessionID"])).Value();
+ authUser.SessionKey = ((json::String)(configDocument["User"]["SessionKey"])).Value();
+ authUser.Username = ((json::String)(configDocument["User"]["Username"])).Value();
+
+ std::string userElevation = ((json::String)(configDocument["User"]["Elevation"])).Value();
+ if(userElevation == "Admin")
+ authUser.UserElevation = User::ElevationAdmin;
+ else if(userElevation == "Mod")
+ authUser.UserElevation = User::ElevationModerator;
+ else
+ authUser.UserElevation = User::ElevationNone;
+ }
+ catch (json::Exception &e)
+ {
+ authUser = User(0, "");
+ std::cerr << "Error: Could not read data from prefs: " << e.what() << std::endl;
+ }
+ }
+ configFile.close();
+ }
+}
+
+void Client::Initialise(std::string proxyString)
+{
+
+ if(GetPrefBool("version.update", false)==true)
+ {
+ SetPref("version.update", false);
+ update_finish();
+ }
+
+ if(proxyString.length())
+ http_init((char*)proxyString.c_str());
+ else
+ http_init(NULL);
+
+ //Read stamps library
+ std::ifstream stampsLib;
+ stampsLib.open(STAMPS_DIR PATH_SEP "stamps.def", std::ios::binary);
+ while(!stampsLib.eof())
+ {
+ char data[11];
+ memset(data, 0, 11);
+ stampsLib.read(data, 10);
+ if(!data[0])
+ break;
+ stampIDs.push_back(data);
+ }
+ stampsLib.close();
+
+ //Begin version check
+ versionCheckRequest = http_async_req_start(NULL, SERVER "/Startup.json", NULL, 0, 1);
+
+ if(authUser.ID)
+ {
+ http_auth_headers(versionCheckRequest, (char *)format::NumberToString<int>(authUser.ID).c_str(), NULL, (char *)authUser.SessionID.c_str());
+ }
+}
+
+bool Client::DoInstallation()
+{
+#if defined(WIN)
+ int returnval;
+ LONG rresult;
+ HKEY newkey;
+ char *currentfilename = exe_name();
+ char *iconname = NULL;
+ char *opencommand = NULL;
+ //char AppDataPath[MAX_PATH];
+ char *AppDataPath = NULL;
+ iconname = (char*)malloc(strlen(currentfilename)+6);
+ sprintf(iconname, "%s,-102", currentfilename);
+
+ //Create Roaming application data folder
+ /*if(!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, AppDataPath)))
+ {
+ returnval = 0;
+ goto finalise;
+ }*/
+
+ AppDataPath = _getcwd(NULL, 0);
+
+ //Move Game executable into application data folder
+ //TODO: Implement
+
+ opencommand = (char*)malloc(strlen(currentfilename)+53+strlen(AppDataPath));
+ /*if((strlen(AppDataPath)+strlen(APPDATA_SUBDIR "\\Powder Toy"))<MAX_PATH)
+ {
+ strappend(AppDataPath, APPDATA_SUBDIR);
+ _mkdir(AppDataPath);
+ strappend(AppDataPath, "\\Powder Toy");
+ _mkdir(AppDataPath);
+ } else {
+ returnval = 0;
+ goto finalise;
+ }*/
+ sprintf(opencommand, "\"%s\" open \"%%1\" ddir \"%s\"", currentfilename, AppDataPath);
+
+ //Create extension entry
+ rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\.cps", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
+ if (rresult != ERROR_SUCCESS) {
+ returnval = 0;
+ goto finalise;
+ }
+ rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"PowderToySave", strlen("PowderToySave")+1);
+ if (rresult != ERROR_SUCCESS) {
+ RegCloseKey(newkey);
+ returnval = 0;
+ goto finalise;
+ }
+ RegCloseKey(newkey);
+
+ rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\.stm", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
+ if (rresult != ERROR_SUCCESS) {
+ returnval = 0;
+ goto finalise;
+ }
+ rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"PowderToySave", strlen("PowderToySave")+1);
+ if (rresult != ERROR_SUCCESS) {
+ RegCloseKey(newkey);
+ returnval = 0;
+ goto finalise;
+ }
+ RegCloseKey(newkey);
+
+ //Create program entry
+ rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
+ if (rresult != ERROR_SUCCESS) {
+ returnval = 0;
+ goto finalise;
+ }
+ rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)"Powder Toy Save", strlen("Powder Toy Save")+1);
+ if (rresult != ERROR_SUCCESS) {
+ RegCloseKey(newkey);
+ returnval = 0;
+ goto finalise;
+ }
+ RegCloseKey(newkey);
+
+ //Set DefaultIcon
+ rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave\\DefaultIcon", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
+ if (rresult != ERROR_SUCCESS) {
+ returnval = 0;
+ goto finalise;
+ }
+ rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)iconname, strlen(iconname)+1);
+ if (rresult != ERROR_SUCCESS) {
+ RegCloseKey(newkey);
+ returnval = 0;
+ goto finalise;
+ }
+ RegCloseKey(newkey);
+
+ //Set Launch command
+ rresult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\Classes\\PowderToySave\\shell\\open\\command", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newkey, NULL);
+ if (rresult != ERROR_SUCCESS) {
+ returnval = 0;
+ goto finalise;
+ }
+ rresult = RegSetValueEx(newkey, 0, 0, REG_SZ, (LPBYTE)opencommand, strlen(opencommand)+1);
+ if (rresult != ERROR_SUCCESS) {
+ RegCloseKey(newkey);
+ returnval = 0;
+ goto finalise;
+ }
+ RegCloseKey(newkey);
+
+ returnval = 1;
+ finalise:
+
+ if(iconname) free(iconname);
+ if(opencommand) free(opencommand);
+ if(currentfilename) free(currentfilename);
+
+ return returnval;
+#elif defined(LIN)
+ #include "icondoc.h"
+
+ char *currentfilename = exe_name();
+ FILE *f;
+ char *mimedata =
+"<?xml version=\"1.0\"?>\n"
+" <mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>\n"
+" <mime-type type=\"application/vnd.powdertoy.save\">\n"
+" <comment>Powder Toy save</comment>\n"
+" <glob pattern=\"*.cps\"/>\n"
+" <glob pattern=\"*.stm\"/>\n"
+" </mime-type>\n"
+"</mime-info>\n";
+ f = fopen("powdertoy-save.xml", "wb");
+ if (!f)
+ return 0;
+ fwrite(mimedata, 1, strlen(mimedata), f);
+ fclose(f);
+
+ char *protocolfiledata_tmp =
+"[Desktop Entry]\n"
+"Type=Application\n"
+"Name=Powder Toy\n"
+"Comment=Physics sandbox game\n"
+"MimeType=x-scheme-handler/ptsave;\n"
+"NoDisplay=true\n";
+ char *protocolfiledata = (char *)malloc(strlen(protocolfiledata_tmp)+strlen(currentfilename)+100);
+ strcpy(protocolfiledata, protocolfiledata_tmp);
+ strappend(protocolfiledata, "Exec=");
+ strappend(protocolfiledata, currentfilename);
+ strappend(protocolfiledata, " ptsave %u\n");
+ f = fopen("powdertoy-tpt-ptsave.desktop", "wb");
+ if (!f)
+ return 0;
+ fwrite(protocolfiledata, 1, strlen(protocolfiledata), f);
+ fclose(f);
+ system("xdg-desktop-menu install powdertoy-tpt-ptsave.desktop");
+
+ char *desktopfiledata_tmp =
+"[Desktop Entry]\n"
+"Type=Application\n"
+"Name=Powder Toy\n"
+"Comment=Physics sandbox game\n"
+"MimeType=application/vnd.powdertoy.save;\n"
+"NoDisplay=true\n";
+ char *desktopfiledata = (char *)malloc(strlen(desktopfiledata_tmp)+strlen(currentfilename)+100);
+ strcpy(desktopfiledata, desktopfiledata_tmp);
+ strappend(desktopfiledata, "Exec=");
+ strappend(desktopfiledata, currentfilename);
+ strappend(desktopfiledata, " open %f\n");
+ f = fopen("powdertoy-tpt.desktop", "wb");
+ if (!f)
+ return 0;
+ fwrite(desktopfiledata, 1, strlen(desktopfiledata), f);
+ fclose(f);
+ system("xdg-mime install powdertoy-save.xml");
+ system("xdg-desktop-menu install powdertoy-tpt.desktop");
+ f = fopen("powdertoy-save-32.png", "wb");
+ if (!f)
+ return 0;
+ fwrite(icon_doc_32_png, 1, sizeof(icon_doc_32_png), f);
+ fclose(f);
+ f = fopen("powdertoy-save-16.png", "wb");
+ if (!f)
+ return 0;
+ fwrite(icon_doc_16_png, 1, sizeof(icon_doc_16_png), f);
+ fclose(f);
+ system("xdg-icon-resource install --noupdate --context mimetypes --size 32 powdertoy-save-32.png application-vnd.powdertoy.save");
+ system("xdg-icon-resource install --noupdate --context mimetypes --size 16 powdertoy-save-16.png application-vnd.powdertoy.save");
+ system("xdg-icon-resource forceupdate");
+ system("xdg-mime default powdertoy-tpt.desktop application/vnd.powdertoy.save");
+ system("xdg-mime default powdertoy-tpt-ptsave.desktop x-scheme-handler/ptsave");
+ unlink("powdertoy-save-32.png");
+ unlink("powdertoy-save-16.png");
+ unlink("powdertoy-save.xml");
+ unlink("powdertoy-tpt.desktop");
+ unlink("powdertoy-tpt-ptsave.desktop");
+ return true;
+#elif defined MACOSX
+ return false;
+#endif
+}
+
+void Client::SetProxy(std::string proxy)
+{
+ http_done();
+ if(proxy.length())
+ http_init((char*)proxy.c_str());
+ else
+ http_init(NULL);
+}
+
+std::vector<std::string> Client::DirectorySearch(std::string directory, std::string search, std::string extension)
+{
+ std::vector<std::string> extensions;
+ extensions.push_back(extension);
+ return DirectorySearch(directory, search, extensions);
+}
+
+std::vector<std::string> Client::DirectorySearch(std::string directory, std::string search, std::vector<std::string> extensions)
+{
+ //Get full file listing
+ std::vector<std::string> directoryList;
+#if defined(WIN) && !defined(__GNUC__)
+ //Windows
+ struct _finddata_t currentFile;
+ intptr_t findFileHandle;
+ std::string fileMatch = directory + "*.*";
+ findFileHandle = _findfirst(fileMatch.c_str(), &currentFile);
+ if (findFileHandle == -1L)
+ {
+ printf("Unable to open directory\n");
+ return std::vector<std::string>();
+ }
+ do
+ {
+ std::string currentFileName = std::string(currentFile.name);
+ if(currentFileName.length()>4)
+ directoryList.push_back(directory+currentFileName);
+ }
+ while (_findnext(findFileHandle, &currentFile) == 0);
+ _findclose(findFileHandle);
+#else
+ //Linux or MinGW
+ struct dirent * directoryEntry;
+ DIR *directoryHandle = opendir(directory.c_str());
+ if(!directoryHandle)
+ {
+ printf("Unable to open directory\n");
+ return std::vector<std::string>();
+ }
+ while(directoryEntry = readdir(directoryHandle))
+ {
+ std::string currentFileName = std::string(directoryEntry->d_name);
+ if(currentFileName.length()>4)
+ directoryList.push_back(directory+currentFileName);
+ }
+ closedir(directoryHandle);
+#endif
+
+ std::vector<std::string> searchResults;
+ for(std::vector<std::string>::iterator iter = directoryList.begin(), end = directoryList.end(); iter != end; ++iter)
+ {
+ std::string filename = *iter;
+ bool extensionMatch = !extensions.size();
+ for(std::vector<std::string>::iterator extIter = extensions.begin(), extEnd = extensions.end(); extIter != extEnd; ++extIter)
+ {
+ if(filename.find(*extIter)==filename.length()-(*extIter).length())
+ {
+ extensionMatch = true;
+ break;
+ }
+ }
+ bool searchMatch = !search.size();
+ if(search.size() && filename.find(search)!=std::string::npos)
+ searchMatch = true;
+
+ if(searchMatch && extensionMatch)
+ searchResults.push_back(filename);
+ }
+
+ //Filter results
+ return searchResults;
+}
+
+int Client::MakeDirectory(const char * dirName)
+{
+#ifdef WIN
+ return _mkdir(dirName);
+#else
+ return mkdir(dirName, 0755);
+#endif
+}
+
+void Client::WriteFile(std::vector<unsigned char> fileData, std::string filename)
+{
+ try
+ {
+ std::ofstream fileStream;
+ fileStream.open(std::string(filename).c_str(), std::ios::binary);
+ if(fileStream.is_open())
+ {
+ fileStream.write((char*)&fileData[0], fileData.size());
+ fileStream.close();
+ }
+ }
+ catch (std::exception & e)
+ {
+ std::cerr << "WriteFile:" << e.what() << std::endl;
+ throw;
+ }
+}
+
+bool Client::FileExists(std::string filename)
+{
+ bool exists = false;
+ try
+ {
+ std::ifstream fileStream;
+ fileStream.open(std::string(filename).c_str(), std::ios::binary);
+ if(fileStream.is_open())
+ {
+ exists = true;
+ fileStream.close();
+ }
+ }
+ catch (std::exception & e)
+ {
+ exists = false;
+ }
+ return exists;
+}
+
+void Client::WriteFile(std::vector<char> fileData, std::string filename)
+{
+ try
+ {
+ std::ofstream fileStream;
+ fileStream.open(std::string(filename).c_str(), std::ios::binary);
+ if(fileStream.is_open())
+ {
+ fileStream.write(&fileData[0], fileData.size());
+ fileStream.close();
+ }
+ }
+ catch (std::exception & e)
+ {
+ std::cerr << "WriteFile:" << e.what() << std::endl;
+ throw;
+ }
+}
+
+std::vector<unsigned char> Client::ReadFile(std::string filename)
+{
+ try
+ {
+ std::ifstream fileStream;
+ fileStream.open(std::string(filename).c_str(), std::ios::binary);
+ if(fileStream.is_open())
+ {
+ fileStream.seekg(0, std::ios::end);
+ size_t fileSize = fileStream.tellg();
+ fileStream.seekg(0);
+
+ unsigned char * tempData = new unsigned char[fileSize];
+ fileStream.read((char *)tempData, fileSize);
+ fileStream.close();
+
+ std::vector<unsigned char> fileData;
+ fileData.insert(fileData.end(), tempData, tempData+fileSize);
+ delete[] tempData;
+
+ return fileData;
+ }
+ else
+ {
+ return std::vector<unsigned char>();
+ }
+ }
+ catch(std::exception & e)
+ {
+ std::cerr << "Readfile: " << e.what() << std::endl;
+ throw;
+ }
+}
+
+void Client::SetMessageOfTheDay(std::string message)
+{
+ messageOfTheDay = message;
+ notifyMessageOfTheDay();
+}
+
+std::string Client::GetMessageOfTheDay()
+{
+ return messageOfTheDay;
+}
+
+void Client::Tick()
+{
+ //Check thumbnail queue
+ ThumbnailBroker::Ref().FlushThumbQueue();
+
+ //Check status on version check request
+ if(versionCheckRequest && http_async_req_status(versionCheckRequest))
+ {
+ int status;
+ int dataLength;
+ char * data = http_async_req_stop(versionCheckRequest, &status, &dataLength);
+ versionCheckRequest = NULL;
+
+ if(status != 200)
+ {
+ if(data)
+ free(data);
+ }
+ else
+ {
+ std::istringstream dataStream(data);
+
+ try
+ {
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ //Check session
+ json::Boolean sessionStatus = objDocument["Session"];
+ if(!sessionStatus.Value())
+ {
+ authUser = User(0, "");
+ }
+
+ //MOTD
+ json::String messageOfTheDay = objDocument["MessageOfTheDay"];
+ this->messageOfTheDay = messageOfTheDay.Value();
+ notifyMessageOfTheDay();
+
+ //Check for updates
+ json::Object versions = objDocument["Updates"];
+
+ json::Object stableVersion = versions["Stable"];
+ json::Object betaVersion = versions["Beta"];
+ json::Object snapshotVersion = versions["Snapshot"];
+
+ json::Number stableMajor = stableVersion["Major"];
+ json::Number stableMinor = stableVersion["Minor"];
+ json::Number stableBuild = stableVersion["Build"];
+ json::String stableFile = stableVersion["File"];
+
+ json::Number betaMajor = betaVersion["Major"];
+ json::Number betaMinor = betaVersion["Minor"];
+ json::Number betaBuild = betaVersion["Build"];
+ json::String betaFile = betaVersion["File"];
+
+ json::Number snapshotSnapshot = snapshotVersion["Snapshot"];
+ json::String snapshotFile = snapshotVersion["File"];
+
+ if(stableMajor.Value()>SAVE_VERSION || (stableMinor.Value()>MINOR_VERSION && stableMajor.Value()==SAVE_VERSION) || stableBuild.Value()>BUILD_NUM)
+ {
+ updateAvailable = true;
+ updateInfo = UpdateInfo(stableMajor.Value(), stableMinor.Value(), stableBuild.Value(), stableFile.Value(), UpdateInfo::Stable);
+ }
+
+#ifdef BETA
+ if(betaMajor.Value()>SAVE_VERSION || (betaMinor.Value()>MINOR_VERSION && betaMajor.Value()==SAVE_VERSION) || betaBuild.Value()>BUILD_NUM)
+ {
+ updateAvailable = true;
+ updateInfo = UpdateInfo(betaMajor.Value(), betaMinor.Value(), betaBuild.Value(), betaFile.Value(), UpdateInfo::Beta);
+ }
+#endif
+
+#ifdef SNAPSHOT
+ if(snapshotSnapshot.Value() > SNAPSHOT_ID)
+ {
+ updateAvailable = true;
+ updateInfo = UpdateInfo(snapshotSnapshot.Value(), snapshotFile.Value(), UpdateInfo::Snapshot);
+ }
+#endif
+
+ if(updateAvailable)
+ {
+ notifyUpdateAvailable();
+ }
+ }
+ catch (json::Exception &e)
+ {
+ //Do nothing
+ }
+
+ if(data)
+ free(data);
+ }
+ }
+}
+
+UpdateInfo Client::GetUpdateInfo()
+{
+ return updateInfo;
+}
+
+void Client::notifyUpdateAvailable()
+{
+ for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
+ {
+ (*iterator)->NotifyUpdateAvailable(this);
+ }
+}
+
+void Client::notifyMessageOfTheDay()
+{
+ for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
+ {
+ (*iterator)->NotifyMessageOfTheDay(this);
+ }
+}
+
+void Client::notifyAuthUserChanged()
+{
+ for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
+ {
+ (*iterator)->NotifyAuthUserChanged(this);
+ }
+}
+
+void Client::AddListener(ClientListener * listener)
+{
+ listeners.push_back(listener);
+}
+
+void Client::RemoveListener(ClientListener * listener)
+{
+ for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
+ {
+ if((*iterator) == listener)
+ {
+ listeners.erase(iterator);
+ return;
+ }
+ }
+}
+
+void Client::WritePrefs()
+{
+ std::ofstream configFile;
+ configFile.open("powder.pref", std::ios::trunc);
+ if(configFile)
+ {
+ if(authUser.ID)
+ {
+ configDocument["User"]["ID"] = json::Number(authUser.ID);
+ configDocument["User"]["SessionID"] = json::String(authUser.SessionID);
+ configDocument["User"]["SessionKey"] = json::String(authUser.SessionKey);
+ configDocument["User"]["Username"] = json::String(authUser.Username);
+ if(authUser.UserElevation == User::ElevationAdmin)
+ configDocument["User"]["Elevation"] = json::String("Admin");
+ else if(authUser.UserElevation == User::ElevationModerator)
+ configDocument["User"]["Elevation"] = json::String("Mod");
+ else
+ configDocument["User"]["Elevation"] = json::String("None");
+ }
+ else
+ {
+ configDocument["User"] = json::Null();
+ }
+ json::Writer::Write(configDocument, configFile);
+ configFile.close();
+ }
+}
+
+void Client::Shutdown()
+{
+ ClearThumbnailRequests();
+ http_done();
+
+ //Save config
+ WritePrefs();
+}
+
+Client::~Client()
+{
+}
+
+
+void Client::SetAuthUser(User user)
+{
+ authUser = user;
+ notifyAuthUserChanged();
+}
+
+User Client::GetAuthUser()
+{
+ return authUser;
+}
+
+RequestStatus Client::UploadSave(SaveInfo & save)
+{
+ lastError = "";
+ int gameDataLength;
+ char * gameData = NULL;
+ int dataStatus;
+ char * data;
+ int dataLength = 0;
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ if(authUser.ID)
+ {
+ if(!save.GetGameSave())
+ {
+ lastError = "Empty game save";
+ return RequestFailure;
+ }
+ save.SetID(0);
+
+ gameData = save.GetGameSave()->Serialise(gameDataLength);
+
+ if(!gameData)
+ {
+ lastError = "Cannot upload game save";
+ return RequestFailure;
+ }
+
+ char *saveName = new char[save.GetName().length() + 1];
+ std::strcpy ( saveName, save.GetName().c_str() );
+ char *saveDescription = new char[save.GetDescription().length() + 1];
+ std::strcpy ( saveDescription, save.GetDescription().c_str() );
+
+ char * postNames[] = { "Name", "Description", "Data:save.bin", "Publish", NULL };
+ char * postDatas[] = { saveName, saveDescription, gameData, (char *)(save.GetPublished()?"Public":"Private") };
+ int postLengths[] = { save.GetName().length(), save.GetDescription().length(), gameDataLength, save.GetPublished()?6:7 };
+ //std::cout << postNames[0] << " " << postDatas[0] << " " << postLengths[0] << std::endl;
+ data = http_multipart_post("http://" SERVER "/Save.api", postNames, postDatas, postLengths, (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+
+ delete[] saveDescription;
+ delete[] saveName;
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(data && dataStatus == 200)
+ {
+ if(strncmp((const char *)data, "OK", 2)!=0)
+ {
+ if(gameData) free(gameData);
+ lastError = std::string((const char *)data);
+ free(data);
+ return RequestFailure;
+ }
+ else
+ {
+ int tempID;
+ std::stringstream saveIDStream((char *)(data+3));
+ saveIDStream >> tempID;
+ if(!tempID)
+ {
+ lastError = "Server did not return Save ID";
+ return RequestFailure;
+ }
+ else
+ {
+ save.SetID(tempID);
+ }
+ }
+ free(data);
+ if(gameData) free(gameData);
+ return RequestOkay;
+ }
+ else if(data)
+ {
+ free(data);
+ }
+ if(gameData) free(gameData);
+ return RequestFailure;
+}
+
+SaveFile * Client::GetStamp(std::string stampID)
+{
+ std::string stampFile = std::string(STAMPS_DIR PATH_SEP + stampID + ".stm");
+ if(FileExists(stampFile))
+ {
+ SaveFile * file = new SaveFile(stampID);
+ try
+ {
+ GameSave * tempSave = new GameSave(ReadFile(stampFile));
+ file->SetGameSave(tempSave);
+ }
+ catch (ParseException & e)
+ {
+ std::cerr << "Client: Invalid stamp file, " << stampID << " " << std::string(e.what()) << std::endl;
+ }
+ return file;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void Client::DeleteStamp(std::string stampID)
+{
+ for (std::list<std::string>::iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator)
+ {
+ if((*iterator) == stampID)
+ {
+ std::stringstream stampFilename;
+ stampFilename << STAMPS_DIR;
+ stampFilename << PATH_SEP;
+ stampFilename << stampID;
+ stampFilename << ".stm";
+ remove(stampFilename.str().c_str());
+ stampIDs.erase(iterator);
+ return;
+ }
+ }
+
+ updateStamps();
+}
+
+std::string Client::AddStamp(GameSave * saveData)
+{
+ unsigned t=(unsigned)time(NULL);
+ if (lastStampTime!=t)
+ {
+ lastStampTime=t;
+ lastStampName=0;
+ }
+ else
+ lastStampName++;
+ std::stringstream saveID;
+ //sprintf(saveID, "%08x%02x", lastStampTime, lastStampName);
+ saveID
+ << std::setw(8) << std::setfill('0') << std::hex << lastStampTime
+ << std::setw(2) << std::setfill('0') << std::hex << lastStampName;
+
+ MakeDirectory(STAMPS_DIR);
+
+ int gameDataLength;
+ char * gameData = saveData->Serialise(gameDataLength);
+
+ std::ofstream stampStream;
+ stampStream.open(std::string(STAMPS_DIR PATH_SEP + saveID.str()+".stm").c_str(), std::ios::binary);
+ stampStream.write((const char *)gameData, gameDataLength);
+ stampStream.close();
+
+ delete[] gameData;
+
+ stampIDs.push_front(saveID.str());
+
+ updateStamps();
+
+ return saveID.str();
+}
+
+void Client::updateStamps()
+{
+ MakeDirectory(STAMPS_DIR);
+
+ std::ofstream stampsStream;
+ stampsStream.open(std::string(STAMPS_DIR PATH_SEP "stamps.def").c_str(), std::ios::binary);
+ for (std::list<std::string>::const_iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator)
+ {
+ stampsStream.write((*iterator).c_str(), 10);
+ }
+ stampsStream.write("\0", 1);
+ stampsStream.close();
+ return;
+}
+
+void Client::RescanStamps()
+{
+ DIR * directory;
+ struct dirent * entry;
+ directory = opendir("stamps");
+ if (directory != NULL)
+ {
+ stampIDs.clear();
+ while (entry = readdir(directory))
+ {
+ if(strncmp(entry->d_name, "..", 3) && strncmp(entry->d_name, ".", 2) && strstr(entry->d_name, ".stm") && strlen(entry->d_name) == 14)
+ {
+ char stampname[11];
+ strncpy(stampname, entry->d_name, 10);
+ stampIDs.push_front(stampname);
+ }
+ }
+ closedir(directory);
+ updateStamps();
+ }
+}
+
+int Client::GetStampsCount()
+{
+ return stampIDs.size();
+}
+
+std::vector<std::string> Client::GetStamps(int start, int count)
+{
+ if(start+count > stampIDs.size()) {
+ if(start > stampIDs.size())
+ return std::vector<std::string>();
+ count = stampIDs.size()-start;
+ }
+
+ std::vector<std::string> stampRange;
+ int index = 0;
+ for (std::list<std::string>::const_iterator iterator = stampIDs.begin(), end = stampIDs.end(); iterator != end; ++iterator, ++index) {
+ if(index>=start && index < start+count)
+ stampRange.push_back(*iterator);
+ }
+ return stampRange;
+}
+
+RequestStatus Client::ExecVote(int saveID, int direction)
+{
+ lastError = "";
+ int dataStatus;
+ char * data;
+ int dataLength = 0;
+ std::stringstream idStream;
+ idStream << saveID;
+
+ std::string saveIDText = format::NumberToString<int>(saveID);
+ std::string directionText = direction==1?"Up":"Down";
+
+ std::string userIDText = format::NumberToString<int>(authUser.ID);
+ if(authUser.ID)
+ {
+ char * postNames[] = { "ID", "Action", NULL };
+ char * postDatas[] = { (char*)(saveIDText.c_str()), (char*)(directionText.c_str()) };
+ int postLengths[] = { saveIDText.length(), directionText.length() };
+ //std::cout << postNames[0] << " " << postDatas[0] << " " << postLengths[0] << std::endl;
+ data = http_multipart_post("http://" SERVER "/Vote.api", postNames, postDatas, postLengths, (char *)(userIDText.c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(data && dataStatus == 200)
+ {
+ if(strncmp((const char *)data, "OK", 2)!=0)
+ {
+ lastError = std::string((const char *)data);
+ free(data);
+ return RequestFailure;
+ }
+ free(data);
+ return RequestOkay;
+ }
+ else if(data)
+ {
+ free(data);
+ }
+ lastError = http_ret_text(dataStatus);
+ return RequestFailure;
+}
+
+unsigned char * Client::GetSaveData(int saveID, int saveDate, int & dataLength)
+{
+ lastError = "";
+ int dataStatus;
+ unsigned char * data;
+ dataLength = 0;
+ std::stringstream urlStream;
+ if(saveDate)
+ {
+ urlStream << "http://" << STATICSERVER << "/" << saveID << "_" << saveDate << ".cps";
+ }
+ else
+ {
+ urlStream << "http://" << STATICSERVER << "/" << saveID << ".cps";
+ }
+
+ data = (unsigned char *)http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength);
+ if(data && dataStatus == 200)
+ {
+ return data;
+ }
+ else if(data)
+ {
+ free(data);
+ }
+ return NULL;
+}
+
+std::vector<unsigned char> Client::GetSaveData(int saveID, int saveDate)
+{
+ int dataSize;
+ unsigned char * data = GetSaveData(saveID, saveDate, dataSize);
+
+ std::vector<unsigned char> saveData(data, data+dataSize);
+
+ delete[] data;
+ return saveData;
+}
+
+LoginStatus Client::Login(std::string username, std::string password, User & user)
+{
+ lastError = "";
+ std::stringstream urlStream;
+ std::stringstream hashStream;
+ char passwordHash[33];
+ char totalHash[33];
+
+ user.ID = 0;
+ user.Username = "";
+ user.SessionID = "";
+ user.SessionKey = "";
+
+ //Doop
+ md5_ascii(passwordHash, (const unsigned char *)password.c_str(), password.length());
+ passwordHash[32] = 0;
+ hashStream << username << "-" << passwordHash;
+ md5_ascii(totalHash, (const unsigned char *)(hashStream.str().c_str()), hashStream.str().length());
+ totalHash[32] = 0;
+
+ char * data;
+ int dataStatus, dataLength;
+ char * postNames[] = { "Username", "Hash", NULL };
+ char * postDatas[] = { (char*)username.c_str(), totalHash };
+ int postLengths[] = { username.length(), 32 };
+ data = http_multipart_post("http://" SERVER "/Login.json", postNames, postDatas, postLengths, NULL, NULL, NULL, &dataStatus, &dataLength);
+ //data = http_auth_get("http://" SERVER "/Login.json", (char*)username.c_str(), (char*)password.c_str(), NULL, &dataStatus, &dataLength);
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+ json::Number tempStatus = objDocument["Status"];
+
+ free(data);
+ if(tempStatus.Value() == 1)
+ {
+ json::Number userIDTemp = objDocument["UserID"];
+ json::String sessionIDTemp = objDocument["SessionID"];
+ json::String sessionKeyTemp = objDocument["SessionKey"];
+ json::String userElevationTemp = objDocument["Elevation"];
+ user.Username = username;
+ user.ID = userIDTemp.Value();
+ user.SessionID = sessionIDTemp.Value();
+ user.SessionKey = sessionKeyTemp.Value();
+ std::string userElevation = userElevationTemp.Value();
+ if(userElevation == "Admin")
+ user.UserElevation = User::ElevationAdmin;
+ else if(userElevation == "Mod")
+ user.UserElevation = User::ElevationModerator;
+ else
+ user.UserElevation= User::ElevationNone;
+ return LoginOkay;
+ }
+ else
+ {
+ json::String tempError = objDocument["Error"];
+ lastError = tempError.Value();
+ return LoginError;
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Server responded with crap";
+ return LoginError;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ {
+ free(data);
+ }
+ return LoginError;
+}
+
+RequestStatus Client::DeleteSave(int saveID)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Delete.json?ID=" << saveID << "&Mode=Delete&Key=" << authUser.SessionKey;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ int status = ((json::Number)objDocument["Status"]).Value();
+
+ if(status!=1)
+ goto failure;
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ goto failure;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ goto failure;
+ }
+ if(data)
+ free(data);
+ return RequestOkay;
+failure:
+ if(data)
+ free(data);
+ return RequestFailure;
+}
+
+RequestStatus Client::AddComment(int saveID, std::string comment)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Comments.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+
+ char * postNames[] = { "Comment", NULL };
+ char * postDatas[] = { (char*)(comment.c_str()) };
+ int postLengths[] = { comment.length() };
+ data = http_multipart_post((char *)urlStream.str().c_str(), postNames, postDatas, postLengths, (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ int status = ((json::Number)objDocument["Status"]).Value();
+
+ if(status!=1)
+ {
+ lastError = ((json::Number)objDocument["Error"]).Value();
+ }
+
+ if(status!=1)
+ goto failure;
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ goto failure;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ goto failure;
+ }
+ if(data)
+ free(data);
+ return RequestOkay;
+failure:
+ if(data)
+ free(data);
+ return RequestFailure;
+}
+
+RequestStatus Client::FavouriteSave(int saveID, bool favourite)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
+ if(!favourite)
+ urlStream << "&Mode=Remove";
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ int status = ((json::Number)objDocument["Status"]).Value();
+
+ if(status!=1)
+ {
+ lastError = ((json::String)objDocument["Error"]).Value();
+ goto failure;
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ goto failure;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ goto failure;
+ }
+ if(data)
+ free(data);
+ return RequestOkay;
+failure:
+ if(data)
+ free(data);
+ return RequestFailure;
+}
+
+RequestStatus Client::ReportSave(int saveID, std::string message)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Report.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+
+ char * postNames[] = { "Reason", NULL };
+ char * postDatas[] = { (char*)(message.c_str()) };
+ int postLengths[] = { message.length() };
+ data = http_multipart_post((char *)urlStream.str().c_str(), postNames, postDatas, postLengths, (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ int status = ((json::Number)objDocument["Status"]).Value();
+
+ if(status!=1)
+ {
+ lastError = ((json::String)objDocument["Error"]).Value();
+ goto failure;
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ goto failure;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ goto failure;
+ }
+ if(data)
+ free(data);
+ return RequestOkay;
+failure:
+ if(data)
+ free(data);
+ return RequestFailure;
+}
+
+RequestStatus Client::UnpublishSave(int saveID)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Delete.json?ID=" << saveID << "&Mode=Unpublish&Key=" << authUser.SessionKey;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return RequestFailure;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ int status = ((json::Number)objDocument["Status"]).Value();
+
+ if(status!=1)
+ goto failure;
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ goto failure;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ goto failure;
+ }
+ if(data)
+ free(data);
+ return RequestOkay;
+failure:
+ if(data)
+ free(data);
+ return RequestFailure;
+}
+
+SaveInfo * Client::GetSave(int saveID, int saveDate)
+{
+ lastError = "";
+ std::stringstream urlStream;
+ urlStream << "http://" << SERVER << "/Browse/View.json?ID=" << saveID;
+ if(saveDate)
+ {
+ urlStream << "&Date=" << saveDate;
+ }
+ char * data;
+ int dataStatus, dataLength;
+ //Save(int _id, int _votesUp, int _votesDown, string _userName, string _name, string description_, string date_, bool published_):
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ data = http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength);
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ json::Number tempID = objDocument["ID"];
+ json::Number tempScoreUp = objDocument["ScoreUp"];
+ json::Number tempScoreDown = objDocument["ScoreDown"];
+ json::Number tempMyScore = objDocument["ScoreMine"];
+ json::String tempUsername = objDocument["Username"];
+ json::String tempName = objDocument["Name"];
+ json::String tempDescription = objDocument["Description"];
+ json::Number tempDate = objDocument["Date"];
+ json::Boolean tempPublished = objDocument["Published"];
+ json::Boolean tempFavourite = objDocument["Favourite"];
+ json::Number tempComments = objDocument["Comments"];
+ json::Number tempViews = objDocument["Views"];
+ json::Number tempVersion = objDocument["Version"];
+
+ json::Array tagsArray = objDocument["Tags"];
+ std::vector<std::string> tempTags;
+
+ for(int j = 0; j < tagsArray.Size(); j++)
+ {
+ json::String tempTag = tagsArray[j];
+ tempTags.push_back(tempTag.Value());
+ }
+
+ SaveInfo * tempSave = new SaveInfo(
+ tempID.Value(),
+ tempDate.Value(),
+ tempScoreUp.Value(),
+ tempScoreDown.Value(),
+ tempMyScore.Value(),
+ tempUsername.Value(),
+ tempName.Value(),
+ tempDescription.Value(),
+ tempPublished.Value(),
+ tempTags
+ );
+ tempSave->Comments = tempComments.Value();
+ tempSave->Favourite = tempFavourite.Value();
+ tempSave->Views = tempViews.Value();
+ tempSave->Version = tempVersion.Value();
+ return tempSave;
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ return NULL;
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ return NULL;
+}
+
+Thumbnail * Client::GetPreview(int saveID, int saveDate)
+{
+ std::stringstream urlStream;
+ urlStream << "http://" << STATICSERVER << "/" << saveID;
+ if(saveDate)
+ {
+ urlStream << "_" << saveDate;
+ }
+ urlStream << "_large.pti";
+ pixel * thumbData;
+ char * data;
+ int status, data_size, imgw, imgh;
+ data = http_simple_get((char *)urlStream.str().c_str(), &status, &data_size);
+ if (status == 200 && data)
+ {
+ thumbData = Graphics::ptif_unpack(data, data_size, &imgw, &imgh);
+ if(data)
+ {
+ free(data);
+ }
+ if(thumbData)
+ {
+ return new Thumbnail(saveID, saveDate, thumbData, ui::Point(imgw, imgh));
+ free(thumbData);
+ }
+ else
+ {
+ thumbData = (pixel *)malloc((128*128) * PIXELSIZE);
+ return new Thumbnail(saveID, saveDate, thumbData, ui::Point(128, 128));
+ free(thumbData);
+ }
+ }
+ else
+ {
+ if(data)
+ {
+ free(data);
+ }
+ }
+ return new Thumbnail(saveID, saveDate, (pixel *)malloc((128*128) * PIXELSIZE), ui::Point(128, 128));
+}
+
+std::vector<SaveComment*> * Client::GetComments(int saveID, int start, int count)
+{
+ lastError = "";
+ std::vector<SaveComment*> * commentArray = new std::vector<SaveComment*>();
+
+ std::stringstream urlStream;
+ char * data;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Comments.json?ID=" << saveID << "&Start=" << start << "&Count=" << count;
+ data = http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength);
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Array commentsArray;
+ json::Reader::Read(commentsArray, dataStream);
+
+ for(int j = 0; j < commentsArray.Size(); j++)
+ {
+ json::Number tempUserID = commentsArray[j]["UserID"];
+ json::String tempUsername = commentsArray[j]["FormattedUsername"];
+ json::String tempComment = commentsArray[j]["Text"];
+ commentArray->push_back(
+ new SaveComment(
+ tempUserID.Value(),
+ tempUsername.Value(),
+ tempComment.Value()
+ )
+ );
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ free(data);
+ return commentArray;
+}
+
+std::vector<std::pair<std::string, int> > * Client::GetTags(int start, int count, std::string query, int & resultCount)
+{
+ lastError = "";
+ resultCount = 0;
+ std::vector<std::pair<std::string, int> > * tagArray = new std::vector<std::pair<std::string, int> >();
+ std::stringstream urlStream;
+ char * data;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
+ if(query.length())
+ {
+ urlStream << "&Search_Query=";
+ if(query.length())
+ urlStream << URLEscape(query);
+ }
+
+ data = http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength);
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ json::Number tempCount = objDocument["TagTotal"];
+ resultCount = tempCount.Value();
+ json::Array tagsArray = objDocument["Tags"];
+ for(int j = 0; j < tagsArray.Size(); j++)
+ {
+ json::Number tagCount = tagsArray[j]["Count"];
+ json::String tag = tagsArray[j]["Tag"];
+ tagArray->push_back(std::pair<std::string, int>(tag.Value(), tagCount.Value()));
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ free(data);
+ return tagArray;
+}
+
+std::vector<SaveInfo*> * Client::SearchSaves(int start, int count, std::string query, std::string sort, std::string category, int & resultCount)
+{
+ lastError = "";
+ resultCount = 0;
+ std::vector<SaveInfo*> * saveArray = new std::vector<SaveInfo*>();
+ std::stringstream urlStream;
+ char * data;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
+ if(query.length() || sort.length())
+ {
+ urlStream << "&Search_Query=";
+ if(query.length())
+ urlStream << URLEscape(query);
+ if(sort == "date")
+ {
+ if(query.length())
+ urlStream << URLEscape(" ");
+ urlStream << URLEscape("sort:") << URLEscape(sort);
+ }
+ }
+ if(category.length())
+ {
+ urlStream << "&Category=" << URLEscape(category);
+ }
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ data = http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength);
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object objDocument;
+ json::Reader::Read(objDocument, dataStream);
+
+ json::Number tempCount = objDocument["Count"];
+ resultCount = tempCount.Value();
+ json::Array savesArray = objDocument["Saves"];
+ for(int j = 0; j < savesArray.Size(); j++)
+ {
+ json::Number tempID = savesArray[j]["ID"];
+ json::Number tempDate = savesArray[j]["Date"];
+ json::Number tempScoreUp = savesArray[j]["ScoreUp"];
+ json::Number tempScoreDown = savesArray[j]["ScoreDown"];
+ json::String tempUsername = savesArray[j]["Username"];
+ json::String tempName = savesArray[j]["Name"];
+ json::Number tempVersion = savesArray[j]["Version"];
+ json::Boolean tempPublished = savesArray[j]["Published"];
+ SaveInfo * tempSaveInfo = new SaveInfo(
+ tempID.Value(),
+ tempDate.Value(),
+ tempScoreUp.Value(),
+ tempScoreDown.Value(),
+ tempUsername.Value(),
+ tempName.Value()
+ );
+ tempSaveInfo->Version = tempVersion.Value();
+ tempSaveInfo->SetPublished(tempPublished);
+ saveArray->push_back(tempSaveInfo);
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ free(data);
+ return saveArray;
+}
+
+void Client::ClearThumbnailRequests()
+{
+ for(int i = 0; i < IMGCONNS; i++)
+ {
+ if(activeThumbRequests[i])
+ {
+ http_async_req_close(activeThumbRequests[i]);
+ activeThumbRequests[i] = NULL;
+ activeThumbRequestTimes[i] = 0;
+ activeThumbRequestCompleteTimes[i] = 0;
+ }
+ }
+}
+
+Thumbnail * Client::GetThumbnail(int saveID, int saveDate)
+{
+ std::stringstream urlStream;
+ std::stringstream idStream;
+ int i = 0, currentTime = time(NULL);
+ //Check active requests for any "forgotten" requests
+ for(i = 0; i < IMGCONNS; i++)
+ {
+ //If the request is active, and we've recieved a response
+ if(activeThumbRequests[i] && http_async_req_status(activeThumbRequests[i]))
+ {
+ //If we haven't already, mark the request as completed
+ if(!activeThumbRequestCompleteTimes[i])
+ {
+ activeThumbRequestCompleteTimes[i] = time(NULL);
+ }
+ else if(activeThumbRequestCompleteTimes[i] < (currentTime-2)) //Otherwise, if it completed more than 2 seconds ago, destroy it.
+ {
+ http_async_req_close(activeThumbRequests[i]);
+ activeThumbRequests[i] = NULL;
+ activeThumbRequestTimes[i] = 0;
+ activeThumbRequestCompleteTimes[i] = 0;
+ }
+ }
+ }
+ for(i = 0; i < THUMB_CACHE_SIZE; i++)
+ {
+ if(thumbnailCache[i] && thumbnailCache[i]->ID == saveID && thumbnailCache[i]->Datestamp == saveDate)
+ return thumbnailCache[i];
+ }
+ urlStream << "http://" << STATICSERVER << "/" << saveID;
+ if(saveDate)
+ {
+ urlStream << "_" << saveDate;
+ }
+ urlStream << "_small.pti";
+ idStream << saveID << ":" << saveDate;
+ std::string idString = idStream.str();
+ bool found = false;
+ for(i = 0; i < IMGCONNS; i++)
+ {
+ if(activeThumbRequests[i] && activeThumbRequestIDs[i] == idString)
+ {
+ found = true;
+ if(http_async_req_status(activeThumbRequests[i]))
+ {
+ pixel * thumbData;
+ char * data;
+ int status, data_size, imgw, imgh;
+ data = http_async_req_stop(activeThumbRequests[i], &status, &data_size);
+ free(activeThumbRequests[i]);
+ activeThumbRequests[i] = NULL;
+ if (status == 200 && data)
+ {
+ thumbData = Graphics::ptif_unpack(data, data_size, &imgw, &imgh);
+ if(data)
+ {
+ free(data);
+ }
+ thumbnailCacheNextID %= THUMB_CACHE_SIZE;
+ if(thumbnailCache[thumbnailCacheNextID])
+ {
+ delete thumbnailCache[thumbnailCacheNextID];
+ }
+ if(thumbData)
+ {
+ thumbnailCache[thumbnailCacheNextID] = new Thumbnail(saveID, saveDate, thumbData, ui::Point(imgw, imgh));
+ free(thumbData);
+ }
+ else
+ {
+ thumbData = (pixel *)malloc((128*128) * PIXELSIZE);
+ thumbnailCache[thumbnailCacheNextID] = new Thumbnail(saveID, saveDate, thumbData, ui::Point(128, 128));
+ free(thumbData);
+ }
+ return thumbnailCache[thumbnailCacheNextID++];
+ }
+ else
+ {
+ if(data)
+ {
+ free(data);
+ }
+ thumbnailCacheNextID %= THUMB_CACHE_SIZE;
+ if(thumbnailCache[thumbnailCacheNextID])
+ {
+ delete thumbnailCache[thumbnailCacheNextID];
+ }
+ thumbData = (pixel *)malloc((128*128) * PIXELSIZE);
+ thumbnailCache[thumbnailCacheNextID] = new Thumbnail(saveID, saveDate, thumbData, ui::Point(128, 128));
+ free(thumbData);
+ return thumbnailCache[thumbnailCacheNextID++];
+ }
+ }
+ }
+ }
+ if(!found)
+ {
+ for(i = 0; i < IMGCONNS; i++)
+ {
+ if(!activeThumbRequests[i])
+ {
+ activeThumbRequests[i] = http_async_req_start(NULL, (char *)urlStream.str().c_str(), NULL, 0, 1);
+ activeThumbRequestTimes[i] = currentTime;
+ activeThumbRequestCompleteTimes[i] = 0;
+ activeThumbRequestIDs[i] = idString;
+ return NULL;
+ }
+ }
+ }
+ //http_async_req_start(http, urlStream.str().c_str(), NULL, 0, 1);
+ return NULL;
+}
+
+std::vector<std::string> * Client::RemoveTag(int saveID, std::string tag)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/EditTag.json?Op=delete&ID=" << saveID << "&Tag=" << tag << "&Key=" << authUser.SessionKey;;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return NULL;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object responseObject;
+ json::Reader::Read(responseObject, dataStream);
+
+ json::Number status = responseObject["Status"];
+
+ if(status.Value()==0)
+ {
+ json::String error = responseObject["Error"];
+ lastError = error.Value();
+ }
+ else
+ {
+ json::Array tagsArray = responseObject["Tags"];
+
+ tags = new std::vector<std::string>();
+
+ for(int j = 0; j < tagsArray.Size(); j++)
+ {
+ json::String tempTag = tagsArray[j];
+ tags->push_back(tempTag.Value());
+ }
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ free(data);
+ return tags;
+}
+
+std::vector<std::string> * Client::AddTag(int saveID, std::string tag)
+{
+ lastError = "";
+ std::vector<std::string> * tags = NULL;
+ std::stringstream urlStream;
+ char * data = NULL;
+ int dataStatus, dataLength;
+ urlStream << "http://" << SERVER << "/Browse/EditTag.json?Op=add&ID=" << saveID << "&Tag=" << tag << "&Key=" << authUser.SessionKey;
+ if(authUser.ID)
+ {
+ std::stringstream userIDStream;
+ userIDStream << authUser.ID;
+ data = http_auth_get((char *)urlStream.str().c_str(), (char *)(userIDStream.str().c_str()), NULL, (char *)(authUser.SessionID.c_str()), &dataStatus, &dataLength);
+ }
+ else
+ {
+ lastError = "Not authenticated";
+ return NULL;
+ }
+ if(dataStatus == 200 && data)
+ {
+ try
+ {
+ std::istringstream dataStream(data);
+ json::Object responseObject;
+ json::Reader::Read(responseObject, dataStream);
+
+ json::Number status = responseObject["Status"];
+
+ if(status.Value()==0)
+ {
+ json::String error = responseObject["Error"];
+ lastError = error.Value();
+ }
+ else
+ {
+ json::Array tagsArray = responseObject["Tags"];
+
+ tags = new std::vector<std::string>();
+
+ for(int j = 0; j < tagsArray.Size(); j++)
+ {
+ json::String tempTag = tagsArray[j];
+ tags->push_back(tempTag.Value());
+ }
+ }
+ }
+ catch (json::Exception &e)
+ {
+ lastError = "Could not read response";
+ }
+ }
+ else
+ {
+ lastError = http_ret_text(dataStatus);
+ }
+ if(data)
+ free(data);
+ return tags;
+}
+
+std::vector<std::string> Client::explodePropertyString(std::string property)
+{
+ std::vector<std::string> stringArray;
+ std::string current = "";
+ for (std::string::iterator iter = property.begin(); iter != property.end(); ++iter) {
+ if (*iter == '.') {
+ if (current.length() > 0) {
+ stringArray.push_back(current);
+ current = "";
+ }
+ } else {
+ current += *iter;
+ }
+ }
+ if(current.length() > 0)
+ stringArray.push_back(current);
+ return stringArray;
+}
+
+std::string Client::GetPrefString(std::string property, std::string defaultValue)
+{
+ try
+ {
+ json::String value = GetPref(property);
+ return value.Value();
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return defaultValue;
+}
+
+double Client::GetPrefNumber(std::string property, double defaultValue)
+{
+ try
+ {
+ json::Number value = GetPref(property);
+ return value.Value();
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return defaultValue;
+}
+
+int Client::GetPrefInteger(std::string property, int defaultValue)
+{
+ try
+ {
+ std::stringstream defHexInt;
+ defHexInt << std::hex << defaultValue;
+
+ std::string hexString = GetPrefString(property, defHexInt.str());
+ int finalValue = defaultValue;
+
+ std::stringstream hexInt;
+ hexInt << hexString;
+
+ hexInt >> std::hex >> finalValue;
+
+ return finalValue;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ catch(std::exception & e)
+ {
+
+ }
+ return defaultValue;
+}
+
+unsigned int Client::GetPrefUInteger(std::string property, unsigned int defaultValue)
+{
+ try
+ {
+ std::stringstream defHexInt;
+ defHexInt << std::hex << defaultValue;
+
+ std::string hexString = GetPrefString(property, defHexInt.str());
+ unsigned int finalValue = defaultValue;
+
+ std::stringstream hexInt;
+ hexInt << hexString;
+
+ hexInt >> std::hex >> finalValue;
+
+ return finalValue;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ catch(std::exception & e)
+ {
+
+ }
+ return defaultValue;
+}
+
+std::vector<std::string> Client::GetPrefStringArray(std::string property)
+{
+ try
+ {
+ json::Array value = GetPref(property);
+ std::vector<std::string> strArray;
+ for(json::Array::iterator iter = value.Begin(); iter != value.End(); ++iter)
+ {
+ try
+ {
+ json::String cValue = *iter;
+ strArray.push_back(cValue.Value());
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ }
+ return strArray;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return std::vector<std::string>();
+}
+
+std::vector<double> Client::GetPrefNumberArray(std::string property)
+{
+ try
+ {
+ json::Array value = GetPref(property);
+ std::vector<double> strArray;
+ for(json::Array::iterator iter = value.Begin(); iter != value.End(); ++iter)
+ {
+ try
+ {
+ json::Number cValue = *iter;
+ strArray.push_back(cValue.Value());
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ }
+ return strArray;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return std::vector<double>();
+}
+
+std::vector<int> Client::GetPrefIntegerArray(std::string property)
+{
+ try
+ {
+ json::Array value = GetPref(property);
+ std::vector<int> intArray;
+ for(json::Array::iterator iter = value.Begin(); iter != value.End(); ++iter)
+ {
+ try
+ {
+ json::String cValue = *iter;
+ int finalValue = 0;
+
+ std::string hexString = cValue.Value();
+ std::stringstream hexInt;
+ hexInt << std::hex << hexString;
+ hexInt >> finalValue;
+
+ intArray.push_back(finalValue);
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ }
+ return intArray;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return std::vector<int>();
+}
+
+std::vector<unsigned int> Client::GetPrefUIntegerArray(std::string property)
+{
+ try
+ {
+ json::Array value = GetPref(property);
+ std::vector<unsigned int> intArray;
+ for(json::Array::iterator iter = value.Begin(); iter != value.End(); ++iter)
+ {
+ try
+ {
+ json::String cValue = *iter;
+ unsigned int finalValue = 0;
+
+ std::string hexString = cValue.Value();
+ std::stringstream hexInt;
+ hexInt << std::hex << hexString;
+ hexInt >> finalValue;
+
+ intArray.push_back(finalValue);
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ }
+ return intArray;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return std::vector<unsigned int>();
+}
+
+std::vector<bool> Client::GetPrefBoolArray(std::string property)
+{
+ try
+ {
+ json::Array value = GetPref(property);
+ std::vector<bool> strArray;
+ for(json::Array::iterator iter = value.Begin(); iter != value.End(); ++iter)
+ {
+ try
+ {
+ json::Boolean cValue = *iter;
+ strArray.push_back(cValue.Value());
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ }
+ return strArray;
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return std::vector<bool>();
+}
+
+bool Client::GetPrefBool(std::string property, bool defaultValue)
+{
+ try
+ {
+ json::Boolean value = GetPref(property);
+ return value.Value();
+ }
+ catch (json::Exception & e)
+ {
+
+ }
+ return defaultValue;
+}
+
+void Client::SetPref(std::string property, std::string value)
+{
+ json::UnknownElement stringValue = json::String(value);
+ SetPref(property, stringValue);
+}
+
+void Client::SetPref(std::string property, double value)
+{
+ json::UnknownElement numberValue = json::Number(value);
+ SetPref(property, numberValue);
+}
+
+void Client::SetPref(std::string property, int value)
+{
+ std::stringstream hexInt;
+ hexInt << std::hex << value;
+ json::UnknownElement intValue = json::String(hexInt.str());
+ SetPref(property, intValue);
+}
+
+void Client::SetPref(std::string property, unsigned int value)
+{
+ std::stringstream hexInt;
+ hexInt << std::hex << value;
+ json::UnknownElement intValue = json::String(hexInt.str());
+ SetPref(property, intValue);
+}
+
+void Client::SetPref(std::string property, std::vector<std::string> value)
+{
+ json::Array newArray;
+ for(std::vector<std::string>::iterator iter = value.begin(); iter != value.end(); ++iter)
+ {
+ newArray.Insert(json::String(*iter));
+ }
+ json::UnknownElement newArrayValue = newArray;
+ SetPref(property, newArrayValue);
+}
+
+void Client::SetPref(std::string property, std::vector<double> value)
+{
+ json::Array newArray;
+ for(std::vector<double>::iterator iter = value.begin(); iter != value.end(); ++iter)
+ {
+ newArray.Insert(json::Number(*iter));
+ }
+ json::UnknownElement newArrayValue = newArray;
+ SetPref(property, newArrayValue);
+}
+
+void Client::SetPref(std::string property, std::vector<bool> value)
+{
+ json::Array newArray;
+ for(std::vector<bool>::iterator iter = value.begin(); iter != value.end(); ++iter)
+ {
+ newArray.Insert(json::Boolean(*iter));
+ }
+ json::UnknownElement newArrayValue = newArray;
+ SetPref(property, newArrayValue);
+}
+
+void Client::SetPref(std::string property, std::vector<int> value)
+{
+ json::Array newArray;
+ for(std::vector<int>::iterator iter = value.begin(); iter != value.end(); ++iter)
+ {
+ std::stringstream hexInt;
+ hexInt << std::hex << *iter;
+
+ newArray.Insert(json::String(hexInt.str()));
+ }
+ json::UnknownElement newArrayValue = newArray;
+ SetPref(property, newArrayValue);
+}
+
+void Client::SetPref(std::string property, std::vector<unsigned int> value)
+{
+ json::Array newArray;
+ for(std::vector<unsigned int>::iterator iter = value.begin(); iter != value.end(); ++iter)
+ {
+ std::stringstream hexInt;
+ hexInt << std::hex << *iter;
+
+ newArray.Insert(json::String(hexInt.str()));
+ }
+ json::UnknownElement newArrayValue = newArray;
+ SetPref(property, newArrayValue);
+}
+
+void Client::SetPref(std::string property, bool value)
+{
+ json::UnknownElement boolValue = json::Boolean(value);
+ SetPref(property, boolValue);
+}
+
+json::UnknownElement Client::GetPref(std::string property)
+{
+ std::vector<std::string> pTokens = Client::explodePropertyString(property);
+ const json::UnknownElement & configDocumentCopy = configDocument;
+ json::UnknownElement currentRef = configDocumentCopy;
+ for(std::vector<std::string>::iterator iter = pTokens.begin(); iter != pTokens.end(); ++iter)
+ {
+ currentRef = ((const json::UnknownElement &)currentRef)[*iter];
+ }
+ return currentRef;
+}
+
+void Client::setPrefR(std::deque<std::string> tokens, json::UnknownElement & element, json::UnknownElement & value)
+{
+ if(tokens.size())
+ {
+ std::string token = tokens.front();
+ tokens.pop_front();
+ setPrefR(tokens, element[token], value);
+ }
+ else
+ element = value;
+}
+
+void Client::SetPref(std::string property, json::UnknownElement & value)
+{
+ std::vector<std::string> pTokens = Client::explodePropertyString(property);
+ std::deque<std::string> dTokens(pTokens.begin(), pTokens.end());
+ std::string token = dTokens.front();
+ dTokens.pop_front();
+ setPrefR(dTokens, configDocument[token], value);
+}
diff --git a/src/client/Client.h b/src/client/Client.h
new file mode 100644
index 0000000..642fd63
--- /dev/null
+++ b/src/client/Client.h
@@ -0,0 +1,176 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <queue>
+#include <vector>
+#include <list>
+
+#include "Config.h"
+#include "Singleton.h"
+
+#include "User.h"
+
+#include "cajun/elements.h"
+
+class Thumbnail;
+class SaveInfo;
+class SaveFile;
+class SaveComment;
+class GameSave;
+
+enum LoginStatus {
+ LoginOkay, LoginError
+};
+
+enum RequestStatus {
+ RequestOkay, RequestFailure
+};
+
+class UpdateInfo
+{
+public:
+ enum BuildType { Stable, Beta, Snapshot };
+ std::string File;
+ int Major;
+ int Minor;
+ int Build;
+ int Time;
+ BuildType Type;
+ UpdateInfo() : Major(0), Minor(0), Build(0), Time(0), File(""), Type(Stable) {}
+ UpdateInfo(int major, int minor, int build, std::string file, BuildType type) : Major(major), Minor(minor), Build(build), Time(0), File(file), Type(type) {}
+ UpdateInfo(int time, std::string file, BuildType type) : Major(0), Minor(0), Build(0), Time(time), File(file), Type(type) {}
+};
+
+class ThumbnailListener;
+class ClientListener;
+class Client: public Singleton<Client> {
+private:
+ std::string messageOfTheDay;
+
+ void * versionCheckRequest;
+ bool updateAvailable;
+ UpdateInfo updateInfo;
+
+
+ std::string lastError;
+
+ std::list<std::string> stampIDs;
+ int lastStampTime;
+ int lastStampName;
+
+ //Auth session
+ User authUser;
+
+ //Thumbnail retreival
+ int thumbnailCacheNextID;
+ Thumbnail * thumbnailCache[THUMB_CACHE_SIZE];
+ void * activeThumbRequests[IMGCONNS];
+ int activeThumbRequestTimes[IMGCONNS];
+ int activeThumbRequestCompleteTimes[IMGCONNS];
+ std::string activeThumbRequestIDs[IMGCONNS];
+ void updateStamps();
+ static std::vector<std::string> explodePropertyString(std::string property);
+ void notifyUpdateAvailable();
+ void notifyAuthUserChanged();
+ void notifyMessageOfTheDay();
+
+ //Config file handle
+ json::Object configDocument;
+public:
+
+ std::vector<ClientListener*> listeners;
+
+ UpdateInfo GetUpdateInfo();
+
+ Client();
+ ~Client();
+
+ std::vector<std::string> DirectorySearch(std::string directory, std::string search, std::vector<std::string> extensions);
+ std::vector<std::string> DirectorySearch(std::string directory, std::string search, std::string extension);
+
+ bool DoInstallation();
+
+ std::vector<unsigned char> ReadFile(std::string filename);
+
+ void SetMessageOfTheDay(std::string message);
+ std::string GetMessageOfTheDay();
+
+ void Initialise(std::string proxyString);
+ void SetProxy(std::string proxy);
+
+ int MakeDirectory(const char * dirname);
+ void WriteFile(std::vector<unsigned char> fileData, std::string filename);
+ void WriteFile(std::vector<char> fileData, std::string filename);
+ bool FileExists(std::string filename);
+
+ void AddListener(ClientListener * listener);
+ void RemoveListener(ClientListener * listener);
+
+ RequestStatus ExecVote(int saveID, int direction);
+ RequestStatus UploadSave(SaveInfo & save);
+
+ SaveFile * GetStamp(std::string stampID);
+ void DeleteStamp(std::string stampID);
+ std::string AddStamp(GameSave * saveData);
+ std::vector<std::string> GetStamps(int start, int count);
+ void RescanStamps();
+ int GetStampsCount();
+ SaveFile * GetFirstStamp();
+
+ RequestStatus AddComment(int saveID, std::string comment);
+
+ unsigned char * GetSaveData(int saveID, int saveDate, int & dataLength);
+ std::vector<unsigned char> GetSaveData(int saveID, int saveDate);
+ LoginStatus Login(std::string username, std::string password, User & user);
+ void ClearThumbnailRequests();
+ std::vector<SaveInfo*> * SearchSaves(int start, int count, std::string query, std::string sort, std::string category, int & resultCount);
+ std::vector<std::pair<std::string, int> > * GetTags(int start, int count, std::string query, int & resultCount);
+ std::vector<SaveComment*> * GetComments(int saveID, int start, int count);
+ Thumbnail * GetPreview(int saveID, int saveDate);
+ Thumbnail * GetThumbnail(int saveID, int saveDate);
+ SaveInfo * GetSave(int saveID, int saveDate);
+ RequestStatus DeleteSave(int saveID);
+ RequestStatus ReportSave(int saveID, std::string message);
+ RequestStatus UnpublishSave(int saveID);
+ RequestStatus FavouriteSave(int saveID, bool favourite);
+ void SetAuthUser(User user);
+ User GetAuthUser();
+ std::vector<std::string> * RemoveTag(int saveID, std::string tag); //TODO RequestStatus
+ std::vector<std::string> * AddTag(int saveID, std::string tag);
+ std::string GetLastError() {
+ return lastError;
+ }
+ void Tick();
+ void Shutdown();
+
+ //Force flushing preferences to file on disk.
+ void WritePrefs();
+
+ std::string GetPrefString(std::string property, std::string defaultValue);
+ double GetPrefNumber(std::string property, double defaultValue);
+ int GetPrefInteger(std::string property, int defaultValue);
+ unsigned int GetPrefUInteger(std::string property, unsigned int defaultValue);
+ std::vector<std::string> GetPrefStringArray(std::string property);
+ std::vector<double> GetPrefNumberArray(std::string property);
+ std::vector<int> GetPrefIntegerArray(std::string property);
+ std::vector<unsigned int> GetPrefUIntegerArray(std::string property);
+ std::vector<bool> GetPrefBoolArray(std::string property);
+ bool GetPrefBool(std::string property, bool defaultValue);
+
+ void SetPref(std::string property, std::string value);
+ void SetPref(std::string property, double value);
+ void SetPref(std::string property, int value);
+ void SetPref(std::string property, unsigned int value);
+ void SetPref(std::string property, std::vector<std::string> value);
+ void SetPref(std::string property, std::vector<double> value);
+ void SetPref(std::string property, std::vector<int> value);
+ void SetPref(std::string property, std::vector<unsigned int> value);
+ void SetPref(std::string property, std::vector<bool> value);
+ void SetPref(std::string property, bool value);
+
+ json::UnknownElement GetPref(std::string property);
+ void setPrefR(std::deque<std::string> tokens, json::UnknownElement & element, json::UnknownElement & value);
+ void SetPref(std::string property, json::UnknownElement & value);
+};
+
+#endif // CLIENT_H
diff --git a/src/client/ClientListener.h b/src/client/ClientListener.h
new file mode 100644
index 0000000..07e784c
--- /dev/null
+++ b/src/client/ClientListener.h
@@ -0,0 +1,24 @@
+/*
+ * ClientListener.h
+ *
+ * Created on: Jun 19, 2012
+ * Author: Simon
+ */
+
+#ifndef CLIENTLISTENER_H_
+#define CLIENTLISTENER_H_
+
+class Client;
+class ClientListener
+{
+public:
+ ClientListener() {}
+ virtual ~ClientListener() {}
+
+ virtual void NotifyUpdateAvailable(Client * sender) {}
+ virtual void NotifyAuthUserChanged(Client * sender) {}
+ virtual void NotifyMessageOfTheDay(Client * sender) {}
+};
+
+
+#endif /* CLIENTLISTENER_H_ */
diff --git a/src/client/GameSave.cpp b/src/client/GameSave.cpp
new file mode 100644
index 0000000..1751c54
--- /dev/null
+++ b/src/client/GameSave.cpp
@@ -0,0 +1,2092 @@
+#include <iostream>
+#include <sstream>
+#include <cmath>
+#include <vector>
+#include <bzlib.h>
+#include "Config.h"
+#include "bson/BSON.h"
+#include "GameSave.h"
+#include "simulation/SimulationData.h"
+#include "ElementClasses.h"
+extern "C"
+{
+ #include "hmap.h"
+}
+
+GameSave::GameSave(GameSave & save) :
+waterEEnabled(save.waterEEnabled),
+legacyEnable(save.legacyEnable),
+gravityEnable(save.gravityEnable),
+paused(save.paused),
+gravityMode(save.gravityMode),
+airMode(save.airMode),
+signs(save.signs),
+expanded(save.expanded),
+hasOriginalData(save.hasOriginalData),
+originalData(save.originalData),
+palette(save.palette)
+{
+ blockMap = NULL;
+ blockMapPtr = NULL;
+ fanVelX = NULL;
+ fanVelXPtr = NULL;
+ fanVelY = NULL;
+ fanVelYPtr = NULL;
+ particles = NULL;
+ if(save.expanded)
+ {
+ setSize(save.blockWidth, save.blockHeight);
+
+ std::copy(save.particles, save.particles+NPART, particles);
+ std::copy(save.blockMapPtr, save.blockMapPtr+(blockHeight*blockWidth), blockMapPtr);
+ std::copy(save.fanVelXPtr, save.fanVelXPtr+(blockHeight*blockWidth), fanVelXPtr);
+ std::copy(save.fanVelYPtr, save.fanVelYPtr+(blockHeight*blockWidth), fanVelYPtr);
+ }
+ else
+ {
+ blockWidth = save.blockWidth;
+ blockHeight = save.blockHeight;
+ }
+ particlesCount = save.particlesCount;
+}
+
+GameSave::GameSave(int width, int height)
+{
+ blockMap = NULL;
+ blockMapPtr = NULL;
+ fanVelX = NULL;
+ fanVelXPtr = NULL;
+ fanVelY = NULL;
+ fanVelYPtr = NULL;
+ particles = NULL;
+
+ hasOriginalData = false;
+ expanded = true;
+ setSize(width, height);
+}
+
+GameSave::GameSave(std::vector<char> data)
+{
+ blockWidth = 0;
+ blockHeight = 0;
+
+ blockMap = NULL;
+ blockMapPtr = NULL;
+ fanVelX = NULL;
+ fanVelXPtr = NULL;
+ fanVelY = NULL;
+ fanVelYPtr = NULL;
+ particles = NULL;
+
+ expanded = false;
+ hasOriginalData = true;
+ originalData = data;
+#ifdef DEBUG
+ std::cout << "Creating Collapsed save from data" << std::endl;
+#endif
+ try
+ {
+ Expand();
+ }
+ catch(ParseException & e)
+ {
+ std::cout << e.what() << std::endl;
+ dealloc(); //Free any allocated memory
+ throw;
+ }
+ Collapse();
+}
+
+GameSave::GameSave(std::vector<unsigned char> data)
+{
+ blockWidth = 0;
+ blockHeight = 0;
+
+ blockMap = NULL;
+ blockMapPtr = NULL;
+ fanVelX = NULL;
+ fanVelXPtr = NULL;
+ fanVelY = NULL;
+ fanVelYPtr = NULL;
+ particles = NULL;
+
+ expanded = false;
+ hasOriginalData = true;
+ originalData = std::vector<char>(data.begin(), data.end());
+#ifdef DEBUG
+ std::cout << "Creating Collapsed save from data" << std::endl;
+#endif
+ try
+ {
+ Expand();
+ }
+ catch(ParseException & e)
+ {
+ std::cout << e.what() << std::endl;
+ dealloc(); //Free any allocated memory
+ throw;
+ }
+ Collapse();
+}
+
+GameSave::GameSave(char * data, int dataSize)
+{
+ blockWidth = 0;
+ blockHeight = 0;
+
+ blockMap = NULL;
+ blockMapPtr = NULL;
+ fanVelX = NULL;
+ fanVelXPtr = NULL;
+ fanVelY = NULL;
+ fanVelYPtr = NULL;
+ particles = NULL;
+
+ expanded = false;
+ hasOriginalData = true;
+ originalData = std::vector<char>(data, data+dataSize);
+#ifdef DEBUG
+ std::cout << "Creating Expanded save from data" << std::endl;
+#endif
+ try
+ {
+ Expand();
+ }
+ catch(ParseException & e)
+ {
+ std::cout << e.what() << std::endl;
+ dealloc(); //Free any allocated memory
+ throw;
+ }
+ //Collapse();
+}
+
+bool GameSave::Collapsed()
+{
+ return !expanded;
+}
+
+void GameSave::Expand()
+{
+ if(hasOriginalData && !expanded)
+ {
+ expanded = true;
+ read(&originalData[0], originalData.size());
+ }
+}
+
+void GameSave::Collapse()
+{
+ if(expanded && hasOriginalData)
+ {
+ expanded = false;
+ if(particles)
+ {
+ delete[] particles;
+ particles = NULL;
+ }
+ if(blockMap)
+ {
+ delete[] blockMap;
+ blockMap = NULL;
+ }
+ if(blockMapPtr)
+ {
+ delete[] blockMapPtr;
+ blockMapPtr = NULL;
+ }
+ if(fanVelX)
+ {
+ delete[] fanVelX;
+ fanVelX = NULL;
+ }
+ if(fanVelXPtr)
+ {
+ delete[] fanVelXPtr;
+ fanVelXPtr = NULL;
+ }
+ if(fanVelY)
+ {
+ delete[] fanVelY;
+ fanVelY = NULL;
+ }
+ if(fanVelYPtr)
+ {
+ delete[] fanVelYPtr;
+ fanVelYPtr = NULL;
+ }
+ }
+}
+
+void GameSave::read(char * data, int dataSize)
+{
+ if(dataSize > 0)
+ {
+ if(data[0] == 0x50 || data[0] == 0x66)
+ {
+#ifdef DEBUG
+ std::cout << "Reading PSv..." << std::endl;
+#endif
+ readPSv(data, dataSize);
+ }
+ else if(data[0] == 'O')
+ {
+#ifdef DEBUG
+ std::cout << "Reading OPS..." << std::endl;
+#endif
+ readOPS(data, dataSize);
+ }
+ else
+ {
+ std::cerr << "Got Magic number '" << data[0] << "'" << std::endl;
+ throw ParseException(ParseException::Corrupt, "Invalid save format");
+ }
+ }
+ else
+ {
+ throw ParseException(ParseException::Corrupt, "No data");
+ }
+}
+
+void GameSave::setSize(int newWidth, int newHeight)
+{
+ this->blockWidth = newWidth;
+ this->blockHeight = newHeight;
+
+ particlesCount = 0;
+ particles = new Particle[NPART];
+
+ blockMapPtr = new unsigned char[blockHeight*blockWidth];
+ std::fill(blockMapPtr, blockMapPtr+(blockHeight*blockWidth), 0);
+ fanVelXPtr = new float[(blockHeight)*(blockWidth)];
+ std::fill(fanVelXPtr, fanVelXPtr+((blockHeight)*(blockWidth)), 0);
+ fanVelYPtr = new float[(blockHeight)*(blockWidth)];
+ std::fill(fanVelYPtr, fanVelYPtr+((blockHeight)*(blockWidth)), 0);
+
+ blockMap = new unsigned char*[blockHeight];
+ for(int y = 0; y < blockHeight; y++)
+ blockMap[y] = &blockMapPtr[y*blockWidth];
+ fanVelX = new float*[blockHeight];
+ for(int y = 0; y < blockHeight; y++)
+ fanVelX[y] = &fanVelXPtr[y*(blockWidth)];
+ fanVelY = new float*[blockHeight];
+ for(int y = 0; y < blockHeight; y++)
+ fanVelY[y] = &fanVelYPtr[y*blockWidth];
+}
+
+std::vector<char> GameSave::Serialise()
+{
+ int dataSize;
+ char * data = Serialise(dataSize);
+ std::vector<char> dataVect(data, data+dataSize);
+ delete data;
+ return dataVect;
+}
+
+char * GameSave::Serialise(int & dataSize)
+{
+ return serialiseOPS(dataSize);
+}
+
+void GameSave::Transform(matrix2d transform, vector2d translate)
+{
+ if(Collapsed())
+ Expand();
+ int i, x, y, nx, ny, width = blockWidth*CELL, height = blockHeight*CELL, newWidth, newHeight, newBlockWidth, newBlockHeight;
+ vector2d pos, tmp, ctl, cbr, vel;
+ vector2d cornerso[4];
+ // undo any translation caused by rotation
+ cornerso[0] = v2d_new(0,0);
+ cornerso[1] = v2d_new(width-1,0);
+ cornerso[2] = v2d_new(0,height-1);
+ cornerso[3] = v2d_new(width-1,height-1);
+ for (i=0; i<4; i++)
+ {
+ tmp = m2d_multiply_v2d(transform,cornerso[i]);
+ if (i==0) ctl = cbr = tmp; // top left, bottom right corner
+ if (tmp.x<ctl.x) ctl.x = tmp.x;
+ if (tmp.y<ctl.y) ctl.y = tmp.y;
+ if (tmp.x>cbr.x) cbr.x = tmp.x;
+ if (tmp.y>cbr.y) cbr.y = tmp.y;
+ }
+ // casting as int doesn't quite do what we want with negative numbers, so use floor()
+ tmp = v2d_new(floor(ctl.x+0.5f),floor(ctl.y+0.5f));
+ translate = v2d_sub(translate,tmp);
+ newWidth = floor(cbr.x+0.5f)-floor(ctl.x+0.5f)+1;
+ newHeight = floor(cbr.y+0.5f)-floor(ctl.y+0.5f)+1;
+ if (newWidth>XRES) newWidth = XRES;
+ if (newHeight>YRES) newHeight = YRES;
+ newBlockWidth = newWidth/CELL;
+ newBlockHeight = newHeight/CELL;
+
+ unsigned char ** blockMapNew;
+ float ** fanVelXNew;
+ float ** fanVelYNew;
+
+ float * fanVelXPtrNew;
+ float * fanVelYPtrNew;
+ unsigned char * blockMapPtrNew;
+
+ blockMapPtrNew = new unsigned char[newBlockHeight*newBlockWidth];
+ std::fill(blockMapPtrNew, blockMapPtrNew+(newBlockHeight*newBlockWidth), 0);
+ fanVelXPtrNew = new float[newBlockHeight*newBlockWidth];
+ std::fill(fanVelXPtrNew, fanVelXPtrNew+(newBlockHeight*newBlockWidth), 0);
+ fanVelYPtrNew = new float[(newBlockHeight)*(newBlockWidth)];
+ std::fill(fanVelYPtrNew, fanVelYPtrNew+(newBlockHeight*newBlockWidth), 0);
+
+ blockMapNew = new unsigned char*[newBlockHeight];
+ for(int y = 0; y < newBlockHeight; y++)
+ blockMapNew[y] = &blockMapPtrNew[y*newBlockWidth];
+ fanVelXNew = new float*[newBlockHeight];
+ for(int y = 0; y < newBlockHeight; y++)
+ fanVelXNew[y] = &fanVelXPtrNew[y*newBlockWidth];
+ fanVelYNew = new float*[newBlockHeight];
+ for(int y = 0; y < newBlockHeight; y++)
+ fanVelYNew[y] = &fanVelYPtrNew[y*newBlockWidth];
+
+ // rotate and translate signs, parts, walls
+ for (i=0; i < signs.size(); i++)
+ {
+ pos = v2d_new(signs[i].x, signs[i].y);
+ pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
+ nx = floor(pos.x+0.5f);
+ ny = floor(pos.y+0.5f);
+ if (nx<0 || nx>=newWidth || ny<0 || ny>=newHeight)
+ {
+ signs[i].text[0] = 0;
+ continue;
+ }
+ signs[i].x = nx;
+ signs[i].y = ny;
+ }
+ for (i=0; i<NPART; i++)
+ {
+ if (!particles[i].type) continue;
+ pos = v2d_new(particles[i].x, particles[i].y);
+ pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
+ nx = floor(pos.x+0.5f);
+ ny = floor(pos.y+0.5f);
+ if (nx<0 || nx>=newWidth || ny<0 || ny>=newHeight)
+ {
+ particles[i].type = PT_NONE;
+ continue;
+ }
+ particles[i].x = nx;
+ particles[i].y = ny;
+ vel = v2d_new(particles[i].vx, particles[i].vy);
+ vel = m2d_multiply_v2d(transform, vel);
+ particles[i].vx = vel.x;
+ particles[i].vy = vel.y;
+ }
+ for (y=0; y<blockHeight; y++)
+ for (x=0; x<blockWidth; x++)
+ {
+ pos = v2d_new(x*CELL+CELL*0.4f, y*CELL+CELL*0.4f);
+ pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
+ nx = pos.x/CELL;
+ ny = pos.y/CELL;
+ if (nx<0 || nx>=newBlockWidth || ny<0 || ny>=newBlockHeight)
+ continue;
+ if (blockMap[y][x])
+ {
+ blockMapNew[ny][nx] = blockMap[y][x];
+ if (blockMap[y][x]==WL_FAN)
+ {
+ vel = v2d_new(fanVelX[y][x], fanVelY[y][x]);
+ vel = m2d_multiply_v2d(transform, vel);
+ fanVelXNew[ny][nx] = vel.x;
+ fanVelYNew[ny][nx] = vel.y;
+ }
+ }
+ }
+ //ndata = build_save(size,0,0,nw,nh,blockMapNew,vxn,vyn,pvn,fanVelXNew,fanVelYNew,signst,partst);
+ blockWidth = newBlockWidth;
+ blockHeight = newBlockHeight;
+
+ delete blockMap;
+ delete fanVelX;
+ delete fanVelY;
+
+ delete blockMapPtr;
+ delete fanVelXPtr;
+ delete fanVelYPtr;
+
+ blockMap = blockMapNew;
+ fanVelX = fanVelXNew;
+ fanVelY = fanVelYNew;
+
+ blockMapPtr = (unsigned char*)blockMapPtrNew;
+ fanVelXPtr = (float*)fanVelXPtrNew;
+ fanVelYPtr = (float*)fanVelYPtrNew;
+}
+
+void GameSave::readOPS(char * data, int dataLength)
+{
+ unsigned char * inputData = (unsigned char*)data, *bsonData = NULL, *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL;
+ unsigned int inputDataLen = dataLength, bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen;
+ unsigned partsCount = 0, *partsSimIndex = NULL;
+ int i, freeIndicesCount, x, y, j;
+ int *freeIndices = NULL;
+ int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH;
+ int savedVersion = inputData[4];
+ bson b;
+ bson_iterator iter;
+
+ //Block sizes
+ blockX = 0;
+ blockY = 0;
+ blockW = inputData[6];
+ blockH = inputData[7];
+
+ //Full size, normalised
+ fullX = blockX*CELL;
+ fullY = blockY*CELL;
+ fullW = blockW*CELL;
+ fullH = blockH*CELL;
+
+ //From newer version
+ if(savedVersion > SAVE_VERSION)
+ throw ParseException(ParseException::WrongVersion, "Save from newer version");
+
+ //Incompatible cell size
+ if(inputData[5] > CELL)
+ throw ParseException(ParseException::InvalidDimensions, "Incorrect CELL size");
+
+ //Too large/off screen
+ if(blockX+blockW > XRES/CELL || blockY+blockH > YRES/CELL)
+ throw ParseException(ParseException::InvalidDimensions, "Save too large");
+
+ setSize(blockW, blockH);
+
+ bsonDataLen = ((unsigned)inputData[8]);
+ bsonDataLen |= ((unsigned)inputData[9]) << 8;
+ bsonDataLen |= ((unsigned)inputData[10]) << 16;
+ bsonDataLen |= ((unsigned)inputData[11]) << 24;
+
+ bsonData = (unsigned char*)malloc(bsonDataLen+1);
+ if(!bsonData)
+ throw ParseException(ParseException::InternalError, "Unable to allocate memory");
+
+ //Make sure bsonData is null terminated, since all string functions need null terminated strings
+ //(bson_iterator_key returns a pointer into bsonData, which is then used with strcmp)
+ bsonData[bsonDataLen] = 0;
+
+ if (BZ2_bzBuffToBuffDecompress((char*)bsonData, &bsonDataLen, (char*)(inputData+12), inputDataLen-12, 0, 0))
+ throw ParseException(ParseException::Corrupt, "Unable to decompress");
+
+ bson_init_data(&b, (char*)bsonData);
+ bson_iterator_init(&iter, &b);
+
+ std::vector<sign> tempSigns;
+
+ while(bson_iterator_next(&iter))
+ {
+ if(strcmp(bson_iterator_key(&iter), "signs")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_ARRAY)
+ {
+ bson_iterator subiter;
+ bson_iterator_subiterator(&iter, &subiter);
+ while(bson_iterator_next(&subiter))
+ {
+ if(strcmp(bson_iterator_key(&subiter), "sign")==0)
+ {
+ if(bson_iterator_type(&subiter)==BSON_OBJECT)
+ {
+ bson_iterator signiter;
+ bson_iterator_subiterator(&subiter, &signiter);
+
+ sign tempSign("", 0, 0, sign::Left);
+ while(bson_iterator_next(&signiter))
+ {
+ if(strcmp(bson_iterator_key(&signiter), "text")==0 && bson_iterator_type(&signiter)==BSON_STRING)
+ {
+ char tempString[256];
+ strncpy(tempString, bson_iterator_string(&signiter), 255);
+ tempString[255] = 0;
+ clean_text((char*)tempSign.text.c_str(), 158-14);
+
+ tempSign.text = tempString;
+ }
+ else if(strcmp(bson_iterator_key(&signiter), "justification")==0 && bson_iterator_type(&signiter)==BSON_INT)
+ {
+ tempSign.ju = (sign::Justification)bson_iterator_int(&signiter);
+ }
+ else if(strcmp(bson_iterator_key(&signiter), "x")==0 && bson_iterator_type(&signiter)==BSON_INT)
+ {
+ tempSign.x = bson_iterator_int(&signiter)+fullX;
+ }
+ else if(strcmp(bson_iterator_key(&signiter), "y")==0 && bson_iterator_type(&signiter)==BSON_INT)
+ {
+ tempSign.y = bson_iterator_int(&signiter)+fullY;
+ }
+ else
+ {
+ fprintf(stderr, "Unknown sign property %s\n", bson_iterator_key(&signiter));
+ }
+ }
+ tempSigns.push_back(tempSign);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&subiter));
+ }
+ }
+ }
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "parts")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (partsDataLen = bson_iterator_bin_len(&iter)) > 0)
+ {
+ partsData = (unsigned char*)bson_iterator_bin_data(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Invalid datatype of particle data: %d[%d] %d[%d] %d[%d]\n", bson_iterator_type(&iter), bson_iterator_type(&iter)==BSON_BINDATA, (unsigned char)bson_iterator_bin_type(&iter), ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER, bson_iterator_bin_len(&iter), bson_iterator_bin_len(&iter)>0);
+ }
+ }
+ if(strcmp(bson_iterator_key(&iter), "partsPos")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (partsPosDataLen = bson_iterator_bin_len(&iter)) > 0)
+ {
+ partsPosData = (unsigned char*)bson_iterator_bin_data(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Invalid datatype of particle position data: %d[%d] %d[%d] %d[%d]\n", bson_iterator_type(&iter), bson_iterator_type(&iter)==BSON_BINDATA, (unsigned char)bson_iterator_bin_type(&iter), ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER, bson_iterator_bin_len(&iter), bson_iterator_bin_len(&iter)>0);
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "wallMap")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (wallDataLen = bson_iterator_bin_len(&iter)) > 0)
+ {
+ wallData = (unsigned char*)bson_iterator_bin_data(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Invalid datatype of wall data: %d[%d] %d[%d] %d[%d]\n", bson_iterator_type(&iter), bson_iterator_type(&iter)==BSON_BINDATA, (unsigned char)bson_iterator_bin_type(&iter), ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER, bson_iterator_bin_len(&iter), bson_iterator_bin_len(&iter)>0);
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "fanMap")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (fanDataLen = bson_iterator_bin_len(&iter)) > 0)
+ {
+ fanData = (unsigned char*)bson_iterator_bin_data(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Invalid datatype of fan data: %d[%d] %d[%d] %d[%d]\n", bson_iterator_type(&iter), bson_iterator_type(&iter)==BSON_BINDATA, (unsigned char)bson_iterator_bin_type(&iter), ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER, bson_iterator_bin_len(&iter), bson_iterator_bin_len(&iter)>0);
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "soapLinks")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (soapLinkDataLen = bson_iterator_bin_len(&iter)) > 0)
+ {
+ soapLinkData = (unsigned char *)bson_iterator_bin_data(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Invalid datatype of soap data: %d[%d] %d[%d] %d[%d]\n", bson_iterator_type(&iter), bson_iterator_type(&iter)==BSON_BINDATA, (unsigned char)bson_iterator_bin_type(&iter), ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER, bson_iterator_bin_len(&iter), bson_iterator_bin_len(&iter)>0);
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "legacyEnable")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BOOL)
+ {
+ legacyEnable = bson_iterator_bool(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "gravityEnable")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BOOL)
+ {
+ gravityEnable = bson_iterator_bool(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "waterEEnabled")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BOOL)
+ {
+ waterEEnabled = bson_iterator_bool(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "paused")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_BOOL)
+ {
+ paused = bson_iterator_bool(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "gravityMode")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_INT)
+ {
+ gravityMode = bson_iterator_int(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "airMode")==0)
+ {
+ if(bson_iterator_type(&iter)==BSON_INT)
+ {
+ airMode = bson_iterator_int(&iter);
+ }
+ else
+ {
+ fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
+ }
+ }
+ else if(strcmp(bson_iterator_key(&iter), "palette")==0)
+ {
+ palette.clear();
+ if(bson_iterator_type(&iter)==BSON_ARRAY)
+ {
+ bson_iterator subiter;
+ bson_iterator_subiterator(&iter, &subiter);
+ while(bson_iterator_next(&subiter))
+ {
+ if(bson_iterator_type(&subiter)==BSON_INT)
+ {
+ std::string id = std::string(bson_iterator_key(&subiter));
+ int num = bson_iterator_int(&subiter);
+ palette.push_back(PaletteItem(id, num));
+ }
+ }
+ }
+ }
+ }
+
+ //Read wall and fan data
+ if(wallData)
+ {
+ j = 0;
+ if(blockW * blockH > wallDataLen)
+ {
+ fprintf(stderr, "Not enough wall data\n");
+ goto fail;
+ }
+ for(x = 0; x < blockW; x++)
+ {
+ for(y = 0; y < blockH; y++)
+ {
+ if (wallData[y*blockW+x])
+ blockMap[blockY+y][blockX+x] = wallData[y*blockW+x];
+
+ if (blockMap[y][x]==O_WL_WALLELEC)
+ blockMap[y][x]=WL_WALLELEC;
+ if (blockMap[y][x]==O_WL_EWALL)
+ blockMap[y][x]=WL_EWALL;
+ if (blockMap[y][x]==O_WL_DETECT)
+ blockMap[y][x]=WL_DETECT;
+ if (blockMap[y][x]==O_WL_STREAM)
+ blockMap[y][x]=WL_STREAM;
+ if (blockMap[y][x]==O_WL_FAN||blockMap[y][x]==O_WL_FANHELPER)
+ blockMap[y][x]=WL_FAN;
+ if (blockMap[y][x]==O_WL_ALLOWLIQUID)
+ blockMap[y][x]=WL_ALLOWLIQUID;
+ if (blockMap[y][x]==O_WL_DESTROYALL)
+ blockMap[y][x]=WL_DESTROYALL;
+ if (blockMap[y][x]==O_WL_ERASE)
+ blockMap[y][x]=WL_ERASE;
+ if (blockMap[y][x]==O_WL_WALL)
+ blockMap[y][x]=WL_WALL;
+ if (blockMap[y][x]==O_WL_ALLOWAIR)
+ blockMap[y][x]=WL_ALLOWAIR;
+ if (blockMap[y][x]==O_WL_ALLOWSOLID)
+ blockMap[y][x]=WL_ALLOWSOLID;
+ if (blockMap[y][x]==O_WL_ALLOWALLELEC)
+ blockMap[y][x]=WL_ALLOWALLELEC;
+ if (blockMap[y][x]==O_WL_EHOLE)
+ blockMap[y][x]=WL_EHOLE;
+ if (blockMap[y][x]==O_WL_ALLOWGAS)
+ blockMap[y][x]=WL_ALLOWGAS;
+ if (blockMap[y][x]==O_WL_GRAV)
+ blockMap[y][x]=WL_GRAV;
+ if (blockMap[y][x]==O_WL_ALLOWENERGY)
+ blockMap[y][x]=WL_ALLOWENERGY;
+
+ if (blockMap[y][x] == WL_FAN && fanData)
+ {
+ if(j+1 >= fanDataLen)
+ {
+ fprintf(stderr, "Not enough fan data\n");
+ }
+ fanVelX[blockY+y][blockX+x] = (fanData[j++]-127.0f)/64.0f;
+ fanVelY[blockY+y][blockX+x] = (fanData[j++]-127.0f)/64.0f;
+ }
+ }
+ }
+ }
+
+ //Read particle data
+ if(partsData && partsPosData)
+ {
+ int newIndex = 0, fieldDescriptor, tempTemp;
+ int posCount, posTotal, partsPosDataIndex = 0;
+ int saved_x, saved_y;
+ if(fullW * fullH * 3 > partsPosDataLen)
+ {
+ fprintf(stderr, "Not enough particle position data\n");
+ goto fail;
+ }
+
+ partsSimIndex = (unsigned int*)calloc(NPART, sizeof(unsigned));
+ partsCount = 0;
+
+ i = 0;
+ newIndex = 0;
+ for (saved_y=0; saved_y<fullH; saved_y++)
+ {
+ for (saved_x=0; saved_x<fullW; saved_x++)
+ {
+ //Read total number of particles at this position
+ posTotal = 0;
+ posTotal |= partsPosData[partsPosDataIndex++]<<16;
+ posTotal |= partsPosData[partsPosDataIndex++]<<8;
+ posTotal |= partsPosData[partsPosDataIndex++];
+ //Put the next posTotal particles at this position
+ for (posCount=0; posCount<posTotal; posCount++)
+ {
+ particlesCount = newIndex+1;
+ if(newIndex>=NPART)
+ {
+ goto fail;
+ }
+
+ //i+3 because we have 4 bytes of required fields (type (1), descriptor (2), temp (1))
+ if (i+3 >= partsDataLen)
+ goto fail;
+ x = saved_x + fullX;
+ y = saved_y + fullY;
+ fieldDescriptor = partsData[i+1];
+ fieldDescriptor |= partsData[i+2] << 8;
+ if(x >= fullW || x < 0 || y >= fullH || y < 0)
+ {
+ fprintf(stderr, "Out of range [%d]: %d %d, [%d, %d], [%d, %d]\n", i, x, y, (unsigned)partsData[i+1], (unsigned)partsData[i+2], (unsigned)partsData[i+3], (unsigned)partsData[i+4]);
+ goto fail;
+ }
+ if(partsData[i] >= PT_NUM)
+ partsData[i] = PT_DMND; //Replace all invalid elements with diamond
+
+ if(newIndex < 0 || newIndex >= NPART)
+ goto fail;
+
+ //Store partsptr index+1 for this saved particle index (0 means not loaded)
+ partsSimIndex[partsCount++] = newIndex+1;
+
+ //Clear the particle, ready for our new properties
+ memset(&(particles[newIndex]), 0, sizeof(Particle));
+
+ //Required fields
+ particles[newIndex].type = partsData[i];
+ particles[newIndex].x = x;
+ particles[newIndex].y = y;
+ i+=3;
+
+ //Read temp
+ if(fieldDescriptor & 0x01)
+ {
+ //Full 16bit int
+ tempTemp = partsData[i++];
+ tempTemp |= (((unsigned)partsData[i++]) << 8);
+ particles[newIndex].temp = tempTemp;
+ }
+ else
+ {
+ //1 Byte room temp offset
+ tempTemp = (char)partsData[i++];
+ particles[newIndex].temp = tempTemp+294.15f;
+ }
+
+ //Read life
+ if(fieldDescriptor & 0x02)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].life = partsData[i++];
+ //Read 2nd byte
+ if(fieldDescriptor & 0x04)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].life |= (((unsigned)partsData[i++]) << 8);
+ }
+ }
+
+ //Read tmp
+ if(fieldDescriptor & 0x08)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].tmp = partsData[i++];
+ //Read 2nd byte
+ if(fieldDescriptor & 0x10)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 8);
+ //Read 3rd and 4th bytes
+ if(fieldDescriptor & 0x1000)
+ {
+ if(i+1 >= partsDataLen) goto fail;
+ particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 24);
+ particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 16);
+ }
+ }
+ }
+
+ //Read ctype
+ if(fieldDescriptor & 0x20)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].ctype = partsData[i++];
+ //Read additional bytes
+ if(fieldDescriptor & 0x200)
+ {
+ if(i+2 >= partsDataLen) goto fail;
+ particles[newIndex].ctype |= (((unsigned)partsData[i++]) << 24);
+ particles[newIndex].ctype |= (((unsigned)partsData[i++]) << 16);
+ particles[newIndex].ctype |= (((unsigned)partsData[i++]) << 8);
+ }
+ }
+
+ //Read dcolour
+ if(fieldDescriptor & 0x40)
+ {
+ if(i+3 >= partsDataLen) goto fail;
+ particles[newIndex].dcolour = (((unsigned)partsData[i++]) << 24);
+ particles[newIndex].dcolour |= (((unsigned)partsData[i++]) << 16);
+ particles[newIndex].dcolour |= (((unsigned)partsData[i++]) << 8);
+ particles[newIndex].dcolour |= ((unsigned)partsData[i++]);
+ }
+
+ //Read vx
+ if(fieldDescriptor & 0x80)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].vx = (partsData[i++]-127.0f)/16.0f;
+ }
+
+ //Read vy
+ if(fieldDescriptor & 0x100)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].vy = (partsData[i++]-127.0f)/16.0f;
+ }
+
+ //Read tmp2
+ if(fieldDescriptor & 0x400)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].tmp2 = partsData[i++];
+ if(fieldDescriptor & 0x800)
+ {
+ if(i >= partsDataLen) goto fail;
+ particles[newIndex].tmp2 |= (((unsigned)partsData[i++]) << 8);
+ }
+ }
+
+ //Particle specific parsing:
+ switch(particles[newIndex].type)
+ {
+ case PT_SOAP:
+ //Clear soap links, links will be added back in if soapLinkData is present
+ particles[newIndex].ctype &= ~6;
+ break;
+ case PT_BOMB:
+ if (particles[newIndex].tmp!=0 && savedVersion < 81)
+ {
+ particles[newIndex].type = PT_EMBR;
+ particles[newIndex].ctype = 0;
+ if (particles[newIndex].tmp==1)
+ particles[newIndex].tmp = 0;
+ }
+ break;
+ case PT_DUST:
+ if (particles[newIndex].life>0 && savedVersion < 81)
+ {
+ particles[newIndex].type = PT_EMBR;
+ particles[newIndex].ctype = (particles[newIndex].tmp2<<16) | (particles[newIndex].tmp<<8) | particles[newIndex].ctype;
+ particles[newIndex].tmp = 1;
+ }
+ break;
+ case PT_FIRW:
+ if (particles[newIndex].tmp>=2 && savedVersion < 81)
+ {
+ int caddress = restrict_flt(restrict_flt((float)(particles[newIndex].tmp-4), 0.0f, 200.0f)*3, 0.0f, (200.0f*3)-3);
+ particles[newIndex].type = PT_EMBR;
+ particles[newIndex].tmp = 1;
+ particles[newIndex].ctype = (((unsigned char)(firw_data[caddress]))<<16) | (((unsigned char)(firw_data[caddress+1]))<<8) | ((unsigned char)(firw_data[caddress+2]));
+ }
+ break;
+ }
+ newIndex++;
+ }
+ }
+ }
+ if (soapLinkData)
+ {
+ int soapLinkDataPos = 0;
+ for (i=0; i<partsCount; i++)
+ {
+ if (partsSimIndex[i] && particles[partsSimIndex[i]-1].type == PT_SOAP)
+ {
+ // Get the index of the particle forward linked from this one, if present in the save data
+ int linkedIndex = 0;
+ if (soapLinkDataPos+3 > soapLinkDataLen) break;
+ linkedIndex |= soapLinkData[soapLinkDataPos++]<<16;
+ linkedIndex |= soapLinkData[soapLinkDataPos++]<<8;
+ linkedIndex |= soapLinkData[soapLinkDataPos++];
+ // All indexes in soapLinkData and partsSimIndex have 1 added to them (0 means not saved/loaded)
+ if (!linkedIndex || linkedIndex-1>=partsCount || !partsSimIndex[linkedIndex-1])
+ continue;
+ linkedIndex = partsSimIndex[linkedIndex-1]-1;
+ newIndex = partsSimIndex[i]-1;
+
+ //Attach the two particles
+ particles[newIndex].ctype |= 2;
+ particles[newIndex].tmp = linkedIndex;
+ particles[linkedIndex].ctype |= 4;
+ particles[linkedIndex].tmp2 = newIndex;
+ }
+ }
+ }
+ }
+
+ if(tempSigns.size())
+ {
+ for (int i = 0; i < tempSigns.size(); i++)
+ {
+ if(signs.size() == MAXSIGNS)
+ break;
+ signs.push_back(tempSigns[i]);
+ }
+ }
+ goto fin;
+fail:
+ //Clean up everything
+ bson_destroy(&b);
+ if(freeIndices)
+ free(freeIndices);
+ if(partsSimIndex)
+ free(partsSimIndex);
+ throw ParseException(ParseException::Corrupt, "Save data corrupt");
+fin:
+ bson_destroy(&b);
+ if(freeIndices)
+ free(freeIndices);
+ if(partsSimIndex)
+ free(partsSimIndex);
+}
+
+void GameSave::readPSv(char * data, int dataLength)
+{
+ unsigned char * d = NULL, * c = (unsigned char *)data;
+ int q,i,j,k,x,y,p=0,*m=NULL, ver, pty, ty, legacy_beta=0, tempGrav = 0;
+ int bx0=0, by0=0, bw, bh, w, h, y0 = 0, x0 = 0;
+ int nf=0, new_format = 0, ttv = 0;
+ int *fp = (int *)malloc(NPART*sizeof(int));
+
+ std::vector<sign> tempSigns;
+ char tempSignText[255];
+ sign tempSign("", 0, 0, sign::Left);
+
+ //Gol data used to read older saves
+ int goltype[NGOL];
+ int grule[NGOL+1][10];
+
+ int golRulesCount;
+ int * golRulesT = LoadGOLRules(golRulesCount);
+ memcpy(grule, golRulesT, sizeof(int) * (golRulesCount*10));
+ free(golRulesT);
+
+ int golTypesCount;
+ int * golTypesT = LoadGOLTypes(golTypesCount);
+ memcpy(goltype, golTypesT, sizeof(int) * (golTypesCount));
+ free(golTypesT);
+
+ std::vector<Element> elements = GetElements();
+
+ try
+ {
+
+ //New file header uses PSv, replacing fuC. This is to detect if the client uses a new save format for temperatures
+ //This creates a problem for old clients, that display and "corrupt" error instead of a "newer version" error
+
+ if (dataLength<16)
+ throw ParseException(ParseException::Corrupt, "No save data");
+ if (!(c[2]==0x43 && c[1]==0x75 && c[0]==0x66) && !(c[2]==0x76 && c[1]==0x53 && c[0]==0x50))
+ throw ParseException(ParseException::Corrupt, "Unknown format");
+ if (c[2]==0x76 && c[1]==0x53 && c[0]==0x50) {
+ new_format = 1;
+ }
+ if (c[4]>SAVE_VERSION)
+ throw ParseException(ParseException::WrongVersion, "Save from newer version");
+ ver = c[4];
+
+ if (ver<34)
+ {
+ legacyEnable = 1;
+ }
+ else
+ {
+ if (ver>=44) {
+ legacyEnable = c[3]&0x01;
+ paused = (c[3]>>1)&0x01;
+ if (ver>=46) {
+ gravityMode = ((c[3]>>2)&0x03);// | ((c[3]>>2)&0x01);
+ airMode = ((c[3]>>4)&0x07);// | ((c[3]>>4)&0x02) | ((c[3]>>4)&0x01);
+ }
+ if (ver>=49) {
+ gravityEnable = ((c[3]>>7)&0x01);
+ }
+ } else {
+ if (c[3]==1||c[3]==0) {
+ legacyEnable = c[3];
+ } else {
+ legacy_beta = 1;
+ }
+ }
+ }
+
+ bw = c[6];
+ bh = c[7];
+ if (bx0+bw > XRES/CELL)
+ bx0 = XRES/CELL - bw;
+ if (by0+bh > YRES/CELL)
+ by0 = YRES/CELL - bh;
+ if (bx0 < 0)
+ bx0 = 0;
+ if (by0 < 0)
+ by0 = 0;
+
+ if (c[5]!=CELL || bx0+bw>XRES/CELL || by0+bh>YRES/CELL)
+ throw ParseException(ParseException::InvalidDimensions, "Save too large");
+ i = (unsigned)c[8];
+ i |= ((unsigned)c[9])<<8;
+ i |= ((unsigned)c[10])<<16;
+ i |= ((unsigned)c[11])<<24;
+ d = (unsigned char *)malloc(i);
+ if (!d)
+ throw ParseException(ParseException::Corrupt, "Cannot allocate memory");
+
+ setSize(bw, bh);
+
+ int bzStatus = 0;
+ if (bzStatus = BZ2_bzBuffToBuffDecompress((char *)d, (unsigned *)&i, (char *)(c+12), dataLength-12, 0, 0))
+ {
+ std::stringstream bzStatusStr;
+ bzStatusStr << bzStatus;
+ throw ParseException(ParseException::Corrupt, "Cannot decompress: " + bzStatusStr.str());
+ }
+ dataLength = i;
+
+ std::cout << "Parsing " << dataLength << " bytes of data, version " << ver << std::endl;
+
+ if (dataLength < bw*bh)
+ throw ParseException(ParseException::Corrupt, "Save data corrupt (missing data)");
+
+ // normalize coordinates
+ x0 = bx0*CELL;
+ y0 = by0*CELL;
+ w = bw *CELL;
+ h = bh *CELL;
+
+ if (ver<46) {
+ gravityMode = 0;
+ airMode = 0;
+ }
+ m = (int *)calloc(XRES*YRES, sizeof(int));
+
+ // load the required air state
+ for (y=by0; y<by0+bh; y++)
+ for (x=bx0; x<bx0+bw; x++)
+ {
+ if (d[p])
+ {
+ //In old saves, ignore walls created by sign tool bug
+ //Not ignoring other invalid walls or invalid walls in new saves, so that any other bugs causing them are easier to notice, find and fix
+ if (ver<71 && d[p]==O_WL_SIGN)
+ {
+ p++;
+ continue;
+ }
+ blockMap[y][x] = d[p];
+ if (blockMap[y][x]==1)
+ blockMap[y][x]=WL_WALL;
+ if (blockMap[y][x]==2)
+ blockMap[y][x]=WL_DESTROYALL;
+ if (blockMap[y][x]==3)
+ blockMap[y][x]=WL_ALLOWLIQUID;
+ if (blockMap[y][x]==4)
+ blockMap[y][x]=WL_FAN;
+ if (blockMap[y][x]==5)
+ blockMap[y][x]=WL_STREAM;
+ if (blockMap[y][x]==6)
+ blockMap[y][x]=WL_DETECT;
+ if (blockMap[y][x]==7)
+ blockMap[y][x]=WL_EWALL;
+ if (blockMap[y][x]==8)
+ blockMap[y][x]=WL_WALLELEC;
+ if (blockMap[y][x]==9)
+ blockMap[y][x]=WL_ALLOWAIR;
+ if (blockMap[y][x]==10)
+ blockMap[y][x]=WL_ALLOWSOLID;
+ if (blockMap[y][x]==11)
+ blockMap[y][x]=WL_ALLOWALLELEC;
+ if (blockMap[y][x]==12)
+ blockMap[y][x]=WL_EHOLE;
+ if (blockMap[y][x]==13)
+ blockMap[y][x]=WL_ALLOWGAS;
+
+ if (blockMap[y][x]==O_WL_WALLELEC)
+ blockMap[y][x]=WL_WALLELEC;
+ if (blockMap[y][x]==O_WL_EWALL)
+ blockMap[y][x]=WL_EWALL;
+ if (blockMap[y][x]==O_WL_DETECT)
+ blockMap[y][x]=WL_DETECT;
+ if (blockMap[y][x]==O_WL_STREAM)
+ blockMap[y][x]=WL_STREAM;
+ if (blockMap[y][x]==O_WL_FAN||blockMap[y][x]==O_WL_FANHELPER)
+ blockMap[y][x]=WL_FAN;
+ if (blockMap[y][x]==O_WL_ALLOWLIQUID)
+ blockMap[y][x]=WL_ALLOWLIQUID;
+ if (blockMap[y][x]==O_WL_DESTROYALL)
+ blockMap[y][x]=WL_DESTROYALL;
+ if (blockMap[y][x]==O_WL_ERASE)
+ blockMap[y][x]=WL_ERASE;
+ if (blockMap[y][x]==O_WL_WALL)
+ blockMap[y][x]=WL_WALL;
+ if (blockMap[y][x]==O_WL_ALLOWAIR)
+ blockMap[y][x]=WL_ALLOWAIR;
+ if (blockMap[y][x]==O_WL_ALLOWSOLID)
+ blockMap[y][x]=WL_ALLOWSOLID;
+ if (blockMap[y][x]==O_WL_ALLOWALLELEC)
+ blockMap[y][x]=WL_ALLOWALLELEC;
+ if (blockMap[y][x]==O_WL_EHOLE)
+ blockMap[y][x]=WL_EHOLE;
+ if (blockMap[y][x]==O_WL_ALLOWGAS)
+ blockMap[y][x]=WL_ALLOWGAS;
+ if (blockMap[y][x]==O_WL_GRAV)
+ blockMap[y][x]=WL_GRAV;
+ if (blockMap[y][x]==O_WL_ALLOWENERGY)
+ blockMap[y][x]=WL_ALLOWENERGY;
+ }
+
+ p++;
+ }
+ for (y=by0; y<by0+bh; y++)
+ for (x=bx0; x<bx0+bw; x++)
+ if (d[(y-by0)*bw+(x-bx0)]==4||d[(y-by0)*bw+(x-bx0)]==O_WL_FAN)
+ {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ fanVelX[y][x] = (d[p++]-127.0f)/64.0f;
+ }
+ for (y=by0; y<by0+bh; y++)
+ for (x=bx0; x<bx0+bw; x++)
+ if (d[(y-by0)*bw+(x-bx0)]==4||d[(y-by0)*bw+(x-bx0)]==O_WL_FAN)
+ {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ fanVelY[y][x] = (d[p++]-127.0f)/64.0f;
+ }
+
+ // load the particle map
+ i = 0;
+ k = 0;
+ pty = p;
+ for (y=y0; y<y0+h; y++)
+ for (x=x0; x<x0+w; x++)
+ {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ j=d[p++];
+ if (j >= PT_NUM) {
+ //TODO: Possibly some server side translation
+ j = PT_DUST;//throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (j)
+ {
+ memset(particles+k, 0, sizeof(Particle));
+ particles[k].type = j;
+ if (j == PT_COAL)
+ particles[k].tmp = 50;
+ if (j == PT_FUSE)
+ particles[k].tmp = 50;
+ if (j == PT_PHOT)
+ particles[k].ctype = 0x3fffffff;
+ if (j == PT_SOAP)
+ particles[k].ctype = 0;
+ if (j==PT_BIZR || j==PT_BIZRG || j==PT_BIZRS)
+ particles[k].ctype = 0x47FFFF;
+ particles[k].x = (float)x;
+ particles[k].y = (float)y;
+ m[(x-x0)+(y-y0)*w] = k+1;
+ particlesCount = ++k;
+ }
+ }
+
+ // load particle properties
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ i--;
+ if (p+1 >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ if (i < NPART)
+ {
+ particles[i].vx = (d[p++]-127.0f)/16.0f;
+ particles[i].vy = (d[p++]-127.0f)/16.0f;
+ }
+ else
+ p += 2;
+ }
+ }
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (ver>=44) {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ ttv = (d[p++])<<8;
+ ttv |= (d[p++]);
+ particles[i-1].life = ttv;
+ } else {
+ p+=2;
+ }
+ } else {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ if (i <= NPART)
+ particles[i-1].life = d[p++]*4;
+ else
+ p++;
+ }
+ }
+ }
+ if (ver>=44) {
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ ttv = (d[p++])<<8;
+ ttv |= (d[p++]);
+ particles[i-1].tmp = ttv;
+ if (ver<53 && !particles[i-1].tmp)
+ for (q = 1; q<=NGOLALT; q++) {
+ if (particles[i-1].type==goltype[q-1] && grule[q][9]==2)
+ particles[i-1].tmp = grule[q][9]-1;
+ }
+ if (ver>=51 && ver<53 && particles[i-1].type==PT_PBCN)
+ {
+ particles[i-1].tmp2 = particles[i-1].tmp;
+ particles[i-1].tmp = 0;
+ }
+ } else {
+ p+=2;
+ }
+ }
+ }
+ }
+ if (ver>=53) {
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ ty = d[pty+j];
+ if (i && (ty==PT_PBCN || (ty==PT_TRON && ver>=77)))
+ {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ if (i <= NPART)
+ particles[i-1].tmp2 = d[p++];
+ else
+ p++;
+ }
+ }
+ }
+ //Read ALPHA component
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (ver>=49) {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ particles[i-1].dcolour = d[p++]<<24;
+ } else {
+ p++;
+ }
+ }
+ }
+ }
+ //Read RED component
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (ver>=49) {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ particles[i-1].dcolour |= d[p++]<<16;
+ } else {
+ p++;
+ }
+ }
+ }
+ }
+ //Read GREEN component
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (ver>=49) {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ particles[i-1].dcolour |= d[p++]<<8;
+ } else {
+ p++;
+ }
+ }
+ }
+ }
+ //Read BLUE component
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ if (i)
+ {
+ if (ver>=49) {
+ if (p >= dataLength) {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART) {
+ particles[i-1].dcolour |= d[p++];
+ } else {
+ p++;
+ }
+ }
+ }
+ }
+ for (j=0; j<w*h; j++)
+ {
+ i = m[j];
+ ty = d[pty+j];
+ if (i)
+ {
+ if (ver>=34&&legacy_beta==0)
+ {
+ if (p >= dataLength)
+ {
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ }
+ if (i <= NPART)
+ {
+ if (ver>=42) {
+ if (new_format) {
+ ttv = (d[p++])<<8;
+ ttv |= (d[p++]);
+ if (particles[i-1].type==PT_PUMP) {
+ particles[i-1].temp = ttv + 0.15;//fix PUMP saved at 0, so that it loads at 0.
+ } else {
+ particles[i-1].temp = ttv;
+ }
+ } else {
+ particles[i-1].temp = (d[p++]*((MAX_TEMP+(-MIN_TEMP))/255))+MIN_TEMP;
+ }
+ } else {
+ particles[i-1].temp = ((d[p++]*((O_MAX_TEMP+(-O_MIN_TEMP))/255))+O_MIN_TEMP)+273;
+ }
+ }
+ else
+ {
+ p++;
+ if (new_format) {
+ p++;
+ }
+ }
+ }
+ else
+ {
+ particles[i-1].temp = elements[particles[i-1].type].Temperature;
+ }
+ }
+ }
+ for (j=0; j<w*h; j++)
+ {
+ int gnum = 0;
+ i = m[j];
+ ty = d[pty+j];
+ if (i && (ty==PT_CLNE || (ty==PT_PCLN && ver>=43) || (ty==PT_BCLN && ver>=44) || (ty==PT_SPRK && ver>=21) || (ty==PT_LAVA && ver>=34) || (ty==PT_PIPE && ver>=43) || (ty==PT_LIFE && ver>=51) || (ty==PT_PBCN && ver>=52) || (ty==PT_WIRE && ver>=55) || (ty==PT_STOR && ver>=59) || (ty==PT_CONV && ver>=60)))
+ {
+ if (p >= dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ if (i <= NPART)
+ particles[i-1].ctype = d[p++];
+ else
+ p++;
+ }
+ //TODO: STKM_init_legs
+ // no more particle properties to load, so we can change type here without messing up loading
+ if (i && i<=NPART)
+ {
+ if (ver<79 && particles[i-1].type == PT_SPNG)
+ {
+ if (fabs(particles[i-1].vx)>0.0f || fabs(particles[i-1].vy)>0.0f)
+ particles[i-1].flags |= FLAG_MOVABLE;
+ }
+
+ if (ver<48 && (ty==OLD_PT_WIND || (ty==PT_BRAY&&particles[i-1].life==0)))
+ {
+ // Replace invisible particles with something sensible and add decoration to hide it
+ x = (int)(particles[i-1].x+0.5f);
+ y = (int)(particles[i-1].y+0.5f);
+ particles[i-1].dcolour = 0xFF000000;
+ particles[i-1].type = PT_DMND;
+ }
+ if(ver<51 && ((ty>=78 && ty<=89) || (ty>=134 && ty<=146 && ty!=141))){
+ //Replace old GOL
+ particles[i-1].type = PT_LIFE;
+ for (gnum = 0; gnum<NGOLALT; gnum++){
+ if (ty==goltype[gnum])
+ particles[i-1].ctype = gnum;
+ }
+ ty = PT_LIFE;
+ }
+ if(ver<52 && (ty==PT_CLNE || ty==PT_PCLN || ty==PT_BCLN)){
+ //Replace old GOL ctypes in clone
+ for (gnum = 0; gnum<NGOLALT; gnum++){
+ if (particles[i-1].ctype==goltype[gnum])
+ {
+ particles[i-1].ctype = PT_LIFE;
+ particles[i-1].tmp = gnum;
+ }
+ }
+ }
+ if(ty==PT_LCRY){
+ if(ver<67)
+ {
+ //New LCRY uses TMP not life
+ if(particles[i-1].life>=10)
+ {
+ particles[i-1].life = 10;
+ particles[i-1].tmp2 = 10;
+ particles[i-1].tmp = 3;
+ }
+ else if(particles[i-1].life<=0)
+ {
+ particles[i-1].life = 0;
+ particles[i-1].tmp2 = 0;
+ particles[i-1].tmp = 0;
+ }
+ else if(particles[i-1].life < 10 && particles[i-1].life > 0)
+ {
+ particles[i-1].tmp = 1;
+ }
+ }
+ else
+ {
+ particles[i-1].tmp2 = particles[i-1].life;
+ }
+ }
+
+ if (ver<81)
+ {
+ if (particles[i-1].type==PT_BOMB && particles[i-1].tmp!=0)
+ {
+ particles[i-1].type = PT_EMBR;
+ particles[i-1].ctype = 0;
+ if (particles[i-1].tmp==1)
+ particles[i-1].tmp = 0;
+ }
+ if (particles[i-1].type==PT_DUST && particles[i-1].life>0)
+ {
+ particles[i-1].type = PT_EMBR;
+ particles[i-1].ctype = (particles[i-1].tmp2<<16) | (particles[i-1].tmp<<8) | particles[i-1].ctype;
+ particles[i-1].tmp = 1;
+ }
+ if (particles[i-1].type==PT_FIRW && particles[i-1].tmp>=2)
+ {
+ int caddress = restrict_flt(restrict_flt((float)(particles[i-1].tmp-4), 0.0f, 200.0f)*3, 0.0f, (200.0f*3)-3);
+ particles[i-1].type = PT_EMBR;
+ particles[i-1].tmp = 1;
+ particles[i-1].ctype = (((unsigned char)(firw_data[caddress]))<<16) | (((unsigned char)(firw_data[caddress+1]))<<8) | ((unsigned char)(firw_data[caddress+2]));
+ }
+ }
+ }
+ }
+
+ if (p >= dataLength)
+ goto version1;
+ j = d[p++];
+ for (i=0; i<j; i++)
+ {
+ if (p+6 > dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ x = d[p++];
+ x |= ((unsigned)d[p++])<<8;
+ tempSign.x = x+x0;
+ x = d[p++];
+ x |= ((unsigned)d[p++])<<8;
+ tempSign.y = x+y0;
+ x = d[p++];
+ tempSign.ju = (sign::Justification)x;
+ x = d[p++];
+ if (p+x > dataLength)
+ throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
+ if(x>254)
+ x = 254;
+ memcpy(tempSignText, d+p, x);
+ tempSignText[x] = 0;
+ tempSign.text = tempSignText;
+ tempSigns.push_back(tempSign);
+ p += x;
+ }
+
+ for (i = 0; i < tempSigns.size(); i++)
+ {
+ if(signs.size() == MAXSIGNS)
+ break;
+ signs.push_back(tempSigns[i]);
+ }
+
+ }
+ catch(ParseException & e)
+ {
+ if (m)
+ {
+ free(m);
+ m = 0;
+ }
+ if (d)
+ {
+ free(d);
+ d = 0;
+ }
+ if (fp)
+ {
+ free(fp);
+ fp = 0;
+ }
+ throw;
+ }
+
+ version1:
+ if (m)
+ {
+ free(m);
+ m = 0;
+ }
+ if (d)
+ {
+ free(d);
+ d = 0;
+ }
+ if (fp)
+ {
+ free(fp);
+ fp = 0;
+ }
+}
+
+char * GameSave::serialiseOPS(int & dataLength)
+{
+ //Particle *particles = sim->parts;
+ unsigned char *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *finalData = NULL, *outputData = NULL, *soapLinkData = NULL;
+ unsigned *partsPosLink = NULL, *partsPosFirstMap = NULL, *partsPosCount = NULL, *partsPosLastMap = NULL;
+ unsigned partsCount = 0, *partsSaveIndex = NULL;
+ unsigned *elementCount = new unsigned[PT_NUM];
+ unsigned int partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, finalDataLen, outputDataLen, soapLinkDataLen;
+ int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH;
+ int x, y, i, wallDataFound = 0;
+ int posCount, signsCount;
+ bson b;
+
+ std::fill(elementCount, elementCount+PT_NUM, 0);
+
+ //Get coords in blocks
+ blockX = 0;//orig_x0/CELL;
+ blockY = 0;//orig_y0/CELL;
+
+ //Snap full coords to block size
+ fullX = blockX*CELL;
+ fullY = blockY*CELL;
+
+ //Original size + offset of original corner from snapped corner, rounded up by adding CELL-1
+ blockW = blockWidth;//(blockWidth-fullX+CELL-1)/CELL;
+ blockH = blockHeight;//(blockHeight-fullY+CELL-1)/CELL;
+ fullW = blockW*CELL;
+ fullH = blockH*CELL;
+
+ //Copy fan and wall data
+ wallData = (unsigned char*)malloc(blockW*blockH);
+ wallDataLen = blockW*blockH;
+ fanData = (unsigned char*)malloc((blockW*blockH)*2);
+ fanDataLen = 0;
+ for(x = blockX; x < blockX+blockW; x++)
+ {
+ for(y = blockY; y < blockY+blockH; y++)
+ {
+ wallData[(y-blockY)*blockW+(x-blockX)] = blockMap[y][x];
+ if(blockMap[y][x] && !wallDataFound)
+ wallDataFound = 1;
+ if(blockMap[y][x]==WL_FAN)
+ {
+ i = (int)(fanVelX[y][x]*64.0f+127.5f);
+ if (i<0) i=0;
+ if (i>255) i=255;
+ fanData[fanDataLen++] = i;
+ i = (int)(fanVelY[y][x]*64.0f+127.5f);
+ if (i<0) i=0;
+ if (i>255) i=255;
+ fanData[fanDataLen++] = i;
+ }
+ }
+ }
+ if(!fanDataLen)
+ {
+ free(fanData);
+ fanData = NULL;
+ }
+ if(!wallDataFound)
+ {
+ free(wallData);
+ wallData = NULL;
+ }
+
+ //Index positions of all particles, using linked lists
+ //partsPosFirstMap is pmap for the first particle in each position
+ //partsPosLastMap is pmap for the last particle in each position
+ //partsPosCount is the number of particles in each position
+ //partsPosLink contains, for each particle, (i<<8)|1 of the next particle in the same position
+ partsPosFirstMap = (unsigned int *)calloc(fullW*fullH, sizeof(unsigned));
+ partsPosLastMap = (unsigned int *)calloc(fullW*fullH, sizeof(unsigned));
+ partsPosCount = (unsigned int *)calloc(fullW*fullH, sizeof(unsigned));
+ partsPosLink = (unsigned int *)calloc(NPART, sizeof(unsigned));
+ for(i = 0; i < NPART; i++)
+ {
+ if(particles[i].type)
+ {
+ x = (int)(particles[i].x+0.5f);
+ y = (int)(particles[i].y+0.5f);
+ //Coordinates relative to top left corner of saved area
+ x -= fullX;
+ y -= fullY;
+ if (!partsPosFirstMap[y*fullW + x])
+ {
+ //First entry in list
+ partsPosFirstMap[y*fullW + x] = (i<<8)|1;
+ partsPosLastMap[y*fullW + x] = (i<<8)|1;
+ }
+ else
+ {
+ //Add to end of list
+ partsPosLink[partsPosLastMap[y*fullW + x]>>8] = (i<<8)|1;//link to current end of list
+ partsPosLastMap[y*fullW + x] = (i<<8)|1;//set as new end of list
+ }
+ partsPosCount[y*fullW + x]++;
+ }
+ }
+
+ //Store number of particles in each position
+ partsPosData = (unsigned char*)malloc(fullW*fullH*3);
+ partsPosDataLen = 0;
+ for (y=0;y<fullH;y++)
+ {
+ for (x=0;x<fullW;x++)
+ {
+ posCount = partsPosCount[y*fullW + x];
+ partsPosData[partsPosDataLen++] = (posCount&0x00FF0000)>>16;
+ partsPosData[partsPosDataLen++] = (posCount&0x0000FF00)>>8;
+ partsPosData[partsPosDataLen++] = (posCount&0x000000FF);
+ }
+ }
+
+ //Copy parts data
+ /* Field descriptor format:
+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+ | tmp2[2] | tmp2 | ctype[2] | vy | vx | dcololour | ctype[1] | tmp[2] | tmp[1] | life[2] | life[1] | temp dbl len|
+ life[2] means a second byte (for a 16 bit field) if life[1] is present
+ */
+ partsData = (unsigned char *)malloc(NPART * (sizeof(Particle)+1));
+ partsDataLen = 0;
+ partsSaveIndex = (unsigned int *)calloc(NPART, sizeof(unsigned));
+ partsCount = 0;
+ for (y=0;y<fullH;y++)
+ {
+ for (x=0;x<fullW;x++)
+ {
+ //Find the first particle in this position
+ i = partsPosFirstMap[y*fullW + x];
+
+ //Loop while there is a pmap entry
+ while (i)
+ {
+ unsigned short fieldDesc = 0;
+ int fieldDescLoc = 0, tempTemp, vTemp;
+
+ //Turn pmap entry into a particles index
+ i = i>>8;
+
+ //Store saved particle index+1 for this partsptr index (0 means not saved)
+ partsSaveIndex[i] = (partsCount++) + 1;
+
+ //Type (required)
+ partsData[partsDataLen++] = particles[i].type;
+ elementCount[particles[i].type]++;
+
+ //Location of the field descriptor
+ fieldDescLoc = partsDataLen++;
+ partsDataLen++;
+
+ //Extra Temperature (2nd byte optional, 1st required), 1 to 2 bytes
+ //Store temperature as an offset of 21C(294.15K) or go into a 16byte int and store the whole thing
+ if(fabs(particles[i].temp-294.15f)<127)
+ {
+ tempTemp = (particles[i].temp-294.15f);
+ partsData[partsDataLen++] = tempTemp;
+ }
+ else
+ {
+ fieldDesc |= 1;
+ tempTemp = particles[i].temp;
+ partsData[partsDataLen++] = tempTemp;
+ partsData[partsDataLen++] = tempTemp >> 8;
+ }
+
+ //Life (optional), 1 to 2 bytes
+ if(particles[i].life)
+ {
+ fieldDesc |= 1 << 1;
+ partsData[partsDataLen++] = particles[i].life;
+ if(particles[i].life > 255)
+ {
+ fieldDesc |= 1 << 2;
+ partsData[partsDataLen++] = particles[i].life >> 8;
+ }
+ }
+
+ //Tmp (optional), 1 to 2 bytes
+ if(particles[i].tmp)
+ {
+ fieldDesc |= 1 << 3;
+ partsData[partsDataLen++] = particles[i].tmp;
+ if(particles[i].tmp > 255)
+ {
+ fieldDesc |= 1 << 4;
+ partsData[partsDataLen++] = particles[i].tmp >> 8;
+ if(particles[i].tmp > 65535)
+ {
+ fieldDesc |= 1 << 12;
+ partsData[partsDataLen++] = (particles[i].tmp&0xFF000000)>>24;
+ partsData[partsDataLen++] = (particles[i].tmp&0x00FF0000)>>16;
+ }
+ }
+ }
+
+ //Ctype (optional), 1 or 4 bytes
+ if(particles[i].ctype)
+ {
+ fieldDesc |= 1 << 5;
+ partsData[partsDataLen++] = particles[i].ctype;
+ if(particles[i].ctype > 255)
+ {
+ fieldDesc |= 1 << 9;
+ partsData[partsDataLen++] = (particles[i].ctype&0xFF000000)>>24;
+ partsData[partsDataLen++] = (particles[i].ctype&0x00FF0000)>>16;
+ partsData[partsDataLen++] = (particles[i].ctype&0x0000FF00)>>8;
+ }
+ }
+
+ //Dcolour (optional), 4 bytes
+ if(particles[i].dcolour && (particles[i].dcolour & 0xFF000000))
+ {
+ fieldDesc |= 1 << 6;
+ partsData[partsDataLen++] = (particles[i].dcolour&0xFF000000)>>24;
+ partsData[partsDataLen++] = (particles[i].dcolour&0x00FF0000)>>16;
+ partsData[partsDataLen++] = (particles[i].dcolour&0x0000FF00)>>8;
+ partsData[partsDataLen++] = (particles[i].dcolour&0x000000FF);
+ }
+
+ //VX (optional), 1 byte
+ if(fabs(particles[i].vx) > 0.001f)
+ {
+ fieldDesc |= 1 << 7;
+ vTemp = (int)(particles[i].vx*16.0f+127.5f);
+ if (vTemp<0) vTemp=0;
+ if (vTemp>255) vTemp=255;
+ partsData[partsDataLen++] = vTemp;
+ }
+
+ //VY (optional), 1 byte
+ if(fabs(particles[i].vy) > 0.001f)
+ {
+ fieldDesc |= 1 << 8;
+ vTemp = (int)(particles[i].vy*16.0f+127.5f);
+ if (vTemp<0) vTemp=0;
+ if (vTemp>255) vTemp=255;
+ partsData[partsDataLen++] = vTemp;
+ }
+
+ //Tmp2 (optional), 1 or 2 bytes
+ if(particles[i].tmp2)
+ {
+ fieldDesc |= 1 << 10;
+ partsData[partsDataLen++] = particles[i].tmp2;
+ if(particles[i].tmp2 > 255)
+ {
+ fieldDesc |= 1 << 11;
+ partsData[partsDataLen++] = particles[i].tmp2 >> 8;
+ }
+ }
+
+ //Write the field descriptor;
+ partsData[fieldDescLoc] = fieldDesc;
+ partsData[fieldDescLoc+1] = fieldDesc>>8;
+
+ //Get the pmap entry for the next particle in the same position
+ i = partsPosLink[i];
+ }
+ }
+ }
+
+ soapLinkData = (unsigned char*)malloc(3*elementCount[PT_SOAP]);
+ soapLinkDataLen = 0;
+ //Iterate through particles in the same order that they were saved
+ for (y=0;y<fullH;y++)
+ {
+ for (x=0;x<fullW;x++)
+ {
+ //Find the first particle in this position
+ i = partsPosFirstMap[y*fullW + x];
+
+ //Loop while there is a pmap entry
+ while (i)
+ {
+ //Turn pmap entry into a partsptr index
+ i = i>>8;
+
+ if (particles[i].type==PT_SOAP)
+ {
+ //Only save forward link for each particle, back links can be deduced from other forward links
+ //linkedIndex is index within saved particles + 1, 0 means not saved or no link
+
+ unsigned linkedIndex = 0;
+ if ((particles[i].ctype&2) && particles[i].tmp>=0 && particles[i].tmp<NPART)
+ {
+ linkedIndex = partsSaveIndex[particles[i].tmp];
+ }
+ soapLinkData[soapLinkDataLen++] = (linkedIndex&0xFF0000)>>16;
+ soapLinkData[soapLinkDataLen++] = (linkedIndex&0x00FF00)>>8;
+ soapLinkData[soapLinkDataLen++] = (linkedIndex&0x0000FF);
+ }
+
+ //Get the pmap entry for the next particle in the same position
+ i = partsPosLink[i];
+ }
+ }
+ }
+ if(!soapLinkDataLen)
+ {
+ free(soapLinkData);
+ soapLinkData = NULL;
+ }
+ if(!partsDataLen)
+ {
+ free(partsData);
+ partsData = NULL;
+ }
+
+ bson_init(&b);
+ bson_append_bool(&b, "waterEEnabled", waterEEnabled);
+ bson_append_bool(&b, "legacyEnable", legacyEnable);
+ bson_append_bool(&b, "gravityEnable", gravityEnable);
+ bson_append_bool(&b, "paused", paused);
+ bson_append_int(&b, "gravityMode", gravityMode);
+ bson_append_int(&b, "airMode", airMode);
+
+ //bson_append_int(&b, "leftSelectedElement", sl);
+ //bson_append_int(&b, "rightSelectedElement", sr);
+ //bson_append_int(&b, "activeMenu", active_menu);
+ if(partsData)
+ bson_append_binary(&b, "parts", BSON_BIN_USER, (const char *)partsData, partsDataLen);
+ if(partsPosData)
+ bson_append_binary(&b, "partsPos", BSON_BIN_USER, (const char *)partsPosData, partsPosDataLen);
+ if(wallData)
+ bson_append_binary(&b, "wallMap", BSON_BIN_USER, (const char *)wallData, wallDataLen);
+ if(fanData)
+ bson_append_binary(&b, "fanMap", BSON_BIN_USER, (const char *)fanData, fanDataLen);
+ if(soapLinkData)
+ bson_append_binary(&b, "soapLinks", BSON_BIN_USER, (const char *)soapLinkData, soapLinkDataLen);
+ if(partsData && palette.size())
+ {
+ bson_append_start_array(&b, "palette");
+ for(std::vector<PaletteItem>::iterator iter = palette.begin(), end = palette.end(); iter != end; ++iter)
+ {
+ bson_append_int(&b, (*iter).first.c_str(), (*iter).second);
+ }
+ bson_append_finish_array(&b);
+ }
+ signsCount = 0;
+ for(i = 0; i < signs.size(); i++)
+ {
+ if(signs[i].text.length() && signs[i].x>=0 && signs[i].x<=fullW && signs[i].y>=0 && signs[i].y<=fullH)
+ {
+ signsCount++;
+ }
+ }
+ if(signsCount)
+ {
+ bson_append_start_array(&b, "signs");
+ for(i = 0; i < signs.size(); i++)
+ {
+ if(signs[i].text.length() && signs[i].x>=0 && signs[i].x<=fullW && signs[i].y>=0 && signs[i].y<=fullH)
+ {
+ bson_append_start_object(&b, "sign");
+ bson_append_string(&b, "text", signs[i].text.c_str());
+ bson_append_int(&b, "justification", signs[i].ju);
+ bson_append_int(&b, "x", signs[i].x);
+ bson_append_int(&b, "y", signs[i].y);
+ bson_append_finish_object(&b);
+ }
+ }
+ bson_append_finish_array(&b);
+ }
+ bson_finish(&b);
+#ifdef DEBUG
+ bson_print(&b);
+#endif
+
+ finalData = (unsigned char *)bson_data(&b);
+ finalDataLen = bson_size(&b);
+ outputDataLen = finalDataLen*2+12;
+ outputData = (unsigned char *)malloc(outputDataLen);
+
+ outputData[0] = 'O';
+ outputData[1] = 'P';
+ outputData[2] = 'S';
+ outputData[3] = '1';
+ outputData[4] = SAVE_VERSION;
+ outputData[5] = CELL;
+ outputData[6] = blockW;
+ outputData[7] = blockH;
+ outputData[8] = finalDataLen;
+ outputData[9] = finalDataLen >> 8;
+ outputData[10] = finalDataLen >> 16;
+ outputData[11] = finalDataLen >> 24;
+
+ if (BZ2_bzBuffToBuffCompress((char*)(outputData+12), &outputDataLen, (char*)finalData, bson_size(&b), 9, 0, 0) != BZ_OK)
+ {
+ puts("Save Error\n");
+ free(outputData);
+ dataLength = 0;
+ outputData = NULL;
+ goto fin;
+ }
+
+#ifdef DEBUG
+ printf("compressed data: %d\n", outputDataLen);
+#endif
+ dataLength = outputDataLen + 12;
+
+fin:
+ bson_destroy(&b);
+ if(partsData)
+ free(partsData);
+ if(wallData)
+ free(wallData);
+ if(fanData)
+ free(fanData);
+ if (elementCount)
+ delete[] elementCount;
+ if (partsSaveIndex)
+ free(partsSaveIndex);
+ if (soapLinkData)
+ free(soapLinkData);
+
+ return (char*)outputData;
+}
+
+void GameSave::dealloc()
+{
+ if(particles)
+ {
+ delete[] particles;
+ particles = NULL;
+ }
+ if(blockMap)
+ {
+ delete[] blockMap;
+ blockMap = NULL;
+ }
+ if(blockMapPtr)
+ {
+ delete[] blockMapPtr;
+ blockMapPtr = NULL;
+ }
+ if(fanVelX)
+ {
+ delete[] fanVelX;
+ fanVelX = NULL;
+ }
+ if(fanVelXPtr)
+ {
+ delete[] fanVelXPtr;
+ fanVelXPtr = NULL;
+ }
+ if(fanVelY)
+ {
+ delete[] fanVelY;
+ fanVelY = NULL;
+ }
+ if(fanVelYPtr)
+ {
+ delete[] fanVelYPtr;
+ fanVelYPtr = NULL;
+ }
+}
+
+GameSave::~GameSave()
+{
+ dealloc();
+}
diff --git a/src/client/GameSave.h b/src/client/GameSave.h
new file mode 100644
index 0000000..8ac1fce
--- /dev/null
+++ b/src/client/GameSave.h
@@ -0,0 +1,112 @@
+//
+// GameSave.h
+// The Powder Toy
+//
+// Created by Simon Robertshaw on 04/06/2012.
+//
+
+#ifndef The_Powder_Toy_GameSave_h
+#define The_Powder_Toy_GameSave_h
+
+#include <vector>
+#include <string>
+#include "Config.h"
+#include "Misc.h"
+
+#include "simulation/Sign.h"
+#include "simulation/Particle.h"
+
+//using namespace std;
+
+struct ParseException: public std::exception {
+ enum ParseResult { OK = 0, Corrupt, WrongVersion, InvalidDimensions, InternalError, MissingElement };
+ std::string message;
+ ParseResult result;
+public:
+ ParseException(ParseResult result, std::string message_): message(message_), result(result) {}
+ const char * what() const throw()
+ {
+ return message.c_str();
+ }
+ ~ParseException() throw() {};
+};
+
+class GameSave
+{
+public:
+
+ int blockWidth, blockHeight;
+
+ //Simulation data
+ //int ** particleMap;
+ int particlesCount;
+ Particle * particles;
+ unsigned char ** blockMap;
+ float ** fanVelX;
+ float ** fanVelY;
+
+ //Simulation Options
+ bool waterEEnabled;
+ bool legacyEnable;
+ bool gravityEnable;
+ bool paused;
+ int gravityMode;
+ int airMode;
+
+ //Signs
+ std::vector<sign> signs;
+
+ //Element palette
+ typedef std::pair<std::string, int> PaletteItem;
+ std::vector<PaletteItem> palette;
+
+ GameSave();
+ GameSave(GameSave & save);
+ GameSave(int width, int height);
+ GameSave(char * data, int dataSize);
+ GameSave(std::vector<char> data);
+ GameSave(std::vector<unsigned char> data);
+ ~GameSave();
+ void setSize(int width, int height);
+ char * Serialise(int & dataSize);
+ std::vector<char> Serialise();
+ void Transform(matrix2d transform, vector2d translate);
+
+ void Expand();
+ void Collapse();
+ bool Collapsed();
+
+ inline GameSave& operator << (Particle v)
+ {
+ if(particlesCount<NPART && v.type)
+ {
+ particles[particlesCount++] = v;
+ }
+ return *this;
+ }
+
+ inline GameSave& operator << (sign v)
+ {
+ if(signs.size()<MAXSIGNS && v.text.length())
+ signs.push_back(v);
+ return *this;
+ }
+
+private:
+ bool expanded;
+ bool hasOriginalData;
+ float * fanVelXPtr;
+ float * fanVelYPtr;
+ unsigned char * blockMapPtr;
+
+ std::vector<char> originalData;
+
+ void dealloc();
+ void read(char * data, int dataSize);
+ void readOPS(char * data, int dataLength);
+ void readPSv(char * data, int dataLength);
+ char * serialiseOPS(int & dataSize);
+ //serialisePSv();
+};
+
+#endif
diff --git a/src/client/HTTP.cpp b/src/client/HTTP.cpp
new file mode 100644
index 0000000..5fc4d08
--- /dev/null
+++ b/src/client/HTTP.cpp
@@ -0,0 +1,1104 @@
+/**
+ * Powder Toy - HTTP Library
+ *
+ * Copyright (c) 2008 - 2010 Stanislaw Skowronek.
+ * Copyright (c) 2010 Simon Robertshaw
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+
+#include <string>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef WIN
+#include <sys/param.h>
+#endif
+#if !defined(MACOSX) && !defined(BSD)
+#include <malloc.h>
+#endif
+#include <time.h>
+#ifdef WIN
+#define _WIN32_WINNT 0x0501
+//#include <iphlpapi.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#include "Config.h"
+#include "HTTP.h"
+#include "MD5.h"
+
+#ifdef WIN
+#define PERROR SOCKET_ERROR
+#define PERRNO WSAGetLastError()
+#define PEAGAIN WSAEWOULDBLOCK
+#define PEINTR WSAEINTR
+#define PEINPROGRESS WSAEINPROGRESS
+#define PEALREADY WSAEALREADY
+#define PCLOSE closesocket
+#else
+#define PERROR -1
+#define PERRNO errno
+#define PEAGAIN EAGAIN
+#define PEINTR EINTR
+#define PEINPROGRESS EINPROGRESS
+#define PEALREADY EALREADY
+#define PCLOSE close
+#endif
+
+char * userAgent;
+static int http_up = 0;
+static long http_timeout = 15;
+static int http_use_proxy = 0;
+static struct sockaddr_in http_proxy;
+
+static char * eatwhitespace(char * s)
+{
+ while(*s)
+ {
+ if(!(*s == ' ' || *s == '\t'))
+ break;
+ s++;
+ }
+ return s;
+}
+
+static char *mystrdup(char *s)
+{
+ char *x;
+ if (s)
+ {
+ x = (char *)malloc(strlen(s)+1);
+ strcpy(x, s);
+ return x;
+ }
+ return s;
+}
+
+static int splituri(char *uri, char **host, char **path)
+{
+ char *p=uri,*q,*x,*y;
+ if (!strncmp(p, "http://", 7))
+ p += 7;
+ q = strchr(p, '/');
+ if (!q)
+ q = p + strlen(p);
+ x = (char *)malloc(q-p+1);
+ if (*q)
+ y = mystrdup(q);
+ else
+ y = mystrdup("/");
+ strncpy(x, p, q-p);
+ x[q-p] = 0;
+ if (q==p || x[q-p-1]==':')
+ {
+ free(x);
+ free(y);
+ return 1;
+ }
+ *host = x;
+ *path = y;
+ return 0;
+}
+
+static char *getserv(char *host)
+{
+ char *q, *x = mystrdup(host);
+ q = strchr(x, ':');
+ if (q)
+ *q = 0;
+ return x;
+}
+
+static char *getport(char *host)
+{
+ char *p, *q;
+ q = strchr(host, ':');
+ if (q)
+ p = mystrdup(q+1);
+ else
+ p = mystrdup("80");
+ return p;
+}
+
+static int resolve(char *dns, char *srv, struct sockaddr_in *addr)
+{
+ struct addrinfo hnt, *res = 0;
+ if (http_use_proxy)
+ {
+ memcpy(addr, &http_proxy, sizeof(struct sockaddr_in));
+ return 0;
+ }
+ memset(&hnt, 0, sizeof(hnt));
+ hnt.ai_family = AF_INET;
+ hnt.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo(dns, srv, &hnt, &res))
+ return 1;
+ if (res)
+ {
+ if (res->ai_family != AF_INET)
+ {
+ freeaddrinfo(res);
+ return 1;
+ }
+ memcpy(addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ return 0;
+ }
+ return 1;
+}
+
+void http_init(char *proxy)
+{
+ char *host, *port;
+#ifdef WIN
+ WSADATA wsadata;
+ if (!WSAStartup(MAKEWORD(2,2), &wsadata))
+ http_up = 1;
+#else
+ signal(SIGPIPE, SIG_IGN);
+ http_up = 1;
+#endif
+ if (proxy)
+ {
+ host = getserv(proxy);
+ port = getport(proxy);
+ if (resolve(host, port, &http_proxy))
+ http_up = 0;
+ else
+ http_use_proxy = 1;
+ free(host);
+ free(port);
+ }
+ std::stringstream userAgentBuilder;
+ userAgentBuilder << "PowderToy/" << SAVE_VERSION << "." << MINOR_VERSION << " ";
+ userAgentBuilder << "(" << IDENT_PLATFORM << "; " << IDENT_BUILD << "; M0) ";
+ userAgentBuilder << "TPTPP/" << SAVE_VERSION << "." << MINOR_VERSION << "." << BUILD_NUM << IDENT_RELTYPE << "." << SNAPSHOT_ID;
+ std::string newUserAgent = userAgentBuilder.str();
+ userAgent = new char[newUserAgent.length()+1];
+ std::copy(newUserAgent.begin(), newUserAgent.end(), userAgent);
+ userAgent[newUserAgent.length()] = 0;
+ //"User-Agent: PowderToy/%d.%d (%s; %s; M%d) TPTPP/%d.%d.%d%s.%d\n", SAVE_VERSION, MINOR_VERSION, IDENT_PLATFORM, IDENT_BUILD, 0, SAVE_VERSION, MINOR_VERSION, BUILD_NUM, IDENT_RELTYPE, SNAPSHOT_ID
+}
+
+void http_done(void)
+{
+#ifdef WIN
+ WSACleanup();
+#endif
+ http_up = 0;
+}
+
+#define CHUNK 4096
+
+#define HTS_STRT 0
+#define HTS_RSLV 1
+#define HTS_CONN 2
+#define HTS_IDLE 3
+#define HTS_XMIT 4
+#define HTS_RECV 5
+#define HTS_DONE 6
+struct http_ctx
+{
+ int state;
+ time_t last;
+ int keep;
+ int ret;
+ char *host, *path;
+ char *thdr;
+ int thlen;
+ char *txd;
+ int txdl;
+ struct sockaddr_in addr;
+ char *tbuf;
+ int tlen, tptr;
+ char *hbuf;
+ int hlen, hptr;
+ char *rbuf;
+ int rlen, rptr;
+ int chunked, chunkhdr, rxtogo, contlen, cclose;
+ int fd;
+ char *fdhost;
+};
+void *http_async_req_start(void *ctx, char *uri, char *data, int dlen, int keep)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ if (!ctx)
+ {
+ ctx = calloc(1, sizeof(struct http_ctx));
+ cx = (http_ctx *)ctx;
+ cx->fd = PERROR;
+ }
+
+ if (!cx->hbuf)
+ {
+ cx->hbuf = (char *)malloc(256);
+ cx->hlen = 256;
+ }
+
+ if (!http_up)
+ {
+ cx->ret = 604;
+ cx->state = HTS_DONE;
+ return ctx;
+ }
+
+ if (cx->state!=HTS_STRT && cx->state!=HTS_IDLE)
+ {
+ fprintf(stderr, "HTTP: unclean request restart state.\n");
+ exit(1);
+ }
+
+ cx->keep = keep;
+ cx->ret = 600;
+ if (splituri(uri, &cx->host, &cx->path))
+ {
+ cx->ret = 601;
+ cx->state = HTS_DONE;
+ return ctx;
+ }
+ if (http_use_proxy)
+ {
+ free(cx->path);
+ cx->path = mystrdup(uri);
+ }
+ if (cx->fdhost && strcmp(cx->host, cx->fdhost))
+ {
+ free(cx->fdhost);
+ cx->fdhost = NULL;
+ PCLOSE(cx->fd);
+ cx->fd = PERROR;
+ cx->state = HTS_STRT;
+ }
+ if (data)
+ {
+ if (!dlen)
+ dlen = strlen(data);
+ cx->txd = (char*)malloc(dlen);
+ memcpy(cx->txd, data, dlen);
+ cx->txdl = dlen;
+ }
+ else
+ cx->txdl = 0;
+
+ cx->contlen = 0;
+ cx->chunked = 0;
+ cx->chunkhdr = 0;
+ cx->rxtogo = 0;
+ cx->cclose = 0;
+
+ cx->tptr = 0;
+ cx->tlen = 0;
+
+ cx->last = time(NULL);
+
+ return ctx;
+}
+
+void http_async_add_header(void *ctx, char *name, char *data)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ cx->thdr = (char *)realloc(cx->thdr, cx->thlen + strlen(name) + strlen(data) + 5);
+ cx->thlen += sprintf(cx->thdr+cx->thlen, "%s: %s\r\n", name, data);
+}
+
+static void process_header(struct http_ctx *cx, char *str)
+{
+ char *p;
+ if (cx->chunkhdr)
+ {
+ p = strchr(str, ';');
+ if (p)
+ *p = 0;
+ cx->rxtogo = strtoul(str, NULL, 16);
+ cx->chunkhdr = 0;
+ if (!cx->rxtogo)
+ cx->chunked = 0;
+ }
+ if (!str[0])
+ {
+ cx->rxtogo = cx->contlen;
+ cx->chunkhdr = cx->chunked;
+ if (!cx->contlen && !cx->chunked && cx->ret!=100)
+ cx->state = HTS_DONE;
+ return;
+ }
+ if (!strncmp(str, "HTTP/", 5))
+ {
+ p = strchr(str, ' ');
+ if (!p)
+ {
+ cx->ret = 603;
+ cx->state = HTS_DONE;
+ return;
+ }
+ p++;
+ cx->ret = atoi(p);
+ return;
+ }
+ if (!strncmp(str, "Content-Length: ", 16))
+ {
+ str = eatwhitespace(str+16);
+ cx->contlen = atoi(str);
+ return;
+ }
+ if (!strncmp(str, "Transfer-Encoding: ", 19))
+ {
+ str = eatwhitespace(str+19);
+ if(!strncmp(str, "chunked", 8))
+ {
+ cx->chunked = 1;
+ }
+ return;
+ }
+ if (!strncmp(str, "Connection: ", 12))
+ {
+ str = eatwhitespace(str+12);
+ if(!strncmp(str, "close", 6))
+ {
+ cx->cclose = 1;
+ }
+ return;
+ }
+}
+
+static void process_byte(struct http_ctx *cx, char ch)
+{
+ if (cx->rxtogo)
+ {
+ cx->rxtogo--;
+
+ if (!cx->rbuf)
+ {
+ cx->rbuf = (char *)malloc(256);
+ cx->rlen = 256;
+ }
+ if (cx->rptr >= cx->rlen-1)
+ {
+ cx->rlen *= 2;
+ cx->rbuf = (char *)realloc(cx->rbuf, cx->rlen);
+ }
+ cx->rbuf[cx->rptr++] = ch;
+
+ if (!cx->rxtogo && !cx->chunked)
+ cx->state = HTS_DONE;
+ }
+ else
+ {
+ if (ch == '\n')
+ {
+ cx->hbuf[cx->hptr] = 0;
+ process_header(cx, cx->hbuf);
+ cx->hptr = 0;
+ }
+ else if (ch != '\r')
+ {
+ if (cx->hptr >= cx->hlen-1)
+ {
+ cx->hlen *= 2;
+ cx->hbuf = (char *)realloc(cx->hbuf, cx->hlen);
+ }
+ cx->hbuf[cx->hptr++] = ch;
+ }
+ }
+}
+
+int http_async_req_status(void *ctx)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ char *dns,*srv,buf[CHUNK];
+ int tmp, i;
+ time_t now = time(NULL);
+#ifdef WIN
+ unsigned long tmp2;
+#endif
+
+ switch (cx->state)
+ {
+ case HTS_STRT:
+ dns = getserv(cx->host);
+ srv = getport(cx->host);
+ if (resolve(dns, srv, &cx->addr))
+ {
+ free(dns);
+ free(srv);
+ cx->state = HTS_DONE;
+ cx->ret = 602;
+ return 1;
+ }
+ free(dns);
+ free(srv);
+ cx->state = HTS_RSLV;
+ return 0;
+ case HTS_RSLV:
+ cx->state = HTS_CONN;
+ cx->last = now;
+ return 0;
+ case HTS_CONN:
+ if (cx->fd == PERROR)
+ {
+ cx->fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (cx->fd == PERROR)
+ goto fail;
+ cx->fdhost = mystrdup(cx->host);
+#ifdef WIN
+ tmp2 = 1;
+ if (ioctlsocket(cx->fd, FIONBIO, &tmp2) == SOCKET_ERROR)
+ goto fail;
+#else
+ tmp = fcntl(cx->fd, F_GETFL);
+ if (tmp < 0)
+ goto fail;
+ if (fcntl(cx->fd, F_SETFL, tmp|O_NONBLOCK) < 0)
+ goto fail;
+#endif
+ }
+ if (!connect(cx->fd, (struct sockaddr *)&cx->addr, sizeof(cx->addr)))
+ cx->state = HTS_IDLE;
+#ifdef WIN
+ else if (PERRNO==WSAEISCONN)
+ cx->state = HTS_IDLE;
+#endif
+#if defined(MACOSX) || defined(BSD)
+ else if (PERRNO==EISCONN)
+ cx->state = HTS_IDLE;
+#endif
+ else if (PERRNO!=PEINPROGRESS && PERRNO!=PEALREADY
+#ifdef WIN
+ && PERRNO!=PEAGAIN && PERRNO!=WSAEINVAL
+#endif
+ )
+ goto fail;
+ if (now-cx->last>http_timeout)
+ goto timeout;
+ return 0;
+ case HTS_IDLE:
+ if (cx->txdl)
+ {
+ // generate POST
+ cx->tbuf = (char *)malloc(strlen(cx->host) + strlen(cx->path) + 132 + strlen(userAgent) + cx->txdl + cx->thlen);
+ cx->tptr = 0;
+ cx->tlen = 0;
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "POST %s HTTP/1.1\r\n", cx->path);
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "Host: %s\r\n", cx->host);
+ if (!cx->keep)
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "Connection: close\r\n");
+ if (cx->thdr)
+ {
+ memcpy(cx->tbuf+cx->tlen, cx->thdr, cx->thlen);
+ cx->tlen += cx->thlen;
+ free(cx->thdr);
+ cx->thdr = NULL;
+ cx->thlen = 0;
+ }
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "Content-Length: %d\r\n", cx->txdl);
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "User-Agent: %s\r\n", userAgent);
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "\r\n");
+ memcpy(cx->tbuf+cx->tlen, cx->txd, cx->txdl);
+ cx->tlen += cx->txdl;
+ free(cx->txd);
+ cx->txd = NULL;
+ cx->txdl = 0;
+ }
+ else
+ {
+ // generate GET
+ cx->tbuf = (char *)malloc(strlen(cx->host) + strlen(cx->path) + 98 + strlen(userAgent) + cx->thlen);
+ cx->tptr = 0;
+ cx->tlen = 0;
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "GET %s HTTP/1.1\r\n", cx->path);
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "Host: %s\r\n", cx->host);
+ if (cx->thdr)
+ {
+ memcpy(cx->tbuf+cx->tlen, cx->thdr, cx->thlen);
+ cx->tlen += cx->thlen;
+ free(cx->thdr);
+ cx->thdr = NULL;
+ cx->thlen = 0;
+ }
+ if (!cx->keep)
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "Connection: close\r\n");
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "User-Agent: %s\r\n", userAgent);
+ cx->tlen += sprintf(cx->tbuf+cx->tlen, "\r\n");
+ }
+ cx->state = HTS_XMIT;
+ cx->last = now;
+ return 0;
+ case HTS_XMIT:
+ tmp = send(cx->fd, cx->tbuf+cx->tptr, cx->tlen-cx->tptr, 0);
+ if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR)
+ goto fail;
+ if (tmp!=PERROR)
+ {
+ cx->tptr += tmp;
+ if (cx->tptr == cx->tlen)
+ {
+ cx->tptr = 0;
+ cx->tlen = 0;
+ if (cx->tbuf)
+ free(cx->tbuf);
+ cx->state = HTS_RECV;
+ }
+ cx->last = now;
+ }
+ if (now-cx->last>http_timeout)
+ goto timeout;
+ return 0;
+ case HTS_RECV:
+ tmp = recv(cx->fd, buf, CHUNK, 0);
+ if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR)
+ goto fail;
+ if (tmp!=PERROR)
+ {
+ for (i=0; i<tmp; i++)
+ {
+ process_byte(cx, buf[i]);
+ if (cx->state == HTS_DONE)
+ return 1;
+ }
+ cx->last = now;
+ }
+ if (now-cx->last>http_timeout)
+ goto timeout;
+ return 0;
+ case HTS_DONE:
+ return 1;
+ }
+ return 0;
+
+fail:
+ cx->ret = 600;
+ cx->state = HTS_DONE;
+ return 1;
+
+timeout:
+ cx->ret = 605;
+ cx->state = HTS_DONE;
+ return 1;
+}
+
+char *http_async_req_stop(void *ctx, int *ret, int *len)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ char *rxd;
+
+ if (cx->state != HTS_DONE)
+ while (!http_async_req_status(ctx)) ;
+
+ if (cx->host)
+ {
+ free(cx->host);
+ cx->host = NULL;
+ }
+ if (cx->path)
+ {
+ free(cx->path);
+ cx->path = NULL;
+ }
+ if (cx->txd)
+ {
+ free(cx->txd);
+ cx->txd = NULL;
+ cx->txdl = 0;
+ }
+ if (cx->hbuf)
+ {
+ free(cx->hbuf);
+ cx->hbuf = NULL;
+ }
+ if (cx->thdr)
+ {
+ free(cx->thdr);
+ cx->thdr = NULL;
+ cx->thlen = 0;
+ }
+
+ if (ret)
+ *ret = cx->ret;
+ if (len)
+ *len = cx->rptr;
+ if (cx->rbuf)
+ cx->rbuf[cx->rptr] = 0;
+ rxd = cx->rbuf;
+ cx->rbuf = NULL;
+ cx->rlen = 0;
+ cx->rptr = 0;
+ cx->contlen = 0;
+
+ if (!cx->keep)
+ http_async_req_close(ctx);
+ else if (cx->cclose)
+ {
+ PCLOSE(cx->fd);
+ cx->fd = PERROR;
+ if (cx->fdhost)
+ {
+ free(cx->fdhost);
+ cx->fdhost = NULL;
+ }
+ cx->state = HTS_STRT;
+ }
+ else
+ cx->state = HTS_IDLE;
+
+ return rxd;
+}
+
+void http_async_get_length(void *ctx, int *total, int *done)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ if (done)
+ *done = cx->rptr;
+ if (total)
+ *total = cx->contlen;
+}
+
+void http_async_req_close(void *ctx)
+{
+ struct http_ctx *cx = (http_ctx *)ctx;
+ void *tmp;
+ if (cx->host)
+ {
+ cx->keep = 1;
+ tmp = http_async_req_stop(ctx, NULL, NULL);
+ if (tmp)
+ free(tmp);
+ }
+ if (cx->fdhost)
+ free(cx->fdhost);
+ PCLOSE(cx->fd);
+ free(ctx);
+}
+
+char *http_simple_get(char *uri, int *ret, int *len)
+{
+ void *ctx = http_async_req_start(NULL, uri, NULL, 0, 0);
+ if (!ctx)
+ {
+ if (ret)
+ *ret = 600;
+ if (len)
+ *len = 0;
+ return NULL;
+ }
+ return http_async_req_stop(ctx, ret, len);
+}
+void http_auth_headers(void *ctx, char *user, char *pass, char *session_id)
+{
+ char *tmp;
+ int i;
+ unsigned char hash[16];
+ unsigned int m;
+ struct md5_context md5;
+
+ if (user)
+ {
+ if (pass)
+ {
+ md5_init(&md5);
+ md5_update(&md5, (unsigned char *)user, strlen(user));
+ md5_update(&md5, (unsigned char *)"-", 1);
+ m = 0;
+
+ md5_update(&md5, (unsigned char *)pass, strlen(pass));
+ md5_final(hash, &md5);
+ tmp = (char *)malloc(33);
+ for (i=0; i<16; i++)
+ {
+ tmp[i*2] = hexChars[hash[i]>>4];
+ tmp[i*2+1] = hexChars[hash[i]&15];
+ }
+ tmp[32] = 0;
+ http_async_add_header(ctx, "X-Auth-Hash", tmp);
+ free(tmp);
+ }
+ if (session_id)
+ {
+ http_async_add_header(ctx, "X-Auth-User-Id", user);
+ http_async_add_header(ctx, "X-Auth-Session-Key", session_id);
+ }
+ else
+ {
+ http_async_add_header(ctx, "X-Auth-User", user);
+ }
+ }
+}
+char *http_auth_get(char *uri, char *user, char *pass, char *session_id, int *ret, int *len)
+{
+ void *ctx = http_async_req_start(NULL, uri, NULL, 0, 0);
+
+ if (!ctx)
+ {
+ if (ret)
+ *ret = 600;
+ if (len)
+ *len = 0;
+ return NULL;
+ }
+ http_auth_headers(ctx, user, pass, session_id);
+ return http_async_req_stop(ctx, ret, len);
+}
+
+char *http_simple_post(char *uri, char *data, int dlen, int *ret, int *len)
+{
+ void *ctx = http_async_req_start(NULL, uri, data, dlen, 0);
+ if (!ctx)
+ {
+ if (ret)
+ *ret = 600;
+ if (len)
+ *len = 0;
+ return NULL;
+ }
+ return http_async_req_stop(ctx, ret, len);
+}
+
+char *http_ret_text(int ret)
+{
+ switch (ret)
+ {
+ case 100:
+ return "Continue";
+ case 101:
+ return "Switching Protocols";
+ case 102:
+ return "Processing";
+
+ case 200:
+ return "OK";
+ case 201:
+ return "Created";
+ case 202:
+ return "Accepted";
+ case 203:
+ return "Non-Authoritative Information";
+ case 204:
+ return "No Content";
+ case 205:
+ return "Reset Content";
+ case 206:
+ return "Partial Content";
+ case 207:
+ return "Multi-Status";
+
+ case 300:
+ return "Multiple Choices";
+ case 301:
+ return "Moved Permanently";
+ case 302:
+ return "Found";
+ case 303:
+ return "See Other";
+ case 304:
+ return "Not Modified";
+ case 305:
+ return "Use Proxy";
+ case 306:
+ return "Switch Proxy";
+ case 307:
+ return "Temporary Redirect";
+
+ case 400:
+ return "Bad Request";
+ case 401:
+ return "Unauthorized";
+ case 402:
+ return "Payment Required";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 405:
+ return "Method Not Allowed";
+ case 406:
+ return "Not Acceptable";
+ case 407:
+ return "Proxy Authentication Required";
+ case 408:
+ return "Request Timeout";
+ case 409:
+ return "Conflict";
+ case 410:
+ return "Gone";
+ case 411:
+ return "Length Required";
+ case 412:
+ return "Precondition Failed";
+ case 413:
+ return "Request Entity Too Large";
+ case 414:
+ return "Request URI Too Long";
+ case 415:
+ return "Unsupported Media Type";
+ case 416:
+ return "Requested Range Not Satisfiable";
+ case 417:
+ return "Expectation Failed";
+ case 418:
+ return "I'm a teapot";
+ case 422:
+ return "Unprocessable Entity";
+ case 423:
+ return "Locked";
+ case 424:
+ return "Failed Dependency";
+ case 425:
+ return "Unordered Collection";
+ case 426:
+ return "Upgrade Required";
+ case 444:
+ return "No Response";
+ case 450:
+ return "Blocked by Windows Parental Controls";
+ case 499:
+ return "Client Closed Request";
+
+ case 500:
+ return "Internal Server Error";
+ case 501:
+ return "Not Implemented";
+ case 502:
+ return "Bad Gateway";
+ case 503:
+ return "Service Unavailable";
+ case 504:
+ return "Gateway Timeout";
+ case 505:
+ return "HTTP Version Not Supported";
+ case 506:
+ return "Variant Also Negotiates";
+ case 507:
+ return "Insufficient Storage";
+ case 509:
+ return "Bandwidth Limit Exceeded";
+ case 510:
+ return "Not Extended";
+
+ case 600:
+ return "Internal Client Error";
+ case 601:
+ return "Unsupported Protocol";
+ case 602:
+ return "Server Not Found";
+ case 603:
+ return "Malformed Response";
+ case 604:
+ return "Network Not Available";
+ case 605:
+ return "Request Timed Out";
+ default:
+ return "Unknown Status Code";
+ }
+}
+char *http_multipart_post(char *uri, char **names, char **parts, int *plens, char *user, char *pass, char *session_id, int *ret, int *len)
+{
+ void *ctx;
+ char *data = NULL, *tmp, *p;
+ int dlen = 0, i, j;
+ unsigned char hash[16];
+ unsigned char boundary[32], ch;
+ int blen = 0;
+ unsigned int map[62], m;
+ struct md5_context md5;
+ //struct md5_context md52;
+ int own_plen = 0;
+
+ if (names)
+ {
+ if (!plens)
+ {
+ own_plen = 1;
+ for (i=0; names[i]; i++) ;
+ plens = (int *)calloc(i, sizeof(int));
+ for (i=0; names[i]; i++)
+ plens[i] = strlen(parts[i]);
+ }
+
+retry:
+ if (blen >= 31)
+ goto fail;
+ memset(map, 0, 62*sizeof(int));
+ for (i=0; names[i]; i++)
+ {
+ for (j=0; j<plens[i]-blen; j++)
+ if (!blen || !memcmp(parts[i]+j, boundary, blen))
+ {
+ ch = parts[i][j+blen];
+ if (ch>='0' && ch<='9')
+ map[ch-'0']++;
+ else if (ch>='A' && ch<='Z')
+ map[ch-'A'+10]++;
+ else if (ch>='a' && ch<='z')
+ map[ch-'a'+36]++;
+ }
+ }
+ m = ~0;
+ j = 61;
+ for (i=0; i<62; i++)
+ if (map[i]<m)
+ {
+ m = map[i];
+ j = i;
+ }
+ if (j<10)
+ boundary[blen] = '0'+j;
+ else if (j<36)
+ boundary[blen] = 'A'+(j-10);
+ else
+ boundary[blen] = 'a'+(j-36);
+ blen++;
+ if (map[j])
+ goto retry;
+ boundary[blen] = 0;
+
+ for (i=0; names[i]; i++)
+ dlen += blen+strlen(names[i])+plens[i]+128;
+ dlen += blen+8;
+ data = (char *)malloc(dlen);
+ dlen = 0;
+ for (i=0; names[i]; i++)
+ {
+ dlen += sprintf(data+dlen, "--%s\r\n", boundary);
+ dlen += sprintf(data+dlen, "Content-transfer-encoding: binary\r\n");
+ if (strchr(names[i], ':'))
+ {
+ tmp = mystrdup(names[i]);
+ p = strchr(tmp, ':');
+ *p = 0;
+ dlen += sprintf(data+dlen, "content-disposition: form-data; name=\"%s\"; ", tmp);
+ free(tmp);
+ p = strchr(names[i], ':');
+ dlen += sprintf(data+dlen, "filename=\"%s\"\r\n\r\n", p+1);
+ }
+ else
+ dlen += sprintf(data+dlen, "content-disposition: form-data; name=\"%s\"\r\n\r\n", names[i]);
+ memcpy(data+dlen, parts[i], plens[i]);
+ dlen += plens[i];
+ dlen += sprintf(data+dlen, "\r\n");
+ }
+ dlen += sprintf(data+dlen, "--%s--\r\n", boundary);
+ }
+
+ ctx = http_async_req_start(NULL, uri, data, dlen, 0);
+ if (!ctx)
+ goto fail;
+
+ if (user)
+ {
+ //http_async_add_header(ctx, "X-Auth-User", user);
+ if (pass)
+ {
+ md5_init(&md5);
+ md5_update(&md5, (unsigned char *)user, strlen(user));
+ md5_update(&md5, (unsigned char *)"-", 1);
+ m = 0;
+ if (names)
+ {
+ for (i=0; names[i]; i++)
+ {
+ //md5_update(&md5, (unsigned char *)parts[i], plens[i]); //WHY?
+ //md5_update(&md5, (unsigned char *)"-", 1);
+ p = strchr(names[i], ':');
+ if (p)
+ m += (p - names[i]) + 1;
+ else
+ m += strlen(names[i])+1;
+ }
+
+ tmp = (char *)malloc(m);
+ m = 0;
+ for (i=0; names[i]; i++)
+ {
+ p = strchr(names[i], ':');
+ if (m)
+ {
+ tmp[m] = ' ';
+ m ++;
+ }
+ if (p)
+ {
+ memcpy(tmp+m, names[i], p-names[i]);
+ m += p - names[i];
+ }
+ else
+ {
+ strcpy(tmp+m, names[i]);
+ m += strlen(names[i]);
+ }
+ }
+ tmp[m] = 0;
+ http_async_add_header(ctx, "X-Auth-Objects", tmp);
+ free(tmp);
+ }
+
+ md5_update(&md5, (unsigned char *)pass, strlen(pass));
+ md5_final(hash, &md5);
+ tmp = (char *)malloc(33);
+ for (i=0; i<16; i++)
+ {
+ tmp[i*2] = hexChars[hash[i]>>4];
+ tmp[i*2+1] = hexChars[hash[i]&15];
+ }
+ tmp[32] = 0;
+ http_async_add_header(ctx, "X-Auth-Hash", tmp);
+ free(tmp);
+ }
+ if (session_id)
+ {
+ http_async_add_header(ctx, "X-Auth-User-Id", user);
+ http_async_add_header(ctx, "X-Auth-Session-Key", session_id);
+ }
+ else
+ {
+ http_async_add_header(ctx, "X-Auth-User", user);
+ }
+ }
+
+ if (data)
+ {
+ tmp = (char *)malloc(32+strlen((char *)boundary));
+ sprintf(tmp, "multipart/form-data, boundary=%s", boundary);
+ http_async_add_header(ctx, "Content-type", tmp);
+ free(tmp);
+ free(data);
+ }
+
+ if (own_plen)
+ free(plens);
+ return http_async_req_stop(ctx, ret, len);
+
+fail:
+ if (data)
+ free(data);
+ if (own_plen)
+ free(plens);
+ if (ret)
+ *ret = 600;
+ if (len)
+ *len = 0;
+ return NULL;
+}
diff --git a/src/client/HTTP.h b/src/client/HTTP.h
new file mode 100644
index 0000000..51b9438
--- /dev/null
+++ b/src/client/HTTP.h
@@ -0,0 +1,45 @@
+/**
+ * Powder Toy - HTTP Library (Header)
+ *
+ * Copyright (c) 2008 - 2010 Stanislaw Skowronek.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+#ifndef HTTP_H
+#define HTTP_H
+
+static char hexChars[] = "0123456789abcdef";
+
+void http_init(char *proxy);
+void http_done(void);
+
+char *http_simple_get(char *uri, int *ret, int *len);
+char *http_auth_get(char *uri, char *user, char *pass, char * session_id, int *ret, int *len);
+char *http_simple_post(char *uri, char *data, int dlen, int *ret, int *len);
+
+void http_auth_headers(void *ctx, char *user, char *pass, char * session_id);
+
+void *http_async_req_start(void *ctx, char *uri, char *data, int dlen, int keep);
+void http_async_add_header(void *ctx, char *name, char *data);
+int http_async_req_status(void *ctx);
+void http_async_get_length(void *ctx, int *total, int *done);
+char *http_async_req_stop(void *ctx, int *ret, int *len);
+void http_async_req_close(void *ctx);
+
+char *http_multipart_post(char *uri, char **names, char **parts, int *plens, char *user, char *pass, char * session_id, int *ret, int *len);
+
+char *http_ret_text(int ret);
+
+#endif
diff --git a/src/client/MD5.cpp b/src/client/MD5.cpp
new file mode 100644
index 0000000..d921bfa
--- /dev/null
+++ b/src/client/MD5.cpp
@@ -0,0 +1,231 @@
+// based on public-domain code from Colin Plumb (1993)
+#include <string.h>
+#include "MD5.h"
+
+static unsigned getu32(const unsigned char *addr)
+{
+ return (((((unsigned long)addr[3] << 8) | addr[2]) << 8) | addr[1]) << 8 | addr[0];
+}
+
+static void putu32(unsigned data, unsigned char *addr)
+{
+ addr[0] = (unsigned char)data;
+ addr[1] = (unsigned char)(data >> 8);
+ addr[2] = (unsigned char)(data >> 16);
+ addr[3] = (unsigned char)(data >> 24);
+}
+
+void md5_init(struct md5_context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+void md5_update(struct md5_context *ctx, unsigned char const *buf, unsigned len)
+{
+ unsigned t;
+
+ // update bit count
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = (t + ((unsigned)len << 3)) & 0xffffffff) < t)
+ ctx->bits[1]++; // carry
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f;
+
+ // use leading data to top up the buffer
+
+ if (t)
+ {
+ unsigned char *p = ctx->in + t;
+
+ t = 64-t;
+ if (len < t)
+ {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ md5_transform(ctx->buf, ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ // following 64-byte chunks
+
+ while (len >= 64)
+ {
+ memcpy(ctx->in, buf, 64);
+ md5_transform(ctx->buf, ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ // save rest of bytes for later
+
+ memcpy(ctx->in, buf, len);
+}
+
+void md5_final(unsigned char digest[16], struct md5_context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ // #bytes mod64
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ // first char of padding = 0x80
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ // calculate # of bytes to pad
+ count = 64 - 1 - count;
+
+ // Pad out to 56 mod 64
+ if (count < 8)
+ {
+ // we need to finish a whole block before padding
+ memset(p, 0, count);
+ md5_transform(ctx->buf, ctx->in);
+ memset(ctx->in, 0, 56);
+ }
+ else
+ {
+ // just pad to 56 bytes
+ memset(p, 0, count-8);
+ }
+
+ // append length & final transform
+ putu32(ctx->bits[0], ctx->in + 56);
+ putu32(ctx->bits[1], ctx->in + 60);
+
+ md5_transform(ctx->buf, ctx->in);
+ putu32(ctx->buf[0], digest);
+ putu32(ctx->buf[1], digest + 4);
+ putu32(ctx->buf[2], digest + 8);
+ putu32(ctx->buf[3], digest + 12);
+ memset(ctx, 0, sizeof(ctx));
+}
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w &= 0xffffffff, w = w<<s | w>>(32-s), w += x )
+
+void md5_transform(unsigned buf[4], const unsigned char inraw[64])
+{
+ unsigned a, b, c, d;
+ unsigned in[16];
+ int i;
+
+ for (i = 0; i < 16; ++i)
+ in[i] = getu32 (inraw + 4 * i);
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+static char hexChars[] = "0123456789abcdef";
+void md5_ascii(char *result, unsigned char const *buf, unsigned len)
+{
+ struct md5_context md5;
+ unsigned char hash[16];
+ int i;
+
+ if (len==0)
+ len = strlen((char *)buf);
+
+ md5_init(&md5);
+ md5_update(&md5, buf, len);
+ md5_final(hash, &md5);
+
+ for (i=0; i<16; i++)
+ {
+ result[i*2] = hexChars[(hash[i]>>4)&0xF];
+ result[i*2+1] = hexChars[hash[i]&0x0F];
+ }
+ result[32] = 0;
+}
diff --git a/src/client/MD5.h b/src/client/MD5.h
new file mode 100644
index 0000000..a8ef123
--- /dev/null
+++ b/src/client/MD5.h
@@ -0,0 +1,18 @@
+#ifndef MD5_H
+#define MD5_H
+
+struct md5_context
+{
+ unsigned buf[4];
+ unsigned bits[2];
+ unsigned char in[64];
+};
+
+void md5_init(struct md5_context *context);
+void md5_update(struct md5_context *context, unsigned char const *buf, unsigned len);
+void md5_final(unsigned char digest[16], struct md5_context *context);
+void md5_transform(unsigned buf[4], const unsigned char in[64]);
+
+void md5_ascii(char *result, unsigned char const *buf, unsigned len);
+
+#endif
diff --git a/src/client/SaveFile.cpp b/src/client/SaveFile.cpp
new file mode 100644
index 0000000..fe7ad4f
--- /dev/null
+++ b/src/client/SaveFile.cpp
@@ -0,0 +1,80 @@
+/*
+ * SaveFile.cpp
+ *
+ * Created on: Jun 6, 2012
+ * Author: Simon
+ */
+
+#include "SaveFile.h"
+#include "GameSave.h"
+#include "Client.h"
+#include "search/Thumbnail.h"
+
+SaveFile::SaveFile(SaveFile & save):
+ gameSave(NULL),
+ thumbnail(NULL),
+ filename(save.filename),
+ displayName(save.displayName)
+{
+ if(save.gameSave)
+ gameSave = new GameSave(*save.gameSave);
+ if(save.thumbnail)
+ thumbnail = new Thumbnail(*save.thumbnail);
+}
+
+Thumbnail * SaveFile::GetThumbnail()
+{
+ return thumbnail;
+}
+
+void SaveFile::SetThumbnail(Thumbnail * thumb)
+{
+ thumbnail = thumb;
+}
+
+SaveFile::SaveFile(std::string filename):
+ filename(filename),
+ displayName(filename),
+ gameSave(NULL),
+ thumbnail(NULL)
+{
+
+}
+
+GameSave * SaveFile::GetGameSave()
+{
+ return gameSave;
+}
+
+void SaveFile::SetGameSave(GameSave * save)
+{
+ gameSave = save;
+}
+
+std::string SaveFile::GetName()
+{
+ return filename;
+}
+
+void SaveFile::SetFileName(std::string fileName)
+{
+ this->filename = fileName;
+}
+
+std::string SaveFile::GetDisplayName()
+{
+ return displayName;
+}
+
+void SaveFile::SetDisplayName(std::string displayName)
+{
+ this->displayName = displayName;
+}
+
+SaveFile::~SaveFile() {
+ if(gameSave)
+ delete gameSave;
+ if(thumbnail)
+ delete thumbnail;
+}
+
diff --git a/src/client/SaveFile.h b/src/client/SaveFile.h
new file mode 100644
index 0000000..b63d181
--- /dev/null
+++ b/src/client/SaveFile.h
@@ -0,0 +1,38 @@
+/*
+ * SaveFile.h
+ *
+ * Created on: Jun 6, 2012
+ * Author: Simon
+ */
+
+#ifndef SAVEFILE_H_
+#define SAVEFILE_H_
+
+#include <string>
+
+class GameSave;
+class Thumbnail;
+
+class SaveFile {
+public:
+ SaveFile(SaveFile & save);
+ SaveFile(std::string filename);
+
+ Thumbnail * GetThumbnail();
+ GameSave * GetGameSave();
+ void SetThumbnail(Thumbnail * thumb);
+ void SetGameSave(GameSave * save);
+ std::string GetDisplayName();
+ void SetDisplayName(std::string displayName);
+ std::string GetName();
+ void SetFileName(std::string fileName);
+
+ virtual ~SaveFile();
+private:
+ Thumbnail * thumbnail;
+ GameSave * gameSave;
+ std::string filename;
+ std::string displayName;
+};
+
+#endif /* SAVEFILE_H_ */
diff --git a/src/client/SaveInfo.cpp b/src/client/SaveInfo.cpp
new file mode 100644
index 0000000..2d8fd52
--- /dev/null
+++ b/src/client/SaveInfo.cpp
@@ -0,0 +1,128 @@
+/*
+ * Save.cpp
+ *
+ * Created on: Jan 26, 2012
+ * Author: Simon
+ */
+
+#include "SaveInfo.h"
+#include "GameSave.h"
+#include "Client.h"
+
+SaveInfo::SaveInfo(SaveInfo & save) :
+ userName(save.userName), name(save.name), Description(save.Description), date(
+ save.date), Published(save.Published), id(save.id), votesUp(
+ save.votesUp), votesDown(save.votesDown), gameSave(NULL), vote(save.vote), tags(save.tags), Comments(save.Comments), Views(save.Views), Version(save.Version) {
+ if(save.gameSave)
+ gameSave = new GameSave(*save.gameSave);
+}
+
+SaveInfo::SaveInfo(int _id, int _date, int _votesUp, int _votesDown, std::string _userName,
+ std::string _name) :
+ id(_id), votesUp(_votesUp), votesDown(_votesDown), userName(_userName), name(
+ _name), Description(""), date(_date), Published(
+ true), gameSave(NULL), vote(0), tags(), Comments(0), Views(0), Version(0) {
+}
+
+SaveInfo::SaveInfo(int _id, int date_, int _votesUp, int _votesDown, int _vote, std::string _userName,
+ std::string _name, std::string description_, bool published_, std::vector<std::string> tags_) :
+ id(_id), votesUp(_votesUp), votesDown(_votesDown), userName(_userName), name(
+ _name), Description(description_), date(date_), Published(
+ published_), gameSave(NULL), vote(_vote), tags(tags_), Views(0), Comments(0), Version(0) {
+}
+
+SaveInfo::~SaveInfo()
+{
+ if(gameSave)
+ {
+ delete gameSave;
+ }
+}
+
+void SaveInfo::SetName(std::string name) {
+ this->name = name;
+}
+std::string SaveInfo::GetName() {
+ return name;
+}
+
+void SaveInfo::SetDescription(std::string description) {
+ Description = description;
+}
+std::string SaveInfo::GetDescription() {
+ return Description;
+}
+
+void SaveInfo::SetPublished(bool published) {
+ Published = published;
+}
+
+bool SaveInfo::GetPublished() {
+ return Published;
+}
+
+void SaveInfo::SetVote(int vote)
+{
+ this->vote = vote;
+}
+int SaveInfo::GetVote()
+{
+ return vote;
+}
+
+void SaveInfo::SetUserName(std::string userName) {
+ this->userName = userName;
+}
+
+std::string SaveInfo::GetUserName() {
+ return userName;
+}
+
+void SaveInfo::SetID(int id) {
+ this->id = id;
+}
+int SaveInfo::GetID() {
+ return id;
+}
+
+void SaveInfo::SetVotesUp(int votesUp) {
+ this->votesUp = votesUp;
+}
+int SaveInfo::GetVotesUp() {
+ return votesUp;
+}
+
+void SaveInfo::SetVotesDown(int votesDown) {
+ this->votesDown = votesDown;
+}
+int SaveInfo::GetVotesDown() {
+ return votesDown;
+}
+
+void SaveInfo::SetVersion(int version) {
+ this->Version = version;
+}
+int SaveInfo::GetVersion() {
+ return Version;
+}
+
+void SaveInfo::SetTags(std::vector<std::string> tags)
+{
+ this->tags = tags;
+}
+std::vector<std::string> SaveInfo::GetTags()
+{
+ return tags;
+}
+
+GameSave * SaveInfo::GetGameSave()
+{
+ return gameSave;
+}
+
+void SaveInfo::SetGameSave(GameSave * saveGame)
+{
+ if(gameSave)
+ delete gameSave;
+ gameSave = saveGame;
+}
diff --git a/src/client/SaveInfo.h b/src/client/SaveInfo.h
new file mode 100644
index 0000000..3f52c25
--- /dev/null
+++ b/src/client/SaveInfo.h
@@ -0,0 +1,78 @@
+#ifndef SAVE_H
+#define SAVE_H
+
+#include <vector>
+#include <string>
+#include <stdlib.h>
+#include <iostream>
+
+class GameSave;
+
+class SaveInfo
+{
+private:
+public:
+ int id;
+ int date;
+ int votesUp, votesDown;
+ bool Favourite;
+ int Comments;
+ int Views;
+ int Version;
+
+ GameSave * gameSave;
+
+ SaveInfo(SaveInfo & save);
+
+ SaveInfo(int _id, int _date, int _votesUp, int _votesDown, std::string _userName, std::string _name);
+
+ SaveInfo(int _id, int date_, int _votesUp, int _votesDown, int _vote, std::string _userName, std::string _name, std::string description_, bool published_, std::vector<std::string> tags);
+
+ ~SaveInfo();
+
+ std::string userName;
+ std::string name;
+
+ std::string Description;
+
+ std::vector<std::string> tags;
+
+ int vote;
+
+ bool Published;
+
+ void SetName(std::string name);
+ std::string GetName();
+
+ void SetDescription(std::string description);
+ std::string GetDescription();
+
+ void SetPublished(bool published);
+ bool GetPublished();
+
+ void SetUserName(std::string userName);
+ std::string GetUserName();
+
+ void SetID(int id);
+ int GetID();
+
+ void SetVote(int vote);
+ int GetVote();
+
+ void SetVotesUp(int votesUp);
+ int GetVotesUp();
+
+ void SetVotesDown(int votesDown);
+ int GetVotesDown();
+
+ void SetVersion(int version);
+ int GetVersion();
+
+ void SetTags(std::vector<std::string> tags);
+ std::vector<std::string> GetTags();
+
+ GameSave * GetGameSave();
+ void SetGameSave(GameSave * gameSave);
+};
+
+#endif // SAVE_H
diff --git a/src/client/ThumbnailBroker.cpp b/src/client/ThumbnailBroker.cpp
new file mode 100644
index 0000000..e884a98
--- /dev/null
+++ b/src/client/ThumbnailBroker.cpp
@@ -0,0 +1,347 @@
+#include <algorithm>
+#include <iostream>
+#include <typeinfo>
+#include "ThumbnailBroker.h"
+#include "ThumbnailListener.h"
+#include "Client.h"
+#include "HTTP.h"
+#include "GameSave.h"
+#include "search/Thumbnail.h"
+#include "simulation/SaveRenderer.h"
+
+//Asynchronous Thumbnail render & request processing
+
+ThumbnailBroker::ThumbnailBroker()
+{
+ thumbnailQueueRunning = false;
+ //thumbnailQueueMutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_init (&thumbnailQueueMutex, NULL);
+
+ //listenersMutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_init (&listenersMutex, NULL);
+}
+
+ThumbnailBroker::~ThumbnailBroker()
+{
+
+}
+
+void ThumbnailBroker::RenderThumbnail(GameSave * gameSave, int width, int height, ThumbnailListener * tListener)
+{
+ RenderThumbnail(gameSave, true, true, width, height, tListener);
+}
+
+void ThumbnailBroker::RenderThumbnail(GameSave * gameSave, bool decorations, bool fire, int width, int height, ThumbnailListener * tListener)
+{
+ AttachThumbnailListener(tListener);
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ bool running = thumbnailQueueRunning;
+ thumbnailQueueRunning = true;
+ renderRequests.push_back(ThumbRenderRequest(new GameSave(*gameSave), decorations, fire, width, height, ListenerHandle(tListener->ListenerRand, tListener)));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+ if(!running)
+ {
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Starting background thread for new " << __FUNCTION__ << " request" << std::endl;
+#endif
+ pthread_create(&thumbnailQueueThread, 0, &ThumbnailBroker::thumbnailQueueProcessHelper, this);
+ }
+}
+
+void ThumbnailBroker::RetrieveThumbnail(int saveID, int saveDate, int width, int height, ThumbnailListener * tListener)
+{
+ AttachThumbnailListener(tListener);
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ bool running = thumbnailQueueRunning;
+ thumbnailQueueRunning = true;
+ thumbnailRequests.push_back(ThumbnailRequest(saveID, saveDate, width, height, ListenerHandle(tListener->ListenerRand, tListener)));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+ if(!running)
+ {
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Starting background thread for new " << __FUNCTION__ << " request" << std::endl;
+#endif
+ pthread_create(&thumbnailQueueThread, 0, &ThumbnailBroker::thumbnailQueueProcessHelper, this);
+ }
+}
+
+void * ThumbnailBroker::thumbnailQueueProcessHelper(void * ref)
+{
+ ((ThumbnailBroker*)ref)->thumbnailQueueProcessTH();
+ return NULL;
+}
+
+void ThumbnailBroker::FlushThumbQueue()
+{
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ while(thumbnailComplete.size())
+ {
+ if(CheckThumbnailListener(thumbnailComplete.front().first))
+ {
+ thumbnailComplete.front().first.second->OnThumbnailReady(thumbnailComplete.front().second);
+ }
+ else
+ {
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Listener lost, discarding request" << std::endl;
+#endif
+ delete thumbnailComplete.front().second;
+ }
+ thumbnailComplete.pop_front();
+ }
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+}
+
+void ThumbnailBroker::thumbnailQueueProcessTH()
+{
+ time_t lastAction = time(NULL);
+ while(true)
+ {
+ //Shutdown after 2 seconds of idle
+ if(time(NULL) - lastAction > 2)
+ {
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ thumbnailQueueRunning = false;
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ break;
+ }
+
+ //Renderer
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ if(renderRequests.size())
+ {
+ lastAction = time(NULL);
+ ThumbRenderRequest req;
+ req = renderRequests.front();
+ renderRequests.pop_front();
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Processing render request" << std::endl;
+#endif
+
+ Thumbnail * thumbnail = SaveRenderer::Ref().Render(req.Save, req.Decorations, req.Fire);
+ delete req.Save;
+
+ if(thumbnail)
+ {
+ thumbnail->Resize(req.Width, req.Height);
+
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ thumbnailComplete.push_back(std::pair<ListenerHandle, Thumbnail*>(req.CompletedListener, thumbnail));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+ }
+ else
+ {
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+
+ //Renderer
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ if(thumbnailRequests.size())
+ {
+ lastAction = time(NULL);
+ Thumbnail * thumbnail = NULL;
+
+ ThumbnailRequest req;
+ req = thumbnailRequests.front();
+
+ //Check the cache
+ for(std::deque<std::pair<ThumbnailID, Thumbnail*> >::iterator iter = thumbnailCache.begin(), end = thumbnailCache.end(); iter != end; ++iter)
+ {
+ if((*iter).first == req.ID)
+ {
+ thumbnail = (*iter).second;
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " " << req.ID.SaveID << ":" << req.ID.SaveDate << " found in cache" << std::endl;
+#endif
+ }
+ }
+
+ if(thumbnail)
+ {
+ //Got thumbnail from cache
+ thumbnailRequests.pop_front();
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+ for(std::vector<ThumbnailSpec>::iterator specIter = req.SubRequests.begin(), specEnd = req.SubRequests.end(); specIter != specEnd; ++specIter)
+ {
+ Thumbnail * tempThumbnail = new Thumbnail(*thumbnail);
+ tempThumbnail->Resize((*specIter).Width, (*specIter).Height);
+
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ thumbnailComplete.push_back(std::pair<ListenerHandle, Thumbnail*>((*specIter).CompletedListener, tempThumbnail));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+ }
+ else
+ {
+ //Check for ongoing requests
+ bool requested = false;
+ for(std::list<ThumbnailRequest>::iterator iter = currentRequests.begin(), end = currentRequests.end(); iter != end; ++iter)
+ {
+ if((*iter).ID == req.ID)
+ {
+ requested = true;
+
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Request for " << req.ID.SaveID << ":" << req.ID.SaveDate << " found, appending." << std::endl;
+#endif
+
+ //Add the current listener to the item already being requested
+ (*iter).SubRequests.push_back(req.SubRequests.front());
+ }
+ }
+
+ if(requested)
+ {
+ //Already requested
+ thumbnailRequests.pop_front();
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+ else if(currentRequests.size() < IMGCONNS) //If it's not already being requested and we still have more space for a new connection, request it
+ {
+ thumbnailRequests.pop_front();
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+ //If it's not already being requested, request it
+ std::stringstream urlStream;
+ urlStream << "http://" << STATICSERVER << "/" << req.ID.SaveID;
+ if(req.ID.SaveDate)
+ {
+ urlStream << "_" << req.ID.SaveDate;
+ }
+ urlStream << "_small.pti";
+
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Creating new request for " << req.ID.SaveID << ":" << req.ID.SaveDate << std::endl;
+#endif
+
+ req.HTTPContext = http_async_req_start(NULL, (char *)urlStream.str().c_str(), NULL, 0, 1);
+ req.RequestTime = time(NULL);
+ currentRequests.push_back(req);
+ }
+ else
+ {
+ //Already full of requests
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+
+ }
+ }
+ }
+ else
+ {
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+
+ std::list<ThumbnailRequest>::iterator iter = currentRequests.begin();
+ while (iter != currentRequests.end())
+ {
+ lastAction = time(NULL);
+
+ ThumbnailRequest req = *iter;
+ Thumbnail * thumbnail = NULL;
+
+ if(http_async_req_status(req.HTTPContext))
+ {
+
+ pixel * thumbData;
+ char * data;
+ int status, data_size, imgw, imgh;
+ data = http_async_req_stop(req.HTTPContext, &status, &data_size);
+ free(req.HTTPContext);
+
+ if (status == 200 && data)
+ {
+ thumbData = Graphics::ptif_unpack(data, data_size, &imgw, &imgh);
+ free(data);
+
+ if(thumbData)
+ {
+ thumbnail = new Thumbnail(req.ID.SaveID, req.ID.SaveID, thumbData, ui::Point(imgw, imgh));
+ free(thumbData);
+ }
+ else
+ {
+ //Error thumbnail
+ VideoBuffer errorThumb(128, 128);
+ errorThumb.SetCharacter(64, 64, 'x', 255, 255, 255, 255);
+
+ thumbnail = new Thumbnail(req.ID.SaveID, req.ID.SaveID, errorThumb.Buffer, ui::Point(errorThumb.Width, errorThumb.Height));
+ }
+
+ if(thumbnailCache.size() >= THUMB_CACHE_SIZE)
+ {
+ delete thumbnailCache.front().second;
+ thumbnailCache.pop_front();
+ }
+ thumbnailCache.push_back(std::pair<ThumbnailID, Thumbnail*>(req.ID, thumbnail));
+
+ for(std::vector<ThumbnailSpec>::iterator specIter = req.SubRequests.begin(), specEnd = req.SubRequests.end(); specIter != specEnd; ++specIter)
+ {
+ Thumbnail * tempThumbnail = new Thumbnail(*thumbnail);
+ tempThumbnail->Resize((*specIter).Width, (*specIter).Height);
+
+ pthread_mutex_lock(&thumbnailQueueMutex);
+ thumbnailComplete.push_back(std::pair<ListenerHandle, Thumbnail*>((*specIter).CompletedListener, tempThumbnail));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+ }
+ else
+ {
+#ifdef DEBUG
+ std::cout << typeid(*this).name() << " Request for " << req.ID.SaveID << ":" << req.ID.SaveDate << " failed with status " << status << std::endl;
+#endif
+ if(data)
+ free(data);
+ }
+ iter = currentRequests.erase(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+
+ }
+}
+
+void ThumbnailBroker::RetrieveThumbnail(int saveID, int width, int height, ThumbnailListener * tListener)
+{
+ RetrieveThumbnail(saveID, 0, width, height, tListener);
+}
+
+bool ThumbnailBroker::CheckThumbnailListener(ListenerHandle handle)
+{
+ pthread_mutex_lock(&listenersMutex);
+ int count = std::count(validListeners.begin(), validListeners.end(), handle);
+ pthread_mutex_unlock(&listenersMutex);
+
+ return count;
+}
+
+void ThumbnailBroker::AttachThumbnailListener(ThumbnailListener * tListener)
+{
+ pthread_mutex_lock(&listenersMutex);
+ validListeners.push_back(ListenerHandle(tListener->ListenerRand, tListener));
+ pthread_mutex_unlock(&listenersMutex);
+}
+
+void ThumbnailBroker::DetachThumbnailListener(ThumbnailListener * tListener)
+{
+ pthread_mutex_lock(&listenersMutex);
+
+ std::vector<ListenerHandle>::iterator iter = validListeners.begin();
+ while (iter != validListeners.end())
+ {
+ if(*iter == ListenerHandle(tListener->ListenerRand, tListener))
+ iter = validListeners.erase(iter);
+ else
+ ++iter;
+ }
+
+ pthread_mutex_unlock(&listenersMutex);
+} \ No newline at end of file
diff --git a/src/client/ThumbnailBroker.h b/src/client/ThumbnailBroker.h
new file mode 100644
index 0000000..ba6d3ae
--- /dev/null
+++ b/src/client/ThumbnailBroker.h
@@ -0,0 +1,108 @@
+#pragma once
+#include <queue>
+#include <list>
+#include <utility>
+#include <deque>
+#include <pthread.h>
+#undef GetUserName //God dammit microsoft!
+
+#include "Singleton.h"
+
+class GameSave;
+class Thumbnail;
+class ThumbnailListener;
+typedef std::pair<int, ThumbnailListener*> ListenerHandle;
+class ThumbnailBroker: public Singleton<ThumbnailBroker>
+{
+private:
+ class ThumbnailSpec
+ {
+ public:
+ int Width, Height;
+ ListenerHandle CompletedListener;
+ ThumbnailSpec(int width, int height, ListenerHandle completedListener) :
+ Width(width), Height(height), CompletedListener(completedListener) {}
+ };
+
+ class ThumbnailID
+ {
+ public:
+ int SaveID, SaveDate;
+ bool operator ==(const ThumbnailID & second)
+ {
+ return SaveID == second.SaveID && SaveDate == second.SaveDate;
+ }
+ ThumbnailID(int saveID, int saveDate) : SaveID(saveID), SaveDate(saveDate) {}
+ ThumbnailID() : SaveID(0), SaveDate(0) {}
+ };
+
+ class ThumbnailRequest
+ {
+ public:
+ bool Complete;
+ void * HTTPContext;
+ int RequestTime;
+
+ ThumbnailID ID;
+ std::vector<ThumbnailSpec> SubRequests;
+
+ ThumbnailRequest(int saveID, int saveDate, int width, int height, ListenerHandle completedListener) :
+ ID(saveID, saveDate), Complete(false), HTTPContext(NULL), RequestTime(0)
+ {
+ SubRequests.push_back(ThumbnailSpec(width, height, completedListener));
+ }
+ ThumbnailRequest() : Complete(false), HTTPContext(NULL), RequestTime(0) {}
+ };
+
+ class ThumbRenderRequest
+ {
+ public:
+ int Width, Height;
+ bool Decorations;
+ bool Fire;
+ GameSave * Save;
+ ListenerHandle CompletedListener;
+ ThumbRenderRequest(GameSave * save, bool decorations, bool fire, int width, int height, ListenerHandle completedListener) :
+ Save(save), Width(width), Height(height), CompletedListener(completedListener), Decorations(decorations), Fire(fire) {}
+ ThumbRenderRequest() : Save(0), Decorations(true), Fire(true), Width(0), Height(0), CompletedListener(ListenerHandle(0, (ThumbnailListener*)NULL)) {}
+ };
+
+ //Thumbnail retreival
+ /*int thumbnailCacheNextID;
+ Thumbnail * thumbnailCache[THUMB_CACHE_SIZE];
+ void * activeThumbRequests[IMGCONNS];
+ int activeThumbRequestTimes[IMGCONNS];
+ int activeThumbRequestCompleteTimes[IMGCONNS];
+ std::string activeThumbRequestIDs[IMGCONNS];*/
+
+ pthread_mutex_t thumbnailQueueMutex;
+ pthread_mutex_t listenersMutex;
+ pthread_t thumbnailQueueThread;
+ bool thumbnailQueueRunning;
+ std::deque<ThumbnailRequest> thumbnailRequests;
+ std::deque<ThumbRenderRequest> renderRequests;
+
+ std::deque<std::pair<ListenerHandle, Thumbnail*> > thumbnailComplete;
+ std::list<ThumbnailRequest> currentRequests;
+ std::deque<std::pair<ThumbnailID, Thumbnail*> > thumbnailCache;
+
+
+ std::vector<ListenerHandle> validListeners;
+
+ static void * thumbnailQueueProcessHelper(void * ref);
+ void thumbnailQueueProcessTH();
+
+public:
+ ThumbnailBroker();
+ virtual ~ThumbnailBroker();
+
+ void FlushThumbQueue();
+ void RenderThumbnail(GameSave * gameSave, bool decorations, bool fire, int width, int height, ThumbnailListener * tListener);
+ void RenderThumbnail(GameSave * gameSave, int width, int height, ThumbnailListener * tListener);
+ void RetrieveThumbnail(int saveID, int saveDate, int width, int height, ThumbnailListener * tListener);
+ void RetrieveThumbnail(int saveID, int width, int height, ThumbnailListener * tListener);
+
+ bool CheckThumbnailListener(ListenerHandle handle);
+ void AttachThumbnailListener(ThumbnailListener * tListener);
+ void DetachThumbnailListener(ThumbnailListener * tListener);
+}; \ No newline at end of file
diff --git a/src/client/ThumbnailListener.h b/src/client/ThumbnailListener.h
new file mode 100644
index 0000000..97bdef5
--- /dev/null
+++ b/src/client/ThumbnailListener.h
@@ -0,0 +1,12 @@
+#pragma once
+
+class Thumbnail;
+class ThumbnailListener
+{
+public:
+ int ListenerRand;
+ ThumbnailListener() { ListenerRand = rand(); }
+ virtual ~ThumbnailListener() {}
+
+ virtual void OnThumbnailReady(Thumbnail * thumb) {}
+};
diff --git a/src/client/User.h b/src/client/User.h
new file mode 100644
index 0000000..5a47a3e
--- /dev/null
+++ b/src/client/User.h
@@ -0,0 +1,38 @@
+/*
+ * User.h
+ *
+ * Created on: Jan 25, 2012
+ * Author: Simon
+ */
+
+#ifndef USER_H_
+#define USER_H_
+
+#include <string>
+
+
+class User
+{
+public:
+ enum Elevation
+ {
+ ElevationAdmin, ElevationModerator, ElevationNone
+ };
+ int ID;
+ std::string Username;
+ std::string SessionID;
+ std::string SessionKey;
+ Elevation UserElevation;
+ User(int id, std::string username):
+ ID(id),
+ Username(username),
+ SessionID(""),
+ SessionKey(""),
+ UserElevation(ElevationNone)
+ {
+
+ }
+};
+
+
+#endif /* USER_H_ */