1 #include "wui/savegameloader.h"
2 
3 #include <boost/algorithm/string.hpp>
4 #include <boost/format.hpp>
5 
6 #include "base/i18n.h"
7 #include "base/log.h"
8 #include "base/time_string.h"
9 #include "game_io/game_loader.h"
10 #include "io/filesystem/layered_filesystem.h"
11 #include "logic/filesystem_constants.h"
12 
SavegameLoader(Widelands::Game & game)13 SavegameLoader::SavegameLoader(Widelands::Game& game) : game_(game) {
14 }
load_files(const std::string & directory)15 std::vector<SavegameData> SavegameLoader::load_files(const std::string& directory) {
16 	std::vector<SavegameData> loaded_games;
17 	FilenameSet gamefiles = g_fs->list_directory(directory);
18 
19 	for (const std::string& gamefilename : gamefiles) {
20 		load(gamefilename, loaded_games);
21 	}
22 	return loaded_games;
23 }
24 
get_savename(const std::string & gamefilename) const25 std::string SavegameLoader::get_savename(const std::string& gamefilename) const {
26 	return gamefilename;
27 }
28 
load(const std::string & to_be_loaded,std::vector<SavegameData> & loaded_games) const29 void SavegameLoader::load(const std::string& to_be_loaded,
30                           std::vector<SavegameData>& loaded_games) const {
31 	if (g_fs->is_directory(to_be_loaded)) {
32 		try {
33 			load_savegame_from_directory(to_be_loaded, loaded_games);
34 		} catch (const std::exception&) {
35 			// loading failed, so this is actually a normal directory
36 			add_sub_dir(to_be_loaded, loaded_games);
37 		}
38 	} else {
39 		load_savegame_from_file(to_be_loaded, loaded_games);
40 	}
41 }
42 
load_savegame_from_directory(const std::string & gamefilename,std::vector<SavegameData> & loaded_games) const43 void SavegameLoader::load_savegame_from_directory(const std::string& gamefilename,
44                                                   std::vector<SavegameData>& loaded_games) const {
45 
46 	Widelands::GamePreloadPacket gpdp;
47 	SavegameData gamedata(gamefilename);
48 
49 	Widelands::GameLoader gl(gamefilename.c_str(), game_);
50 	gl.preload_game(gpdp);
51 	gamedata.gametype = gpdp.get_gametype();
52 	if (!is_valid_gametype(gamedata)) {
53 		return;
54 	}
55 
56 	add_general_information(gamedata, gpdp);
57 	add_time_info(gamedata, gpdp);
58 	loaded_games.push_back(gamedata);
59 }
60 
load_savegame_from_file(const std::string & gamefilename,std::vector<SavegameData> & loaded_games) const61 void SavegameLoader::load_savegame_from_file(const std::string& gamefilename,
62                                              std::vector<SavegameData>& loaded_games) const {
63 	std::string savename = get_savename(gamefilename);
64 
65 	if (!g_fs->file_exists(savename.c_str())) {
66 		return;
67 	}
68 
69 	Widelands::GamePreloadPacket gpdp;
70 	SavegameData gamedata(gamefilename);
71 	try {
72 		Widelands::GameLoader gl(savename.c_str(), game_);
73 		gl.preload_game(gpdp);
74 		gamedata.gametype = gpdp.get_gametype();
75 		if (!is_valid_gametype(gamedata)) {
76 			return;
77 		}
78 
79 		add_general_information(gamedata, gpdp);
80 		add_time_info(gamedata, gpdp);
81 
82 	} catch (const std::exception& e) {
83 		add_error_info(gamedata, e.what());
84 	}
85 
86 	loaded_games.push_back(gamedata);
87 }
88 
add_general_information(SavegameData & gamedata,const Widelands::GamePreloadPacket & gpdp) const89 void SavegameLoader::add_general_information(SavegameData& gamedata,
90                                              const Widelands::GamePreloadPacket& gpdp) const {
91 	gamedata.set_mapname(gpdp.get_mapname());
92 	gamedata.set_gametime(gpdp.get_gametime());
93 	gamedata.set_nrplayers(gpdp.get_number_of_players());
94 	gamedata.version = gpdp.get_version();
95 	gamedata.wincondition = gpdp.get_localized_win_condition();
96 	gamedata.minimap_path = gpdp.get_minimap_path();
97 }
98 
add_error_info(SavegameData & gamedata,std::string errormessage) const99 void SavegameLoader::add_error_info(SavegameData& gamedata, std::string errormessage) const {
100 	boost::replace_all(errormessage, "\n", "<br>");
101 	gamedata.errormessage =
102 	   ((boost::format("<p>%s</p><p>%s</p><p>%s</p>"))
103 	    /** TRANSLATORS: Error message introduction for when an old savegame can't be loaded */
104 	    % _("This file has the wrong format and can’t be loaded."
105 	        " Maybe it was created with an older version of Widelands.")
106 	    /** TRANSLATORS: This text is on a separate line with an error message below */
107 	    % _("Error message:") % errormessage)
108 	      .str();
109 
110 	gamedata.mapname = FileSystem::filename_without_ext(gamedata.filename.c_str());
111 }
112 
add_time_info(SavegameData & gamedata,const Widelands::GamePreloadPacket & gpdp) const113 void SavegameLoader::add_time_info(SavegameData& gamedata,
114                                    const Widelands::GamePreloadPacket& gpdp) const {
115 	gamedata.savetimestamp = gpdp.get_savetimestamp();
116 	time_t t;
117 	time(&t);
118 	struct tm* currenttime = localtime(&t);
119 	// We need to put these into variables because of a sideeffect of the localtime function.
120 	int8_t current_year = currenttime->tm_year;
121 	int8_t current_month = currenttime->tm_mon;
122 	int8_t current_day = currenttime->tm_mday;
123 
124 	struct tm* savedate = localtime(&gamedata.savetimestamp);
125 
126 	if (gamedata.savetimestamp > 0) {
127 		if (savedate->tm_year == current_year && savedate->tm_mon == current_month &&
128 		    savedate->tm_mday == current_day) {  // Today
129 
130 			// Adding the 0 padding in a separate statement so translators won't have to deal
131 			// with it
132 			const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
133 
134 			gamedata.savedatestring =
135 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
136 			                                                    hour:minute */
137 			   (boost::format(_("Today, %1%:%2%")) % savedate->tm_hour % minute).str();
138 			gamedata.savedonstring =
139 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
140 			                                                    hour:minute. This is part of a list.
141 			    */
142 			   (boost::format(_("saved today at %1%:%2%")) % savedate->tm_hour % minute).str();
143 		} else if ((savedate->tm_year == current_year && savedate->tm_mon == current_month &&
144 		            savedate->tm_mday == current_day - 1) ||
145 		           (savedate->tm_year == current_year - 1 && savedate->tm_mon == 11 &&
146 		            current_month == 0 && savedate->tm_mday == 31 &&
147 		            current_day == 1)) {  // Yesterday
148 			// Adding the 0 padding in a separate statement so translators won't have to deal
149 			// with it
150 			const std::string minute = (boost::format("%02u") % savedate->tm_min).str();
151 
152 			gamedata.savedatestring =
153 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
154 			                                                    hour:minute */
155 			   (boost::format(_("Yesterday, %1%:%2%")) % savedate->tm_hour % minute).str();
156 			gamedata.savedonstring =
157 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
158 			                                                    hour:minute. This is part of a list.
159 			    */
160 			   (boost::format(_("saved yesterday at %1%:%2%")) % savedate->tm_hour % minute).str();
161 		} else {  // Older
162 			gamedata.savedatestring =
163 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
164 			                                                    month day, year */
165 			   (boost::format(_("%1% %2%, %3%")) % localize_month(savedate->tm_mon) %
166 			    savedate->tm_mday % (1900 + savedate->tm_year))
167 			      .str();
168 			gamedata.savedonstring =
169 			   /** TRANSLATORS: Display date for choosing a savegame/replay. Placeholders are:
170 			                                                    month (short name) day (number),
171 			      year (number). This is part of a list. */
172 			   (boost::format(_("saved on %1% %2%, %3%")) % savedate->tm_mday %
173 			    localize_month(savedate->tm_mon) % (1900 + savedate->tm_year))
174 			      .str();
175 		}
176 	}
177 }
178 
add_sub_dir(const std::string & gamefilename,std::vector<SavegameData> & loaded_games) const179 void SavegameLoader::add_sub_dir(const std::string& gamefilename,
180                                  std::vector<SavegameData>& loaded_games) const {
181 	// Add subdirectory to the list
182 	const char* fs_filename = FileSystem::fs_filename(gamefilename.c_str());
183 	if (!strcmp(fs_filename, ".") || !strcmp(fs_filename, "..")) {
184 		return;
185 	}
186 	loaded_games.push_back(SavegameData::create_sub_dir(gamefilename));
187 }
188 
ReplayLoader(Widelands::Game & game)189 ReplayLoader::ReplayLoader(Widelands::Game& game) : SavegameLoader(game) {
190 }
191 
is_valid_gametype(const SavegameData &) const192 bool ReplayLoader::is_valid_gametype(const SavegameData&) const {
193 	return true;  // TODO(jmoerschbach): why?? what is the purpose of
194 	              // GameController::GameType::kReplay? return gamedata.is_replay(); <-- should be
195 	              // this, right?!
196 }
197 
get_savename(const std::string & gamefilename) const198 std::string ReplayLoader::get_savename(const std::string& gamefilename) const {
199 	std::string savename = gamefilename;
200 	savename += kSavegameExtension;
201 	return savename;
202 }
203 
MultiPlayerLoader(Widelands::Game & game)204 MultiPlayerLoader::MultiPlayerLoader(Widelands::Game& game) : SavegameLoader(game) {
205 }
206 
is_valid_gametype(const SavegameData & gamedata) const207 bool MultiPlayerLoader::is_valid_gametype(const SavegameData& gamedata) const {
208 	// TODO(jmoerschbach): workaround to be able to load replays in multiplayer loading screen
209 	return gamedata.is_multiplayer() || gamedata.is_replay();
210 }
211 
SinglePlayerLoader(Widelands::Game & game)212 SinglePlayerLoader::SinglePlayerLoader(Widelands::Game& game) : SavegameLoader(game) {
213 }
214 
is_valid_gametype(const SavegameData & gamedata) const215 bool SinglePlayerLoader::is_valid_gametype(const SavegameData& gamedata) const {
216 	return gamedata.is_singleplayer();
217 }
218 
EverythingLoader(Widelands::Game & game)219 EverythingLoader::EverythingLoader(Widelands::Game& game) : SavegameLoader(game) {
220 }
221 
is_valid_gametype(const SavegameData &) const222 bool EverythingLoader::is_valid_gametype(const SavegameData&) const {
223 	return true;
224 }
225