Files
starruler-linux/source/game/util/save_file.cpp
T
2018-07-17 14:15:37 +02:00

356 lines
7.8 KiB
C++

#include "save_file.h"
#ifdef _MSC_VER
#include "zlib/zlib.h"
#else
#include "zlib.h"
#endif
#include <stdio.h>
#include "compat/misc.h"
#include "obj/object.h"
#include <unordered_map>
#include "files.h"
#include "main/logging.h"
#include "main/game_platform.h"
#include "main/references.h"
#include "scripts/context_cache.h"
const char* saveIdentifier = "$SR2";
const unsigned gzBufferSize = 128000;
class SaveFileWriter : public SaveFile {
unsigned boundaryID;
gzFile out;
const std::string destName, tempName;
std::vector<std::unordered_map<std::string,int>> identifiers;
bool addIdentifier(unsigned type, int id, const std::string& ident) {
if(type >= identifiers.size()) {
identifiers.resize(type+1);
}
else {
auto it = identifiers[type].find(ident);
if(it != identifiers[type].end())
return false;
}
identifiers[type][ident] = id;
return true;
}
void saveIdentifiers() {
unsigned cnt = identifiers.size();
*this << cnt;
for(unsigned i = 0; i < cnt; ++i) {
auto& mp = identifiers[i];
unsigned icnt = mp.size();
*this << icnt;
foreach(it, mp) {
*this << it->first;
*this << it->second;
}
}
}
void writeIdentifier(unsigned type, int id) {
*this << id;
}
void write(const void* source, unsigned bytes) {
if(bytes != 0)
gzwrite(out,source,bytes);
}
void close() {
std::string destTempName = destName + ".temp";
gzclose(out); out = 0;
//Move the existing save file to a .temp version
int oldMoved = rename(destName.c_str(), destTempName.c_str());
//Move our temp file to the intended destination
int result = rename(tempName.c_str(), destName.c_str());
if(result == 0) {
//Saving succeeded, remove the old save file
remove(destTempName.c_str());
if(devices.cloud)
devices.cloud->writeCloudFile(destName, std::string("saves/") + getBasename(destName));
delete this;
}
else {
//Saving failed, remove the temp file and replace the old save file
remove(tempName.c_str());
if(oldMoved == 0)
rename(destTempName.c_str(), destName.c_str());
delete this;
throw SaveFileError("Could not save file");
}
}
void read(void* dest, unsigned bytes) {
throw SaveFileError("Cannot read while writing");
}
void boundary() {
*this << boundaryID++;
}
public:
SaveFileWriter(const std::string& file) : boundaryID(100), destName(file), tempName(getTemporaryFile()) {
out = gzopen(tempName.c_str(),"wb1f");
scriptVersion = 0;
startVersion = 0;
if(out == 0)
throw SaveFileError("Could not open temporary file");
gzbuffer(out, gzBufferSize);
write(saveIdentifier, 4);
*this << SFV_Current;
}
};
void readSaveFileInfo(SaveFile& file, SaveFileInfo& info) {
file >> info.version;
if(file >= SFV_0005)
file >> info.startVersion;
else
info.startVersion = info.version;
unsigned cnt = 0;
file >> cnt;
info.mods.resize(cnt);
for(unsigned i = 0; i < cnt; ++i) {
file >> info.mods[i].id;
if(file >= SFV_0011)
file >> info.mods[i].version;
else
info.mods[i].version = 0;
}
}
bool getSaveFileInfo(const std::string& fname, SaveFileInfo& info) {
try {
auto* pfile = SaveFile::open(fname, SM_Read);
if(pfile == nullptr)
return false;
pfile->loadIdentifiers();
readSaveFileInfo(*pfile, info);
pfile->close();
return true;
}
catch(SaveFileError& err) {
error("Failed to read save '%s':\n %s", fname.c_str(), err.text);
return false;
}
}
class SaveFileReader : public SaveFile {
unsigned boundaryID;
gzFile in;
std::vector<std::unordered_map<std::string,int>> identifiers;
std::vector<std::unordered_map<int,int>> identMap;
std::vector<std::unordered_map<std::string,int>> loaded;
bool addIdentifier(unsigned type, int id, const std::string& ident) {
if(type >= identifiers.size()) {
identifiers.resize(type+1);
}
else {
auto it = identifiers[type].find(ident);
if(it != identifiers[type].end())
return false;
}
identifiers[type][ident] = id;
return true;
}
void addDummyLoadIdentifier(unsigned type, int id, const std::string& ident) {
if(type >= loaded.size())
loaded.resize(type+1);
loaded[type][ident] = id;
}
void loadIdentifiers() {
unsigned cnt = *this;
loaded.resize(cnt);
for(unsigned i = 0; i < cnt; ++i) {
auto& table = loaded[i];
unsigned icnt = *this;
std::string ident;
int id;
for(unsigned j = 0; j < icnt; ++j) {
*this >> ident;
*this >> id;
table[ident] = id;
}
}
}
void finalizeIdentifiers() {
unsigned cnt = (unsigned)loaded.size();
identMap.resize(cnt);
if(cnt > identifiers.size())
identifiers.resize(cnt);
for(unsigned i = 0; i < cnt; ++i) {
auto& mp = identifiers[i];
auto& table = identMap[i];
foreach(it, loaded[i]) {
auto ft = mp.find(it->first);
if(ft != mp.end())
table[it->second] = ft->second;
}
}
}
unsigned getPrevIdentifierCount(unsigned type) {
if(type >= identMap.size())
return 0;
return identMap[type].size();
}
unsigned getIdentifierCount(unsigned type) {
if(type >= identifiers.size())
return 0;
return identifiers[type].size();
}
int getIdentifier(unsigned type, int id) {
if(type >= identMap.size())
return -1;
auto ft = identMap[type].find(id);
if(ft == identMap[type].end())
return -1;
return ft->second;
}
int readIdentifier(unsigned type) {
int id;
*this >> id;
if(type >= identMap.size())
return -1;
auto ft = identMap[type].find(id);
if(ft == identMap[type].end())
return -1;
return ft->second;
}
void read(void* dest, unsigned bytes) {
if(bytes != 0) {
int readBytes = gzread(in, dest, bytes);
if(readBytes < int(bytes)) {
scripts::logException();
throw SaveFileError("Unexpected end of file");
}
}
}
void write(const void* source, unsigned bytes) {
throw SaveFileError("Cannot write while reading");
}
void close() {
gzclose(in);
delete this;
}
void boundary() {
unsigned checkID = *this;
if(checkID != boundaryID)
throw SaveFileError("Boundary did not match");
++boundaryID;
}
public:
SaveFileReader(const std::string& file) : boundaryID(100) {
scriptVersion = 0;
startVersion = 0;
in = gzopen(file.c_str(), "rb");
if(in == 0)
throw SaveFileError("Could not open save file");
gzbuffer(in, gzBufferSize);
char buff[5]; buff[4] = '\0';
read(buff, 4);
if(strcmp(buff, saveIdentifier) != 0)
throw SaveFileError("Not a Star Ruler save");
*this >> version;
if(version < SFV_EarliestSupported)
throw SaveFileError("Save file version no longer supported");
else if(version >= SFV_Future)
throw SaveFileError("Save file from a newer version, please update");
}
};
SaveFile* SaveFile::open(const std::string& file, SaveMode mode) {
if(mode == SM_Read)
return new SaveFileReader(file);
else if(mode == SM_Write)
return new SaveFileWriter(file);
else
throw SaveFileError("Invalid save file mode");
}
SaveFile& SaveFile::operator<<(const char* str) {
size_t len = strlen(str);
if(len > 0xffff)
throw SaveFileError("String too long");
unsigned short shortLen = (unsigned short)len;
write(&shortLen,2);
write(str,shortLen);
return *this;
}
SaveFile& SaveFile::operator<<(const std::string& str) {
size_t len = str.size();
if(len > 0xffff)
throw SaveFileError("String too long");
unsigned short shortLen = (unsigned short)len;
write(&shortLen,2);
if(shortLen > 0)
write(str.c_str(),shortLen);
return *this;
}
SaveFile& SaveFile::operator>>(std::string& str) {
unsigned short length;
read(&length,2);
if(length > 0) {
void* temp = alloca(length);
read(temp,length);
str.assign((const char*)temp, length);
}
return *this;
}
SaveFile& SaveFile::operator>>(Object*& obj) {
obj = getObjectByID(read<int>(), true);
return *this;
}
Object* SaveFile::readExistingObject() {
return getObjectByID(read<int>());
}
SaveFile& SaveFile::operator<<(const Object* obj) {
int id = obj ? obj->id : 0;
return *this << id;
}
SaveFile::~SaveFile() {}