diff options
| author | Simon 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) |
| commit | 058a2edd75debbd0297f92572316daa704bd379f (patch) | |
| tree | ad303f091f9a08b209b91eb34a9fcad996a3de69 /src/client | |
| parent | e3594aba9e05c6865d396418c028049cda92c2f3 (diff) | |
| parent | 7a21ae192fe19868539956f3fe28e62b2c7c4429 (diff) | |
| download | powder-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.cpp | 2351 | ||||
| -rw-r--r-- | src/client/Client.h | 176 | ||||
| -rw-r--r-- | src/client/ClientListener.h | 24 | ||||
| -rw-r--r-- | src/client/GameSave.cpp | 2092 | ||||
| -rw-r--r-- | src/client/GameSave.h | 112 | ||||
| -rw-r--r-- | src/client/HTTP.cpp | 1104 | ||||
| -rw-r--r-- | src/client/HTTP.h | 45 | ||||
| -rw-r--r-- | src/client/MD5.cpp | 231 | ||||
| -rw-r--r-- | src/client/MD5.h | 18 | ||||
| -rw-r--r-- | src/client/SaveFile.cpp | 80 | ||||
| -rw-r--r-- | src/client/SaveFile.h | 38 | ||||
| -rw-r--r-- | src/client/SaveInfo.cpp | 128 | ||||
| -rw-r--r-- | src/client/SaveInfo.h | 78 | ||||
| -rw-r--r-- | src/client/ThumbnailBroker.cpp | 347 | ||||
| -rw-r--r-- | src/client/ThumbnailBroker.h | 108 | ||||
| -rw-r--r-- | src/client/ThumbnailListener.h | 12 | ||||
| -rw-r--r-- | src/client/User.h | 38 |
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(), ¤tFile); + 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, ¤tFile) == 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_ */ |
