1 #include "OptionsDB.h"
2 #include "SaveGamePreviewUtils.h"
3
4 #include "i18n.h"
5 #include "Directories.h"
6 #include "Logger.h"
7 #include "EnumText.h"
8 #include "Serialize.h"
9 #include "Serialize.ipp"
10 #include "ScopedTimer.h"
11 #include "Version.h"
12
13 #include <boost/filesystem.hpp>
14 #include <boost/filesystem/operations.hpp>
15 #include <boost/filesystem/fstream.hpp>
16 #include <boost/date_time/posix_time/posix_time.hpp>
17 #include <boost/graph/graph_concepts.hpp>
18 #include <boost/iostreams/filter/zlib.hpp>
19 #include <boost/iostreams/filtering_streambuf.hpp>
20 #include <boost/iostreams/copy.hpp>
21 #include <boost/iostreams/device/back_inserter.hpp>
22 #include <boost/iostreams/stream.hpp>
23
24
25 #include <fstream>
26
27 namespace fs = boost::filesystem;
28
29 namespace {
30 const std::string UNABLE_TO_OPEN_FILE("Unable to open file");
31 const std::string XML_SAVE_FILE_DESCRIPTION("This is an XML archive FreeOrion saved game. Initial header information is uncompressed. The main gamestate information follows, possibly stored as zlib-comprssed XML archive in the last entry in the main archive.");
32 const std::string BIN_SAVE_FILE_DESCRIPTION("This is binary archive FreeOrion saved game.");
33
34 const std::string XML_COMPRESSED_MARKER("zlib-xml");
35
36 /// Splits time and date on separate lines for an ISO datetime string
split_time(const std::string & time)37 std::string split_time(const std::string& time) {
38 std::string result = time;
39 std::string::size_type pos = result.find('T');
40 if (pos != std::string::npos) {
41 result.replace(pos, 1, "\n");
42 }
43 return result;
44 }
45
46 /// Populates a SaveGamePreviewData from a given file
47 /// returns true on success, false if preview data could not be found
LoadSaveGamePreviewData(const fs::path & path,FullPreview & full)48 bool LoadSaveGamePreviewData(const fs::path& path, FullPreview& full) {
49 if (!fs::exists(path)) {
50 DebugLogger() << "LoadSaveGamePreviewData: Save file note found: " << path.string();
51 return false;
52 }
53
54 fs::ifstream ifs(path, std::ios_base::binary);
55
56 full.filename = PathToString(path.filename());
57
58 if (!ifs)
59 throw std::runtime_error(UNABLE_TO_OPEN_FILE);
60
61 // alias structs so variable passed into NVP deserialization macro has the
62 // same name as that passed into serialization macro in SaveGame function.
63 SaveGamePreviewData& save_preview_data = full.preview;
64 GalaxySetupData& galaxy_setup_data = full.galaxy;
65
66 DebugLogger() << "LoadSaveGamePreviewData: Loading preview from: " << path.string();
67 try {
68 // read the first five letters of the stream and check if it is opening an xml file
69 std::string xxx5(5, ' ');
70 ifs.read(&xxx5[0], 5);
71 const std::string xml5{"<?xml"};
72 // reset to start of stream
73 boost::iostreams::seek(ifs, 0, std::ios_base::beg);
74 // binary deserialization iff document is not xml
75 if (xml5 != xxx5) {
76 // first attempt binary deserialziation
77 freeorion_bin_iarchive ia(ifs);
78
79 ia >> BOOST_SERIALIZATION_NVP(save_preview_data);
80 ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data);
81
82 } else {
83 freeorion_xml_iarchive ia(ifs);
84 ia >> BOOST_SERIALIZATION_NVP(save_preview_data);
85
86 if (BOOST_VERSION >= 106600 && save_preview_data.save_format_marker == XML_COMPRESSED_MARKER)
87 throw std::invalid_argument("Save Format Not Compatible with Boost Version " BOOST_LIB_VERSION);
88
89 ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data);
90 }
91
92 DebugLogger() << "Loaded preview with: " << save_preview_data.number_of_human_players << " human players";
93
94 } catch (const std::exception& e) {
95 ErrorLogger() << "LoadSaveGamePreviewData: Failed to read preview of " << path.string() << " because: " << e.what();
96 return false;
97 }
98
99 if (full.preview.Valid()) {
100 DebugLogger() << "LoadSaveGamePreviewData: Successfully loaded preview from: " << path.string();
101 return true;
102 } else {
103 DebugLogger() << "LoadSaveGamePreviewData: Passing save file with no preview: " << path.string();
104 return false;
105 }
106 }
107 }
108
109
SaveGamePreviewData()110 SaveGamePreviewData::SaveGamePreviewData() :
111 magic_number(PREVIEW_PRESENT_MARKER),
112 description(),
113 freeorion_version(UserString("UNKNOWN_VALUE_SYMBOL_2")),
114 main_player_name(UserString("UNKNOWN_VALUE_SYMBOL_2")),
115 main_player_empire_name(UserString("UNKNOWN_VALUE_SYMBOL_2")),
116 current_turn(-1),
117 number_of_empires(-1),
118 number_of_human_players(-1),
119 save_format_marker("")
120 {}
121
Valid() const122 bool SaveGamePreviewData::Valid() const
123 { return magic_number == SaveGamePreviewData::PREVIEW_PRESENT_MARKER && current_turn >= -1; }
124
SetBinary(bool bin)125 void SaveGamePreviewData::SetBinary(bool bin)
126 { description = bin ? BIN_SAVE_FILE_DESCRIPTION : XML_SAVE_FILE_DESCRIPTION; }
127
128 template <typename Archive>
serialize(Archive & ar,unsigned int version)129 void SaveGamePreviewData::serialize(Archive& ar, unsigned int version)
130 {
131 if (version >= 2) {
132 if (Archive::is_saving::value) {
133 freeorion_version = FreeOrionVersionString();
134 }
135 ar & BOOST_SERIALIZATION_NVP(description)
136 & BOOST_SERIALIZATION_NVP(freeorion_version);
137 if (version >= 3) {
138 ar & BOOST_SERIALIZATION_NVP(save_format_marker);
139 if (version >= 4) {
140 ar & BOOST_SERIALIZATION_NVP(uncompressed_text_size)
141 & BOOST_SERIALIZATION_NVP(compressed_text_size);
142 }
143 }
144 }
145 ar & BOOST_SERIALIZATION_NVP(magic_number)
146 & BOOST_SERIALIZATION_NVP(main_player_name);
147 ar & BOOST_SERIALIZATION_NVP(main_player_empire_name)
148 & BOOST_SERIALIZATION_NVP(main_player_empire_colour)
149 & BOOST_SERIALIZATION_NVP(save_time)
150 & BOOST_SERIALIZATION_NVP(current_turn);
151 if (version > 0) {
152 ar & BOOST_SERIALIZATION_NVP(number_of_empires)
153 & BOOST_SERIALIZATION_NVP(number_of_human_players);
154 }
155 }
156
157 template void SaveGamePreviewData::serialize<freeorion_bin_oarchive>(freeorion_bin_oarchive&, unsigned int);
158 template void SaveGamePreviewData::serialize<freeorion_bin_iarchive>(freeorion_bin_iarchive&, unsigned int);
159 template void SaveGamePreviewData::serialize<freeorion_xml_oarchive>(freeorion_xml_oarchive&, unsigned int);
160 template void SaveGamePreviewData::serialize<freeorion_xml_iarchive>(freeorion_xml_iarchive&, unsigned int);
161
162
163 template <typename Archive>
serialize(Archive & ar,unsigned int version)164 void FullPreview::serialize(Archive& ar, unsigned int version)
165 {
166 ar & BOOST_SERIALIZATION_NVP(filename)
167 & BOOST_SERIALIZATION_NVP(preview)
168 & BOOST_SERIALIZATION_NVP(galaxy);
169 }
170
171 template void FullPreview::serialize<freeorion_bin_oarchive>(freeorion_bin_oarchive&, unsigned int);
172 template void FullPreview::serialize<freeorion_bin_iarchive>(freeorion_bin_iarchive&, unsigned int);
173 template void FullPreview::serialize<freeorion_xml_oarchive>(freeorion_xml_oarchive&, unsigned int);
174 template void FullPreview::serialize<freeorion_xml_iarchive>(freeorion_xml_iarchive&, unsigned int);
175
176
177 template<typename Archive>
serialize(Archive & ar,const unsigned int version)178 void PreviewInformation::serialize(Archive& ar, const unsigned int version)
179 {
180 ar & BOOST_SERIALIZATION_NVP(subdirectories)
181 & BOOST_SERIALIZATION_NVP(folder)
182 & BOOST_SERIALIZATION_NVP(previews);
183 }
184
185 template void PreviewInformation::serialize<freeorion_bin_oarchive>(freeorion_bin_oarchive&, const unsigned int);
186 template void PreviewInformation::serialize<freeorion_bin_iarchive>(freeorion_bin_iarchive&, const unsigned int);
187 template void PreviewInformation::serialize<freeorion_xml_oarchive>(freeorion_xml_oarchive&, const unsigned int);
188 template void PreviewInformation::serialize<freeorion_xml_iarchive>(freeorion_xml_iarchive&, const unsigned int);
189
190
SaveFileWithValidHeader(const boost::filesystem::path & path)191 bool SaveFileWithValidHeader(const boost::filesystem::path& path) {
192 if (!fs::exists(path))
193 return false;
194
195 fs::ifstream ifs(path, std::ios_base::binary);
196 if (!ifs)
197 return false;
198
199 // dummy holders for deserialized data
200 SaveGamePreviewData ignored_save_preview_data;
201 GalaxySetupData ignored_galaxy_setup_data;
202 ServerSaveGameData ignored_server_save_game_data;
203 std::vector<PlayerSaveHeaderData> ignored_player_save_header_data;
204 std::map<int, SaveGameEmpireData> ignored_empire_save_game_data;
205
206 DebugLogger() << "SaveFileWithValidHeader: Loading headers from: " << path.string();
207 try {
208 // read the first five letters of the stream and check if it is opening an xml file
209 std::string xxx5(5, ' ');
210 ifs.read(&xxx5[0], 5);
211 const std::string xml5{"<?xml"};
212 // reset to start of stream
213 boost::iostreams::seek(ifs, 0, std::ios_base::beg);
214 // binary deserialization iff document is not xml
215 if (xml5 != xxx5) {
216 ScopedTimer timer("SaveFileWithValidHeader (binary): " + path.string(), true);
217
218 freeorion_bin_iarchive ia(ifs);
219
220 ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data);
221 ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data);
222 ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data);
223 ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data);
224 ia >> BOOST_SERIALIZATION_NVP(ignored_empire_save_game_data);
225 } else {
226 DebugLogger() << "Deserializing XML data";
227 freeorion_xml_iarchive ia(ifs);
228
229 ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data);
230
231 if (BOOST_VERSION >= 106600 && ignored_save_preview_data.save_format_marker == XML_COMPRESSED_MARKER)
232 throw std::invalid_argument("Save Format Not Compatible with Boost Version " BOOST_LIB_VERSION);
233
234 ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data);
235 ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data);
236 ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_header_data);
237 ia >> BOOST_SERIALIZATION_NVP(ignored_empire_save_game_data);
238 }
239
240 } catch (const std::exception& e) {
241 ErrorLogger() << "SaveFileWithValidHeader: Failed to read headers of " << path.string() << " because: " << e.what();
242 return false;
243 }
244 return true;
245 }
246
ColumnInPreview(const FullPreview & full,const std::string & name,bool thin)247 std::string ColumnInPreview(const FullPreview& full, const std::string& name, bool thin) {
248 if (name == "player") {
249 return full.preview.main_player_name;
250 } else if (name == "empire") {
251 return full.preview.main_player_empire_name;
252 } else if (name == "turn") {
253 return std::to_string(full.preview.current_turn);
254 } else if (name == "time") {
255 if (thin) {
256 return split_time(full.preview.save_time);
257 } else {
258 return full.preview.save_time;
259 }
260 } else if (name == "file") {
261 return full.filename;
262 } else if (name == "galaxy_size") {
263 return std::to_string(full.galaxy.m_size);
264 } else if (name == "seed") {
265 return full.galaxy.m_seed;
266 } else if (name == "galaxy_age") {
267 return TextForGalaxySetupSetting(full.galaxy.m_age);
268 } else if (name == "monster_freq") {
269 return TextForGalaxySetupSetting(full.galaxy.m_monster_freq);
270 } else if (name == "native_freq") {
271 return TextForGalaxySetupSetting(full.galaxy.m_native_freq);
272 } else if (name == "planet_freq") {
273 return TextForGalaxySetupSetting(full.galaxy.m_planet_density);
274 } else if (name == "specials_freq") {
275 return TextForGalaxySetupSetting(full.galaxy.m_specials_freq);
276 } else if (name == "starlane_freq") {
277 return TextForGalaxySetupSetting(full.galaxy.m_starlane_freq);
278 } else if (name == "galaxy_shape") {
279 return TextForGalaxyShape(full.galaxy.m_shape);
280 } else if (name == "ai_aggression") {
281 return TextForAIAggression(full.galaxy.m_ai_aggr);
282 } else if (name == "number_of_empires") {
283 return std::to_string(full.preview.number_of_empires);
284 } else if (name == "number_of_humans") {
285 return std::to_string(full.preview.number_of_human_players);
286 } else {
287 ErrorLogger() << "FullPreview::Value Error: no such preview field: " << name;
288 return "??";
289 }
290 }
291
LoadSaveGamePreviews(const fs::path & orig_path,const std::string & extension,std::vector<FullPreview> & previews)292 void LoadSaveGamePreviews(const fs::path& orig_path, const std::string& extension, std::vector<FullPreview>& previews) {
293 FullPreview data;
294 fs::directory_iterator end_it;
295
296 fs::path path = orig_path;
297 // Relative path relative to the save directory
298 if (path.is_relative()) {
299 ErrorLogger() << "LoadSaveGamePreviews: supplied path must not be relative, \"" << path << "\" ";
300 return;
301 }
302
303 if (!fs::exists(path)) {
304 ErrorLogger() << "LoadSaveGamePreviews: Save Game directory \"" << path << "\" not found";
305 return;
306 }
307 if (!fs::is_directory(path)) {
308 ErrorLogger() << "LoadSaveGamePreviews: Save Game directory \"" << path << "\" was not a directory";
309 return;
310 }
311
312 for (fs::directory_iterator it(path); it != end_it; ++it) {
313 try {
314 if ((it->path().filename().extension() == extension) && !fs::is_directory(it->path())) {
315 if (LoadSaveGamePreviewData(*it, data)) {
316 // Add preview entry to list
317 previews.push_back(data);
318 }
319 }
320 } catch (const std::exception& e) {
321 ErrorLogger() << "LoadSaveGamePreviews: Failed loading preview from " << it->path() << " because: " << e.what();
322 }
323 }
324 }
325