SaveGameIterator: change ->release() to .release to avoid double free.
[gemrb.git] / gemrb / core / SaveGameIterator.cpp
blob92d35adbf0fd2353c6912f6d858d85189edb2d9f
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "SaveGameIterator.h"
23 #include "iless.h"
24 #include "strrefs.h"
25 #include "win32def.h"
27 #include "GameControl.h"
28 #include "GameData.h" // For ResourceHolder
29 #include "ImageMgr.h"
30 #include "ImageWriter.h"
31 #include "Interface.h"
32 #include "SaveGameMgr.h"
33 #include "Video.h"
35 #include <algorithm>
36 #include <cassert>
37 #include <iterator>
38 #include <set>
39 #include <time.h>
41 const TypeID SaveGame::ID = { "SaveGame" };
43 /** Extract date from save game ds into Date. */
44 static void ParseGameDate(DataStream *ds, char *Date)
46 Date[0] = '\0';
48 char Signature[8];
49 ieDword GameTime;
50 ds->Read(Signature, 8);
51 ds->ReadDword(&GameTime);
52 delete ds;
53 if (memcmp(Signature,"GAME",4) ) {
54 strcpy(Date, "ERROR");
55 return;
58 int hours = ((int)GameTime)/300;
59 int days = hours/24;
60 hours -= days*24;
61 char *a=NULL,*b=NULL,*c=NULL;
63 core->GetTokenDictionary()->SetAtCopy("GAMEDAYS", days);
64 if (days) {
65 if (days==1) a=core->GetString(10698);
66 else a=core->GetString(10697);
68 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
69 if (hours || !a) {
70 if (a) b=core->GetString(10699);
71 if (hours==1) c=core->GetString(10701);
72 else c=core->GetString(10700);
74 if (b) {
75 strcat(Date, a);
76 strcat(Date, " ");
77 strcat(Date, b);
78 strcat(Date, " ");
79 if (c)
80 strcat(Date, c);
81 } else {
82 if (a)
83 strcat(Date, a);
84 if (c)
85 strcat(Date, c);
87 core->FreeString(a);
88 core->FreeString(b);
89 core->FreeString(c);
92 SaveGame::SaveGame(const char* path, const char* name, const char* prefix, const char* slotname, int pCount, int saveID)
94 strncpy( Prefix, prefix, sizeof( Prefix ) );
95 strncpy( Path, path, sizeof( Path ) );
96 strncpy( Name, name, sizeof( Name ) );
97 strncpy( SlotName, slotname, sizeof( SlotName ) );
98 PortraitCount = pCount;
99 SaveID = saveID;
100 char nPath[_MAX_PATH];
101 struct stat my_stat;
102 PathJoinExt(nPath, Path, Prefix, "bmp");
103 memset(&my_stat,0,sizeof(my_stat));
104 stat( nPath, &my_stat );
105 strftime( Date, _MAX_PATH, "%c", localtime( &my_stat.st_mtime ) );
106 manager.AddSource(Path, Name, PLUGIN_RESOURCE_DIRECTORY);
107 GameDate[0] = '\0';
110 SaveGame::~SaveGame()
114 Sprite2D* SaveGame::GetPortrait(int index) const
116 if (index > PortraitCount) {
117 return NULL;
119 char nPath[_MAX_PATH];
120 sprintf( nPath, "PORTRT%d", index );
121 ResourceHolder<ImageMgr> im(nPath, manager, true);
122 if (!im)
123 return NULL;
124 return im->GetSprite2D();
127 Sprite2D* SaveGame::GetPreview() const
129 ResourceHolder<ImageMgr> im(Prefix, manager, true);
130 if (!im)
131 return NULL;
132 return im->GetSprite2D();
135 DataStream* SaveGame::GetGame() const
137 return manager.GetResource(Prefix, IE_GAM_CLASS_ID, true);
140 DataStream* SaveGame::GetWmap() const
142 return manager.GetResource(core->WorldMapName, IE_WMP_CLASS_ID, true);
145 DataStream* SaveGame::GetSave() const
147 return manager.GetResource(Prefix, IE_SAV_CLASS_ID, true);
150 const char* SaveGame::GetGameDate() const
152 if (GameDate[0] == '\0')
153 ParseGameDate(GetGame(), GameDate);
154 return GameDate;
157 SaveGameIterator::SaveGameIterator(void)
161 SaveGameIterator::~SaveGameIterator(void)
165 /* mission pack save */
166 static const char* SaveDir()
168 ieDword playmode = 0;
169 core->GetDictionary()->Lookup( "SaveDir", playmode );
170 if (playmode == 1) {
171 return "mpsave";
173 return "save";
176 #define FormatQuickSavePath(destination, i) \
177 snprintf(destination,sizeof(destination),"%s%s%s%09d-%s", \
178 core->SavePath,SaveDir(), SPathDelimiter,i,folder);
181 * Returns the first 0 bit position of an integer
183 static int GetHole(int n)
185 int mask = 1;
186 int value = 0;
187 while(n&mask) {
188 mask<<=1;
189 value++;
191 return value;
195 * Returns the age of a quickslot entry. Returns 0 if it isn't a quickslot
197 static int IsQuickSaveSlot(const char* match, const char* slotname)
199 char savegameName[_MAX_PATH];
200 int savegameNumber = 0;
201 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
202 if (cnt != 2) {
203 return 0;
205 if (stricmp(savegameName, match) )
207 return 0;
209 return savegameNumber;
212 * Return true if directory Path/slotname is a potential save game
213 * slot, otherwise return false.
215 static bool IsSaveGameSlot(const char* Path, const char* slotname)
217 char savegameName[_MAX_PATH];
218 int savegameNumber = 0;
220 if (slotname[0] == '.')
221 return false;
223 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
224 if (cnt != 2) {
225 //The matcher didn't match: either this is not a valid dir
226 //or the SAVEGAME_DIRECTORY_MATCHER needs updating.
227 printMessage( "SaveGameIterator", " ", LIGHT_RED );
228 printf( "Invalid savegame directory '%s' in %s.\n", slotname, Path );
229 return false;
232 //The matcher got matched correctly.
233 char dtmp[_MAX_PATH];
234 PathJoin(dtmp, Path, slotname, NULL);
236 struct stat fst;
237 if (stat( dtmp, &fst ))
238 return false;
240 if (! S_ISDIR( fst.st_mode ))
241 return false;
243 char ftmp[_MAX_PATH];
244 PathJoinExt(ftmp, dtmp, core->GameNameResRef, "bmp");
246 if (access( ftmp, R_OK )) {
247 printMessage("SaveGameIterator"," ",YELLOW);
248 printf("Ignoring slot %s because of no appropriate preview!\n", dtmp);
249 return false;
252 PathJoinExt(ftmp, dtmp, core->WorldMapName, "wmp");
253 if (access( ftmp, R_OK )) {
254 printMessage("SaveGameIterator"," ",YELLOW);
255 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp);
256 return false;
259 return true;
262 bool SaveGameIterator::RescanSaveGames()
264 // delete old entries
265 save_slots.clear();
267 char Path[_MAX_PATH];
268 PathJoin(Path, core->SavePath, SaveDir(), NULL);
270 DIR* dir = opendir( Path );
271 // create the save game directory at first access
272 if (dir == NULL) {
273 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
274 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
275 dir = opendir( Path );
277 if (dir == NULL) { //If we cannot open the Directory
278 return false;
280 struct dirent* de = readdir( dir ); //Lookup the first entry in the Directory
281 if (de == NULL) {
282 closedir( dir );
283 return false;
286 std::set<char*,iless> slots;
287 do {
288 if (IsSaveGameSlot( Path, de->d_name )) {
289 slots.insert(strdup(de->d_name));
291 } while (( de = readdir( dir ) ) != NULL);
292 closedir( dir ); //No other files in the directory, close it
294 std::transform(slots.begin(), slots.end(), std::back_inserter(save_slots), GetSaveGame);
295 return true;
298 const std::vector<Holder<SaveGame> >& SaveGameIterator::GetSaveGames()
300 RescanSaveGames();
302 return save_slots;
305 Holder<SaveGame> SaveGameIterator::GetSaveGame(const char *slotname)
307 if (!slotname) {
308 return NULL;
311 int prtrt = 0;
312 char Path[_MAX_PATH];
313 //lets leave space for the filenames
314 PathJoin(Path, core->SavePath, SaveDir(), slotname, NULL);
316 char savegameName[_MAX_PATH]={0};
317 int savegameNumber = 0;
319 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
320 //maximum pathlength == 240, without 8+3 filenames
321 if ( (cnt != 2) || (strlen(Path)>240) ) {
322 printf( "Invalid savegame directory '%s' in %s.\n", slotname, Path );
323 return NULL;
326 DIR* ndir = opendir( Path );
327 //If we cannot open the Directory
328 if (ndir == NULL) {
329 return NULL;
331 struct dirent* de2 = readdir( ndir ); //Lookup the first entry in the Directory
332 if (de2 == NULL) {
333 // No first entry!!!
334 closedir( ndir );
335 return NULL;
337 do {
338 if (strnicmp( de2->d_name, "PORTRT", 6 ) == 0)
339 prtrt++;
340 } while (( de2 = readdir( ndir ) ) != NULL);
341 closedir( ndir ); //No other files in the directory, close it
343 SaveGame* sg = new SaveGame( Path, savegameName, core->GameNameResRef, slotname, prtrt, savegameNumber );
344 return sg;
347 void SaveGameIterator::PruneQuickSave(const char *folder)
349 char from[_MAX_PATH];
350 char to[_MAX_PATH];
352 //storing the quicksave ages in an array
353 std::vector<int> myslots;
354 for (charlist::iterator m = save_slots.begin();m!=save_slots.end();m++) {
355 int tmp = IsQuickSaveSlot(folder, (*m)->GetSlotName() );
356 if (tmp) {
357 size_t pos = myslots.size();
358 while(pos-- && myslots[pos]>tmp) ;
359 myslots.insert(myslots.begin()+pos+1,tmp);
362 //now we got an integer array in myslots
363 size_t size = myslots.size();
365 if (!size) {
366 return;
369 int n=myslots[size-1];
370 size_t hole = GetHole(n);
371 size_t i;
372 if (hole<size) {
373 //prune second path
374 FormatQuickSavePath(from, myslots[hole]);
375 myslots.erase(myslots.begin()+hole);
376 core->DelTree(from, false);
377 rmdir(from);
379 //shift paths, always do this, because they are aging
380 size = myslots.size();
381 for(i=size;i--;) {
382 FormatQuickSavePath(from, myslots[i]);
383 FormatQuickSavePath(to, myslots[i]+1);
384 rename(from,to);
388 /** Save game to given directory */
389 static bool DoSaveGame(const char *Path)
391 Game *game = core->GetGame();
392 //saving areas to cache currently in memory
393 unsigned int mc = (unsigned int) game->GetLoadedMapCount();
394 while (mc--) {
395 Map *map = game->GetMap(mc);
396 if (core->SwapoutArea(map)) {
397 return false;
401 //compress files in cache named: .STO and .ARE
402 //no .CRE would be saved in cache
403 if (core->CompressSave(Path)) {
404 return false;
407 //Create .gam file from Game() object
408 if (core->WriteGame(Path)) {
409 return false;
412 //Create .wmp file from WorldMap() object
413 if (core->WriteWorldMap(Path)) {
414 return false;
417 PluginHolder<ImageWriter> im(PLUGIN_IMAGE_WRITER_BMP);
418 if (!im) {
419 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED );
420 return false;
423 //Create portraits
424 for (int i = 0; i < game->GetPartySize( false ); i++) {
425 Sprite2D* portrait = core->GetGameControl()->GetPortraitPreview( i );
426 if (portrait) {
427 char FName[_MAX_PATH];
428 snprintf( FName, sizeof(FName), "PORTRT%d", i );
429 FileStream outfile;
430 outfile.Create( Path, FName, IE_BMP_CLASS_ID );
431 im->PutImage( &outfile, portrait );
435 // Create area preview
436 Sprite2D* preview = core->GetGameControl()->GetPreview();
437 FileStream outfile;
438 outfile.Create( Path, core->GameNameResRef, IE_BMP_CLASS_ID );
439 im->PutImage( &outfile, preview );
441 return true;
444 int CanSave()
446 //some of these restrictions might not be needed
447 Store * store = core->GetCurrentStore();
448 if (store) {
449 return 1; //can't save while store is open
451 GameControl *gc = core->GetGameControl();
452 if (!gc) {
453 return -1; //no gamecontrol!!!
455 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
456 return 2; //can't save while in dialog?
458 //TODO: can't save while in combat
459 //TODO: can't save while (party) actors are in helpless states
460 //TODO: can't save while AOE spells are in effect
461 //TODO: can't save while IF_NOINT is set on an actor
462 return 0;
465 static void CreateSavePath(char *Path, int index, const char *slotname)
467 PathJoin( Path, core->SavePath, SaveDir(), NULL );
469 //if the path exists in different case, don't make it again
470 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
471 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
472 //keep the first part we already determined existing
474 char dir[_MAX_PATH];
475 snprintf( dir, _MAX_PATH, "%09d-%s", index, slotname );
476 snprintf( dir, _MAX_PATH, "%09d-%s", (int)index, slotname );
477 PathJoin(Path, Path, dir, NULL);
478 //this is required in case the old slot wasn't recognised but still there
479 core->DelTree(Path, false);
480 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
481 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
484 int SaveGameIterator::CreateSaveGame(int index, bool mqs)
486 AutoTable tab("savegame");
487 const char *slotname = NULL;
488 if (tab) {
489 slotname = tab->QueryField(index);
492 if (mqs) {
493 assert(index==1);
494 PruneQuickSave(slotname);
497 //if index is not an existing savegame, we create a unique slotname
498 for (size_t i = 0; i < save_slots.size(); ++i) {
499 Holder<SaveGame> save = save_slots[i];
500 if (save->GetSaveID() == index) {
501 DeleteSaveGame(save);
502 break;
505 char Path[_MAX_PATH];
506 CreateSavePath(Path, index, slotname);
508 if (!DoSaveGame(Path)) {
509 return -1;
512 // Save succesful / Quick-save succesful
513 if (index == 1) {
514 core->DisplayConstantString(STR_QSAVESUCCEED, 0xbcefbc);
515 if (core->GetGameControl()) {
516 core->GetGameControl()->SetDisplayText(STR_QSAVESUCCEED, 30);
518 } else {
519 core->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
520 if (core->GetGameControl()) {
521 core->GetGameControl()->SetDisplayText(STR_SAVESUCCEED, 30);
524 return 0;
527 int SaveGameIterator::CreateSaveGame(Holder<SaveGame> save, const char *slotname)
529 if (!slotname) {
530 return -1;
533 if (int cansave = CanSave())
534 return cansave;
536 int index;
537 if (save) {
538 index = save->GetSaveID();
540 DeleteSaveGame(save);
541 save.release();
542 } else {
543 //leave space for autosaves
544 //probably the hardcoded slot names should be read by this object
545 //in that case 7 == size of hardcoded slot names array (savegame.2da)
546 index = 7;
547 for (size_t i = 0; i < save_slots.size(); ++i) {
548 Holder<SaveGame> save = save_slots[i];
549 if (save->GetSaveID() >= index) {
550 index = save->GetSaveID() + 1;
555 char Path[_MAX_PATH];
556 CreateSavePath(Path, index, slotname);
558 if (!DoSaveGame(Path)) {
559 return -1;
562 // Save succesful
563 core->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
564 if (core->GetGameControl()) {
565 core->GetGameControl()->SetDisplayText(STR_SAVESUCCEED, 30);
567 return 0;
570 void SaveGameIterator::DeleteSaveGame(Holder<SaveGame> game)
572 if (!game) {
573 return;
576 core->DelTree( game->GetPath(), false ); //remove all files from folder
577 rmdir( game->GetPath() );