GUIScript: Make LoadSymbol return an object.
[gemrb.git] / gemrb / core / SaveGameIterator.cpp
blobc0c288a693d3b9bcfb1c321d395158946ef8f13c
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 char ftmp[_MAX_PATH];
237 PathJoinExt(ftmp, dtmp, core->GameNameResRef, "bmp");
239 if (access( ftmp, R_OK )) {
240 printMessage("SaveGameIterator"," ",YELLOW);
241 printf("Ignoring slot %s because of no appropriate preview!\n", dtmp);
242 return false;
245 PathJoinExt(ftmp, dtmp, core->WorldMapName, "wmp");
246 if (access( ftmp, R_OK )) {
247 printMessage("SaveGameIterator"," ",YELLOW);
248 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp);
249 return false;
252 return true;
255 bool SaveGameIterator::RescanSaveGames()
257 // delete old entries
258 save_slots.clear();
260 char Path[_MAX_PATH];
261 PathJoin(Path, core->SavePath, SaveDir(), NULL);
263 DirectoryIterator dir(Path);
264 // create the save game directory at first access
265 if (!dir) {
266 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
267 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
268 dir.Rewind();
270 if (!dir) { //If we cannot open the Directory
271 return false;
274 std::set<char*,iless> slots;
275 do {
276 const char *name = dir.GetName();
277 if (dir.IsDirectory() && IsSaveGameSlot( Path, name )) {
278 slots.insert(strdup(name));
280 } while (++dir);
282 std::transform(slots.begin(), slots.end(), std::back_inserter(save_slots), GetSaveGame);
283 return true;
286 const std::vector<Holder<SaveGame> >& SaveGameIterator::GetSaveGames()
288 RescanSaveGames();
290 return save_slots;
293 Holder<SaveGame> SaveGameIterator::GetSaveGame(const char *slotname)
295 if (!slotname) {
296 return NULL;
299 int prtrt = 0;
300 char Path[_MAX_PATH];
301 //lets leave space for the filenames
302 PathJoin(Path, core->SavePath, SaveDir(), slotname, NULL);
304 char savegameName[_MAX_PATH]={0};
305 int savegameNumber = 0;
307 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
308 //maximum pathlength == 240, without 8+3 filenames
309 if ( (cnt != 2) || (strlen(Path)>240) ) {
310 printf( "Invalid savegame directory '%s' in %s.\n", slotname, Path );
311 return NULL;
314 DirectoryIterator dir(Path);
315 if (!dir) {
316 return NULL;
318 do {
319 if (strnicmp( dir.GetName(), "PORTRT", 6 ) == 0)
320 prtrt++;
321 } while (++dir);
323 SaveGame* sg = new SaveGame( Path, savegameName, core->GameNameResRef, slotname, prtrt, savegameNumber );
324 return sg;
327 void SaveGameIterator::PruneQuickSave(const char *folder)
329 char from[_MAX_PATH];
330 char to[_MAX_PATH];
332 //storing the quicksave ages in an array
333 std::vector<int> myslots;
334 for (charlist::iterator m = save_slots.begin();m!=save_slots.end();m++) {
335 int tmp = IsQuickSaveSlot(folder, (*m)->GetSlotName() );
336 if (tmp) {
337 size_t pos = myslots.size();
338 while(pos-- && myslots[pos]>tmp) ;
339 myslots.insert(myslots.begin()+pos+1,tmp);
342 //now we got an integer array in myslots
343 size_t size = myslots.size();
345 if (!size) {
346 return;
349 int n=myslots[size-1];
350 size_t hole = GetHole(n);
351 size_t i;
352 if (hole<size) {
353 //prune second path
354 FormatQuickSavePath(from, myslots[hole]);
355 myslots.erase(myslots.begin()+hole);
356 core->DelTree(from, false);
357 rmdir(from);
359 //shift paths, always do this, because they are aging
360 size = myslots.size();
361 for(i=size;i--;) {
362 FormatQuickSavePath(from, myslots[i]);
363 FormatQuickSavePath(to, myslots[i]+1);
364 rename(from,to);
368 /** Save game to given directory */
369 static bool DoSaveGame(const char *Path)
371 Game *game = core->GetGame();
372 //saving areas to cache currently in memory
373 unsigned int mc = (unsigned int) game->GetLoadedMapCount();
374 while (mc--) {
375 Map *map = game->GetMap(mc);
376 if (core->SwapoutArea(map)) {
377 return false;
381 //compress files in cache named: .STO and .ARE
382 //no .CRE would be saved in cache
383 if (core->CompressSave(Path)) {
384 return false;
387 //Create .gam file from Game() object
388 if (core->WriteGame(Path)) {
389 return false;
392 //Create .wmp file from WorldMap() object
393 if (core->WriteWorldMap(Path)) {
394 return false;
397 PluginHolder<ImageWriter> im(PLUGIN_IMAGE_WRITER_BMP);
398 if (!im) {
399 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED );
400 return false;
403 //Create portraits
404 for (int i = 0; i < game->GetPartySize( false ); i++) {
405 Sprite2D* portrait = core->GetGameControl()->GetPortraitPreview( i );
406 if (portrait) {
407 char FName[_MAX_PATH];
408 snprintf( FName, sizeof(FName), "PORTRT%d", i );
409 FileStream outfile;
410 outfile.Create( Path, FName, IE_BMP_CLASS_ID );
411 im->PutImage( &outfile, portrait );
415 // Create area preview
416 Sprite2D* preview = core->GetGameControl()->GetPreview();
417 FileStream outfile;
418 outfile.Create( Path, core->GameNameResRef, IE_BMP_CLASS_ID );
419 im->PutImage( &outfile, preview );
421 return true;
424 int CanSave()
426 //some of these restrictions might not be needed
427 Store * store = core->GetCurrentStore();
428 if (store) {
429 return 1; //can't save while store is open
431 GameControl *gc = core->GetGameControl();
432 if (!gc) {
433 return -1; //no gamecontrol!!!
435 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
436 return 2; //can't save while in dialog?
438 //TODO: can't save while in combat
439 //TODO: can't save while (party) actors are in helpless states
440 //TODO: can't save while AOE spells are in effect
441 //TODO: can't save while IF_NOINT is set on an actor
442 return 0;
445 static void CreateSavePath(char *Path, int index, const char *slotname)
447 PathJoin( Path, core->SavePath, SaveDir(), NULL );
449 //if the path exists in different case, don't make it again
450 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
451 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
452 //keep the first part we already determined existing
454 char dir[_MAX_PATH];
455 snprintf( dir, _MAX_PATH, "%09d-%s", index, slotname );
456 snprintf( dir, _MAX_PATH, "%09d-%s", (int)index, slotname );
457 PathJoin(Path, Path, dir, NULL);
458 //this is required in case the old slot wasn't recognised but still there
459 core->DelTree(Path, false);
460 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
461 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
464 int SaveGameIterator::CreateSaveGame(int index, bool mqs)
466 AutoTable tab("savegame");
467 const char *slotname = NULL;
468 if (tab) {
469 slotname = tab->QueryField(index);
472 if (mqs) {
473 assert(index==1);
474 PruneQuickSave(slotname);
477 //if index is not an existing savegame, we create a unique slotname
478 for (size_t i = 0; i < save_slots.size(); ++i) {
479 Holder<SaveGame> save = save_slots[i];
480 if (save->GetSaveID() == index) {
481 DeleteSaveGame(save);
482 break;
485 char Path[_MAX_PATH];
486 CreateSavePath(Path, index, slotname);
488 if (!DoSaveGame(Path)) {
489 return -1;
492 // Save succesful / Quick-save succesful
493 if (index == 1) {
494 core->DisplayConstantString(STR_QSAVESUCCEED, 0xbcefbc);
495 if (core->GetGameControl()) {
496 core->GetGameControl()->SetDisplayText(STR_QSAVESUCCEED, 30);
498 } else {
499 core->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
500 if (core->GetGameControl()) {
501 core->GetGameControl()->SetDisplayText(STR_SAVESUCCEED, 30);
504 return 0;
507 int SaveGameIterator::CreateSaveGame(Holder<SaveGame> save, const char *slotname)
509 if (!slotname) {
510 return -1;
513 if (int cansave = CanSave())
514 return cansave;
516 int index;
517 if (save) {
518 index = save->GetSaveID();
520 DeleteSaveGame(save);
521 save.release();
522 } else {
523 //leave space for autosaves
524 //probably the hardcoded slot names should be read by this object
525 //in that case 7 == size of hardcoded slot names array (savegame.2da)
526 index = 7;
527 for (size_t i = 0; i < save_slots.size(); ++i) {
528 Holder<SaveGame> save = save_slots[i];
529 if (save->GetSaveID() >= index) {
530 index = save->GetSaveID() + 1;
535 char Path[_MAX_PATH];
536 CreateSavePath(Path, index, slotname);
538 if (!DoSaveGame(Path)) {
539 return -1;
542 // Save succesful
543 core->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
544 if (core->GetGameControl()) {
545 core->GetGameControl()->SetDisplayText(STR_SAVESUCCEED, 30);
547 return 0;
550 void SaveGameIterator::DeleteSaveGame(Holder<SaveGame> game)
552 if (!game) {
553 return;
556 core->DelTree( game->GetPath(), false ); //remove all files from folder
557 rmdir( game->GetPath() );