From ab600780d07be33b22dc2684c57c9dee0cb62df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Thu, 10 Nov 2022 14:59:09 +0100 Subject: [PATCH] Clean up GameSave somewhat Namely: - get rid of unsafe memory management; - use vectors / Planes everywhere; - return a vector from serialization functions; - have read functions take a vector; - improve constness; - hide a few implementation details from GameSave.h; - get rid of GameSave copy constructor; - better member initialization; - use the slightly more C++-looking BZ2 wrappers. The BSON library still takes ownership of the data it parses, and GameSave ownership is still a joke. Those will need to be fixed later. --- src/Config.template.h | 1 + src/bzip2/bz2wrap.cpp | 18 +- src/client/Client.cpp | 24 +- src/client/GameSave.cpp | 447 ++++++++++------------------- src/client/GameSave.h | 140 +++++---- src/gui/game/GameController.cpp | 2 +- src/gui/save/LocalSaveActivity.cpp | 2 +- src/simulation/ElementDefs.h | 1 - src/simulation/Sign.cpp | 4 +- src/simulation/Sign.h | 4 +- 10 files changed, 242 insertions(+), 401 deletions(-) diff --git a/src/Config.template.h b/src/Config.template.h index 00e8fd266..cdfc7147e 100644 --- a/src/Config.template.h +++ b/src/Config.template.h @@ -153,5 +153,6 @@ #define GLASS_DISP 0.07 #define SDEUT +#define R_TEMP 22 #endif /* CONFIG_H */ diff --git a/src/bzip2/bz2wrap.cpp b/src/bzip2/bz2wrap.cpp index 0b59ac193..0d0adb991 100644 --- a/src/bzip2/bz2wrap.cpp +++ b/src/bzip2/bz2wrap.cpp @@ -36,7 +36,14 @@ BZ2WCompressResult BZ2WCompress(std::vector &dest, const char *srcData, si { return BZ2WCompressLimit; } - dest.resize(newSize); + try + { + dest.resize(newSize); + } + catch (const std::bad_alloc &) + { + return BZ2WCompressNomem; + } stream.next_out = &dest[stream.total_out_lo32]; stream.avail_out = dest.size() - stream.total_out_lo32; if (BZ2_bzCompress(&stream, BZ_FINISH) == BZ_STREAM_END) @@ -75,7 +82,14 @@ BZ2WDecompressResult BZ2WDecompress(std::vector &dest, const char *srcData { return BZ2WDecompressLimit; } - dest.resize(newSize); + try + { + dest.resize(newSize); + } + catch (const std::bad_alloc &) + { + return BZ2WDecompressNomem; + } stream.next_out = &dest[stream.total_out_lo32]; stream.avail_out = dest.size() - stream.total_out_lo32; switch (BZ2_bzDecompress(&stream)) diff --git a/src/client/Client.cpp b/src/client/Client.cpp index d03c637d3..a3730febf 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -2,6 +2,7 @@ #include "client/http/Request.h" // includes curl.h, needs to come first to silence a warning on windows +#include #include #include #include @@ -496,8 +497,6 @@ User Client::GetAuthUser() RequestStatus Client::UploadSave(SaveInfo & save) { lastError = ""; - unsigned int gameDataLength; - char * gameData = NULL; int dataStatus; ByteString data; ByteString userID = ByteString::Build(authUser.UserID); @@ -511,15 +510,15 @@ RequestStatus Client::UploadSave(SaveInfo & save) save.SetID(0); - gameData = save.GetGameSave()->Serialise(gameDataLength); + auto [ fromNewerVersion, gameData ] = save.GetGameSave()->Serialise(); - if (!gameData) + if (!gameData.size()) { lastError = "Cannot serialize game save"; return RequestFailure; } #if defined(SNAPSHOT) || defined(BETA) || defined(DEBUG) || MOD_ID > 0 - else if (save.gameSave->fromNewerVersion && save.GetPublished()) + else if (fromNewerVersion && save.GetPublished()) { lastError = "Cannot publish save, incompatible with latest release version."; return RequestFailure; @@ -529,7 +528,7 @@ RequestStatus Client::UploadSave(SaveInfo & save) data = http::Request::SimpleAuth(SCHEME SERVER "/Save.api", &dataStatus, userID, authUser.SessionID, { { "Name", save.GetName().ToUtf8() }, { "Description", save.GetDescription().ToUtf8() }, - { "Data:save.bin", ByteString(gameData, gameData + gameDataLength) }, + { "Data:save.bin", ByteString(gameData.begin(), gameData.end()) }, { "Publish", save.GetPublished() ? "Public" : "Private" }, }); } @@ -551,7 +550,6 @@ RequestStatus Client::UploadSave(SaveInfo & save) else save.SetID(saveID); } - delete[] gameData; return ret; } @@ -623,17 +621,11 @@ ByteString Client::AddStamp(GameSave * saveData) } saveData->authors = stampInfo; - unsigned int gameDataLength; - char * gameData = saveData->Serialise(gameDataLength); - if (gameData == NULL) + auto [ _, gameData ] = saveData->Serialise(); + if (!gameData.size()) return ""; - std::ofstream stampStream; - stampStream.open(filename.c_str(), std::ios::binary); - stampStream.write((const char *)gameData, gameDataLength); - stampStream.close(); - - delete[] gameData; + Platform::WriteFile(gameData, filename); stampIDs.push_front(saveID); diff --git a/src/client/GameSave.cpp b/src/client/GameSave.cpp index ba5eddfe7..f0dc0e1f8 100644 --- a/src/client/GameSave.cpp +++ b/src/client/GameSave.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include "bzip2/bz2wrap.h" #include "Config.h" #include "Format.h" #include "hmap.h" @@ -17,49 +17,17 @@ #include "common/tpt-minmax.h" #include "common/tpt-compat.h" +#include "bson/BSON.h" -GameSave::GameSave(const GameSave & save): - majorVersion(save.majorVersion), - waterEEnabled(save.waterEEnabled), - legacyEnable(save.legacyEnable), - gravityEnable(save.gravityEnable), - aheatEnable(save.aheatEnable), - paused(save.paused), - gravityMode(save.gravityMode), - customGravityX(save.customGravityX), - customGravityY(save.customGravityY), - airMode(save.airMode), - ambientAirTemp(save.ambientAirTemp), - edgeMode(save.edgeMode), - signs(save.signs), - stkm(save.stkm), - palette(save.palette), - pmapbits(save.pmapbits) -{ - InitData(); - hasPressure = save.hasPressure; - hasAmbientHeat = save.hasAmbientHeat; - setSize(save.blockWidth, save.blockHeight); - - std::copy(save.particles, save.particles+NPART, particles); - for (int j = 0; j < blockHeight; j++) - { - std::copy(save.blockMap[j], save.blockMap[j]+blockWidth, blockMap[j]); - std::copy(save.fanVelX[j], save.fanVelX[j]+blockWidth, fanVelX[j]); - std::copy(save.fanVelY[j], save.fanVelY[j]+blockWidth, fanVelY[j]); - std::copy(save.pressure[j], save.pressure[j]+blockWidth, pressure[j]); - std::copy(save.velocityX[j], save.velocityX[j]+blockWidth, velocityX[j]); - std::copy(save.velocityY[j], save.velocityY[j]+blockWidth, velocityY[j]); - std::copy(save.ambientHeat[j], save.ambientHeat[j]+blockWidth, ambientHeat[j]); - } - particlesCount = save.particlesCount; - authors = save.authors; -} +static void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0); +static void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0); +static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen); +static void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag); +static void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting); +static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting); GameSave::GameSave(int width, int height) { - InitData(); - InitVars(); setSize(width, height); } @@ -68,8 +36,6 @@ GameSave::GameSave(const std::vector &data) blockWidth = 0; blockHeight = 0; - InitData(); - InitVars(); try { Expand(data); @@ -77,67 +43,25 @@ GameSave::GameSave(const std::vector &data) catch(ParseException & e) { std::cout << e.what() << std::endl; - dealloc(); //Free any allocated memory throw; } } -// Called on every new GameSave, including the copy constructor -void GameSave::InitData() -{ - blockMap = NULL; - fanVelX = NULL; - fanVelY = NULL; - particles = NULL; - pressure = NULL; - velocityX = NULL; - velocityY = NULL; - ambientHeat = NULL; - fromNewerVersion = false; - hasPressure = false; - hasAmbientHeat = false; - authors.clear(); -} - -// Called on every new GameSave, except the copy constructor -void GameSave::InitVars() -{ - majorVersion = 0; - minorVersion = 0; - waterEEnabled = false; - legacyEnable = false; - gravityEnable = false; - aheatEnable = false; - paused = false; - gravityMode = 0; - customGravityX = 0.0f; - customGravityY = 0.0f; - airMode = 0; - ambientAirTemp = R_TEMP + 273.15f; - edgeMode = 0; - translated.x = translated.y = 0; - pmapbits = 8; // default to 8 bits for older saves -} - void GameSave::Expand(const std::vector &data) { - InitVars(); - read(&data[0], data.size()); -} - -void GameSave::read(const char * data, int dataSize) -{ - if(dataSize > 15) + try + { + if(data.size() > 15) { if ((data[0]==0x66 && data[1]==0x75 && data[2]==0x43) || (data[0]==0x50 && data[1]==0x53 && data[2]==0x76)) { - readPSv(data, dataSize); + readPSv(data); } else if(data[0] == 'O' && data[1] == 'P' && data[2] == 'S') { if (data[3] != '1') throw ParseException(ParseException::WrongVersion, "Save format from newer version"); - readOPS(data, dataSize); + readOPS(data); } else { @@ -149,59 +73,45 @@ void GameSave::read(const char * data, int dataSize) { throw ParseException(ParseException::Corrupt, "No data"); } -} - -template -T ** GameSave::Allocate2DArray(int blockWidth, int blockHeight, T defaultVal) -{ - T ** temp = new T*[blockHeight]; - for (int y = 0; y < blockHeight; y++) - { - temp[y] = new T[blockWidth]; - std::fill(&temp[y][0], &temp[y][0]+blockWidth, defaultVal); } - return temp; + catch (const std::bad_alloc &) + { + throw ParseException(ParseException::Corrupt, "Cannot allocate memory"); + } } void GameSave::setSize(int newWidth, int newHeight) { - this->blockWidth = newWidth; - this->blockHeight = newHeight; + blockWidth = newWidth; + blockHeight = newHeight; particlesCount = 0; - particles = new Particle[NPART]; + particles = std::vector(NPART); - blockMap = Allocate2DArray(blockWidth, blockHeight, 0); - fanVelX = Allocate2DArray(blockWidth, blockHeight, 0.0f); - fanVelY = Allocate2DArray(blockWidth, blockHeight, 0.0f); - pressure = Allocate2DArray(blockWidth, blockHeight, 0.0f); - velocityX = Allocate2DArray(blockWidth, blockHeight, 0.0f); - velocityY = Allocate2DArray(blockWidth, blockHeight, 0.0f); - ambientHeat = Allocate2DArray(blockWidth, blockHeight, 0.0f); + blockMap = Plane(blockWidth, blockHeight, 0); + fanVelX = Plane(blockWidth, blockHeight, 0.0f); + fanVelY = Plane(blockWidth, blockHeight, 0.0f); + pressure = Plane(blockWidth, blockHeight, 0.0f); + velocityX = Plane(blockWidth, blockHeight, 0.0f); + velocityY = Plane(blockWidth, blockHeight, 0.0f); + ambientHeat = Plane(blockWidth, blockHeight, 0.0f); } -std::vector GameSave::Serialise() -{ - unsigned int dataSize; - char * data = Serialise(dataSize); - if (data == NULL) - return std::vector(); - std::vector dataVect(data, data+dataSize); - delete[] data; - return dataVect; -} - -char * GameSave::Serialise(unsigned int & dataSize) +std::pair> GameSave::Serialise() const { try { - return serialiseOPS(dataSize); + return serialiseOPS(); + } + catch (const std::bad_alloc &) + { + std::cout << "Save error, out of memory" << std::endl; } catch (BuildException & e) { std::cout << e.what() << std::endl; - return NULL; } + return { false, {} }; } vector2d GameSave::Translate(vector2d translate) @@ -312,16 +222,13 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl int x, y, nx, ny, newBlockWidth = newWidth / CELL, newBlockHeight = newHeight / CELL; vector2d pos, vel; - unsigned char ** blockMapNew; - float **fanVelXNew, **fanVelYNew, **pressureNew, **velocityXNew, **velocityYNew, **ambientHeatNew; - - blockMapNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0); - fanVelXNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); - fanVelYNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); - pressureNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); - velocityXNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); - velocityYNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); - ambientHeatNew = Allocate2DArray(newBlockWidth, newBlockHeight, 0.0f); + Plane blockMapNew(newBlockWidth, newBlockHeight, 0); + Plane fanVelXNew(newBlockWidth, newBlockHeight, 0.0f); + Plane fanVelYNew(newBlockWidth, newBlockHeight, 0.0f); + Plane pressureNew(newBlockWidth, newBlockHeight, 0.0f); + Plane velocityXNew(newBlockWidth, newBlockHeight, 0.0f); + Plane velocityYNew(newBlockWidth, newBlockHeight, 0.0f); + Plane ambientHeatNew(newBlockWidth, newBlockHeight, 0.0f); // Match these up with the matrices provided in GameView::OnKeyPress. bool patchPipeR = transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0; @@ -423,28 +330,9 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl } translated = v2d_add(m2d_multiply_v2d(transform, translated), translateReal); - for (int j = 0; j < blockHeight; j++) - { - delete[] blockMap[j]; - delete[] fanVelX[j]; - delete[] fanVelY[j]; - delete[] pressure[j]; - delete[] velocityX[j]; - delete[] velocityY[j]; - delete[] ambientHeat[j]; - } - blockWidth = newBlockWidth; blockHeight = newBlockHeight; - delete[] blockMap; - delete[] fanVelX; - delete[] fanVelY; - delete[] pressure; - delete[] velocityX; - delete[] velocityY; - delete[] ambientHeat; - blockMap = blockMapNew; fanVelX = fanVelXNew; fanVelY = fanVelYNew; @@ -454,7 +342,7 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl ambientHeat = ambientHeatNew; } -void GameSave::CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen) +static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen) { if (!strcmp(bson_iterator_key(&iter), field)) { @@ -469,7 +357,7 @@ void GameSave::CheckBsonFieldUser(bson_iterator iter, const char *field, unsigne } } -void GameSave::CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag) +static void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag) { if (!strcmp(bson_iterator_key(&iter), field)) { @@ -484,7 +372,7 @@ void GameSave::CheckBsonFieldBool(bson_iterator iter, const char *field, bool *f } } -void GameSave::CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting) +static void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting) { if (!strcmp(bson_iterator_key(&iter), field)) { @@ -499,7 +387,7 @@ void GameSave::CheckBsonFieldInt(bson_iterator iter, const char *field, int *set } } -void GameSave::CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting) +static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting) { if (!strcmp(bson_iterator_key(&iter), field)) { @@ -514,11 +402,12 @@ void GameSave::CheckBsonFieldFloat(bson_iterator iter, const char *field, float } } -void GameSave::readOPS(const char * data, int dataLength) +void GameSave::readOPS(const std::vector &data) { - unsigned char *inputData = (unsigned char*)data, *bsonData = NULL, *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL; + + unsigned char *inputData = (unsigned char*)&data[0], *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL; unsigned char *pressData = NULL, *vxData = NULL, *vyData = NULL, *ambientData = NULL; - unsigned int inputDataLen = dataLength, bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen; + unsigned int inputDataLen = data.size(), bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen; unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen; unsigned partsCount = 0; unsigned int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH; @@ -572,26 +461,32 @@ void GameSave::readOPS(const char * data, int dataLength) bsonDataLen |= ((unsigned)inputData[11]) << 24; //Check for overflows, don't load saves larger than 200MB - unsigned int toAlloc = bsonDataLen+1; + unsigned int toAlloc = bsonDataLen; if (toAlloc > 209715200 || !toAlloc) throw ParseException(ParseException::InvalidDimensions, "Save data too large, refusing"); - bsonData = (unsigned char*)malloc(toAlloc); - 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; - - int bz2ret; - if ((bz2ret = BZ2_bzBuffToBuffDecompress((char*)bsonData, &bsonDataLen, (char*)(inputData+12), inputDataLen-12, 0, 0)) != BZ_OK) { - throw ParseException(ParseException::Corrupt, String::Build("Unable to decompress (ret ", bz2ret, ")")); + std::vector bsonData; + switch (auto status = BZ2WDecompress(bsonData, (char *)(inputData + 12), inputDataLen - 12, toAlloc)) + { + case BZ2WDecompressOk: break; + case BZ2WDecompressNomem: throw ParseException(ParseException::Corrupt, "Cannot allocate memory"); + default: throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: status ", int(status))); + } + + bsonDataLen = bsonData.size(); + //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.push_back(0); + + // apparently bson_* takes ownership of the data passed into it????????? + auto *pleaseFixMe = (char *)malloc(bsonData.size()); + std::copy(bsonData.begin(), bsonData.end(), pleaseFixMe); + bson_init_data_size(&b, pleaseFixMe, bsonDataLen); } set_bson_err_handler([](const char* err) { throw ParseException(ParseException::Corrupt, "BSON error when parsing save: " + ByteString(err).FromUtf8()); }); - bson_init_data_size(&b, (char*)bsonData, bsonDataLen); + bson_iterator_init(&iter, &b); std::vector tempSigns; @@ -1346,9 +1241,10 @@ void GameSave::readOPS(const char * data, int dataLength) } } -void GameSave::readPSv(const char * saveDataChar, int dataLength) +void GameSave::readPSv(const std::vector &dataVec) { - unsigned char * saveData = (unsigned char *)saveDataChar; + unsigned char * saveData = (unsigned char *)&dataVec[0]; + auto dataLength = int(dataVec.size()); int q,j,k,x,y,p=0, ver, pty, ty, legacy_beta=0; int bx0=0, by0=0, bw, bh, w, h, y0 = 0, x0 = 0; int new_format = 0, ttv = 0; @@ -1421,17 +1317,16 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength) if (size > 209715200 || !size) throw ParseException(ParseException::InvalidDimensions, "Save data too large"); - auto dataPtr = std::unique_ptr(new unsigned char[size]); - unsigned char *data = dataPtr.get(); - if (!data) - throw ParseException(ParseException::Corrupt, "Cannot allocate memory"); + std::vector data; + switch (auto status = BZ2WDecompress(data, (char *)(saveData + 12), dataLength - 12, size)) + { + case BZ2WDecompressOk: break; + case BZ2WDecompressNomem: throw ParseException(ParseException::Corrupt, "Cannot allocate memory"); + default: throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: status ", int(status))); + } setSize(bw, bh); - - int bzStatus = 0; - if ((bzStatus = BZ2_bzBuffToBuffDecompress((char *)data, (unsigned *)&size, (char *)(saveData+12), dataLength-12, 0, 0))) - throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: ", bzStatus)); - dataLength = size; + dataLength = data.size(); #ifdef DEBUG std::cout << "Parsing " << dataLength << " bytes of data, version " << ver << std::endl; @@ -1451,11 +1346,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength) airMode = 0; } - auto particleIDMapPtr = std::unique_ptr(new int[XRES*YRES]); - int *particleIDMap = particleIDMapPtr.get(); - std::fill(&particleIDMap[0], &particleIDMap[XRES*YRES], 0); - if (!particleIDMap) - throw ParseException(ParseException::Corrupt, "Cannot allocate memory"); + std::vector particleIDMap(XRES * YRES, 0); // load the required air state for (y=by0; y254) x = 254; - memcpy(tempSignText, data+p, x); + memcpy(tempSignText, &data[0]+p, x); tempSignText[x] = 0; tempSign.text = format::CleanString(ByteString(tempSignText).FromUtf8(), true, true, true).Substr(0, 45); if (tempSign.text == "{t}") @@ -2009,7 +1900,7 @@ void GameSave::readPSv(const char * saveDataChar, int dataLength) minimumMinorVersion = minor;\ } -char * GameSave::serialiseOPS(unsigned int & dataLength) +std::pair> GameSave::serialiseOPS() const { int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH; int x, y, i; @@ -2033,16 +1924,13 @@ char * GameSave::serialiseOPS(unsigned int & dataLength) fullH = blockH*CELL; // Copy fan and wall data - auto wallData = std::unique_ptr(new unsigned char[blockWidth*blockHeight]); + std::vector wallData(blockWidth*blockHeight); bool hasWallData = false; - auto fanData = std::unique_ptr(new unsigned char[blockWidth*blockHeight*2]); - auto pressData = std::unique_ptr(new unsigned char[blockWidth*blockHeight*2]); - auto vxData = std::unique_ptr(new unsigned char[blockWidth*blockHeight*2]); - auto vyData = std::unique_ptr(new unsigned char[blockWidth*blockHeight*2]); - auto ambientData = std::unique_ptr(new unsigned char[blockWidth*blockHeight*2]); - std::fill(&ambientData[0], &ambientData[blockWidth*blockHeight*2], 0); - if (!wallData || !fanData || !pressData || !vxData || !vyData || !ambientData) - throw BuildException("Save error, out of memory (blockmaps)"); + std::vector fanData(blockWidth*blockHeight*2); + std::vector pressData(blockWidth*blockHeight*2); + std::vector vxData(blockWidth*blockHeight*2); + std::vector vyData(blockWidth*blockHeight*2); + std::vector ambientData(blockWidth*blockHeight*2, 0); unsigned int wallDataLen = blockWidth*blockHeight, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 0; for (x = blockX; x < blockX+blockW; x++) @@ -2099,16 +1987,10 @@ char * GameSave::serialiseOPS(unsigned int & dataLength) //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 - auto partsPosFirstMap = std::unique_ptr(new unsigned[fullW*fullH]); - auto partsPosLastMap = std::unique_ptr(new unsigned[fullW*fullH]); - auto partsPosCount = std::unique_ptr(new unsigned[fullW*fullH]); - auto partsPosLink = std::unique_ptr(new unsigned[NPART]); - if (!partsPosFirstMap || !partsPosLastMap || !partsPosCount || !partsPosLink) - throw BuildException("Save error, out of memory (partmaps)"); - std::fill(&partsPosFirstMap[0], &partsPosFirstMap[fullW*fullH], 0); - std::fill(&partsPosLastMap[0], &partsPosLastMap[fullW*fullH], 0); - std::fill(&partsPosCount[0], &partsPosCount[fullW*fullH], 0); - std::fill(&partsPosLink[0], &partsPosLink[NPART], 0); + std::vector partsPosFirstMap(fullW*fullH, 0); + std::vector partsPosLastMap(fullW*fullH, 0); + std::vector partsPosCount(fullW*fullH, 0); + std::vector partsPosLink(NPART, 0); unsigned int soapCount = 0; for(i = 0; i < particlesCount; i++) { @@ -2136,10 +2018,8 @@ char * GameSave::serialiseOPS(unsigned int & dataLength) } //Store number of particles in each position - auto partsPosData = std::unique_ptr(new unsigned char[fullW*fullH*3]); + std::vector partsPosData(fullW*fullH*3); unsigned int partsPosDataLen = 0; - if (!partsPosData) - throw BuildException("Save error, out of memory (partposdata)"); for (y=0;y(new unsigned char[NPART * (sizeof(Particle)+3)]); + std::vector partsData(NPART * (sizeof(Particle)+3)); unsigned int partsDataLen = 0; - auto partsSaveIndex = std::unique_ptr(new unsigned[NPART]); + std::vector partsSaveIndex(NPART); unsigned int partsCount = 0; - if (!partsData || !partsSaveIndex) - throw BuildException("Save error, out of memory (partsdata)"); std::fill(&partsSaveIndex[0], &partsSaveIndex[NPART], 0); for (y=0;y(); unsigned int soapLinkDataLen = 0; + std::vector soapLinkData(3*soapCount); if (soapCount) { - soapLinkData = new unsigned char[3*soapCount]; - if (!soapLinkData) - throw BuildException("Save error, out of memory (SOAP)"); - soapLinkDataPtr = std::unique_ptr(soapLinkData); //Iterate through particles in the same order that they were saved for (y=0;y 0 // Mark save as incompatible with latest release if (minimumMajorVersion > SAVE_VERSION || (minimumMajorVersion == SAVE_VERSION && minimumMinorVersion > MINOR_VERSION)) - fromNewerVersion = true; + fakeFromNewerVersion = true; #endif bson b; @@ -2583,37 +2457,37 @@ char * GameSave::serialiseOPS(unsigned int & dataLength) } bson_append_int(&b, "pmapbits", pmapbits); - if (partsData && partsDataLen) + if (partsDataLen) { - bson_append_binary(&b, "parts", (char)BSON_BIN_USER, (const char *)partsData.get(), partsDataLen); + bson_append_binary(&b, "parts", (char)BSON_BIN_USER, (const char *)&partsData[0], partsDataLen); if (palette.size()) { bson_append_start_array(&b, "palette"); - for(std::vector::iterator iter = palette.begin(), end = palette.end(); iter != end; ++iter) + for(auto iter = palette.begin(), end = palette.end(); iter != end; ++iter) { bson_append_int(&b, (*iter).first.c_str(), (*iter).second); } bson_append_finish_array(&b); } - if (partsPosData && partsPosDataLen) - bson_append_binary(&b, "partsPos", (char)BSON_BIN_USER, (const char *)partsPosData.get(), partsPosDataLen); + if (partsPosDataLen) + bson_append_binary(&b, "partsPos", (char)BSON_BIN_USER, (const char *)&partsPosData[0], partsPosDataLen); } - if (wallData && hasWallData) - bson_append_binary(&b, "wallMap", (char)BSON_BIN_USER, (const char *)wallData.get(), wallDataLen); - if (fanData && fanDataLen) - bson_append_binary(&b, "fanMap", (char)BSON_BIN_USER, (const char *)fanData.get(), fanDataLen); - if (pressData && hasPressure && pressDataLen) - bson_append_binary(&b, "pressMap", (char)BSON_BIN_USER, (const char*)pressData.get(), pressDataLen); - if (vxData && hasPressure && vxDataLen) - bson_append_binary(&b, "vxMap", (char)BSON_BIN_USER, (const char*)vxData.get(), vxDataLen); - if (vyData && hasPressure && vyDataLen) - bson_append_binary(&b, "vyMap", (char)BSON_BIN_USER, (const char*)vyData.get(), vyDataLen); - if (ambientData && hasAmbientHeat && this->aheatEnable && ambientDataLen) - bson_append_binary(&b, "ambientMap", (char)BSON_BIN_USER, (const char*)ambientData.get(), ambientDataLen); - if (soapLinkData && soapLinkDataLen) - bson_append_binary(&b, "soapLinks", (char)BSON_BIN_USER, (const char *)soapLinkData, soapLinkDataLen); + if (hasWallData) + bson_append_binary(&b, "wallMap", (char)BSON_BIN_USER, (const char *)&wallData[0], wallDataLen); + if (fanDataLen) + bson_append_binary(&b, "fanMap", (char)BSON_BIN_USER, (const char *)&fanData[0], fanDataLen); + if (hasPressure && pressDataLen) + bson_append_binary(&b, "pressMap", (char)BSON_BIN_USER, (const char*)&pressData[0], pressDataLen); + if (hasPressure && vxDataLen) + bson_append_binary(&b, "vxMap", (char)BSON_BIN_USER, (const char*)&vxData[0], vxDataLen); + if (hasPressure && vyDataLen) + bson_append_binary(&b, "vyMap", (char)BSON_BIN_USER, (const char*)&vyData[0], vyDataLen); + if (hasAmbientHeat && this->aheatEnable && ambientDataLen) + bson_append_binary(&b, "ambientMap", (char)BSON_BIN_USER, (const char*)&ambientData[0], ambientDataLen); + if (soapLinkDataLen) + bson_append_binary(&b, "soapLinks", (char)BSON_BIN_USER, (const char *)&soapLinkData[0], soapLinkDataLen); unsigned int signsCount = 0; for (size_t i = 0; i < signs.size(); i++) { @@ -2650,40 +2524,43 @@ char * GameSave::serialiseOPS(unsigned int & dataLength) unsigned char *finalData = (unsigned char*)bson_data(&b); unsigned int finalDataLen = bson_size(&b); - auto outputData = std::unique_ptr(new unsigned char[finalDataLen*2+12]); - if (!outputData) - throw BuildException(String::Build("Save error, out of memory (finalData): ", finalDataLen*2+12)); - 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; - unsigned int compressedSize = finalDataLen*2, bz2ret; - if ((bz2ret = BZ2_bzBuffToBuffCompress((char*)(outputData.get()+12), &compressedSize, (char*)finalData, bson_size(&b), 9, 0, 0)) != BZ_OK) + std::vector outputData; + switch (auto status = BZ2WCompress(outputData, (char *)finalData, finalDataLen)) { - throw BuildException(String::Build("Save error, could not compress (ret ", bz2ret, ")")); + case BZ2WCompressOk: break; + case BZ2WCompressNomem: throw BuildException(String::Build("Save error, out of memory")); + default: throw BuildException(String::Build("Cannot compress: status ", int(status))); } + auto compressedSize = int(outputData.size()); #ifdef DEBUG printf("compressed data: %d\n", compressedSize); #endif - dataLength = compressedSize + 12; + outputData.resize(compressedSize + 12); - char *saveData = new char[dataLength]; - std::copy(&outputData[0], &outputData[dataLength], &saveData[0]); - return saveData; + auto header = (unsigned char *)&outputData[compressedSize]; + header[0] = 'O'; + header[1] = 'P'; + header[2] = 'S'; + header[3] = '1'; + header[4] = SAVE_VERSION; + header[5] = CELL; + header[6] = blockW; + header[7] = blockH; + header[8] = finalDataLen; + header[9] = finalDataLen >> 8; + header[10] = finalDataLen >> 16; + header[11] = finalDataLen >> 24; + + // move header to front + std::rotate(outputData.begin(), outputData.begin() + compressedSize, outputData.end()); + + return { fakeFromNewerVersion, outputData }; } -void GameSave::ConvertBsonToJson(bson_iterator *iter, Json::Value *j, int depth) +static void ConvertBsonToJson(bson_iterator *iter, Json::Value *j, int depth) { bson_iterator subiter; bson_iterator_subiterator(iter, &subiter); @@ -2754,7 +2631,7 @@ std::set GetNestedSaveIDs(Json::Value j) } // converts a json object to bson -void GameSave::ConvertJsonToBson(bson *b, Json::Value j, int depth) +static void ConvertJsonToBson(bson *b, Json::Value j, int depth) { Json::Value::Members members = j.getMemberNames(); for (Json::Value::Members::iterator iter = members.begin(), end = members.end(); iter != end; ++iter) @@ -2805,19 +2682,6 @@ void GameSave::ConvertJsonToBson(bson *b, Json::Value j, int depth) } } -// deallocates a pointer to a 2D array and sets it to NULL -template -void GameSave::Deallocate2DArray(T ***array, int blockHeight) -{ - if (*array) - { - for (int y = 0; y < blockHeight; y++) - delete[] (*array)[y]; - delete[] (*array); - *array = NULL; - } -} - bool GameSave::TypeInCtype(int type, int ctype) { return ctype >= 0 && ctype < PT_NUM && @@ -2843,27 +2707,6 @@ bool GameSave::PressureInTmp3(int type) return type == PT_QRTZ || type == PT_GLAS || type == PT_TUNG; } -void GameSave::dealloc() -{ - if (particles) - { - delete[] particles; - particles = NULL; - } - Deallocate2DArray(&blockMap, blockHeight); - Deallocate2DArray(&fanVelX, blockHeight); - Deallocate2DArray(&fanVelY, blockHeight); - Deallocate2DArray(&pressure, blockHeight); - Deallocate2DArray(&velocityX, blockHeight); - Deallocate2DArray(&velocityY, blockHeight); - Deallocate2DArray(&ambientHeat, blockHeight); -} - -GameSave::~GameSave() -{ - dealloc(); -} - GameSave& GameSave::operator << (Particle &v) { if(particlesCount #include "common/String.h" +#include "simulation/Sign.h" +#include "simulation/Particle.h" #include "Misc.h" -#include "bson/BSON.h" #include struct sign; @@ -43,63 +43,80 @@ public: bool rocketBoots2 = false; bool fan1 = false; bool fan2 = false; - std::vector rocketBootsFigh = std::vector(); - std::vector fanFigh = std::vector(); + std::vector rocketBootsFigh; + std::vector fanFigh; - StkmData() = default; - - StkmData(const StkmData & stkmData): - rocketBoots1(stkmData.rocketBoots1), - rocketBoots2(stkmData.rocketBoots2), - fan1(stkmData.fan1), - fan2(stkmData.fan2), - rocketBootsFigh(stkmData.rocketBootsFigh), - fanFigh(stkmData.fanFigh) - { - - } - - bool hasData() + bool hasData() const { return rocketBoots1 || rocketBoots2 || fan1 || fan2 || rocketBootsFigh.size() || fanFigh.size(); } }; +template +struct Plane +{ + int width = 0; + int height = 0; + std::vector items; + // invariant: items.size() == width * height + + Item *operator [](int y) + { + return &items[y * width]; + } + + const Item *operator [](int y) const + { + return &items[y * width]; + } + + Plane() = default; + Plane(int newWidth, int newHeight, Item defaultVal) : width(newWidth), height(newHeight), items(width * height, defaultVal) + { + } +}; + class GameSave { -public: + // number of pixels translated. When translating CELL pixels, shift all CELL grids + vector2d translated = { 0, 0 }; + void readOPS(const std::vector &data); + void readPSv(const std::vector &data); + std::pair> serialiseOPS() const; - int blockWidth, blockHeight; - bool fromNewerVersion; - int majorVersion, minorVersion; - bool hasPressure; - bool hasAmbientHeat; +public: + int blockWidth = 0; + int blockHeight = 0; + bool fromNewerVersion = false; + int majorVersion = 0; + int minorVersion = 0; + bool hasPressure = false; + bool hasAmbientHeat = false; //Simulation data - //int ** particleMap; - int particlesCount; - Particle * particles; - unsigned char ** blockMap; - float ** fanVelX; - float ** fanVelY; - float ** pressure; - float ** velocityX; - float ** velocityY; - float ** ambientHeat; + int particlesCount = 0; + std::vector particles; + Plane blockMap; + Plane fanVelX; + Plane fanVelY; + Plane pressure; + Plane velocityX; + Plane velocityY; + Plane ambientHeat; //Simulation Options - bool waterEEnabled; - bool legacyEnable; - bool gravityEnable; - bool aheatEnable; - bool paused; - int gravityMode; - float customGravityX; - float customGravityY; - int airMode; - float ambientAirTemp; - int edgeMode; + bool waterEEnabled = false; + bool legacyEnable = false; + bool gravityEnable = false; + bool aheatEnable = false; + bool paused = false; + int gravityMode = 0; + float customGravityX = 0.0f; + float customGravityY = 0.0f; + int airMode = 0; + float ambientAirTemp = R_TEMP + 273.15f; + int edgeMode = 0; //Signs std::vector signs; @@ -112,16 +129,13 @@ public: // author information Json::Value authors; - int pmapbits; + int pmapbits = 8; // default to 8 bits for older saves - GameSave(); - GameSave(const GameSave & save); GameSave(int width, int height); GameSave(const std::vector &data); - ~GameSave(); void setSize(int width, int height); - char * Serialise(unsigned int & dataSize); - std::vector Serialise(); + // return value is [ fakeFromNewerVersion, gameData ] + std::pair> Serialise() const; vector2d Translate(vector2d translate); void Transform(matrix2d transform, vector2d translate); void Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight); @@ -135,26 +149,4 @@ public: GameSave& operator << (Particle &v); GameSave& operator << (sign &v); - -private: - // number of pixels translated. When translating CELL pixels, shift all CELL grids - vector2d translated; - - void InitData(); - void InitVars(); - void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen); - void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag); - void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting); - void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting); - template T ** Allocate2DArray(int blockWidth, int blockHeight, T defaultVal); - template void Deallocate2DArray(T ***array, int blockHeight); - void dealloc(); - void read(const char * data, int dataSize); - void readOPS(const char * data, int dataLength); - void readPSv(const char * data, int dataLength); - char * serialiseOPS(unsigned int & dataSize); - void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0); - void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0); }; - -#endif diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index 51ac12ac3..8fe0f31bc 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -1203,7 +1203,7 @@ void GameController::OpenLocalSaveWindow(bool asCurrent) gameModel->SetSaveFile(&tempSave, gameView->ShiftBehaviour()); Platform::MakeDirectory(LOCAL_SAVE_DIR); - std::vector saveData = gameSave->Serialise(); + auto [ _, saveData ] = gameSave->Serialise(); if (saveData.size() == 0) new ErrorMessage("Error", "Unable to serialize game data."); else if (!Platform::WriteFile(saveData, gameModel->GetSaveFile()->GetName())) diff --git a/src/gui/save/LocalSaveActivity.cpp b/src/gui/save/LocalSaveActivity.cpp index 63d66a113..20c991cdd 100644 --- a/src/gui/save/LocalSaveActivity.cpp +++ b/src/gui/save/LocalSaveActivity.cpp @@ -112,7 +112,7 @@ void LocalSaveActivity::saveWrite(ByteString finalFilename) localSaveInfo["date"] = (Json::Value::UInt64)time(NULL); Client::Ref().SaveAuthorInfo(&localSaveInfo); gameSave->authors = localSaveInfo; - std::vector saveData = gameSave->Serialise(); + auto [ _, saveData ] = gameSave->Serialise(); if (saveData.size() == 0) new ErrorMessage("Error", "Unable to serialize game data."); else if (!Platform::WriteFile(saveData, finalFilename)) diff --git a/src/simulation/ElementDefs.h b/src/simulation/ElementDefs.h index 6b9ac045d..b48660ff8 100644 --- a/src/simulation/ElementDefs.h +++ b/src/simulation/ElementDefs.h @@ -4,7 +4,6 @@ #include "Config.h" //#include "Simulation.h" -#define R_TEMP 22 #define MAX_TEMP 9999 #define MIN_TEMP 0 #define O_MAX_TEMP 3500 diff --git a/src/simulation/Sign.cpp b/src/simulation/Sign.cpp index df33e2763..735e2dd86 100644 --- a/src/simulation/Sign.cpp +++ b/src/simulation/Sign.cpp @@ -11,7 +11,7 @@ sign::sign(String text_, int x_, int y_, Justification justification_): { } -String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, bool colorize, bool *v95) +String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, bool colorize, bool *v95) const { String drawable_text; auto si = std::make_pair(0, Type::Normal); @@ -142,7 +142,7 @@ String sign::getDisplayText(Simulation *sim, int &x0, int &y0, int &w, int &h, b return drawable_text; } -std::pair sign::split() +std::pair sign::split() const { String::size_type pipe = 0; if (text.size() >= 4 && text.front() == '{' && text.back() == '}') diff --git a/src/simulation/Sign.h b/src/simulation/Sign.h index 4e7f933b1..a5ebfd157 100644 --- a/src/simulation/Sign.h +++ b/src/simulation/Sign.h @@ -32,8 +32,8 @@ struct sign String text; sign(String text_, int x_, int y_, Justification justification_); - String getDisplayText(Simulation *sim, int &x, int &y, int &w, int &h, bool colorize = true, bool *v95 = nullptr); - std::pair split(); + String getDisplayText(Simulation *sim, int &x, int &y, int &w, int &h, bool colorize = true, bool *v95 = nullptr) const; + std::pair split() const; }; #endif