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
);
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
);
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
);
255 bool SaveGameIterator::RescanSaveGames()
257 // delete old entries
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
266 mkdir(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
267 chmod(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
270 if (!dir
) { //If we cannot open the Directory
274 std::set
<char*,iless
> slots
;
276 const char *name
= dir
.GetName();
277 if (dir
.IsDirectory() && IsSaveGameSlot( Path
, name
)) {
278 slots
.insert(strdup(name
));
282 std::transform(slots
.begin(), slots
.end(), std::back_inserter(save_slots
), GetSaveGame
);
286 const std::vector
<Holder
<SaveGame
> >& SaveGameIterator::GetSaveGames()
293 Holder
<SaveGame
> SaveGameIterator::GetSaveGame(const char *slotname
)
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
);
314 DirectoryIterator
dir(Path
);
319 if (strnicmp( dir
.GetName(), "PORTRT", 6 ) == 0)
323 SaveGame
* sg
= new SaveGame( Path
, savegameName
, core
->GameNameResRef
, slotname
, prtrt
, savegameNumber
);
327 void SaveGameIterator::PruneQuickSave(const char *folder
)
329 char from
[_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() );
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();
349 int n
=myslots
[size
-1];
350 size_t hole
= GetHole(n
);
354 FormatQuickSavePath(from
, myslots
[hole
]);
355 myslots
.erase(myslots
.begin()+hole
);
356 core
->DelTree(from
, false);
359 //shift paths, always do this, because they are aging
360 size
= myslots
.size();
362 FormatQuickSavePath(from
, myslots
[i
]);
363 FormatQuickSavePath(to
, myslots
[i
]+1);
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();
375 Map
*map
= game
->GetMap(mc
);
376 if (core
->SwapoutArea(map
)) {
381 //compress files in cache named: .STO and .ARE
382 //no .CRE would be saved in cache
383 if (core
->CompressSave(Path
)) {
387 //Create .gam file from Game() object
388 if (core
->WriteGame(Path
)) {
392 //Create .wmp file from WorldMap() object
393 if (core
->WriteWorldMap(Path
)) {
397 PluginHolder
<ImageWriter
> im(PLUGIN_IMAGE_WRITER_BMP
);
399 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED
);
404 for (int i
= 0; i
< game
->GetPartySize( false ); i
++) {
405 Sprite2D
* portrait
= core
->GetGameControl()->GetPortraitPreview( i
);
407 char FName
[_MAX_PATH
];
408 snprintf( FName
, sizeof(FName
), "PORTRT%d", i
);
410 outfile
.Create( Path
, FName
, IE_BMP_CLASS_ID
);
411 im
->PutImage( &outfile
, portrait
);
415 // Create area preview
416 Sprite2D
* preview
= core
->GetGameControl()->GetPreview();
418 outfile
.Create( Path
, core
->GameNameResRef
, IE_BMP_CLASS_ID
);
419 im
->PutImage( &outfile
, preview
);
426 //some of these restrictions might not be needed
427 Store
* store
= core
->GetCurrentStore();
429 return 1; //can't save while store is open
431 GameControl
*gc
= core
->GetGameControl();
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
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
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
;
469 slotname
= tab
->QueryField(index
);
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
);
485 char Path
[_MAX_PATH
];
486 CreateSavePath(Path
, index
, slotname
);
488 if (!DoSaveGame(Path
)) {
492 // Save succesful / Quick-save succesful
494 core
->DisplayConstantString(STR_QSAVESUCCEED
, 0xbcefbc);
495 if (core
->GetGameControl()) {
496 core
->GetGameControl()->SetDisplayText(STR_QSAVESUCCEED
, 30);
499 core
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
500 if (core
->GetGameControl()) {
501 core
->GetGameControl()->SetDisplayText(STR_SAVESUCCEED
, 30);
507 int SaveGameIterator::CreateSaveGame(Holder
<SaveGame
> save
, const char *slotname
)
513 if (int cansave
= CanSave())
518 index
= save
->GetSaveID();
520 DeleteSaveGame(save
);
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)
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
)) {
543 core
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
544 if (core
->GetGameControl()) {
545 core
->GetGameControl()->SetDisplayText(STR_SAVESUCCEED
, 30);
550 void SaveGameIterator::DeleteSaveGame(Holder
<SaveGame
> game
)
556 core
->DelTree( game
->GetPath(), false ); //remove all files from folder
557 rmdir( game
->GetPath() );