This repository has been archived on 2025-03-20. You can view files and clone it, but cannot push or open issues or pull requests.
The-Powder-Toy/src/client/GameSave.cpp
jacob1 b5159ab74e Changes to save format and PIPE
Update save format to optionally store type as two bits
PIPE now stores element in ctype
Disallow uploading saves using two bytes in type or other fields
update save format to store pmapbits and automatically convert data
2017-12-31 23:55:41 -05:00

2609 lines
79 KiB
C++

#include "common/tpt-minmax.h"
#include <iostream>
#include <sstream>
#include <cmath>
#include <climits>
#include <memory>
#include <vector>
#include <set>
#include <bzlib.h>
#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):
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),
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<char> 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<unsigned char> data)
{
blockWidth = 0;
blockHeight = 0;
InitData();
InitVars();
expanded = false;
hasOriginalData = true;
originalData = std::vector<char>(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<char>(data, data+dataSize);
#ifdef DEBUG
std::cout << "Creating Expanded save from data" << std::endl;
#endif
try
{
Expand();
}
catch(ParseException & e)
{
std::cout << e.what() << std::endl;
dealloc(); //Free any allocated memory
throw;
}
//Collapse();
}
// 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()
{
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 <typename T>
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<unsigned char>(blockWidth, blockHeight, 0);
fanVelX = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
fanVelY = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
pressure = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
velocityX = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
velocityY = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
ambientHeat = Allocate2DArray<float>(blockWidth, blockHeight, 0.0f);
}
std::vector<char> GameSave::Serialise()
{
unsigned int dataSize;
char * data = Serialise(dataSize);
if (data == NULL)
return std::vector<char>();
std::vector<char> 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.x<ctl.x) ctl.x = tmp.x;
if (tmp.y<ctl.y) ctl.y = tmp.y;
if (tmp.x>cbr.x) cbr.x = tmp.x;
if (tmp.y>cbr.y) cbr.y = tmp.y;
}
// casting as int doesn't quite do what we want with negative numbers, so use floor()
tmp = v2d_new(floor(ctl.x+0.5f),floor(ctl.y+0.5f));
translate = v2d_sub(translate,tmp);
newWidth = floor(cbr.x+0.5f)-floor(ctl.x+0.5f)+1;
newHeight = floor(cbr.y+0.5f)-floor(ctl.y+0.5f)+1;
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<unsigned char>(newBlockWidth, newBlockHeight, 0);
fanVelXNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
fanVelYNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
pressureNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
velocityXNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
velocityYNew = Allocate2DArray<float>(newBlockWidth, newBlockHeight, 0.0f);
ambientHeatNew = Allocate2DArray<float>(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<blockHeight; y++)
for (x=0; x<blockWidth; x++)
{
pos = v2d_new(x*CELL+CELL*0.4f+translateX, y*CELL+CELL*0.4f+translateY);
pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
nx = pos.x/CELL;
ny = pos.y/CELL;
if (pos.x<0 || nx>=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];
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<bson, decltype(bson_deleter)> 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<int>(bz2ret) + ")");
}
set_bson_err_handler([](const char* err) { throw ParseException(ParseException::Corrupt, "BSON error when parsing save: " + std::string(err)); });
bson_init_data_size(&b, (char*)bsonData, bsonDataLen);
bson_iterator_init(&iter, &b);
std::vector<sign> 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(bson_iterator_string(&signiter), 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), "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)
{
std::string id = std::string(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;
bson_iterator subiter;
bson_iterator_subiterator(&iter, &subiter);
while (bson_iterator_next(&subiter))
{
if (bson_iterator_type(&subiter) == BSON_INT)
{
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);
else
fprintf(stderr, "Wrong type for %s\n", bson_iterator_key(&iter));
}
}
#if 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
{
std::stringstream errorMessage;
errorMessage << "Save from a newer version: Requires version " << major << "." << minor;
throw ParseException(ParseException::WrongVersion, errorMessage.str());
}
#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_STKM:
case PT_STKM2:
case PT_FIGH:
if (savedVersion < 88 && particles[newIndex].ctype == OLD_SPC_AIR)
particles[newIndex].ctype = SPC_AIR;
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;
}
//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<sign> tempSigns;
char tempSignText[255];
sign tempSign("", 0, 0, sign::Left);
//Gol data used to read older saves
int goltype[NGOL];
int grule[NGOL+1][10];
int golRulesCount;
int * golRulesT = LoadGOLRules(golRulesCount);
memcpy(grule, golRulesT, sizeof(int) * (golRulesCount*10));
free(golRulesT);
int golTypesCount;
int * golTypesT = LoadGOLTypes(golTypesCount);
memcpy(goltype, golTypesT, sizeof(int) * (golTypesCount));
free(golTypesT);
std::vector<Element> elements = GetElements();
//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];
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<unsigned char[]>(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)))
{
std::stringstream bzStatusStr;
bzStatusStr << bzStatus;
throw ParseException(ParseException::Corrupt, "Cannot decompress: " + bzStatusStr.str());
}
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<int[]>(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<by0+bh; y++)
for (x=bx0; x<bx0+bw; x++)
{
if (data[p])
{
//In old saves, ignore walls created by sign tool bug
//Not ignoring other invalid walls or invalid walls in new saves, so that any other bugs causing them are easier to notice, find and fix
if (ver>=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<by0+bh; y++)
for (x=bx0; x<bx0+bw; x++)
if (data[(y-by0)*bw+(x-bx0)]==4||(ver>=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<by0+bh; y++)
for (x=bx0; x<bx0+bw; x++)
if (data[(y-by0)*bw+(x-bx0)]==4||(ver>=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<y0+h; y++)
for (x=x0; x<x0+w; x++)
{
if (p >= dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
j=data[p++];
if (j >= PT_NUM) {
//TODO: Possibly some server side translation
j = PT_DUST;//throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (j)
{
memset(particles+k, 0, sizeof(Particle));
particles[k].type = j;
if (j == PT_COAL)
particles[k].tmp = 50;
if (j == PT_FUSE)
particles[k].tmp = 50;
if (j == PT_PHOT)
particles[k].ctype = 0x3fffffff;
if (j == PT_SOAP)
particles[k].ctype = 0;
if (j==PT_BIZR || j==PT_BIZRG || j==PT_BIZRS)
particles[k].ctype = 0x47FFFF;
particles[k].x = (float)x;
particles[k].y = (float)y;
particleIDMap[(x-x0)+(y-y0)*w] = k+1;
particlesCount = ++k;
}
}
// load particle properties
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
i--;
if (p+1 >= dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
if (i < NPART)
{
particles[i].vx = (data[p++]-127.0f)/16.0f;
particles[i].vy = (data[p++]-127.0f)/16.0f;
}
else
p += 2;
}
}
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
if (ver>=44) {
if (p >= dataLength) {
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART) {
ttv = (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<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
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].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<w*h; j++)
{
i = particleIDMap[j];
ty = data[pty+j];
if (i && (ty==PT_PBCN || (ty==PT_TRON && ver>=77)))
{
if (p >= dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
if (i <= NPART)
particles[i-1].tmp2 = data[p++];
else
p++;
}
}
}
//Read ALPHA component
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
if (ver>=49) {
if (p >= dataLength) {
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART) {
particles[i-1].dcolour = data[p++]<<24;
} else {
p++;
}
}
}
}
//Read RED component
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
if (ver>=49) {
if (p >= dataLength) {
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART) {
particles[i-1].dcolour |= data[p++]<<16;
} else {
p++;
}
}
}
}
//Read GREEN component
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
if (ver>=49) {
if (p >= dataLength) {
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART) {
particles[i-1].dcolour |= data[p++]<<8;
} else {
p++;
}
}
}
}
//Read BLUE component
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
if (i)
{
if (ver>=49) {
if (p >= dataLength) {
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART) {
particles[i-1].dcolour |= data[p++];
} else {
p++;
}
}
}
}
for (j=0; j<w*h; j++)
{
i = particleIDMap[j];
ty = data[pty+j];
if (i)
{
if (ver>=34&&legacy_beta==0)
{
if (p >= dataLength)
{
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
}
if (i <= NPART)
{
if (ver>=42) {
if (new_format) {
ttv = (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<w*h; j++)
{
int gnum = 0;
i = particleIDMap[j];
ty = data[pty+j];
if (i && (ty==PT_CLNE || (ty==PT_PCLN && ver>=43) || (ty==PT_BCLN && ver>=44) || (ty==PT_SPRK && ver>=21) || (ty==PT_LAVA && ver>=34) || (ty==PT_PIPE && ver>=43) || (ty==PT_LIFE && ver>=51) || (ty==PT_PBCN && ver>=52) || (ty==PT_WIRE && ver>=55) || (ty==PT_STOR && ver>=59) || (ty==PT_CONV && ver>=60)))
{
if (p >= dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
if (i <= NPART)
particles[i-1].ctype = data[p++];
else
p++;
}
//TODO: STKM_init_legs
// no more particle properties to load, so we can change type here without messing up loading
if (i && i<=NPART)
{
if (ver<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<NGOL; gnum++){
if (ty==goltype[gnum])
particles[i-1].ctype = gnum;
}
ty = PT_LIFE;
}
if(ver<52 && (ty==PT_CLNE || ty==PT_PCLN || ty==PT_BCLN)){
//Replace old GOL ctypes in clone
for (gnum = 0; gnum<NGOL; gnum++){
if (particles[i-1].ctype==goltype[gnum])
{
particles[i-1].ctype = PT_LIFE;
particles[i-1].tmp = gnum;
}
}
}
if(ty==PT_LCRY){
if(ver<67)
{
//New LCRY uses TMP not life
if(particles[i-1].life>=10)
{
particles[i-1].life = 10;
particles[i-1].tmp2 = 10;
particles[i-1].tmp = 3;
}
else if(particles[i-1].life<=0)
{
particles[i-1].life = 0;
particles[i-1].tmp2 = 0;
particles[i-1].tmp = 0;
}
else if(particles[i-1].life < 10 && particles[i-1].life > 0)
{
particles[i-1].tmp = 1;
}
}
else
{
particles[i-1].tmp2 = particles[i-1].life;
}
}
if (ver<81)
{
if (particles[i-1].type==PT_BOMB && particles[i-1].tmp!=0)
{
particles[i-1].type = PT_EMBR;
particles[i-1].ctype = 0;
if (particles[i-1].tmp==1)
particles[i-1].tmp = 0;
}
if (particles[i-1].type==PT_DUST && particles[i-1].life>0)
{
particles[i-1].type = PT_EMBR;
particles[i-1].ctype = (particles[i-1].tmp2<<16) | (particles[i-1].tmp<<8) | particles[i-1].ctype;
particles[i-1].tmp = 1;
}
if (particles[i-1].type==PT_FIRW && particles[i-1].tmp>=2)
{
int caddress = restrict_flt(restrict_flt((float)(particles[i-1].tmp-4), 0.0f, 200.0f)*3, 0.0f, (200.0f*3)-3);
particles[i-1].type = PT_EMBR;
particles[i-1].tmp = 1;
particles[i-1].ctype = (((firw_data[caddress]))<<16) | (((firw_data[caddress+1]))<<8) | ((firw_data[caddress+2]));
}
}
if (ver < 88) //fix air blowing stickmen
if ((particles[i-1].type == PT_STKM || particles[i-1].type == PT_STKM2 || particles[i-1].type == PT_FIGH) && particles[i-1].ctype == OLD_SPC_AIR)
particles[i-1].ctype = SPC_AIR;
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;
}
}
}
// Version 93.0
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;
}
}
}
if (p >= dataLength)
throw ParseException(ParseException::Corrupt, "Ran past data buffer");
j = data[p++];
for (i=0; i<j; i++)
{
if (p+6 > dataLength)
throw ParseException(ParseException::Corrupt, "Not enough data at line " MTOS(__LINE__) " in " MTOS(__FILE__));
x = 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(tempSignText, 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;\
}
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;
//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<unsigned char[]>(new unsigned char[blockWidth*blockHeight]);
bool hasWallData = false;
auto fanData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto pressData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto vxData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto vyData = std::unique_ptr<unsigned char[]>(new unsigned char[blockWidth*blockHeight*2]);
auto ambientData = std::unique_ptr<unsigned char[]>(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<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosLastMap = std::unique_ptr<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosCount = std::unique_ptr<unsigned[]>(new unsigned[fullW*fullH]);
auto partsPosLink = std::unique_ptr<unsigned[]>(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<unsigned char[]>(new unsigned char[fullW*fullH*3]);
unsigned int partsPosDataLen = 0;
if (!partsPosData)
throw BuildException("Save error, out of memory (partposdata)");
for (y=0;y<fullH;y++)
{
for (x=0;x<fullW;x++)
{
unsigned int posCount = partsPosCount[y*fullW + x];
partsPosData[partsPosDataLen++] = (posCount&0x00FF0000)>>16;
partsPosData[partsPosDataLen++] = (posCount&0x0000FF00)>>8;
partsPosData[partsPosDataLen++] = (posCount&0x000000FF);
}
}
//Copy parts data
/* Field descriptor format:
| 0 | 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<unsigned char[]>(new unsigned char[NPART * (sizeof(Particle)+1)]);
unsigned int partsDataLen = 0;
auto partsSaveIndex = std::unique_ptr<unsigned[]>(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<fullH;y++)
{
for (x=0;x<fullW;x++)
{
//Find the first particle in this position
i = partsPosFirstMap[y*fullW + x];
//Loop while there is a pmap entry
while (i)
{
unsigned short fieldDesc = 0;
int fieldDescLoc = 0, tempTemp, vTemp;
//Turn pmap entry into a particles index
i = i>>8;
//Store saved particle index+1 for this partsptr index (0 means not saved)
partsSaveIndex[i] = (partsCount++) + 1;
//Type (required)
partsData[partsDataLen++] = particles[i].type;
//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);
fromNewerVersion = true; // TODO: remove on 93.0 release
}
//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);
fromNewerVersion = true; // TODO: remove on 93.0 release
}
if (PMAPBITS > 8)
{
if (Simulation::TypeInCtype(particles[i].type) && particles[i].ctype > 0xFF)
{
RESTRICTVERSION(93, 0);
fromNewerVersion = true; // TODO: remove on 93.0 release
}
else if (Simulation::TypeInTmp(particles[i].type) && particles[i].tmp > 0xFF)
{
RESTRICTVERSION(93, 0);
fromNewerVersion = true; // TODO: remove on 93.0 release
}
else if (Simulation::TypeInTmp2(particles[i].type) && particles[i].tmp2 > 0xFF)
{
RESTRICTVERSION(93, 0);
fromNewerVersion = true; // TODO: remove on 93.0 release
}
}
//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 char[]>();
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<unsigned char[]>(soapLinkData));
//Iterate through particles in the same order that they were saved
for (y=0;y<fullH;y++)
{
for (x=0;x<fullW;x++)
{
//Find the first particle in this position
i = partsPosFirstMap[y*fullW + x];
//Loop while there is a pmap entry
while (i)
{
//Turn pmap entry into a partsptr index
i = i>>8;
if (particles[i].type==PT_SOAP)
{
//Only save forward link for each particle, back links can be deduced from other forward links
//linkedIndex is index within saved particles + 1, 0 means not saved or no link
unsigned linkedIndex = 0;
if ((particles[i].ctype&2) && particles[i].tmp>=0 && particles[i].tmp<NPART)
{
linkedIndex = partsSaveIndex[particles[i].tmp];
}
soapLinkData[soapLinkDataLen++] = (linkedIndex&0xFF0000)>>16;
soapLinkData[soapLinkDataLen++] = (linkedIndex&0x00FF00)>>8;
soapLinkData[soapLinkDataLen++] = (linkedIndex&0x0000FF);
}
//Get the pmap entry for the next particle in the same position
i = partsPosLink[i];
}
}
}
}
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<bson, decltype(bson_deleter)> b_ptr(&b, bson_deleter);
set_bson_err_handler([](const char* err) { throw BuildException("BSON error when parsing save: " + std::string(err)); });
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_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);
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<PaletteItem>::iterator iter = palette.begin(), end = palette.end(); iter != end; ++iter)
{
bson_append_int(&b, (*iter).first.c_str(), (*iter).second);
}
bson_append_finish_array(&b);
}
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.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<unsigned char[]>(new unsigned char[finalDataLen*2+12]);
if (!outputData)
throw BuildException("Save error, out of memory (finalData): " + format::NumberToString<unsigned int>(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<int>(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))
{
std::string 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<int> GetNestedSaveIDs(Json::Value j)
{
Json::Value::Members members = j.getMemberNames();
std::set<int> saveIDs = std::set<int>();
for (Json::Value::Members::iterator iter = members.begin(), end = members.end(); iter != end; ++iter)
{
std::string 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<int> 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)
{
std::string 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<int> saveIDs = std::set<int>();
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<int> 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<int>::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 <typename T>
void GameSave::Deallocate2DArray(T ***array, int blockHeight)
{
if (*array)
{
for (int y = 0; y < blockHeight; y++)
delete[] (*array)[y];
delete[] (*array);
*array = NULL;
}
}
void GameSave::dealloc()
{
if (particles)
{
delete[] particles;
particles = NULL;
}
Deallocate2DArray<unsigned char>(&blockMap, blockHeight);
Deallocate2DArray<float>(&fanVelX, blockHeight);
Deallocate2DArray<float>(&fanVelY, blockHeight);
Deallocate2DArray<float>(&pressure, blockHeight);
Deallocate2DArray<float>(&velocityX, blockHeight);
Deallocate2DArray<float>(&velocityY, blockHeight);
Deallocate2DArray<float>(&ambientHeat, blockHeight);
}
GameSave::~GameSave()
{
dealloc();
}