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"
27 #include "GameControl.h"
28 #include "GameData.h" // For ResourceHolder
30 #include "ImageWriter.h"
31 #include "Interface.h"
32 #include "SaveGameMgr.h"
41 const TypeID
SaveGame::ID
= { "SaveGame" };
43 /** Extract date from save game ds into Date. */
44 static void ParseGameDate(DataStream
*ds
, char *Date
)
50 ds
->Read(Signature
, 8);
51 ds
->ReadDword(&GameTime
);
53 if (memcmp(Signature
,"GAME",4) ) {
54 strcpy(Date
, "ERROR");
58 int hours
= ((int)GameTime
)/300;
61 char *a
=NULL
,*b
=NULL
,*c
=NULL
;
63 core
->GetTokenDictionary()->SetAtCopy("GAMEDAYS", days
);
65 if (days
==1) a
=core
->GetString(10698);
66 else a
=core
->GetString(10697);
68 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
70 if (a
) b
=core
->GetString(10699);
71 if (hours
==1) c
=core
->GetString(10701);
72 else c
=core
->GetString(10700);
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
;
100 char nPath
[_MAX_PATH
];
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
);
110 SaveGame::~SaveGame()
114 Sprite2D
* SaveGame::GetPortrait(int index
) const
116 if (index
> PortraitCount
) {
119 char nPath
[_MAX_PATH
];
120 sprintf( nPath
, "PORTRT%d", index
);
121 ResourceHolder
<ImageMgr
> im(nPath
, manager
, true);
124 return im
->GetSprite2D();
127 Sprite2D
* SaveGame::GetPreview() const
129 ResourceHolder
<ImageMgr
> im(Prefix
, manager
, true);
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
);
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
);
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
)
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
);
205 if (stricmp(savegameName
, match
) )
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] == '.')
223 int cnt
= sscanf( slotname
, SAVEGAME_DIRECTORY_MATCHER
, &savegameNumber
, savegameName
);
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
);
232 //The matcher got matched correctly.
233 char dtmp
[_MAX_PATH
];
234 PathJoin(dtmp
, Path
, slotname
, NULL
);
237 if (stat( dtmp
, &fst
))
240 if (! S_ISDIR( fst
.st_mode
))
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
);
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
);
262 bool SaveGameIterator::RescanSaveGames()
264 // delete old entries
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
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
280 struct dirent
* de
= readdir( dir
); //Lookup the first entry in the Directory
286 std::set
<char*,iless
> slots
;
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
);
298 const std::vector
<Holder
<SaveGame
> >& SaveGameIterator::GetSaveGames()
305 Holder
<SaveGame
> SaveGameIterator::GetSaveGame(const char *slotname
)
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
);
326 DIR* ndir
= opendir( Path
);
327 //If we cannot open the Directory
331 struct dirent
* de2
= readdir( ndir
); //Lookup the first entry in the Directory
338 if (strnicmp( de2
->d_name
, "PORTRT", 6 ) == 0)
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
);
347 void SaveGameIterator::PruneQuickSave(const char *folder
)
349 char from
[_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() );
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();
369 int n
=myslots
[size
-1];
370 size_t hole
= GetHole(n
);
374 FormatQuickSavePath(from
, myslots
[hole
]);
375 myslots
.erase(myslots
.begin()+hole
);
376 core
->DelTree(from
, false);
379 //shift paths, always do this, because they are aging
380 size
= myslots
.size();
382 FormatQuickSavePath(from
, myslots
[i
]);
383 FormatQuickSavePath(to
, myslots
[i
]+1);
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();
395 Map
*map
= game
->GetMap(mc
);
396 if (core
->SwapoutArea(map
)) {
401 //compress files in cache named: .STO and .ARE
402 //no .CRE would be saved in cache
403 if (core
->CompressSave(Path
)) {
407 //Create .gam file from Game() object
408 if (core
->WriteGame(Path
)) {
412 //Create .wmp file from WorldMap() object
413 if (core
->WriteWorldMap(Path
)) {
417 PluginHolder
<ImageWriter
> im(PLUGIN_IMAGE_WRITER_BMP
);
419 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED
);
424 for (int i
= 0; i
< game
->GetPartySize( false ); i
++) {
425 Sprite2D
* portrait
= core
->GetGameControl()->GetPortraitPreview( i
);
427 char FName
[_MAX_PATH
];
428 snprintf( FName
, sizeof(FName
), "PORTRT%d", i
);
430 outfile
.Create( Path
, FName
, IE_BMP_CLASS_ID
);
431 im
->PutImage( &outfile
, portrait
);
435 // Create area preview
436 Sprite2D
* preview
= core
->GetGameControl()->GetPreview();
438 outfile
.Create( Path
, core
->GameNameResRef
, IE_BMP_CLASS_ID
);
439 im
->PutImage( &outfile
, preview
);
446 //some of these restrictions might not be needed
447 Store
* store
= core
->GetCurrentStore();
449 return 1; //can't save while store is open
451 GameControl
*gc
= core
->GetGameControl();
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
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
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
;
489 slotname
= tab
->QueryField(index
);
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
);
505 char Path
[_MAX_PATH
];
506 CreateSavePath(Path
, index
, slotname
);
508 if (!DoSaveGame(Path
)) {
512 // Save succesful / Quick-save succesful
514 core
->DisplayConstantString(STR_QSAVESUCCEED
, 0xbcefbc);
515 if (core
->GetGameControl()) {
516 core
->GetGameControl()->SetDisplayText(STR_QSAVESUCCEED
, 30);
519 core
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
520 if (core
->GetGameControl()) {
521 core
->GetGameControl()->SetDisplayText(STR_SAVESUCCEED
, 30);
527 int SaveGameIterator::CreateSaveGame(Holder
<SaveGame
> save
, const char *slotname
)
533 if (int cansave
= CanSave())
538 index
= save
->GetSaveID();
540 DeleteSaveGame(save
);
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)
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
)) {
563 core
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
564 if (core
->GetGameControl()) {
565 core
->GetGameControl()->SetDisplayText(STR_SAVESUCCEED
, 30);
570 void SaveGameIterator::DeleteSaveGame(Holder
<SaveGame
> game
)
576 core
->DelTree( game
->GetPath(), false ); //remove all files from folder
577 rmdir( game
->GetPath() );