356 lines
7.8 KiB
C++
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() {}
|