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