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