#include "common/tpt-minmax.h" #include #include #include #include #include #include #include #include "Config.h" #include "Format.h" #include "GameSave.h" #include "simulation/SimulationData.h" #include "ElementClasses.h" extern "C" { #include "hmap.h" } GameSave::GameSave(GameSave & save): majorVersion(save.majorVersion), waterEEnabled(save.waterEEnabled), legacyEnable(save.legacyEnable), gravityEnable(save.gravityEnable), aheatEnable(save.aheatEnable), paused(save.paused), gravityMode(save.gravityMode), airMode(save.airMode), edgeMode(save.edgeMode), signs(save.signs), stkm(save.stkm), palette(save.palette), pmapbits(save.pmapbits), expanded(save.expanded), hasOriginalData(save.hasOriginalData), originalData(save.originalData) { InitData(); hasPressure = save.hasPressure; hasAmbientHeat = save.hasAmbientHeat; if (save.expanded) { 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]); } } else { blockWidth = save.blockWidth; blockHeight = save.blockHeight; } particlesCount = save.particlesCount; authors = save.authors; } GameSave::GameSave(int width, int height) { InitData(); InitVars(); hasOriginalData = false; expanded = true; setSize(width, height); } GameSave::GameSave(std::vector data) { blockWidth = 0; blockHeight = 0; InitData(); InitVars(); expanded = false; hasOriginalData = true; originalData = data; try { Expand(); } catch(ParseException & e) { std::cout << e.what() << std::endl; dealloc(); //Free any allocated memory throw; } Collapse(); } GameSave::GameSave(std::vector data) { blockWidth = 0; blockHeight = 0; InitData(); InitVars(); expanded = false; hasOriginalData = true; originalData = std::vector(data.begin(), data.end()); 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; InitData(); InitVars(); expanded = false; hasOriginalData = true; originalData = std::vector(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(); } // 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; waterEEnabled = false; legacyEnable = false; gravityEnable = false; aheatEnable = false; paused = false; gravityMode = 0; airMode = 0; edgeMode = 0; translated.x = translated.y = 0; pmapbits = 8; // default to 8 bits for older saves } bool GameSave::Collapsed() { return !expanded; } void GameSave::Expand() { if(hasOriginalData && !expanded) { InitVars(); expanded = true; read(&originalData[0], originalData.size()); } } void GameSave::Collapse() { if(expanded && hasOriginalData) { expanded = false; dealloc(); signs.clear(); } } void GameSave::read(char * data, int dataSize) { if(dataSize > 15) { if ((data[0]==0x66 && data[1]==0x75 && data[2]==0x43) || (data[0]==0x50 && data[1]==0x53 && data[2]==0x76)) { readPSv(data, dataSize); } 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); } else { std::cerr << "Got Magic number '" << data[0] << data[1] << data[2] << "'" << std::endl; throw ParseException(ParseException::Corrupt, "Invalid save format"); } } else { 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; } void GameSave::setSize(int newWidth, int newHeight) { this->blockWidth = newWidth; this->blockHeight = newHeight; particlesCount = 0; particles = new Particle[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); } 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) { try { return serialiseOPS(dataSize); } catch (BuildException e) { std::cout << e.what() << std::endl; return NULL; } } vector2d GameSave::Translate(vector2d translate) { if (Collapsed()) Expand(); int nx, ny; vector2d pos; vector2d translateReal = translate; float minx = 0, miny = 0, maxx = 0, maxy = 0; // determine minimum and maximum position of all particles / signs for (size_t i = 0; i < signs.size(); i++) { pos = v2d_new(signs[i].x, signs[i].y); pos = v2d_add(pos,translate); nx = floor(pos.x+0.5f); ny = floor(pos.y+0.5f); if (nx < minx) minx = nx; if (ny < miny) miny = ny; if (nx > maxx) maxx = nx; if (ny > maxy) maxy = ny; } for (int i = 0; i < particlesCount; i++) { if (!particles[i].type) continue; pos = v2d_new(particles[i].x, particles[i].y); pos = v2d_add(pos,translate); nx = floor(pos.x+0.5f); ny = floor(pos.y+0.5f); if (nx < minx) minx = nx; if (ny < miny) miny = ny; if (nx > maxx) maxx = nx; if (ny > maxy) maxy = ny; } // determine whether corrections are needed. If moving in this direction would delete stuff, expand the save vector2d backCorrection = v2d_new( (minx < 0) ? (-floor(minx / CELL)) : 0, (miny < 0) ? (-floor(miny / CELL)) : 0 ); int blockBoundsX = int(maxx / CELL) + 1, blockBoundsY = int(maxy / CELL) + 1; vector2d frontCorrection = v2d_new( (blockBoundsX > blockWidth) ? (blockBoundsX - blockWidth) : 0, (blockBoundsY > blockHeight) ? (blockBoundsY - blockHeight) : 0 ); // get new width based on corrections int newWidth = (blockWidth + backCorrection.x + frontCorrection.x) * CELL; int newHeight = (blockHeight + backCorrection.y + frontCorrection.y) * CELL; if (newWidth > XRES) frontCorrection.x = backCorrection.x = 0; if (newHeight > YRES) frontCorrection.y = backCorrection.y = 0; // call Transform to do the transformation we wanted when calling this function translate = v2d_add(translate, v2d_multiply_float(backCorrection, CELL)); Transform(m2d_identity, translate, translateReal, (blockWidth + backCorrection.x + frontCorrection.x) * CELL, (blockHeight + backCorrection.y + frontCorrection.y) * CELL ); // return how much we corrected. This is used to offset the position of the current stamp // otherwise it would attempt to recenter it with the current size return v2d_add(v2d_multiply_float(backCorrection, -CELL), v2d_multiply_float(frontCorrection, CELL)); } void GameSave::Transform(matrix2d transform, vector2d translate) { if (Collapsed()) Expand(); int width = blockWidth*CELL, height = blockHeight*CELL, newWidth, newHeight; vector2d tmp, ctl, cbr; vector2d cornerso[4]; vector2d translateReal = translate; // 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 (int 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.xcbr.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; Transform(transform, translate, translateReal, newWidth, newHeight); } // transform is a matrix describing how we want to rotate this save // translate can vary depending on whether the save is bring rotated, or if a normal translate caused it to expand // translateReal is the original amount we tried to translate, used to calculate wall shifting void GameSave::Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight) { if (Collapsed()) Expand(); if (newWidth>XRES) newWidth = XRES; if (newHeight>YRES) newHeight = YRES; 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); // rotate and translate signs, parts, walls for (size_t 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 (int i = 0; i < particlesCount; 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; } // translate walls and other grid items when the stamp is shifted more than 4 pixels in any direction int translateX = 0, translateY = 0; if (translateReal.x > 0 && ((int)translated.x%CELL == 3 || (translated.x < 0 && (int)translated.x%CELL == 0))) translateX = CELL; else if (translateReal.x < 0 && ((int)translated.x%CELL == -3 || (translated.x > 0 && (int)translated.x%CELL == 0))) translateX = -CELL; if (translateReal.y > 0 && ((int)translated.y%CELL == 3 || (translated.y < 0 && (int)translated.y%CELL == 0))) translateY = CELL; else if (translateReal.y < 0 && ((int)translated.y%CELL == -3 || (translated.y > 0 && (int)translated.y%CELL == 0))) translateY = -CELL; for (y=0; y=newBlockWidth || pos.y<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; } } pressureNew[ny][nx] = pressure[y][x]; velocityXNew[ny][nx] = velocityX[y][x]; velocityYNew[ny][nx] = velocityY[y][x]; ambientHeatNew[ny][nx] = ambientHeat[y][x]; } 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; pressure = pressureNew; velocityX = velocityXNew; velocityY = velocityYNew; ambientHeat = ambientHeatNew; } void GameSave::CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen) { if (!strcmp(bson_iterator_key(&iter), field)) { if (bson_iterator_type(&iter)==BSON_BINDATA && ((unsigned char)bson_iterator_bin_type(&iter))==BSON_BIN_USER && (*fieldLen = bson_iterator_bin_len(&iter)) > 0) { *data = (unsigned char*)bson_iterator_bin_data(&iter); } else { fprintf(stderr, "Invalid datatype for %s: %d[%d] %d[%d] %d[%d]\n", field, 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); } } } void GameSave::CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag) { if (!strcmp(bson_iterator_key(&iter), field)) { if (bson_iterator_type(&iter) == BSON_BOOL) { *flag = bson_iterator_bool(&iter); } else { fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter)); } } } void GameSave::CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting) { if (!strcmp(bson_iterator_key(&iter), field)) { if (bson_iterator_type(&iter) == BSON_INT) { *setting = bson_iterator_int(&iter); } else { fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter)); } } } 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 char *pressData = NULL, *vxData = NULL, *vyData = NULL, *ambientData = NULL; unsigned int inputDataLen = dataLength, 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; int savedVersion = inputData[4]; majorVersion = savedVersion; bool fakeNewerVersion = false; // used for development builds only bson b; b.data = NULL; bson_iterator iter; auto bson_deleter = [](bson * b) { bson_destroy(b); }; // Use unique_ptr with a custom deleter to ensure that bson_destroy is called even when an exception is thrown std::unique_ptr b_ptr(&b, bson_deleter); //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) { fromNewerVersion = true; //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; //Check for overflows, don't load saves larger than 200MB unsigned int toAlloc = bsonDataLen+1; 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, "Unable to decompress (ret " + format::NumberToString(bz2ret) + ")"); } 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; while (bson_iterator_next(&iter)) { CheckBsonFieldUser(iter, "parts", &partsData, &partsDataLen); CheckBsonFieldUser(iter, "partsPos", &partsPosData, &partsPosDataLen); CheckBsonFieldUser(iter, "wallMap", &wallData, &wallDataLen); CheckBsonFieldUser(iter, "pressMap", &pressData, &pressDataLen); CheckBsonFieldUser(iter, "vxMap", &vxData, &vxDataLen); CheckBsonFieldUser(iter, "vyMap", &vyData, &vyDataLen); CheckBsonFieldUser(iter, "ambientMap", &ambientData, &ambientDataLen); CheckBsonFieldUser(iter, "fanMap", &fanData, &fanDataLen); CheckBsonFieldUser(iter, "soapLinks", &soapLinkData, &soapLinkDataLen); CheckBsonFieldBool(iter, "legacyEnable", &legacyEnable); CheckBsonFieldBool(iter, "gravityEnable", &gravityEnable); CheckBsonFieldBool(iter, "aheat_enable", &aheatEnable); CheckBsonFieldBool(iter, "waterEEnabled", &waterEEnabled); CheckBsonFieldBool(iter, "paused", &paused); CheckBsonFieldInt(iter, "gravityMode", &gravityMode); CheckBsonFieldInt(iter, "airMode", &airMode); CheckBsonFieldInt(iter, "edgeMode", &edgeMode); CheckBsonFieldInt(iter, "pmapbits", &pmapbits); if (!strcmp(bson_iterator_key(&iter), "signs")) { 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")) { 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") && bson_iterator_type(&signiter) == BSON_STRING) { tempSign.text = format::CleanString(ByteString(bson_iterator_string(&signiter)).FromUtf8(), true, true, true).Substr(0, 45); } else if (!strcmp(bson_iterator_key(&signiter), "justification") && bson_iterator_type(&signiter) == BSON_INT) { tempSign.ju = (sign::Justification)bson_iterator_int(&signiter); } else if (!strcmp(bson_iterator_key(&signiter), "x") && bson_iterator_type(&signiter) == BSON_INT) { tempSign.x = bson_iterator_int(&signiter)+fullX; } else if (!strcmp(bson_iterator_key(&signiter), "y") && 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), "stkm")) { if (bson_iterator_type(&iter) == BSON_OBJECT) { bson_iterator stkmiter; bson_iterator_subiterator(&iter, &stkmiter); while (bson_iterator_next(&stkmiter)) { CheckBsonFieldBool(stkmiter, "rocketBoots1", &stkm.rocketBoots1); CheckBsonFieldBool(stkmiter, "rocketBoots1", &stkm.rocketBoots1); CheckBsonFieldBool(stkmiter, "fan1", &stkm.fan1); CheckBsonFieldBool(stkmiter, "fan2", &stkm.fan2); if (!strcmp(bson_iterator_key(&stkmiter), "rocketBootsFigh") && bson_iterator_type(&stkmiter) == BSON_ARRAY) { bson_iterator fighiter; bson_iterator_subiterator(&stkmiter, &fighiter); while (bson_iterator_next(&fighiter)) { if (bson_iterator_type(&fighiter) == BSON_INT) stkm.rocketBootsFigh.push_back(bson_iterator_int(&fighiter)); } } else if (!strcmp(bson_iterator_key(&stkmiter), "fanFigh") && bson_iterator_type(&stkmiter) == BSON_ARRAY) { bson_iterator fighiter; bson_iterator_subiterator(&stkmiter, &fighiter); while (bson_iterator_next(&fighiter)) { if (bson_iterator_type(&fighiter) == BSON_INT) stkm.fanFigh.push_back(bson_iterator_int(&fighiter)); } } else fprintf(stderr, "Unknown stkm property %s\n", bson_iterator_key(&stkmiter)); } } else { fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter)); } } else if (!strcmp(bson_iterator_key(&iter), "palette")) { 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) { ByteString id = bson_iterator_key(&subiter); int num = bson_iterator_int(&subiter); palette.push_back(PaletteItem(id, num)); } } } } else if (!strcmp(bson_iterator_key(&iter), "minimumVersion")) { if (bson_iterator_type(&iter) == BSON_OBJECT) { int major = INT_MAX, minor = INT_MAX; #ifdef RENDERER int renderMajor = INT_MAX, renderMinor = INT_MAX; #endif bson_iterator subiter; bson_iterator_subiterator(&iter, &subiter); while (bson_iterator_next(&subiter)) { if (bson_iterator_type(&subiter) == BSON_INT) { #ifdef RENDERER if (!strcmp(bson_iterator_key(&subiter), "rendermajor")) renderMajor = bson_iterator_int(&subiter); else if (!strcmp(bson_iterator_key(&subiter), "renderminor")) renderMinor = bson_iterator_int(&subiter); #else if (!strcmp(bson_iterator_key(&subiter), "major")) major = bson_iterator_int(&subiter); else if (!strcmp(bson_iterator_key(&subiter), "minor")) minor = bson_iterator_int(&subiter); #endif } } #ifdef RENDERER if (renderMajor > SAVE_VERSION || (renderMajor == SAVE_VERSION && renderMinor > MINOR_VERSION)) #elif defined(SNAPSHOT) || defined(DEBUG) if (major > FUTURE_SAVE_VERSION || (major == FUTURE_SAVE_VERSION && minor > FUTURE_MINOR_VERSION)) #else if (major > SAVE_VERSION || (major == SAVE_VERSION && minor > MINOR_VERSION)) #endif { #ifdef RENDERER String errorMessage = String::Build("Save from a newer version: Requires render version ", renderMajor, ".", renderMinor); #else String errorMessage = String::Build("Save from a newer version: Requires version ", major, ".", minor); #endif throw ParseException(ParseException::WrongVersion, errorMessage); } #if defined(SNAPSHOT) || defined(DEBUG) else if (major > SAVE_VERSION || (major == SAVE_VERSION && minor > MINOR_VERSION)) fakeNewerVersion = true; #endif } else { fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter)); } } #ifndef RENDERER else if (!strcmp(bson_iterator_key(&iter), "authors")) { if (bson_iterator_type(&iter) == BSON_OBJECT) { // we need to clear authors because the save may be read multiple times in the stamp browser (loading and rendering twice) // seems inefficient ... authors.clear(); ConvertBsonToJson(&iter, &authors); } else { fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter)); } } #endif } //Read wall and fan data if(wallData) { unsigned int j = 0; if (blockW * blockH > wallDataLen) throw ParseException(ParseException::Corrupt, "Not enough wall data"); for (unsigned int x = 0; x < blockW; x++) { for (unsigned int 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_ALLOWPOWDER; 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; } if (blockMap[y][x] >= UI_WALLCOUNT) blockMap[y][x] = 0; } } } //Read pressure data if (pressData) { unsigned int j = 0; unsigned char i, i2; if (blockW * blockH > pressDataLen) throw ParseException(ParseException::Corrupt, "Not enough pressure data"); for (unsigned int x = 0; x < blockW; x++) { for (unsigned int y = 0; y < blockH; y++) { i = pressData[j++]; i2 = pressData[j++]; pressure[blockY+y][blockX+x] = ((i+(i2<<8))/128.0f)-256; } } hasPressure = true; } //Read vx data if (vxData) { unsigned int j = 0; unsigned char i, i2; if (blockW * blockH > vxDataLen) throw ParseException(ParseException::Corrupt, "Not enough vx data"); for (unsigned int x = 0; x < blockW; x++) { for (unsigned int y = 0; y < blockH; y++) { i = vxData[j++]; i2 = vxData[j++]; velocityX[blockY+y][blockX+x] = ((i+(i2<<8))/128.0f)-256; } } } //Read vy data if (vyData) { unsigned int j = 0; unsigned char i, i2; if (blockW * blockH > vyDataLen) throw ParseException(ParseException::Corrupt, "Not enough vy data"); for (unsigned int x = 0; x < blockW; x++) { for (unsigned int y = 0; y < blockH; y++) { i = vyData[j++]; i2 = vyData[j++]; velocityY[blockY+y][blockX+x] = ((i+(i2<<8))/128.0f)-256; } } } //Read ambient data if (ambientData) { unsigned int i = 0, tempTemp; if (blockW * blockH > ambientDataLen) throw ParseException(ParseException::Corrupt, "Not enough ambient heat data"); for (unsigned int x = 0; x < blockW; x++) { for (unsigned int y = 0; y < blockH; y++) { tempTemp = ambientData[i++]; tempTemp |= (((unsigned)ambientData[i++]) << 8); ambientHeat[blockY+y][blockX+x] = tempTemp; } } hasAmbientHeat = true; } //Read particle data if (partsData && partsPosData) { int newIndex = 0, fieldDescriptor, tempTemp; int posCount, posTotal, partsPosDataIndex = 0; if (fullW * fullH * 3 > partsPosDataLen) throw ParseException(ParseException::Corrupt, "Not enough particle position data"); partsCount = 0; unsigned int i = 0; unsigned int saved_x, saved_y, x, y; 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; //i+3 because we have 4 bytes of required fields (type (1), descriptor (2), temp (1)) if (i+3 >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer"); x = saved_x + fullX; y = saved_y + fullY; fieldDescriptor = partsData[i+1]; fieldDescriptor |= partsData[i+2] << 8; if (x >= fullW || y >= fullH) throw ParseException(ParseException::Corrupt, "Particle out of range"); if (newIndex < 0 || newIndex >= NPART) throw ParseException(ParseException::Corrupt, "Too many particles"); //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 type (2nd byte) if (fieldDescriptor & 0x4000) particles[newIndex].type |= (((unsigned)partsData[i++]) << 8); //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) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading life"); particles[newIndex].life = partsData[i++]; //i++; //Read 2nd byte if(fieldDescriptor & 0x04) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading life"); particles[newIndex].life |= (((unsigned)partsData[i++]) << 8); } } //Read tmp if(fieldDescriptor & 0x08) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading tmp"); particles[newIndex].tmp = partsData[i++]; //Read 2nd byte if(fieldDescriptor & 0x10) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading tmp"); particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 8); //Read 3rd and 4th bytes if(fieldDescriptor & 0x1000) { if (i+1 >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading tmp"); particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 24); particles[newIndex].tmp |= (((unsigned)partsData[i++]) << 16); } } } //Read ctype if(fieldDescriptor & 0x20) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading ctype"); particles[newIndex].ctype = partsData[i++]; //Read additional bytes if(fieldDescriptor & 0x200) { if (i+2 >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading ctype"); 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) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading deco"); 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) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading vx"); particles[newIndex].vx = (partsData[i++]-127.0f)/16.0f; } //Read vy if(fieldDescriptor & 0x100) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading vy"); particles[newIndex].vy = (partsData[i++]-127.0f)/16.0f; } //Read tmp2 if(fieldDescriptor & 0x400) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading tmp2"); particles[newIndex].tmp2 = partsData[i++]; if(fieldDescriptor & 0x800) { if (i >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading tmp2"); particles[newIndex].tmp2 |= (((unsigned)partsData[i++]) << 8); } } //Read pavg if(fieldDescriptor & 0x2000) { if (i+3 >= partsDataLen) throw ParseException(ParseException::Corrupt, "Ran past particle data buffer while loading pavg"); int pavg; pavg = partsData[i++]; pavg |= (((unsigned)partsData[i++]) << 8); particles[newIndex].pavg[0] = (float)pavg; pavg = partsData[i++]; pavg |= (((unsigned)partsData[i++]) << 8); particles[newIndex].pavg[1] = (float)pavg; } //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 = (((firw_data[caddress]))<<16) | (((firw_data[caddress+1]))<<8) | ((firw_data[caddress+2])); } break; case PT_PSTN: if (savedVersion < 87 && particles[newIndex].ctype) particles[newIndex].life = 1; if (savedVersion < 91) particles[newIndex].temp = 283.15; break; case PT_FILT: if (savedVersion < 89) { if (particles[newIndex].tmp<0 || particles[newIndex].tmp>3) particles[newIndex].tmp = 6; particles[newIndex].ctype = 0; } break; case PT_QRTZ: case PT_PQRT: if (savedVersion < 89) { particles[newIndex].tmp2 = particles[newIndex].tmp; particles[newIndex].tmp = particles[newIndex].ctype; particles[newIndex].ctype = 0; } break; case PT_PHOT: if (savedVersion < 90) { particles[newIndex].flags |= FLAG_PHOTDECO; } break; case PT_VINE: if (savedVersion < 91) { particles[newIndex].tmp = 1; } break; case PT_DLAY: // correct DLAY temperature in older saves // due to either the +.5f now done in DLAY (higher temps), or rounding errors in the old DLAY code (room temperature temps), // the delay in all DLAY from older versions will always be one greater than it should if (savedVersion < 91) { particles[newIndex].temp = particles[newIndex].temp - 1.0f; } break; case PT_CRAY: if (savedVersion < 91) { if (particles[newIndex].tmp2) { particles[newIndex].ctype |= particles[newIndex].tmp2<<8; particles[newIndex].tmp2 = 0; } } break; case PT_CONV: if (savedVersion < 91) { if (particles[newIndex].tmp) { particles[newIndex].ctype |= particles[newIndex].tmp<<8; particles[newIndex].tmp = 0; } } break; case PT_PIPE: case PT_PPIP: if (savedVersion < 93 && !fakeNewerVersion) { if (particles[newIndex].ctype == 1) particles[newIndex].tmp |= 0x00020000; //PFLAG_INITIALIZING particles[newIndex].tmp |= (particles[newIndex].ctype-1)<<18; particles[newIndex].ctype = particles[newIndex].tmp&0xFF; } break; case PT_TSNS: case PT_HSWC: case PT_PSNS: case PT_PUMP: if (savedVersion < 93 && !fakeNewerVersion) { particles[newIndex].tmp = 0; } break; } //note: PSv was used in version 77.0 and every version before, add something in PSv too if the element is that old newIndex++; partsCount++; } } } if (i != partsDataLen) throw ParseException(ParseException::Corrupt, "Didn't reach end of particle data buffer"); } if (soapLinkData) { unsigned int soapLinkDataPos = 0; for (unsigned int i = 0; i < partsCount; i++) { if (particles[i].type == PT_SOAP) { // Get the index of the particle forward linked from this one, if present in the save data unsigned 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) continue; linkedIndex = linkedIndex-1; //Attach the two particles particles[i].ctype |= 2; particles[i].tmp = linkedIndex; particles[linkedIndex].ctype |= 4; particles[linkedIndex].tmp2 = i; } } } if (tempSigns.size()) { for (size_t i = 0; i < tempSigns.size(); i++) { if(signs.size() == MAXSIGNS) break; signs.push_back(tempSigns[i]); } } } void GameSave::readPSv(char * saveDataChar, int dataLength) { unsigned char * saveData = (unsigned char *)saveDataChar; 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; std::vector tempSigns; char tempSignText[255]; sign tempSign("", 0, 0, sign::Left); //Gol data used to read older saves std::vector goltype = LoadGOLTypes(); std::vector > grule = LoadGOLRules(); std::vector elements = GetElements(); //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 (!(saveData[2]==0x43 && saveData[1]==0x75 && saveData[0]==0x66) && !(saveData[2]==0x76 && saveData[1]==0x53 && saveData[0]==0x50)) throw ParseException(ParseException::Corrupt, "Unknown format"); if (saveData[2]==0x76 && saveData[1]==0x53 && saveData[0]==0x50) { new_format = 1; } if (saveData[4]>SAVE_VERSION) throw ParseException(ParseException::WrongVersion, "Save from newer version"); ver = saveData[4]; majorVersion = saveData[4]; if (ver<34) { legacyEnable = 1; } else { if (ver>=44) { legacyEnable = saveData[3]&0x01; paused = (saveData[3]>>1)&0x01; if (ver>=46) { gravityMode = ((saveData[3]>>2)&0x03);// | ((c[3]>>2)&0x01); airMode = ((saveData[3]>>4)&0x07);// | ((c[3]>>4)&0x02) | ((c[3]>>4)&0x01); } if (ver>=49) { gravityEnable = ((saveData[3]>>7)&0x01); } } else { if (saveData[3]==1||saveData[3]==0) { legacyEnable = saveData[3]; } else { legacy_beta = 1; } } } bw = saveData[6]; bh = saveData[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 (saveData[5]!=CELL || bx0+bw>XRES/CELL || by0+bh>YRES/CELL) throw ParseException(ParseException::InvalidDimensions, "Save too large"); int size = (unsigned)saveData[8]; size |= ((unsigned)saveData[9])<<8; size |= ((unsigned)saveData[10])<<16; size |= ((unsigned)saveData[11])<<24; 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"); 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, "Cannot decompress: " + format::NumberToString(bzStatus)); dataLength = size; #ifdef DEBUG std::cout << "Parsing " << dataLength << " bytes of data, version " << ver << std::endl; #endif 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; } 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"); // load the required air state for (y=by0; y=44 && ver<71 && data[p]==O_WL_SIGN) { p++; continue; } blockMap[y][x] = data[p]; if (blockMap[y][x]==1) blockMap[y][x]=WL_WALL; else if (blockMap[y][x]==2) blockMap[y][x]=WL_DESTROYALL; else if (blockMap[y][x]==3) blockMap[y][x]=WL_ALLOWLIQUID; else if (blockMap[y][x]==4) blockMap[y][x]=WL_FAN; else if (blockMap[y][x]==5) blockMap[y][x]=WL_STREAM; else if (blockMap[y][x]==6) blockMap[y][x]=WL_DETECT; else if (blockMap[y][x]==7) blockMap[y][x]=WL_EWALL; else if (blockMap[y][x]==8) blockMap[y][x]=WL_WALLELEC; else if (blockMap[y][x]==9) blockMap[y][x]=WL_ALLOWAIR; else if (blockMap[y][x]==10) blockMap[y][x]=WL_ALLOWPOWDER; else if (blockMap[y][x]==11) blockMap[y][x]=WL_ALLOWALLELEC; else if (blockMap[y][x]==12) blockMap[y][x]=WL_EHOLE; else if (blockMap[y][x]==13) blockMap[y][x]=WL_ALLOWGAS; if (ver>=44) { /* The numbers used to save walls were changed, starting in v44. * The new numbers are ignored for older versions due to some corruption of bmap in saves from older versions. */ if (blockMap[y][x]==O_WL_WALLELEC) blockMap[y][x]=WL_WALLELEC; else if (blockMap[y][x]==O_WL_EWALL) blockMap[y][x]=WL_EWALL; else if (blockMap[y][x]==O_WL_DETECT) blockMap[y][x]=WL_DETECT; else if (blockMap[y][x]==O_WL_STREAM) blockMap[y][x]=WL_STREAM; else if (blockMap[y][x]==O_WL_FAN||blockMap[y][x]==O_WL_FANHELPER) blockMap[y][x]=WL_FAN; else if (blockMap[y][x]==O_WL_ALLOWLIQUID) blockMap[y][x]=WL_ALLOWLIQUID; else if (blockMap[y][x]==O_WL_DESTROYALL) blockMap[y][x]=WL_DESTROYALL; else if (blockMap[y][x]==O_WL_ERASE) blockMap[y][x]=WL_ERASE; else if (blockMap[y][x]==O_WL_WALL) blockMap[y][x]=WL_WALL; else if (blockMap[y][x]==O_WL_ALLOWAIR) blockMap[y][x]=WL_ALLOWAIR; else if (blockMap[y][x]==O_WL_ALLOWSOLID) blockMap[y][x]=WL_ALLOWPOWDER; else if (blockMap[y][x]==O_WL_ALLOWALLELEC) blockMap[y][x]=WL_ALLOWALLELEC; else if (blockMap[y][x]==O_WL_EHOLE) blockMap[y][x]=WL_EHOLE; else if (blockMap[y][x]==O_WL_ALLOWGAS) blockMap[y][x]=WL_ALLOWGAS; else if (blockMap[y][x]==O_WL_GRAV) blockMap[y][x]=WL_GRAV; else if (blockMap[y][x]==O_WL_ALLOWENERGY) blockMap[y][x]=WL_ALLOWENERGY; } if (blockMap[y][x] >= UI_WALLCOUNT) blockMap[y][x] = 0; } p++; } for (y=by0; y=44 && data[(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] = (data[p++]-127.0f)/64.0f; } for (y=by0; y=44 && data[(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] = (data[p++]-127.0f)/64.0f; } // load the particle map int i = 0; k = 0; pty = p; for (y=y0; y= dataLength) throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__)); j=data[p++]; if (j >= PT_NUM) { 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; particleIDMap[(x-x0)+(y-y0)*w] = k+1; particlesCount = ++k; } } // load particle properties for (j=0; j= dataLength) throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__)); if (i < NPART) { particles[i].vx = (data[p++]-127.0f)/16.0f; particles[i].vy = (data[p++]-127.0f)/16.0f; } else p += 2; } } for (j=0; j=44) { if (p >= dataLength) { throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__)); } if (i <= NPART) { ttv = (data[p++])<<8; ttv |= (data[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 = data[p++]*4; else p++; } } } if (ver>=44) { for (j=0; j= dataLength) { throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__)); } if (i <= NPART) { ttv = (data[p++])<<8; ttv |= (data[p++]); particles[i-1].tmp = ttv; if (ver<53 && !particles[i-1].tmp) for (q = 1; q<=NGOL; 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=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 = data[p++]; else p++; } } } //Read ALPHA component for (j=0; j=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 = data[p++]<<24; } else { p++; } } } } //Read RED component for (j=0; j=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 |= data[p++]<<16; } else { p++; } } } } //Read GREEN component for (j=0; j=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 |= data[p++]<<8; } else { p++; } } } } //Read BLUE component for (j=0; j=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 |= data[p++]; } else { p++; } } } } for (j=0; j=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 = (data[p++])<<8; ttv |= (data[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 = (data[p++]*((MAX_TEMP+(-MIN_TEMP))/255))+MIN_TEMP; } } else { particles[i-1].temp = ((data[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=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 = data[p++]; else p++; } // no more particle properties to load, so we can change type here without messing up loading if (i && i<=NPART) { if (ver<90 && particles[i-1].type == PT_PHOT) { particles[i-1].flags |= FLAG_PHOTDECO; } 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=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 = (((firw_data[caddress]))<<16) | (((firw_data[caddress+1]))<<8) | ((firw_data[caddress+2])); } } if (ver < 89) { if (particles[i-1].type == PT_FILT) { if (particles[i-1].tmp<0 || particles[i-1].tmp>3) particles[i-1].tmp = 6; particles[i-1].ctype = 0; } else if (particles[i-1].type == PT_QRTZ || particles[i-1].type == PT_PQRT) { particles[i-1].tmp2 = particles[i-1].tmp; particles[i-1].tmp = particles[i-1].ctype; particles[i-1].ctype = 0; } } if (ver < 91) { if (particles[i-1].type == PT_VINE) particles[i-1].tmp = 1; else if (particles[i-1].type == PT_CONV) { if (particles[i-1].tmp) { particles[i-1].ctype |= particles[i-1].tmp<<8; particles[i-1].tmp = 0; } } } if (ver < 93) { if (particles[i-1].type == PT_PIPE || particles[i-1].type == PT_PPIP) { if (particles[i-1].ctype == 1) particles[i-1].tmp |= 0x00020000; //PFLAG_INITIALIZING particles[i-1].tmp |= (particles[i-1].ctype-1)<<18; particles[i-1].ctype = particles[i-1].tmp&0xFF; } else if (particles[i-1].type == PT_HSWC || particles[i-1].type == PT_PUMP) { particles[i-1].tmp = 0; } } } } if (p >= dataLength) throw ParseException(ParseException::Corrupt, "Ran past data buffer"); j = data[p++]; for (i=0; i dataLength) throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__)); x = data[p++]; x |= ((unsigned)data[p++])<<8; tempSign.x = x+x0; x = data[p++]; x |= ((unsigned)data[p++])<<8; tempSign.y = x+y0; x = data[p++]; tempSign.ju = (sign::Justification)x; x = data[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, data+p, x); tempSignText[x] = 0; tempSign.text = format::CleanString(ByteString(tempSignText).FromUtf8(), true, true, true).Substr(0, 45); tempSigns.push_back(tempSign); p += x; } for (size_t i = 0; i < tempSigns.size(); i++) { if(signs.size() == MAXSIGNS) break; signs.push_back(tempSigns[i]); } } // restrict the minimum version this save can be opened with #define RESTRICTVERSION(major, minor) if ((major) > minimumMajorVersion || (((major) == minimumMajorVersion && (minor) > minimumMinorVersion))) {\ minimumMajorVersion = major;\ minimumMinorVersion = minor;\ } // restrict the minimum version this save can be rendered with #define RESTRICTRENDERVERSION(major, minor) if ((major) > blameSimon_major || (((major) == blameSimon_major && (minor) > blameSimon_minor))) {\ blameSimon_major = major;\ blameSimon_minor = minor;\ } char * GameSave::serialiseOPS(unsigned int & dataLength) { int blockX, blockY, blockW, blockH, fullX, fullY, fullW, fullH; int x, y, i; // minimum version this save is compatible with // when building, this number may be increased depending on what elements are used // or what properties are detected int minimumMajorVersion = 90, minimumMinorVersion = 2; // blame simon for always being slow updating the renderer int blameSimon_major = 92, blameSimon_minor = 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 auto wallData = std::unique_ptr(new unsigned char[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)"); unsigned int wallDataLen = blockWidth*blockHeight, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 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]) hasWallData = true; //save pressure and x/y velocity grids float pres = std::max(-255.0f,std::min(255.0f,pressure[y][x]))+256.0f; float velX = std::max(-255.0f,std::min(255.0f,velocityX[y][x]))+256.0f; float velY = std::max(-255.0f,std::min(255.0f,velocityY[y][x]))+256.0f; pressData[pressDataLen++] = (unsigned char)((int)(pres*128)&0xFF); pressData[pressDataLen++] = (unsigned char)((int)(pres*128)>>8); vxData[vxDataLen++] = (unsigned char)((int)(velX*128)&0xFF); vxData[vxDataLen++] = (unsigned char)((int)(velX*128)>>8); vyData[vyDataLen++] = (unsigned char)((int)(velY*128)&0xFF); vyData[vyDataLen++] = (unsigned char)((int)(velY*128)>>8); int tempTemp = (int)(ambientHeat[y][x]+0.5f); ambientData[ambientDataLen++] = tempTemp; ambientData[ambientDataLen++] = tempTemp >> 8; 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; } } } //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 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); unsigned int soapCount = 0; for(i = 0; i < particlesCount; 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 auto partsPosData = std::unique_ptr(new unsigned char[fullW*fullH*3]); unsigned int partsPosDataLen = 0; if (!partsPosData) throw BuildException("Save error, out of memory (partposdata)"); for (y=0;y>16; partsPosData[partsPosDataLen++] = (posCount&0x0000FF00)>>8; partsPosData[partsPosDataLen++] = (posCount&0x000000FF); } } //Copy parts data /* Field descriptor format: | 0 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | RESERVED | type[2] | pavg | tmp[3+4] | tmp2[2] | tmp2 | ctype[2] | vy | vx | decorations | 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 last bit is reserved. If necessary, use it to signify that fieldDescriptor will have another byte That way, if we ever need a 17th bit, we won't have to change the save format */ auto partsData = std::unique_ptr(new unsigned char[NPART * (sizeof(Particle)+1)]); unsigned int partsDataLen = 0; auto partsSaveIndex = std::unique_ptr(new unsigned[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>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; //Location of the field descriptor fieldDescLoc = partsDataLen++; partsDataLen++; // Extra type byte if necessary if (particles[i].type & 0xFF00) { partsData[partsDataLen++] = particles[i].type >> 8; fieldDesc |= 1 << 14; RESTRICTVERSION(93, 0); RESTRICTRENDERVERSION(93, 0); } //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 = floor(particles[i].temp-294.15f+0.5f); partsData[partsDataLen++] = tempTemp; } else { fieldDesc |= 1; tempTemp = (int)(particles[i].temp+0.5f); partsData[partsDataLen++] = tempTemp; partsData[partsDataLen++] = tempTemp >> 8; } //Life (optional), 1 to 2 bytes if(particles[i].life) { int life = particles[i].life; if (life > 0xFFFF) life = 0xFFFF; else if (life < 0) life = 0; fieldDesc |= 1 << 1; partsData[partsDataLen++] = life; if (life & 0xFF00) { fieldDesc |= 1 << 2; partsData[partsDataLen++] = life >> 8; } } //Tmp (optional), 1, 2, or 4 bytes if(particles[i].tmp) { fieldDesc |= 1 << 3; partsData[partsDataLen++] = particles[i].tmp; if(particles[i].tmp & 0xFFFFFF00) { fieldDesc |= 1 << 4; partsData[partsDataLen++] = particles[i].tmp >> 8; if(particles[i].tmp & 0xFFFF0000) { 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 & 0xFFFFFF00) { 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 & 0xFF00) { fieldDesc |= 1 << 11; partsData[partsDataLen++] = particles[i].tmp2 >> 8; } } //Pavg, 4 bytes //Don't save pavg for things that break under pressure, because then they will break when the save is loaded, since pressure isn't also loaded if ((particles[i].pavg[0] || particles[i].pavg[1]) && !(particles[i].type == PT_QRTZ || particles[i].type == PT_GLAS || particles[i].type == PT_TUNG)) { fieldDesc |= 1 << 13; partsData[partsDataLen++] = (int)particles[i].pavg[0]; partsData[partsDataLen++] = ((int)particles[i].pavg[0])>>8; partsData[partsDataLen++] = (int)particles[i].pavg[1]; partsData[partsDataLen++] = ((int)particles[i].pavg[1])>>8; } //Write the field descriptor partsData[fieldDescLoc] = fieldDesc; partsData[fieldDescLoc+1] = fieldDesc>>8; if (particles[i].type == PT_SOAP) soapCount++; if (particles[i].type == PT_RPEL && particles[i].ctype) { RESTRICTVERSION(91, 4); } else if (particles[i].type == PT_NWHL && particles[i].tmp) { RESTRICTVERSION(91, 5); } if (particles[i].type == PT_HEAC || particles[i].type == PT_SAWD || particles[i].type == PT_POLO || particles[i].type == PT_RFRG || particles[i].type == PT_RFGL || particles[i].type == PT_LSNS) { RESTRICTVERSION(92, 0); } else if ((particles[i].type == PT_FRAY || particles[i].type == PT_INVIS) && particles[i].tmp) { RESTRICTVERSION(92, 0); } /*else if (particles[i].type == PT_PIPE || particles[i].type == PT_PPIP) { RESTRICTVERSION(93, 0); } if (particles[i].type == PT_TSNS || particles[i].type == PT_PSNS || particles[i].type == PT_HSWC || particles[i].type == PT_PUMP) { if (particles[i].tmp == 1) { RESTRICTVERSION(93, 0); } }*/ if (PMAPBITS > 8) { if (TypeInCtype(particles[i].type, particles[i].ctype) && particles[i].ctype > 0xFF) { RESTRICTVERSION(93, 0); } else if (TypeInTmp(particles[i].type) && particles[i].tmp > 0xFF) { RESTRICTVERSION(93, 0); } else if (TypeInTmp2(particles[i].type, particles[i].tmp2) && particles[i].tmp2 > 0xFF) { RESTRICTVERSION(93, 0); } } //Get the pmap entry for the next particle in the same position i = partsPosLink[i]; } } } unsigned char *soapLinkData = NULL; auto soapLinkDataPtr = std::unique_ptr(); unsigned int soapLinkDataLen = 0; if (soapCount) { soapLinkData = new unsigned char[3*soapCount]; if (!soapLinkData) throw BuildException("Save error, out of memory (SOAP)"); soapLinkDataPtr = std::move(std::unique_ptr(soapLinkData)); //Iterate through particles in the same order that they were saved for (y=0;y>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>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]; } } } } bson b; b.data = NULL; auto bson_deleter = [](bson * b) { bson_destroy(b); }; // Use unique_ptr with a custom deleter to ensure that bson_destroy is called even when an exception is thrown std::unique_ptr b_ptr(&b, bson_deleter); set_bson_err_handler([](const char* err) { throw BuildException("BSON error when parsing save: " + ByteString(err).FromUtf8()); }); bson_init(&b); bson_append_start_object(&b, "origin"); bson_append_int(&b, "majorVersion", SAVE_VERSION); bson_append_int(&b, "minorVersion", MINOR_VERSION); bson_append_int(&b, "buildNum", BUILD_NUM); bson_append_int(&b, "snapshotId", SNAPSHOT_ID); bson_append_int(&b, "modId", MOD_ID); bson_append_string(&b, "releaseType", IDENT_RELTYPE); bson_append_string(&b, "platform", IDENT_PLATFORM); bson_append_string(&b, "builtType", IDENT_BUILD); bson_append_finish_object(&b); bson_append_start_object(&b, "minimumVersion"); bson_append_int(&b, "major", minimumMajorVersion); bson_append_int(&b, "minor", minimumMinorVersion); bson_append_int(&b, "rendermajor", blameSimon_major); bson_append_int(&b, "renderminor", blameSimon_minor); bson_append_finish_object(&b); bson_append_bool(&b, "waterEEnabled", waterEEnabled); bson_append_bool(&b, "legacyEnable", legacyEnable); bson_append_bool(&b, "gravityEnable", gravityEnable); bson_append_bool(&b, "aheat_enable", aheatEnable); bson_append_bool(&b, "paused", paused); bson_append_int(&b, "gravityMode", gravityMode); bson_append_int(&b, "airMode", airMode); bson_append_int(&b, "edgeMode", edgeMode); if (stkm.hasData()) { bson_append_start_object(&b, "stkm"); if (stkm.rocketBoots1) bson_append_bool(&b, "rocketBoots1", stkm.rocketBoots1); if (stkm.rocketBoots2) bson_append_bool(&b, "rocketBoots2", stkm.rocketBoots2); if (stkm.fan1) bson_append_bool(&b, "fan1", stkm.fan1); if (stkm.fan2) bson_append_bool(&b, "fan2", stkm.fan2); if (stkm.rocketBootsFigh.size()) { bson_append_start_array(&b, "rocketBootsFigh"); for (unsigned int fighNum : stkm.rocketBootsFigh) bson_append_int(&b, "num", fighNum); bson_append_finish_array(&b); } if (stkm.fanFigh.size()) { bson_append_start_array(&b, "fanFigh"); for (unsigned int fighNum : stkm.fanFigh) bson_append_int(&b, "num", fighNum); bson_append_finish_array(&b); } bson_append_finish_object(&b); } bson_append_int(&b, "pmapbits", pmapbits); if (partsData && partsDataLen) { bson_append_binary(&b, "parts", BSON_BIN_USER, (const char *)partsData.get(), partsDataLen); if (palette.size()) { bson_append_start_array(&b, "palette"); for(std::vector::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); } if (partsPosData && partsPosDataLen) bson_append_binary(&b, "partsPos", BSON_BIN_USER, (const char *)partsPosData.get(), partsPosDataLen); } if (wallData && hasWallData) bson_append_binary(&b, "wallMap", BSON_BIN_USER, (const char *)wallData.get(), wallDataLen); if (fanData && fanDataLen) bson_append_binary(&b, "fanMap", BSON_BIN_USER, (const char *)fanData.get(), fanDataLen); if (pressData && pressDataLen) bson_append_binary(&b, "pressMap", (char)BSON_BIN_USER, (const char*)pressData.get(), pressDataLen); if (vxData && vxDataLen) bson_append_binary(&b, "vxMap", (char)BSON_BIN_USER, (const char*)vxData.get(), vxDataLen); if (vyData && vyDataLen) bson_append_binary(&b, "vyMap", (char)BSON_BIN_USER, (const char*)vyData.get(), vyDataLen); if (ambientData && 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", BSON_BIN_USER, (const char *)soapLinkData, soapLinkDataLen); unsigned int signsCount = 0; for (size_t 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 (size_t 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.ToUtf8().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); } if (authors.size()) { bson_append_start_object(&b, "authors"); ConvertJsonToBson(&b, authors); bson_append_finish_object(&b); } if (bson_finish(&b) == BSON_ERROR) throw BuildException("Error building bson data"); 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("Save error, out of memory (finalData): " + format::NumberToString(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) { throw BuildException("Save error, could not compress (ret " + format::NumberToString(bz2ret) + ")"); } #ifdef DEBUG printf("compressed data: %d\n", compressedSize); #endif dataLength = compressedSize + 12; char *saveData = new char[dataLength]; std::copy(&outputData[0], &outputData[dataLength], &saveData[0]); return saveData; } void GameSave::ConvertBsonToJson(bson_iterator *iter, Json::Value *j, int depth) { bson_iterator subiter; bson_iterator_subiterator(iter, &subiter); while (bson_iterator_next(&subiter)) { ByteString key = bson_iterator_key(&subiter); if (bson_iterator_type(&subiter) == BSON_STRING) (*j)[key] = bson_iterator_string(&subiter); else if (bson_iterator_type(&subiter) == BSON_BOOL) (*j)[key] = bson_iterator_bool(&subiter); else if (bson_iterator_type(&subiter) == BSON_INT) (*j)[key] = bson_iterator_int(&subiter); else if (bson_iterator_type(&subiter) == BSON_LONG) (*j)[key] = (Json::Value::UInt64)bson_iterator_long(&subiter); else if (bson_iterator_type(&subiter) == BSON_ARRAY && depth < 5) { bson_iterator arrayiter; bson_iterator_subiterator(&subiter, &arrayiter); int length = 0, length2 = 0; while (bson_iterator_next(&arrayiter)) { if (bson_iterator_type(&arrayiter) == BSON_OBJECT && !strcmp(bson_iterator_key(&arrayiter), "part")) { Json::Value tempPart; ConvertBsonToJson(&arrayiter, &tempPart, depth + 1); (*j)["links"].append(tempPart); length++; } else if (bson_iterator_type(&arrayiter) == BSON_INT && !strcmp(bson_iterator_key(&arrayiter), "saveID")) { (*j)["links"].append(bson_iterator_int(&arrayiter)); } length2++; if (length > (int)(40 / ((depth+1) * (depth+1))) || length2 > 50) break; } } } } std::set GetNestedSaveIDs(Json::Value j) { Json::Value::Members members = j.getMemberNames(); std::set saveIDs = std::set(); for (Json::Value::Members::iterator iter = members.begin(), end = members.end(); iter != end; ++iter) { ByteString member = *iter; if (member == "id" && j[member].isInt()) saveIDs.insert(j[member].asInt()); else if (j[member].isArray()) { for (Json::Value::ArrayIndex i = 0; i < j[member].size(); i++) { // only supports objects and ints here because that is all we need if (j[member][i].isInt()) { saveIDs.insert(j[member][i].asInt()); continue; } if (!j[member][i].isObject()) continue; std::set nestedSaveIDs = GetNestedSaveIDs(j[member][i]); saveIDs.insert(nestedSaveIDs.begin(), nestedSaveIDs.end()); } } } return saveIDs; } // converts a json object to bson void GameSave::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) { ByteString member = *iter; if (j[member].isString()) bson_append_string(b, member.c_str(), j[member].asCString()); else if (j[member].isBool()) bson_append_bool(b, member.c_str(), j[member].asBool()); else if (j[member].type() == Json::intValue) bson_append_int(b, member.c_str(), j[member].asInt()); else if (j[member].type() == Json::uintValue) bson_append_long(b, member.c_str(), j[member].asInt64()); else if (j[member].isArray()) { bson_append_start_array(b, member.c_str()); std::set saveIDs = std::set(); int length = 0; for (Json::Value::ArrayIndex i = 0; i < j[member].size(); i++) { // only supports objects and ints here because that is all we need if (j[member][i].isInt()) { saveIDs.insert(j[member][i].asInt()); continue; } if (!j[member][i].isObject()) continue; if (depth > 4 || length > (int)(40 / ((depth+1) * (depth+1)))) { std::set nestedSaveIDs = GetNestedSaveIDs(j[member][i]); saveIDs.insert(nestedSaveIDs.begin(), nestedSaveIDs.end()); } else { bson_append_start_object(b, "part"); ConvertJsonToBson(b, j[member][i], depth+1); bson_append_finish_object(b); } length++; } for (std::set::iterator iter = saveIDs.begin(), end = saveIDs.end(); iter != end; ++iter) { bson_append_int(b, "saveID", *iter); } bson_append_finish_array(b); } } } // 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 && (type == PT_CLNE || type == PT_PCLN || type == PT_BCLN || type == PT_PBCN || type == PT_STOR || type == PT_CONV || type == PT_STKM || type == PT_STKM2 || type == PT_FIGH || type == PT_LAVA || type == PT_SPRK || type == PT_PSTN || type == PT_CRAY || type == PT_DTEC || type == PT_DRAY || type == PT_PIPE || type == PT_PPIP || type == PT_RAYT); } bool GameSave::TypeInTmp(int type) { return type == PT_STOR; } bool GameSave::TypeInTmp2(int type, int tmp2) { return (type == PT_VIRS || type == PT_VRSG || type == PT_VRSS) && (tmp2 >= 0 && tmp2 < PT_NUM); } 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(); }