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 "DisplayMessage.h"
28 #include "GameData.h" // For ResourceHolder
30 #include "ImageWriter.h"
31 #include "Interface.h"
32 #include "SaveGameMgr.h"
34 #include "GUI/GameControl.h"
36 #if defined(__HAIKU__)
44 const TypeID
SaveGame::ID
= { "SaveGame" };
46 /** Extract date from save game ds into Date. */
47 static void ParseGameDate(DataStream
*ds
, char *Date
)
53 ds
->Read(Signature
, 8);
54 ds
->ReadDword(&GameTime
);
56 if (memcmp(Signature
,"GAME",4) ) {
57 strcpy(Date
, "ERROR");
61 int hours
= ((int)GameTime
)/300;
64 char *a
=NULL
,*b
=NULL
,*c
=NULL
;
66 core
->GetTokenDictionary()->SetAtCopy("GAMEDAYS", days
);
68 if (days
==1) a
=core
->GetString(10698);
69 else a
=core
->GetString(10697);
71 core
->GetTokenDictionary()->SetAtCopy("HOUR", hours
);
73 if (a
) b
=core
->GetString(10699);
74 if (hours
==1) c
=core
->GetString(10701);
75 else c
=core
->GetString(10700);
95 SaveGame::SaveGame(const char* path
, const char* name
, const char* prefix
, const char* slotname
, int pCount
, int saveID
)
97 strncpy( Prefix
, prefix
, sizeof( Prefix
) );
98 strncpy( Path
, path
, sizeof( Path
) );
99 strncpy( Name
, name
, sizeof( Name
) );
100 strncpy( SlotName
, slotname
, sizeof( SlotName
) );
101 PortraitCount
= pCount
;
103 char nPath
[_MAX_PATH
];
105 PathJoinExt(nPath
, Path
, Prefix
, "bmp");
106 memset(&my_stat
,0,sizeof(my_stat
));
107 stat( nPath
, &my_stat
);
108 strftime( Date
, _MAX_PATH
, "%c", localtime( &my_stat
.st_mtime
) );
109 manager
.AddSource(Path
, Name
, PLUGIN_RESOURCE_DIRECTORY
);
113 SaveGame::~SaveGame()
117 Sprite2D
* SaveGame::GetPortrait(int index
) const
119 if (index
> PortraitCount
) {
122 char nPath
[_MAX_PATH
];
123 sprintf( nPath
, "PORTRT%d", index
);
124 ResourceHolder
<ImageMgr
> im(nPath
, manager
, true);
127 return im
->GetSprite2D();
130 Sprite2D
* SaveGame::GetPreview() const
132 ResourceHolder
<ImageMgr
> im(Prefix
, manager
, true);
135 return im
->GetSprite2D();
138 DataStream
* SaveGame::GetGame() const
140 return manager
.GetResource(Prefix
, IE_GAM_CLASS_ID
, true);
143 DataStream
* SaveGame::GetWmap(int idx
) const
145 return manager
.GetResource(core
->WorldMapName
[idx
], IE_WMP_CLASS_ID
, true);
148 DataStream
* SaveGame::GetSave() const
150 return manager
.GetResource(Prefix
, IE_SAV_CLASS_ID
, true);
153 const char* SaveGame::GetGameDate() const
155 if (GameDate
[0] == '\0')
156 ParseGameDate(GetGame(), GameDate
);
160 SaveGameIterator::SaveGameIterator(void)
164 SaveGameIterator::~SaveGameIterator(void)
168 /* mission pack save */
169 static const char* SaveDir()
171 ieDword playmode
= 0;
172 core
->GetDictionary()->Lookup( "SaveDir", playmode
);
179 #define FormatQuickSavePath(destination, i) \
180 snprintf(destination,sizeof(destination),"%s%s%s%09d-%s", \
181 core->SavePath,SaveDir(), SPathDelimiter,i,folder);
184 * Returns the first 0 bit position of an integer
186 static int GetHole(int n
)
198 * Returns the age of a quickslot entry. Returns 0 if it isn't a quickslot
200 static int IsQuickSaveSlot(const char* match
, const char* slotname
)
202 char savegameName
[_MAX_PATH
];
203 int savegameNumber
= 0;
204 int cnt
= sscanf( slotname
, SAVEGAME_DIRECTORY_MATCHER
, &savegameNumber
, savegameName
);
208 if (stricmp(savegameName
, match
) )
212 return savegameNumber
;
215 * Return true if directory Path/slotname is a potential save game
216 * slot, otherwise return false.
218 static bool IsSaveGameSlot(const char* Path
, const char* slotname
)
220 char savegameName
[_MAX_PATH
];
221 int savegameNumber
= 0;
223 if (slotname
[0] == '.')
226 int cnt
= sscanf( slotname
, SAVEGAME_DIRECTORY_MATCHER
, &savegameNumber
, savegameName
);
228 //The matcher didn't match: either this is not a valid dir
229 //or the SAVEGAME_DIRECTORY_MATCHER needs updating.
230 printMessage( "SaveGameIterator", " ", LIGHT_RED
);
231 printf( "Invalid savegame directory '%s' in %s.\n", slotname
, Path
);
235 //The matcher got matched correctly.
236 char dtmp
[_MAX_PATH
];
237 PathJoin(dtmp
, Path
, slotname
, NULL
);
239 char ftmp
[_MAX_PATH
];
240 PathJoinExt(ftmp
, dtmp
, core
->GameNameResRef
, "bmp");
242 if (access( ftmp
, R_OK
)) {
243 printMessage("SaveGameIterator"," ",YELLOW
);
244 printf("Ignoring slot %s because of no appropriate preview!\n", dtmp
);
248 PathJoinExt(ftmp
, dtmp
, core
->WorldMapName
[0], "wmp");
249 if (access( ftmp
, R_OK
)) {
250 printMessage("SaveGameIterator"," ",YELLOW
);
251 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp
);
255 /* we might need something here as well
256 PathJoinExt(ftmp, dtmp, core->WorldMapName[1], "wmp");
257 if (access( ftmp, R_OK )) {
258 printMessage("SaveGameIterator"," ",YELLOW);
259 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp);
267 bool SaveGameIterator::RescanSaveGames()
269 // delete old entries
272 char Path
[_MAX_PATH
];
273 PathJoin(Path
, core
->SavePath
, SaveDir(), NULL
);
275 DirectoryIterator
dir(Path
);
276 // create the save game directory at first access
278 mkdir(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
279 chmod(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
282 if (!dir
) { //If we cannot open the Directory
286 std::set
<char*,iless
> slots
;
288 const char *name
= dir
.GetName();
289 if (dir
.IsDirectory() && IsSaveGameSlot( Path
, name
)) {
290 slots
.insert(strdup(name
));
294 for (std::set
<char*,iless
>::iterator i
= slots
.begin(); i
!= slots
.end(); i
++) {
295 save_slots
.push_back(GetSaveGame(*i
));
302 const std::vector
<Holder
<SaveGame
> >& SaveGameIterator::GetSaveGames()
309 Holder
<SaveGame
> SaveGameIterator::GetSaveGame(const char *slotname
)
316 char Path
[_MAX_PATH
];
317 //lets leave space for the filenames
318 PathJoin(Path
, core
->SavePath
, SaveDir(), slotname
, NULL
);
320 char savegameName
[_MAX_PATH
]={0};
321 int savegameNumber
= 0;
323 int cnt
= sscanf( slotname
, SAVEGAME_DIRECTORY_MATCHER
, &savegameNumber
, savegameName
);
324 //maximum pathlength == 240, without 8+3 filenames
325 if ( (cnt
!= 2) || (strlen(Path
)>240) ) {
326 printf( "Invalid savegame directory '%s' in %s.\n", slotname
, Path
);
330 DirectoryIterator
dir(Path
);
335 if (strnicmp( dir
.GetName(), "PORTRT", 6 ) == 0)
339 SaveGame
* sg
= new SaveGame( Path
, savegameName
, core
->GameNameResRef
, slotname
, prtrt
, savegameNumber
);
343 void SaveGameIterator::PruneQuickSave(const char *folder
)
345 char from
[_MAX_PATH
];
348 //storing the quicksave ages in an array
349 std::vector
<int> myslots
;
350 for (charlist::iterator m
= save_slots
.begin();m
!=save_slots
.end();m
++) {
351 int tmp
= IsQuickSaveSlot(folder
, (*m
)->GetSlotName() );
353 size_t pos
= myslots
.size();
354 while(pos
-- && myslots
[pos
]>tmp
) ;
355 myslots
.insert(myslots
.begin()+pos
+1,tmp
);
358 //now we got an integer array in myslots
359 size_t size
= myslots
.size();
365 int n
=myslots
[size
-1];
366 size_t hole
= GetHole(n
);
370 FormatQuickSavePath(from
, myslots
[hole
]);
371 myslots
.erase(myslots
.begin()+hole
);
372 core
->DelTree(from
, false);
375 //shift paths, always do this, because they are aging
376 size
= myslots
.size();
378 FormatQuickSavePath(from
, myslots
[i
]);
379 FormatQuickSavePath(to
, myslots
[i
]+1);
384 /** Save game to given directory */
385 static bool DoSaveGame(const char *Path
)
387 Game
*game
= core
->GetGame();
388 //saving areas to cache currently in memory
389 unsigned int mc
= (unsigned int) game
->GetLoadedMapCount();
391 Map
*map
= game
->GetMap(mc
);
392 if (core
->SwapoutArea(map
)) {
397 //compress files in cache named: .STO and .ARE
398 //no .CRE would be saved in cache
399 if (core
->CompressSave(Path
)) {
403 //Create .gam file from Game() object
404 if (core
->WriteGame(Path
)) {
408 //Create .wmp file from WorldMap() object
409 if (core
->WriteWorldMap(Path
)) {
413 PluginHolder
<ImageWriter
> im(PLUGIN_IMAGE_WRITER_BMP
);
415 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED
);
420 for (int i
= 0; i
< game
->GetPartySize( false ); i
++) {
421 Sprite2D
* portrait
= core
->GetGameControl()->GetPortraitPreview( i
);
423 char FName
[_MAX_PATH
];
424 snprintf( FName
, sizeof(FName
), "PORTRT%d", i
);
426 outfile
.Create( Path
, FName
, IE_BMP_CLASS_ID
);
427 im
->PutImage( &outfile
, portrait
);
431 // Create area preview
432 Sprite2D
* preview
= core
->GetGameControl()->GetPreview();
434 outfile
.Create( Path
, core
->GameNameResRef
, IE_BMP_CLASS_ID
);
435 im
->PutImage( &outfile
, preview
);
442 //some of these restrictions might not be needed
443 Store
* store
= core
->GetCurrentStore();
445 return 1; //can't save while store is open
447 GameControl
*gc
= core
->GetGameControl();
449 return -1; //no gamecontrol!!!
451 if (gc
->GetDialogueFlags()&DF_IN_DIALOG
) {
452 return 2; //can't save while in dialog?
455 //TODO: can't save while in combat
456 Game
*game
= core
->GetGame();
460 if (game
->CombatCounter
) {
464 Map
*map
= game
->GetCurrentArea();
469 if (map
->AreaFlags
&AF_SAVE
) {
470 //cannot save in area
474 int i
= game
->GetPartySize(true);
476 Actor
*actor
= game
->GetPC(i
, true);
477 //TODO: can't save while (party) actors are in helpless states
478 if (actor
->GetStat(IE_STATE_ID
) & STATE_NOSAVE
) {
479 //some actor is in nosave state
482 if (actor
->GetCurrentArea()!=map
) {
488 //TODO: can't save while AOE spells are in effect
489 //TODO: can't save while IF_NOINT is set on any actor
494 static void CreateSavePath(char *Path
, int index
, const char *slotname
)
496 PathJoin( Path
, core
->SavePath
, SaveDir(), NULL
);
498 //if the path exists in different case, don't make it again
499 mkdir(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
500 chmod(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
501 //keep the first part we already determined existing
504 snprintf( dir
, _MAX_PATH
, "%09d-%s", index
, slotname
);
505 PathJoin(Path
, Path
, dir
, NULL
);
506 //this is required in case the old slot wasn't recognised but still there
507 core
->DelTree(Path
, false);
508 mkdir(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
509 chmod(Path
,S_IWRITE
|S_IREAD
|S_IEXEC
);
512 int SaveGameIterator::CreateSaveGame(int index
, bool mqs
)
514 AutoTable
tab("savegame");
515 const char *slotname
= NULL
;
519 slotname
= tab
->QueryField(index
);
520 qsave
= atoi(tab
->QueryField(index
, 1));
525 PruneQuickSave(slotname
);
528 if (int cansave
= CanSave())
531 //if index is not an existing savegame, we create a unique slotname
532 for (size_t i
= 0; i
< save_slots
.size(); ++i
) {
533 Holder
<SaveGame
> save
= save_slots
[i
];
534 if (save
->GetSaveID() == index
) {
535 DeleteSaveGame(save
);
539 char Path
[_MAX_PATH
];
540 CreateSavePath(Path
, index
, slotname
);
541 GameControl
*gc
= core
->GetGameControl();
543 if (!DoSaveGame(Path
)) {
544 displaymsg
->DisplayConstantString(STR_CANTSAVE
, 0xbcefbc);
546 gc
->SetDisplayText(STR_CANTSAVE
, 30);
551 // Save succesful / Quick-save succesful
553 displaymsg
->DisplayConstantString(STR_QSAVESUCCEED
, 0xbcefbc);
555 gc
->SetDisplayText(STR_QSAVESUCCEED
, 30);
558 displaymsg
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
560 gc
->SetDisplayText(STR_SAVESUCCEED
, 30);
566 int SaveGameIterator::CreateSaveGame(Holder
<SaveGame
> save
, const char *slotname
)
572 if (int cansave
= CanSave())
575 GameControl
*gc
= core
->GetGameControl();
579 index
= save
->GetSaveID();
581 DeleteSaveGame(save
);
584 //leave space for autosaves
585 //probably the hardcoded slot names should be read by this object
586 //in that case 7 == size of hardcoded slot names array (savegame.2da)
588 for (size_t i
= 0; i
< save_slots
.size(); ++i
) {
589 Holder
<SaveGame
> save
= save_slots
[i
];
590 if (save
->GetSaveID() >= index
) {
591 index
= save
->GetSaveID() + 1;
596 char Path
[_MAX_PATH
];
597 CreateSavePath(Path
, index
, slotname
);
599 if (!DoSaveGame(Path
)) {
600 displaymsg
->DisplayConstantString(STR_CANTSAVE
, 0xbcefbc);
602 gc
->SetDisplayText(STR_CANTSAVE
, 30);
608 displaymsg
->DisplayConstantString(STR_SAVESUCCEED
, 0xbcefbc);
610 gc
->SetDisplayText(STR_SAVESUCCEED
, 30);
615 void SaveGameIterator::DeleteSaveGame(Holder
<SaveGame
> game
)
621 core
->DelTree( game
->GetPath(), false ); //remove all files from folder
622 rmdir( game
->GetPath() );