1
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
7
#include "LuaLoadSaveHandler.h"
9
#include "lib/minizip/zip.h"
11
#include "ExternalAI/EngineOutHandler.h"
12
#include "Game/GameSetup.h"
13
#include "Lua/LuaZip.h"
14
#include "Map/MapDamage.h"
15
#include "Map/ReadMap.h"
16
#include "System/FileSystem/FileSystem.h"
17
#include "System/FileSystem/ArchiveZip.h"
18
#include "System/Platform/byteorder.h"
19
#include "System/EventHandler.h"
20
#include "System/Exceptions.h"
21
#include "System/LogOutput.h"
26
// Prefix for all files in the save file.
27
// May be used to prevent clashes with other code. (AI, Lua)
28
#define PREFIX "Spring/"
30
// Names of files in save file for various components of engine.
31
// They all have a version number as suffix. When breaking compatibility
32
// in the respective file format, please increment its version number.
33
static const char* FILE_STARTSCRIPT = PREFIX"startscript.0";
34
static const char* FILE_AIDATA = PREFIX"aidata.0";
35
static const char* FILE_HEIGHTMAP = PREFIX"heightmap.0";
40
CLuaLoadSaveHandler::CLuaLoadSaveHandler()
41
: savefile(NULL), loadfile(NULL)
46
CLuaLoadSaveHandler::~CLuaLoadSaveHandler()
52
void CLuaLoadSaveHandler::SaveGame(const std::string& file)
54
const std::string realname = filesystem.LocateFile(file, FileSystem::WRITE).c_str();
60
// Remove any existing file
61
filesystem.Remove(realname);
64
if (realname.empty() ||
65
(savefile = zipOpen(realname.c_str(), APPEND_STATUS_CREATE)) == NULL) {
66
throw content_error("Unable to open save file \"" + filename + "\"");
75
if (Z_OK != zipClose(savefile, "Spring save file, visit http://springrts.com/ for details.")) {
76
logOutput.Print("Unable to close save file \"" + filename + "\"");
80
catch (content_error &e) {
81
logOutput.Print("Save failed(content error): %s", e.what());
83
catch (std::exception &e) {
84
logOutput.Print("Save failed: %s", e.what());
87
logOutput.Print("Save failed: %s", e);
90
logOutput.Print("Save failed(unknown error)");
94
if (savefile != NULL) {
95
zipClose(savefile, NULL);
97
filesystem.Remove(realname);
102
void CLuaLoadSaveHandler::SaveEventClients()
104
// FIXME: need some way to 'chroot' them into a single directory?
105
// (maybe abstract zipFile void* after all...)
106
eventHandler.Save(savefile);
110
void CLuaLoadSaveHandler::SaveGameStartInfo()
112
const std::string scriptText = gameSetup->gameSetupText;
113
SaveEntireFile(FILE_STARTSCRIPT, "game setup", scriptText.data(), scriptText.size());
117
void CLuaLoadSaveHandler::SaveAIData()
119
// Save to a stringstream first, to be able to use current interface.
120
// FIXME: maybe expose richer stream to AI interface?
121
// (e.g. one file in the zip per AI?)
122
std::stringstream aidata;
124
SaveEntireFile(FILE_AIDATA, "AI data", aidata.str().data(), aidata.tellp());
128
void CLuaLoadSaveHandler::SaveHeightmap()
130
// This implements a trivial compression algorithm (relying on zip):
131
// For every heightmap pixel the bits are XOR'ed with the orig bits,
132
// so that unmodified terrain comes out as 0.
133
// Big chunks of 0s are then very well compressed by zip.
134
const int* currHeightmap = (const int*) (const char*) readmap->GetHeightmap();
135
const int* origHeightmap = (const int*) (const char*) readmap->orgheightmap;
136
const int size = (gs->mapx + 1) * (gs->mapy + 1);
137
int* temp = new int[size];
138
for (int i = 0; i < size; ++i) {
139
temp[i] = swabdword(currHeightmap[i] ^ origHeightmap[i]);
141
SaveEntireFile(FILE_HEIGHTMAP, "heightmap", temp, size * sizeof(int));
146
void CLuaLoadSaveHandler::SaveEntireFile(const char* file, const char* what, const void* data, int size, bool throwOnError)
150
if (Z_OK != zipOpenNewFileInZip(savefile, file, NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_COMPRESSION)) {
153
else if (Z_OK != zipWriteInFileInZip(savefile, data, size)) {
156
else if (Z_OK != zipCloseFileInZip(savefile)) {
161
err = "Unable to " + err + " " + what + " file in save file \"" + filename + "\"";
163
throw content_error(err);
166
logOutput.Print(err);
172
void CLuaLoadSaveHandler::LoadGameStartInfo(const std::string& file)
174
const std::string realfile = filesystem.LocateFile(FindSaveFile(file)).c_str();
177
loadfile = new CArchiveZip(realfile);
179
if (!loadfile->IsOpen()) {
180
logOutput.Print("Unable to open save file \"" + filename + "\"");
184
scriptText = LoadEntireFile(FILE_STARTSCRIPT);
188
void CLuaLoadSaveHandler::LoadGame()
196
void CLuaLoadSaveHandler::LoadEventClients()
198
// FIXME: need some way to 'chroot' them into a single directory?
199
eventHandler.Load(loadfile);
203
void CLuaLoadSaveHandler::LoadAIData()
205
std::stringstream aidata(LoadEntireFile(FILE_AIDATA));
210
void CLuaLoadSaveHandler::LoadHeightmap()
212
std::vector<boost::uint8_t> buf;
214
if (loadfile->GetFile(FILE_HEIGHTMAP, buf)) {
215
const int size = (gs->mapx + 1) * (gs->mapy + 1);
216
const int* temp = (const int*) (const char*) &*buf.begin();
217
const int* origHeightmap = (const int*) (const char*) readmap->orgheightmap;
219
for (int i = 0; i < size; ++i) {
220
const int newHeightBits = swabdword(temp[i]) ^ origHeightmap[i];
221
const float newHeight = *(const float*) (const char*) &newHeightBits;
222
readmap->SetHeight(i, newHeight);
224
mapDamage->RecalcArea(0, gs->mapx, 0, gs->mapy);
227
logOutput.Print("Unable to load heightmap from save file \"" + filename + "\"");
232
std::string CLuaLoadSaveHandler::LoadEntireFile(const std::string& file)
234
std::vector<boost::uint8_t> buf;
235
if (loadfile->GetFile(file, buf)) {
236
return std::string((char*) &*buf.begin(), buf.size());