1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
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.
8
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.
13
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.
17 *
18 *
19 */
20
21 #include "SaveGameIterator.h"
22
23 #include "iless.h"
24 #include "strrefs.h"
25
26 #include "DisplayMessage.h"
27 #include "GameData.h" // For ResourceHolder
28 #include "ImageMgr.h"
29 #include "ImageWriter.h"
30 #include "Interface.h"
31 #include "PluginMgr.h"
32 #include "SaveGameMgr.h"
33 #include "Sprite2D.h"
34 #include "TableMgr.h"
35 #include "GUI/GameControl.h"
36 #include "Scriptable/Actor.h"
37 #include "System/FileStream.h"
38
39 #ifndef R_OK
40 #define R_OK 04
41 #endif
42
43 #include <cassert>
44 #include <set>
45 #include <time.h>
46
47 #ifdef VITA
48 #include <dirent.h>
49 #endif
50
51 namespace GemRB {
52
53 const TypeID SaveGame::ID = { "SaveGame" };
54
55 /** Extract date from save game ds into Date. */
ParseGameDate(DataStream * ds,char * Date)56 static void ParseGameDate(DataStream *ds, char *Date)
57 {
58 Date[0] = '\0';
59
60 char Signature[8];
61 ieDword GameTime;
62 ds->Read(Signature, 8);
63 ds->ReadDword(&GameTime);
64 delete ds;
65 if (memcmp(Signature,"GAME",4) ) {
66 strcpy(Date, "ERROR");
67 return;
68 }
69
70 int hours = ((int)GameTime)/core->Time.hour_sec;
71 int days = hours/24;
72 hours -= days*24;
73 char *a=NULL,*b=NULL,*c=NULL;
74
75 // pst has a nice single string for everything 41277 (individual ones lack tokens)
76 core->GetTokenDictionary()->SetAtCopy("GAMEDAYS", days);
77 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
78 int dayref = displaymsg->GetStringReference(STR_DAY);
79 int daysref = displaymsg->GetStringReference(STR_DAYS);
80 if (dayref == daysref) {
81 strcat(Date, core->GetCString(41277));
82 return;
83 }
84
85 if (days) {
86 if (days==1) a = core->GetCString(dayref, 0);
87 else a = core->GetCString(daysref, 0);
88 }
89 if (hours || !a) {
90 if (a) b=core->GetCString(10699); // and
91 if (hours==1) c = core->GetCString(displaymsg->GetStringReference(STR_HOUR), 0);
92 else c = core->GetCString(displaymsg->GetStringReference(STR_HOURS), 0);
93 }
94 if (b) {
95 strcat(Date, a);
96 strcat(Date, " ");
97 strcat(Date, b);
98 strcat(Date, " ");
99 if (c)
100 strcat(Date, c);
101 } else {
102 if (a)
103 strcat(Date, a);
104 if (c)
105 strcat(Date, c);
106 }
107 free(a);
108 free(b);
109 free(c);
110 }
111
SaveGame(const char * path,const char * name,const char * prefix,const char * slotname,int pCount,int saveID)112 SaveGame::SaveGame(const char* path, const char* name, const char* prefix, const char* slotname, int pCount, int saveID)
113 {
114 strlcpy( Prefix, prefix, sizeof( Prefix ) );
115 strlcpy( Path, path, sizeof( Path ) );
116 strlcpy( Name, name, sizeof( Name ) );
117 strlcpy( SlotName, slotname, sizeof( SlotName ) );
118 PortraitCount = pCount;
119 SaveID = saveID;
120 char nPath[_MAX_PATH];
121 struct stat my_stat;
122 PathJoinExt(nPath, Path, Prefix, "bmp");
123 memset(&my_stat,0,sizeof(my_stat));
124 if (stat(nPath, &my_stat)) {
125 Log(ERROR, "SaveGameIterator", "Stat call failed, using dummy time!");
126 strlcpy(Date, "Sun 31 Feb 00:00:01 2099", _MAX_PATH);
127 } else {
128 strftime(Date, _MAX_PATH, "%c", localtime(&my_stat.st_mtime));
129 }
130 manager.AddSource(Path, Name, PLUGIN_RESOURCE_DIRECTORY);
131 GameDate[0] = '\0';
132 }
133
~SaveGame()134 SaveGame::~SaveGame()
135 {
136 }
137
GetPortrait(int index) const138 Holder<Sprite2D> SaveGame::GetPortrait(int index) const
139 {
140 if (index > PortraitCount) {
141 return NULL;
142 }
143 char nPath[_MAX_PATH];
144 snprintf(nPath, _MAX_PATH, "PORTRT%d", index);
145 ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(nPath, manager, true);
146 if (!im)
147 return NULL;
148 return im->GetSprite2D();
149 }
150
GetPreview() const151 Holder<Sprite2D> SaveGame::GetPreview() const
152 {
153 ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(Prefix, manager, true);
154 if (!im)
155 return NULL;
156 return im->GetSprite2D();
157 }
158
GetGame() const159 DataStream* SaveGame::GetGame() const
160 {
161 return manager.GetResource(Prefix, IE_GAM_CLASS_ID, true);
162 }
163
GetWmap(int idx) const164 DataStream* SaveGame::GetWmap(int idx) const
165 {
166 return manager.GetResource(core->WorldMapName[idx], IE_WMP_CLASS_ID, true);
167 }
168
GetSave() const169 DataStream* SaveGame::GetSave() const
170 {
171 return manager.GetResource(Prefix, IE_SAV_CLASS_ID, true);
172 }
173
GetGameDate() const174 const char* SaveGame::GetGameDate() const
175 {
176 if (GameDate[0] == '\0')
177 ParseGameDate(GetGame(), GameDate);
178 return GameDate;
179 }
180
SaveGameIterator(void)181 SaveGameIterator::SaveGameIterator(void)
182 {
183 }
184
~SaveGameIterator(void)185 SaveGameIterator::~SaveGameIterator(void)
186 {
187 }
188
189 // mission pack save dir or the main one?
190 static char saveDir[10];
SaveDir()191 static const char* SaveDir()
192 {
193 if (core->GetTokenDictionary()->GetValueLength("SaveDir")) {
194 core->GetTokenDictionary()->Lookup("SaveDir", saveDir, 9);
195 return saveDir;
196 } else {
197 return "save";
198 }
199 }
200
201 #define FormatQuickSavePath(destination, i) \
202 snprintf(destination,sizeof(destination),"%s%s%s%09d-%s", \
203 core->SavePath,SaveDir(), SPathDelimiter,i,folder);
204
205 /*
206 * Returns the first 0 bit position of an integer
207 */
GetHole(int n)208 static int GetHole(int n)
209 {
210 int mask = 1;
211 int value = 0;
212 while(n&mask) {
213 mask<<=1;
214 value++;
215 }
216 return value;
217 }
218
219 /*
220 * Returns the age of a quickslot entry. Returns 0 if it isn't a quickslot
221 */
IsQuickSaveSlot(const char * match,const char * slotname)222 static int IsQuickSaveSlot(const char* match, const char* slotname)
223 {
224 char savegameName[_MAX_PATH];
225 int savegameNumber = 0;
226 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
227 if (cnt != 2) {
228 return 0;
229 }
230 if (stricmp(savegameName, match) )
231 {
232 return 0;
233 }
234 return savegameNumber;
235 }
236 /*
237 * Return true if directory Path/slotname is a potential save game
238 * slot, otherwise return false.
239 */
IsSaveGameSlot(const char * Path,const char * slotname)240 static bool IsSaveGameSlot(const char* Path, const char* slotname)
241 {
242 char savegameName[_MAX_PATH];
243 int savegameNumber = 0;
244
245 if (slotname[0] == '.')
246 return false;
247
248 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
249 if (cnt != 2) {
250 //The matcher didn't match: either this is not a valid dir
251 //or the SAVEGAME_DIRECTORY_MATCHER needs updating.
252 Log(ERROR, "SaveGameIterator", "Invalid savegame directory '%s' in %s.",
253 slotname, Path);
254 return false;
255 }
256
257 //The matcher got matched correctly.
258 char dtmp[_MAX_PATH];
259 PathJoin(dtmp, Path, slotname, nullptr);
260
261 char ftmp[_MAX_PATH];
262 PathJoinExt(ftmp, dtmp, core->GameNameResRef, "bmp");
263
264 if (access( ftmp, R_OK )) {
265 Log(WARNING, "SaveGameIterator", "Ignoring slot %s because of no appropriate preview!", dtmp);
266 return false;
267 }
268
269 PathJoinExt(ftmp, dtmp, core->WorldMapName[0], "wmp");
270 if (access( ftmp, R_OK )) {
271 return false;
272 }
273
274 if (core->WorldMapName[1]) {
275 PathJoinExt(ftmp, dtmp, core->WorldMapName[1], "wmp");
276 if (access(ftmp, R_OK)) {
277 Log(WARNING, "SaveGameIterator", "Ignoring slot %s because of no appropriate second worldmap!", dtmp);
278 return false;
279 }
280 }
281
282 return true;
283 }
284
RescanSaveGames()285 bool SaveGameIterator::RescanSaveGames()
286 {
287 // delete old entries
288 save_slots.clear();
289
290 char Path[_MAX_PATH];
291 PathJoin(Path, core->SavePath, SaveDir(), nullptr);
292
293 DirectoryIterator dir(Path);
294 // create the save game directory at first access
295 if (!dir) {
296 if (!MakeDirectories(Path)) {
297 Log(ERROR, "SaveGameIterator", "Unable to create save game directory '%s'", Path);
298 return false;
299 }
300 dir.Rewind();
301 }
302 if (!dir) { //If we cannot open the Directory
303 return false;
304 }
305
306 std::set<char*,iless> slots;
307 dir.SetFlags(DirectoryIterator::Directories);
308 do {
309 const char *name = dir.GetName();
310 if (IsSaveGameSlot( Path, name )) {
311 slots.insert(strdup(name));
312 }
313 } while (++dir);
314
315 for (std::set<char*,iless>::iterator i = slots.begin(); i != slots.end(); ++i) {
316 save_slots.push_back(BuildSaveGame(*i));
317 free(*i);
318 }
319
320 return true;
321 }
322
GetSaveGames()323 const std::vector<Holder<SaveGame> >& SaveGameIterator::GetSaveGames()
324 {
325 RescanSaveGames();
326
327 return save_slots;
328 }
329
GetSaveGame(const char * name)330 Holder<SaveGame> SaveGameIterator::GetSaveGame(const char *name)
331 {
332 RescanSaveGames();
333
334 for (std::vector<Holder<SaveGame> >::iterator i = save_slots.begin(); i != save_slots.end(); ++i) {
335 if (strcmp(name, (*i)->GetName()) == 0)
336 return *i;
337 }
338 return NULL;
339 }
340
BuildSaveGame(const char * slotname)341 Holder<SaveGame> SaveGameIterator::BuildSaveGame(const char *slotname)
342 {
343 if (!slotname) {
344 return NULL;
345 }
346
347 int prtrt = 0;
348 char Path[_MAX_PATH];
349 //lets leave space for the filenames
350 PathJoin(Path, core->SavePath, SaveDir(), slotname, nullptr);
351
352 char savegameName[_MAX_PATH]={0};
353 int savegameNumber = 0;
354
355 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
356 //maximum pathlength == 240, without 8+3 filenames
357 if ( (cnt != 2) || (strlen(Path)>240) ) {
358 Log(WARNING, "SaveGame", "Invalid savegame directory '%s' in %s.", slotname, Path );
359 return NULL;
360 }
361
362 DirectoryIterator dir(Path);
363 if (!dir) {
364 return NULL;
365 }
366 do {
367 if (strnicmp( dir.GetName(), "PORTRT", 6 ) == 0)
368 prtrt++;
369 } while (++dir);
370
371 SaveGame* sg = new SaveGame( Path, savegameName, core->GameNameResRef, slotname, prtrt, savegameNumber );
372 return sg;
373 }
374
PruneQuickSave(const char * folder)375 void SaveGameIterator::PruneQuickSave(const char *folder)
376 {
377 // FormatQuickSavePath needs: _MAX_PATH + 6 + 1 + 9 + 17
378 char from[_MAX_PATH + 40];
379 char to[_MAX_PATH + 40];
380
381 //storing the quicksave ages in an array
382 std::vector<int> myslots;
383 for (charlist::iterator m = save_slots.begin(); m != save_slots.end(); ++m) {
384 int tmp = IsQuickSaveSlot(folder, (*m)->GetSlotName() );
385 if (tmp) {
386 size_t pos = myslots.size();
387 while(pos-- && myslots[pos]>tmp) ;
388 myslots.insert(myslots.begin()+pos+1,tmp);
389 }
390 }
391 //now we got an integer array in myslots
392 size_t size = myslots.size();
393
394 if (!size) {
395 return;
396 }
397
398 int n=myslots[size-1];
399 size_t hole = GetHole(n);
400 size_t i;
401 if (hole<size) {
402 //prune second path
403 FormatQuickSavePath(from, myslots[hole]);
404 myslots.erase(myslots.begin()+hole);
405 core->DelTree(from, false);
406 rmdir(from);
407 }
408 //shift paths, always do this, because they are aging
409 size = myslots.size();
410 for(i=size;i--;) {
411 FormatQuickSavePath(from, myslots[i]);
412 FormatQuickSavePath(to, myslots[i]+1);
413 int errnum = rename(from, to);
414 if (errnum) {
415 error("SaveGameIterator", "Rename error %d when pruning quicksaves!\n", errnum);
416 }
417 }
418 }
419
420 /** Save game to given directory */
DoSaveGame(const char * Path)421 static bool DoSaveGame(const char *Path)
422 {
423 Game *game = core->GetGame();
424 //saving areas to cache currently in memory
425 unsigned int mc = (unsigned int) game->GetLoadedMapCount();
426 while (mc--) {
427 Map *map = game->GetMap(mc);
428 if (core->SwapoutArea(map)) {
429 return false;
430 }
431 }
432
433 gamedata->SaveAllStores();
434
435 //compress files in cache named: .STO and .ARE
436 //no .CRE would be saved in cache
437 if (core->CompressSave(Path)) {
438 return false;
439 }
440
441 //Create .gam file from Game() object
442 if (core->WriteGame(Path)) {
443 return false;
444 }
445
446 //Create .wmp file from WorldMap() object
447 if (core->WriteWorldMap(Path)) {
448 return false;
449 }
450
451 PluginHolder<ImageWriter> im(PLUGIN_IMAGE_WRITER_BMP);
452 if (!im) {
453 Log(ERROR, "SaveGameIterator", "Couldn't create the BMPWriter!");
454 return false;
455 }
456
457 //Create portraits
458 for (int i = 0; i < game->GetPartySize( false ); i++) {
459 Actor *actor = game->GetPC( i, false );
460 Holder<Sprite2D> portrait = actor->CopyPortrait(true);
461
462 if (portrait) {
463 char FName[_MAX_PATH];
464 snprintf( FName, sizeof(FName), "PORTRT%d", i );
465 FileStream outfile;
466 outfile.Create(Path, FName, IE_BMP_CLASS_ID);
467 // NOTE: we save the true portrait size, even tho the preview buttons arent (always) the same
468 // we do this because: 1. the GUI should be able to use whatever size it wants
469 // and 2. its more appropriate to have a flag on the buttons to do the scaling/cropping
470 im->PutImage(&outfile, portrait);
471 }
472 }
473
474 // Create area preview
475 // FIXME: the preview should be passed in by the caller!
476
477 WindowManager* wm = core->GetWindowManager();
478 Holder<Sprite2D> preview = wm->GetScreenshot(wm->GetGameWindow());
479
480 // scale down to get more of the screen and reduce the size
481 preview = core->GetVideoDriver()->SpriteScaleDown(preview, 5);
482 FileStream outfile;
483 outfile.Create( Path, core->GameNameResRef, IE_BMP_CLASS_ID );
484 im->PutImage( &outfile, preview );
485
486 return true;
487 }
488
CanSave()489 static int CanSave()
490 {
491 //some of these restrictions might not be needed
492 const Store *store = core->GetCurrentStore();
493 if (store) {
494 displaymsg->DisplayConstantString(STR_CANTSAVESTORE, DMC_BG2XPGREEN);
495 return 1; //can't save while store is open
496 }
497 const GameControl *gc = core->GetGameControl();
498 if (!gc) {
499 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
500 return -1; //no gamecontrol!!!
501 }
502 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
503 displaymsg->DisplayConstantString(STR_CANTSAVEDIALOG, DMC_BG2XPGREEN);
504 return 2; //can't save while in dialog
505 }
506
507 const Game *game = core->GetGame();
508 if (!game) {
509 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
510 return -1;
511 }
512 if (game->CombatCounter) {
513 displaymsg->DisplayConstantString(STR_CANTSAVECOMBAT, DMC_BG2XPGREEN);
514 return 3;
515 }
516
517 const Map *map = game->GetCurrentArea();
518 if (!map) {
519 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
520 return -1;
521 }
522
523 if (map->AreaFlags&AF_NOSAVE) {
524 //cannot save in area
525 displaymsg->DisplayConstantString(STR_CANTSAVEMONS, DMC_BG2XPGREEN);
526 return 4;
527 }
528
529 int i = game->GetPartySize(true);
530 while(i--) {
531 const Actor *actor = game->GetPC(i, true);
532 // can't save while (party) actors are in helpless or dead states
533 // STATE_NOSAVE tracks actors not to be stored in GAM, not game saveability
534 if (actor->GetStat(IE_STATE_ID) & (STATE_NOSAVE|STATE_MINDLESS)) {
535 //some actor is in nosave state
536 displaymsg->DisplayConstantString(STR_CANTSAVENOCTRL, DMC_BG2XPGREEN);
537 return 5;
538 }
539 if (actor->GetCurrentArea()!=map) {
540 //scattered
541 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
542 return 6;
543 }
544 if (map->AnyEnemyNearPoint(actor->Pos)) {
545 displaymsg->DisplayConstantString( STR_CANTSAVEMONS, DMC_BG2XPGREEN );
546 return 7;
547 }
548 }
549
550 Point pc1 = game->GetPC(0, true)->Pos;
551 std::vector<Actor *> nearActors = map->GetAllActorsInRadius(pc1, GA_NO_DEAD|GA_NO_UNSCHEDULED, 15);
552 std::vector<Actor *>::iterator neighbour;
553 for (neighbour = nearActors.begin(); neighbour != nearActors.end(); ++neighbour) {
554 if ((*neighbour)->GetInternalFlag() & IF_NOINT) {
555 // dialog about to start or similar
556 displaymsg->DisplayConstantString(STR_CANTSAVEDIALOG2, DMC_BG2XPGREEN);
557 return 8;
558 }
559 }
560
561 //TODO: can't save while AOE spells are in effect -> CANTSAVE
562 //TODO: can't save during a rest, chapter information or movie -> CANTSAVEMOVIE
563
564 return 0;
565 }
566
567 static bool CreateSavePath(char *Path, int index, const char *slotname) WARN_UNUSED;
CreateSavePath(char * Path,int index,const char * slotname)568 static bool CreateSavePath(char *Path, int index, const char *slotname)
569 {
570 PathJoin(Path, core->SavePath, SaveDir(), nullptr);
571
572 //if the path exists in different case, don't make it again
573 if (!MakeDirectory(Path)) {
574 Log(ERROR, "SaveGameIterator", "Unable to create save game directory '%s'", Path);
575 return false;
576 }
577 //keep the first part we already determined existing
578
579 char dir[_MAX_PATH];
580 snprintf( dir, _MAX_PATH, "%09d-%s", index, slotname );
581 PathJoin(Path, Path, dir, nullptr);
582 //this is required in case the old slot wasn't recognised but still there
583 core->DelTree(Path, false);
584 if (!MakeDirectory(Path)) {
585 Log(ERROR, "SaveGameIterator", "Unable to create save game directory '%s'", Path);
586 return false;
587 }
588 return true;
589 }
590
CreateSaveGame(int index,bool mqs)591 int SaveGameIterator::CreateSaveGame(int index, bool mqs)
592 {
593 AutoTable tab("savegame");
594 const char *slotname = NULL;
595 int qsave = 0;
596
597 if (tab) {
598 slotname = tab->QueryField(index);
599 qsave = atoi(tab->QueryField(index, 1));
600 }
601
602 if (mqs) {
603 assert(qsave);
604 PruneQuickSave(slotname);
605 }
606
607 if (int cansave = CanSave())
608 return cansave;
609
610 //if index is not an existing savegame, we create a unique slotname
611 for (size_t i = 0; i < save_slots.size(); ++i) {
612 Holder<SaveGame> save = save_slots[i];
613 if (save->GetSaveID() == index) {
614 DeleteSaveGame(save);
615 break;
616 }
617 }
618 char Path[_MAX_PATH];
619 GameControl *gc = core->GetGameControl();
620
621 if (!CreateSavePath(Path, index, slotname)) {
622 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
623 if (gc) {
624 gc->SetDisplayText(STR_CANTSAVE, 30);
625 }
626 return -1;
627 }
628
629 if (!DoSaveGame(Path)) {
630 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
631 if (gc) {
632 gc->SetDisplayText(STR_CANTSAVE, 30);
633 }
634 return -1;
635 }
636
637 // Save successful / Quick-save successful
638 if (qsave) {
639 displaymsg->DisplayConstantString(STR_QSAVESUCCEED, DMC_BG2XPGREEN);
640 if (gc) {
641 gc->SetDisplayText(STR_QSAVESUCCEED, 30);
642 }
643 } else {
644 displaymsg->DisplayConstantString(STR_SAVESUCCEED, DMC_BG2XPGREEN);
645 if (gc) {
646 gc->SetDisplayText(STR_SAVESUCCEED, 30);
647 }
648 }
649 return 0;
650 }
651
CreateSaveGame(Holder<SaveGame> save,const char * slotname)652 int SaveGameIterator::CreateSaveGame(Holder<SaveGame> save, const char *slotname)
653 {
654 if (!slotname) {
655 return -1;
656 }
657
658 if (int cansave = CanSave())
659 return cansave;
660
661 GameControl *gc = core->GetGameControl();
662 int index;
663
664 if (save) {
665 index = save->GetSaveID();
666
667 DeleteSaveGame(save);
668 save.release();
669 } else {
670 //leave space for autosaves
671 //probably the hardcoded slot names should be read by this object
672 //in that case 7 == size of hardcoded slot names array (savegame.2da)
673 index = 7;
674 for (size_t i = 0; i < save_slots.size(); ++i) {
675 Holder<SaveGame> save = save_slots[i];
676 if (save->GetSaveID() >= index) {
677 index = save->GetSaveID() + 1;
678 }
679 }
680 }
681
682 char Path[_MAX_PATH];
683 if (!CreateSavePath(Path, index, slotname)) {
684 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
685 if (gc) {
686 gc->SetDisplayText(STR_CANTSAVE, 30);
687 }
688 return -1;
689 }
690
691 if (!DoSaveGame(Path)) {
692 displaymsg->DisplayConstantString(STR_CANTSAVE, DMC_BG2XPGREEN);
693 if (gc) {
694 gc->SetDisplayText(STR_CANTSAVE, 30);
695 }
696 return -1;
697 }
698
699 // Save successful
700 displaymsg->DisplayConstantString(STR_SAVESUCCEED, DMC_BG2XPGREEN);
701 if (gc) {
702 gc->SetDisplayText(STR_SAVESUCCEED, 30);
703 }
704 return 0;
705 }
706
DeleteSaveGame(Holder<SaveGame> game)707 void SaveGameIterator::DeleteSaveGame(Holder<SaveGame> game)
708 {
709 if (!game) {
710 return;
711 }
712
713 core->DelTree( game->GetPath(), false ); //remove all files from folder
714 rmdir( game->GetPath() );
715 }
716
717 }
718