1 /*
2  *
3  *   Copyright (c) 2002, 2003 Johannes Prix
4  *   Copyright (c) 2004-2010 Arthur Huillet
5  *
6  *  This file is part of Freedroid
7  *
8  *  Freedroid is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  Freedroid is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with Freedroid; see the file COPYING. If not, write to the
20  *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21  *  MA  02111-1307  USA
22  *
23  */
24 
25 /**
26  * All functions that have to do with loading and saving of games.
27  */
28 
29 #define _saveloadgame_c
30 
31 #include "system.h"
32 
33 #include "defs.h"
34 #include "struct.h"
35 #include "global.h"
36 #include "proto.h"
37 #include <sys/stat.h>
38 #include "widgets/widgets.h"
39 #include "savestruct.h"
40 #include "savegame/savegame.h"
41 
42 #include "scandir.h"
43 
44 #ifdef HAVE_LANGINFO_H
45 #include <langinfo.h>
46 #endif
47 
48 #ifndef HAVE_NL_LANGINFO
49 #if __WIN32__
50 #define nl_langinfo(X) "%a %b %#d %H:%M:%S %Y"
51 #else
52 #define nl_langinfo(X) "%a %b %e %H:%M:%S %Y"
53 #endif
54 #endif
55 
56 #define SAVEDGAME_EXT ".sav.gz"
57 #define SAVE_GAME_THUMBNAIL_EXT ".thumbnail.png"
58 
59 /**
60  * Filter function for scandir calls.
61  * This function keeps files with the SAVEDGAME_EXT extension.
62  */
filename_filter_func(const struct dirent * file)63 static int filename_filter_func(const struct dirent *file)
64 {
65 	char *pos = strstr(file->d_name, SAVEDGAME_EXT);
66 
67 	if (pos != NULL) {	// SAVEDGAME_EXT found
68 		if (strlen(pos) == strlen(SAVEDGAME_EXT)) {
69 			// d_name *ENDS* with SAVEDGAME_EXT
70 
71 			if (strstr(file->d_name, ".bkp"SAVEDGAME_EXT) + 4 == pos) {
72 				//then we have .bkp+SAVEDGAME_EXT = filter it out
73 				return 0;
74 			}
75 			return 1;
76 		}
77 	}
78 	return 0;
79 }
80 
81 /**
82  * Find all currently saved games.
83  * @param namelist the found files -- must bee freed just like scandir(3)
84  * @return the number of found save games
85  */
find_saved_games(struct dirent *** namelist)86 int find_saved_games(struct dirent ***namelist)
87 {
88 	int n = scandir(our_config_dir, namelist, filename_filter_func, alphasort);
89 
90 	if (n == -1)
91 	{
92 		error_message(__FUNCTION__, "Error occurred while reading save game directory.",
93 					 NO_REPORT);
94 		return 0;
95 	}
96 
97 	// For each element in list, remove the suffix SAVEDGAME_EXT
98 	int i;
99 	for (i = 0; i < n; i++) {
100 		*strstr((*namelist)[i]->d_name, SAVEDGAME_EXT) = 0;
101 		if (!strlen((*namelist)[i]->d_name))
102 			strcpy((*namelist)[i]->d_name, "INVALID");
103 	}
104 
105 	return n;
106 }
107 
LoadAndShowThumbnail(char * CoreFilename)108 void LoadAndShowThumbnail(char *CoreFilename)
109 {
110 	char filename[1000];
111 	struct image thumbnail = EMPTY_IMAGE;
112 	SDL_Rect TargetRectangle;
113 
114 	if (!our_config_dir)
115 		return;
116 
117 	sprintf(filename, "%s/%s%s", our_config_dir, CoreFilename, SAVE_GAME_THUMBNAIL_EXT);
118 
119 	/* Load the image */
120 	thumbnail.surface = our_IMG_load_wrapper(filename);
121 	if (!thumbnail.surface)
122 		return;
123 
124 	thumbnail.w = thumbnail.surface->w;
125 	thumbnail.h = thumbnail.surface->h;
126 	if (use_open_gl)
127 		make_texture_out_of_surface(&thumbnail);
128 
129 	TargetRectangle.x = 10;
130 	TargetRectangle.y = GameConfig.screen_height - thumbnail.h - 10;
131 
132 	display_image_on_screen(&thumbnail, TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
133 
134 	delete_image(&thumbnail);
135 }
136 
137 /**
138  *
139  *
140  */
LoadAndShowStats(char * CoreFilename)141 void LoadAndShowStats(char *CoreFilename)
142 {
143 	char filename[1000];
144 	struct stat FileInfoBuffer;
145 	char InfoString[5000];
146 	struct tm *LocalTimeSplitup;
147 	long int FileSize;
148 
149 	if (!our_config_dir)
150 		return;
151 
152 	DebugPrintf(2, "\nTrying to get file stats for character '%s'. ", CoreFilename);
153 
154 	// First we save the full ship information, same as with the level editor
155 	//
156 
157 	sprintf(filename, "%s/%s%s", our_config_dir, CoreFilename, SAVEDGAME_EXT);
158 
159 	if (stat(filename, &(FileInfoBuffer))) {
160 		fprintf(stderr, "\n\nfilename: %s. \n", filename);
161 		error_message(__FUNCTION__, "\
162 FreedroidRPG was unable to determine the time of the last modification on\n\
163 your saved game file.\n\
164 This is either a bug in FreedroidRPG or an indication, that the directory\n\
165 or file permissions of ~/.freedroid_rpg are somehow not right.", NO_REPORT);
166 		return;
167 	};
168 
169 	LocalTimeSplitup = localtime(&(FileInfoBuffer.st_mtime));
170 	strftime(InfoString, sizeof(InfoString), nl_langinfo(D_T_FMT), LocalTimeSplitup);
171 
172 	put_string(get_current_font(), UNIVERSAL_COORD_W(240), GameConfig.screen_height - 3 * get_font_height(get_current_font()), _("Last Modified:"));
173 	put_string(get_current_font(), UNIVERSAL_COORD_W(240), GameConfig.screen_height - 2 * get_font_height(get_current_font()), InfoString);
174 
175 	// Now that the modification time has been set up, we can start to compute
176 	// the overall disk space of all files in question.
177 	//
178 	FileSize = FileInfoBuffer.st_size;
179 
180 	// The saved ship must exist.  On not, it's a sever error!
181 	//
182 	sprintf(filename, "%s/%s%s", our_config_dir, CoreFilename, ".shp");
183 	if (stat(filename, &(FileInfoBuffer))) {
184 		fprintf(stderr, "\n\nfilename: %s. \n", filename);
185 		error_message(__FUNCTION__, "\
186 FreedroidRPG was unable to determine the time of the last modification on\n\
187 your saved game file.\n\
188 This is either a bug in FreedroidRPG or an indication, that the directory\n\
189 or file permissions of ~/.freedroid_rpg are somehow not right.", IS_FATAL);
190 	}
191 	FileSize += FileInfoBuffer.st_size;
192 
193 	// A thumbnail may not yet exist.  We won't make much fuss if it doesn't.
194 	//
195 	sprintf(filename, "%s/%s%s", our_config_dir, CoreFilename, SAVE_GAME_THUMBNAIL_EXT);
196 	if (!stat(filename, &(FileInfoBuffer))) {
197 		FileSize += FileInfoBuffer.st_size;
198 	}
199 
200 	sprintf(InfoString, _("File Size: %2.3f MB"), ((float)FileSize) / (1024.0 * 1024.0));
201 
202 	put_string(get_current_font(), UNIVERSAL_COORD_W(240), GameConfig.screen_height - 1 * get_font_height(get_current_font()), InfoString);
203 
204 };				// void LoadAndShowStats ( char* filename );
205 
206 /**
207  * This function stores a thumbnail of the currently running game, so that
208  * these thumbnails can be browsed when choosing which game to load.
209  */
SaveThumbnailOfGame(void)210 void SaveThumbnailOfGame(void)
211 {
212 	char filename[1000];
213 	if (!our_config_dir)
214 		return;
215 
216 	sprintf(filename, "%s/%s%s", our_config_dir, Me.character_name, SAVE_GAME_THUMBNAIL_EXT);
217 
218 	AssembleCombatPicture(SHOW_ITEMS | NO_CURSOR);
219 
220 	save_screenshot(filename, 210);
221 };				// void SaveThumbnailOfGame ( void )
222 
223 /**
224  * This function saves the current game of FreedroidRPG to a file.
225  */
226 
SaveGame(void)227 int SaveGame(void)
228 {
229 	char filename[1000];
230 	char filename2[1000];
231 	int ret;
232 	FILE *SaveGameFile;
233 
234 	if (Me.energy <= 0) {
235 		alert_window(_("You are dead. Savegame not modified."));
236 		return (ERR);
237 	}
238 
239 	if (!our_config_dir)
240 		return (OK);
241 
242 	/* Start with a 1MB string */
243 	savestruct_autostr = alloc_autostr(1048576);
244 
245 	Activate_Conservative_Frame_Computation();
246 
247 	sprintf(filename, "%s/%s%s", our_config_dir, Me.character_name, ".shp");
248 	sprintf(filename2, "%s/%s%s", our_config_dir, Me.character_name, ".bkp.shp");
249 
250 	unlink(filename2);
251 	ret = rename(filename, filename2);
252 
253 	if (ret && errno != ENOENT) {
254 		error_message(__FUNCTION__, "Unable to create the shipfile backup", PLEASE_INFORM);
255 	}
256 
257 	put_string_centered(Menu_Font, 10, _("Saving"));
258 	our_SDL_flip_wrapper();
259 
260 	if (SaveShip(filename, FALSE, 1) != OK) {
261 		error_message(__FUNCTION__, "\
262 The SAVING OF THE SHIP DATA FOR THE SAVED GAME FAILED!\n\
263 This is either a bug in FreedroidRPG or an indication, that the directory\n\
264 or file permissions of ~/.freedroid_rpg are somehow not right.", PLEASE_INFORM | IS_FATAL);
265 	} else {
266 		DebugPrintf(SAVE_LOAD_GAME_DEBUG, "\nShip data for saved game seems to have been saved correctly.\n");
267 	}
268 
269 	sprintf(filename, "%s/%s%s", our_config_dir, Me.character_name, SAVEDGAME_EXT);
270 	sprintf(filename2, "%s/%s%s", our_config_dir, Me.character_name, ".bkp"SAVEDGAME_EXT);
271 
272 	unlink(filename2);
273 	ret = rename(filename, filename2);
274 
275 	if (ret && errno != ENOENT) {
276 		error_message(__FUNCTION__, "Unable to create the savegame backup", PLEASE_INFORM);
277 	}
278 
279 	if ((SaveGameFile = fopen(filename, "wb")) == NULL) {
280 		error_message(__FUNCTION__, "Error opening save game file for writing...\n\nTerminating...", IS_FATAL);
281 	}
282 
283 	save_game_data(savestruct_autostr);
284 
285 	deflate_to_stream((unsigned char *)savestruct_autostr->value, savestruct_autostr->length+1, SaveGameFile);
286 	fclose(SaveGameFile);
287 
288 	SaveThumbnailOfGame();
289 
290 	append_new_game_message(_("Game saved."));
291 
292 	DebugPrintf(SAVE_LOAD_GAME_DEBUG, "\nint SaveGame( void ): end of function reached.");
293 
294 	free_autostr(savestruct_autostr);
295 	savestruct_autostr = NULL;
296 	return OK;
297 };				// int SaveGame( void )
298 
299 /**
300  * This function delete a saved game and its backup (if there's one)
301  */
DeleteGame(void)302 int DeleteGame(void)
303 {
304 	char filename[FILENAME_MAX];
305 
306 	if (!our_config_dir)
307 		return (OK);
308 
309 	snprintf(filename, sizeof(filename), "%s/%s%s", our_config_dir, Me.character_name, ".shp");
310 	remove(filename);
311 	snprintf(filename, sizeof(filename), "%s/%s%s", our_config_dir, Me.character_name, SAVEDGAME_EXT);
312 	remove(filename);
313 
314 	snprintf(filename, sizeof(filename), "%s/%s%s", our_config_dir, Me.character_name, SAVE_GAME_THUMBNAIL_EXT);
315 	remove(filename);
316 
317 	// We do not check if the backup files exists before to remove it, but since
318 	// do not check the returned value of remove(), this is not an issue
319 	snprintf(filename, sizeof(filename), "%s/%s%s", our_config_dir, Me.character_name, ".bkp.shp");
320 	remove(filename);
321 	snprintf(filename, sizeof(filename), "%s/%s%s", our_config_dir, Me.character_name, ".bkp"SAVEDGAME_EXT);
322 	remove(filename);
323 
324 	return (OK);
325 }
326 
load_saved_game(int use_backup)327 static int load_saved_game(int use_backup)
328 {
329 	char filename[1000], prefix[1000];
330 
331 	if (!our_config_dir) {
332 		DebugPrintf(0, "No Configuration-directory, cannot load any games\n");
333 		return (OK);
334 	}
335 	sprintf(prefix, "%s/%s%s", our_config_dir, Me.character_name, (use_backup) ? ".bkp" : "");
336 
337 	put_string_centered(Menu_Font, 10, _("Loading"));
338 	StoreMenuBackground(1);
339 	our_SDL_flip_wrapper();
340 	RestoreMenuBackground(1);
341 
342 	Activate_Conservative_Frame_Computation();
343 	clean_error_msg_store();
344 
345 	/*
346 	 * Load maps (still using the legacy format)
347 	**/
348 
349 	sprintf(filename, "%s%s", prefix, ".shp");
350 	LoadShip(filename, 1);
351 
352 	/*
353 	 * Load data
354 	*/
355 
356 	int loaded_size = 0;
357 	char *game_data = NULL;
358 
359 	// Read savegame into a buffer
360 
361 	sprintf(filename, "%s%s", prefix, SAVEDGAME_EXT);
362 	FILE *data_file = fopen(filename, "rb");
363 
364 	if (inflate_stream(data_file, (unsigned char **)&game_data, &loaded_size)) {
365 		fclose(data_file);
366 		alert_window(_("Unable to decompress saved game - this is probably a very old, incompatible game. Sorry."));
367 		return ERR;
368 	}
369 	fclose(data_file);
370 
371 	// Try to apply savegame converters
372 
373 	int rtc = convert_old_savegame(&game_data, &loaded_size);
374 	if (rtc == FILTER_APPLIED) {
375 		// A fix/conversion was needed. Save the new data.
376 		data_file = fopen(filename, "wb");
377 		deflate_to_stream((unsigned char *)game_data, loaded_size, data_file);
378 		fclose(data_file);
379 	} else if (rtc == FILTER_ABORT) {
380 		// Conversion was aborted.
381 		// However game_data was possibly changed by a filter function.
382 		// We reload the savegame and use it as is.
383 		free(game_data);
384 		data_file = fopen(filename, "rb");
385 		int loaded_size = 0;
386 		inflate_stream(data_file, (unsigned char **)&game_data, &loaded_size);
387 		fclose(data_file);
388 	}
389 
390 	// Load the savegame (lua format)
391 
392 	clear_out_arrays_for_fresh_game();
393 	reset_lua_state();
394 
395 	if (setjmp(saveload_jmpbuf)) {
396 		// We longjump here if load_game_data() detects an error
397 		if (game_data != NULL)
398 			free((char *)game_data);
399 		game_data = NULL;
400 		return (ERR);
401 	}
402 
403 	load_game_data(game_data);
404 	free(game_data);
405 	game_data = NULL;
406 
407 	/*
408 	 * Post-loading: Some transient states have to be adapted
409 	**/
410 
411 	reset_visible_levels();
412 	get_visible_levels();
413 	animation_timeline_reset();
414 	switch_background_music(curShip.AllLevels[Me.pos.z]->Background_Song_Name);
415 
416 	// Reset animation of dead bots
417 
418 	enemy *erot;
419 	BROWSE_DEAD_BOTS(erot) {
420 		 // It is sufficient to set ->animation_type, since ->animation_phase will be set to
421 		 // last_death_animation_image[erot->type] inside animate_enemy();
422 		 //
423 		 // Also, we might not know the correct value of last_death_animation_image[] yet,
424 		 // since it's initialized by grab_enemy_images_from_archive(erot->type), and that
425 		 // function may not have been called for the current bot type.
426 		if (!level_is_visible(erot->pos.z))
427 			erot->animation_type = DEAD_ANIMATION;
428 	}
429 
430 	// Count and initialize the number of droids used in this ship.
431 	// Otherwise we might ignore some robots.
432 
433 	CountNumberOfDroidsOnShip();
434 
435 	// Maybe someone just lost in the game and has then pressed the load
436 	// button.  Then a new game is loaded and the game-over status has
437 	// to be restored as well of course.
438 
439 	GameOver = FALSE;
440 
441 	// Now we know that right after loading an old saved game, the Tux might have
442 	// to 'change clothes' i.e. a lot of tux images need to be updated which can
443 	// take a little time.  Therefore we print some message so the user will not
444 	// panic and push the reset button :)
445 
446 	put_string(FPS_Display_Font, 75, 150, _("Updating Tux images (this may take a little while...)"));
447 	our_SDL_flip_wrapper();
448 
449 	/*
450 	 * We're now ready to start playing !
451 	**/
452 	widget_text_init(message_log, _("--- Message Log ---"));
453 	append_new_game_message(_("Game loaded."));
454 
455 	return OK;
456 }
457 
458 /**
459  * This function loads an old saved game of FreedroidRPG from a file.
460  */
load_game(void)461 int load_game(void)
462 {
463 	return load_saved_game(0);
464 }
465 
466 /**
467  * This loads the backup for the current player name
468  */
load_backup_game()469 int load_backup_game()
470 {
471 	return load_saved_game(1);
472 }
473 
474 #undef _saveloadgame_c
475