- major work on savegame code

Not tested yet!

* Added a JSON-based header to the savegames so that the unified menu can read from a common data source.
* moved loading and saving of frontend independent data to the wrapper so that support is automatic.
This commit is contained in:
Christoph Oelckers 2019-11-27 00:41:26 +01:00
parent 3b7aa74c27
commit 723b210c95
25 changed files with 371 additions and 539 deletions

View file

@ -92,6 +92,8 @@ struct GameInterface : ::GameInterface
void set_hud_scale(int size) override; void set_hud_scale(int size) override;
bool mouseInactiveConditional(bool condition) override; bool mouseInactiveConditional(bool condition) override;
FString statFPS() override; FString statFPS() override;
FSavegameInfo GetSaveSig() override;
}; };
END_BLD_NS END_BLD_NS

View file

@ -46,8 +46,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "sound.h" #include "sound.h"
#include "i_specialpaths.h" #include "i_specialpaths.h"
#include "view.h" #include "view.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h" #include "savegamehelp.h"
BEGIN_BLD_NS BEGIN_BLD_NS
@ -102,7 +100,7 @@ void LoadSave::Write(void *pData, int nSize)
ThrowError("File error #%d writing save file.", errno); ThrowError("File error #%d writing save file.", errno);
} }
void LoadSave::LoadGame(char *pzFile) void LoadSave::LoadGame(const char *pzFile)
{ {
bool demoWasPlayed = gDemo.at1; bool demoWasPlayed = gDemo.at1;
if (gDemo.at1) if (gDemo.at1)
@ -128,8 +126,6 @@ void LoadSave::LoadGame(char *pzFile)
rover->Load(); rover->Load();
rover = rover->next; rover = rover->next;
} }
if (!ReadStatistics() || !SECRET_Load()) // read the rest...
ThrowError("Error loading save file.");
hLFile.Close(); hLFile.Close();
FinishSavegameRead(); FinishSavegameRead();
@ -194,7 +190,7 @@ void LoadSave::LoadGame(char *pzFile)
//sndPlaySong(gGameOptions.zLevelSong, 1); //sndPlaySong(gGameOptions.zLevelSong, 1);
} }
void LoadSave::SaveGame(char *pzFile) void LoadSave::SaveGame(const char *pzFile)
{ {
OpenSaveGameForWrite(pzFile); OpenSaveGameForWrite(pzFile);
hSFile = WriteSavegameChunk("snapshot.bld"); hSFile = WriteSavegameChunk("snapshot.bld");
@ -211,8 +207,9 @@ void LoadSave::SaveGame(char *pzFile)
dword_27AA38 = 0; dword_27AA38 = 0;
rover = rover->next; rover = rover->next;
} }
SaveStatistics(); auto & li = gEpisodeInfo[gGameOptions.nEpisode].at28[gGameOptions.nLevel];
SECRET_Save(); G_WriteSaveHeader(gGameOptions.szUserGameName, li.at0, li.at90);
FinishSavegameWrite(); FinishSavegameWrite();
hSFile = NULL; hSFile = NULL;
} }

View file

@ -49,8 +49,8 @@ public:
virtual void Load(void); virtual void Load(void);
void Read(void *, int); void Read(void *, int);
void Write(void *, int); void Write(void *, int);
static void LoadGame(char *); static void LoadGame(const char *);
static void SaveGame(char *); static void SaveGame(const char *);
}; };
extern unsigned int gSavedOffset; extern unsigned int gSavedOffset;

View file

@ -43,6 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "view.h" #include "view.h"
#include "cmdlib.h" #include "cmdlib.h"
#include "i_specialpaths.h" #include "i_specialpaths.h"
#include "savegamehelp.h"
EXTERN_CVAR(Bool, hud_powerupduration) EXTERN_CVAR(Bool, hud_powerupduration)
@ -2069,7 +2070,6 @@ short gQuickSaveSlot = -1;
void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event) void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
{ {
char strSaveGameName[BMAX_PATH];
int nSlot = pItem->at28; int nSlot = pItem->at28;
if (gGameOptions.nGameType > 0 || !gGameStarted) if (gGameOptions.nGameType > 0 || !gGameStarted)
return; return;
@ -2078,21 +2078,21 @@ void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
gGameMenuMgr.Deactivate(); gGameMenuMgr.Deactivate();
return; return;
} }
snprintf(strSaveGameName, BMAX_PATH, "%sgame00%02d.sav", M_GetSavegamesPath().GetChars(), nSlot); FStringf basename("save%04d", nSlot);
auto strSaveGameName = G_BuildSaveName(basename);
strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[nSlot]); strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[nSlot]);
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName); sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName.GetChars());
gGameOptions.nSaveGameSlot = nSlot; gGameOptions.nSaveGameSlot = nSlot;
viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[nSlot]); viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[nSlot]);
videoNextPage(); videoNextPage();
gSaveGameNum = nSlot; gSaveGameNum = nSlot;
LoadSave::SaveGame(strSaveGameName); LoadSave::SaveGame(strSaveGameName.GetChars());
gQuickSaveSlot = nSlot; gQuickSaveSlot = nSlot;
gGameMenuMgr.Deactivate(); gGameMenuMgr.Deactivate();
} }
void QuickSaveGame(void) void QuickSaveGame(void)
{ {
char strSaveGameName[BMAX_PATH];
if (gGameOptions.nGameType > 0 || !gGameStarted) if (gGameOptions.nGameType > 0 || !gGameStarted)
return; return;
/*if (strSaveGameName[0]) /*if (strSaveGameName[0])
@ -2100,9 +2100,11 @@ void QuickSaveGame(void)
gGameMenuMgr.Deactivate(); gGameMenuMgr.Deactivate();
return; return;
}*/ }*/
snprintf(strSaveGameName, BMAX_PATH, "%sgame00%02d.sav", M_GetSavegamesPath().GetChars(), gQuickSaveSlot); FStringf basename("save%04d", gQuickSaveSlot);
auto strSaveGameName = G_BuildSaveName(basename);
strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[gQuickSaveSlot]); strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[gQuickSaveSlot]);
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName); sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName.GetChars());
gGameOptions.nSaveGameSlot = gQuickSaveSlot; gGameOptions.nSaveGameSlot = gQuickSaveSlot;
viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[gQuickSaveSlot]); viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[gQuickSaveSlot]);
videoNextPage(); videoNextPage();
@ -2116,11 +2118,11 @@ void QuickSaveGame(void)
void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event) void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
{ {
UNREFERENCED_PARAMETER(event); UNREFERENCED_PARAMETER(event);
char strLoadGameName[BMAX_PATH];
int nSlot = pItem->at28; int nSlot = pItem->at28;
if (gGameOptions.nGameType > 0) if (gGameOptions.nGameType > 0)
return; return;
snprintf(strLoadGameName, BMAX_PATH, "%sgame00%02d.sav", M_GetSavegamesPath().GetChars(), nSlot); FStringf basename("save%04d", nSlot);
auto strLoadGameName = G_BuildSaveName(basename);
if (!FileExists(strLoadGameName)) if (!FileExists(strLoadGameName))
return; return;
viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[nSlot]); viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[nSlot]);
@ -2132,10 +2134,11 @@ void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
void QuickLoadGame(void) void QuickLoadGame(void)
{ {
char strLoadGameName[BMAX_PATH];
if (gGameOptions.nGameType > 0) if (gGameOptions.nGameType > 0)
return; return;
snprintf(strLoadGameName, BMAX_PATH, "%sgame00%02d.sav", M_GetSavegamesPath().GetChars(), gQuickLoadSlot); FStringf basename("save%04d", gQuickSaveSlot);
auto strLoadGameName = G_BuildSaveName(basename);
if (!FileExists(strLoadGameName)) if (!FileExists(strLoadGameName))
return; return;
viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[gQuickLoadSlot]); viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[gQuickLoadSlot]);
@ -2270,4 +2273,9 @@ void drawLoadingScreen(void)
viewLoadingScreen(2049, buffer, levelGetTitle(), NULL); viewLoadingScreen(2049, buffer, levelGetTitle(), NULL);
} }
FSavegameInfo GameInterface::GetSaveSig()
{
return { SAVESIG_BLD, MINSAVEVER_BLD, SAVEVER_BLD };
}
END_BLD_NS END_BLD_NS

View file

@ -177,6 +177,13 @@ struct FGameStartup
int CustomLevel2; int CustomLevel2;
}; };
struct FSavegameInfo
{
const char *savesig;
int minsavever;
int currentsavever;
};
struct GameInterface struct GameInterface
{ {
enum EMenuSounds enum EMenuSounds
@ -202,6 +209,7 @@ struct GameInterface
virtual bool CanSave() { return true; } virtual bool CanSave() { return true; }
virtual void CustomMenuSelection(int menu, int item) {} virtual void CustomMenuSelection(int menu, int item) {}
virtual void StartGame(FGameStartup& gs) {} virtual void StartGame(FGameStartup& gs) {}
virtual FSavegameInfo GetSaveSig() { return { "", 0, 0}; }
}; };
extern GameInterface* gi; extern GameInterface* gi;

View file

@ -267,7 +267,7 @@ static void D_AddDirectory (TArray<FString> &wadfiles, const char *dir)
{ {
skindir[stuffstart++] = '/'; skindir[stuffstart++] = '/';
int savedstart = stuffstart; int savedstart = stuffstart;
const char* validexts[] = { "*.grp", "*.zip", "*.pk3", "*.pk4", "*.7z", "*.pk7" }; const char* validexts[] = { "*.grp", "*.zip", "*.pk3", "*.pk4", "*.7z", "*.pk7", "*.dat" };
for (auto ext : validexts) for (auto ext : validexts)
{ {
stuffstart = savedstart; stuffstart = savedstart;

View file

@ -42,6 +42,11 @@
#include "gstrings.h" #include "gstrings.h"
#include "d_gui.h" #include "d_gui.h"
#include "v_draw.h" #include "v_draw.h"
#include "files.h"
#include "resourcefile.h"
#include "sjson.h"
#include "savegamehelp.h"
#include "i_specialpaths.h"
#include "../../platform/win32/i_findfile.h" // This is a temporary direct path. Needs to be fixed when stuff gets cleaned up. #include "../../platform/win32/i_findfile.h" // This is a temporary direct path. Needs to be fixed when stuff gets cleaned up.
@ -207,7 +212,7 @@ void DLoadSaveMenu::ReadSaveStrings ()
LastSaved = LastAccessed = -1; LastSaved = LastAccessed = -1;
quickSaveSlot = NULL; quickSaveSlot = NULL;
filter = "";// G_BuildSaveName("*.zds", -1); filter = G_BuildSaveName("*");
filefirst = I_FindFirst (filter.GetChars(), &c_file); filefirst = I_FindFirst (filter.GetChars(), &c_file);
if (filefirst != ((void *)(-1))) if (filefirst != ((void *)(-1)))
{ {
@ -215,25 +220,30 @@ void DLoadSaveMenu::ReadSaveStrings ()
{ {
// I_FindName only returns the file's name and not its full path // I_FindName only returns the file's name and not its full path
FString filepath = "";// G_BuildSaveName(I_FindName(&c_file), -1); FString filepath = "";// G_BuildSaveName(I_FindName(&c_file), -1);
FILE *file = fopen (filepath, "rb");
if (file != NULL) FResourceFile *savegame = FResourceFile::OpenResourceFile(filepath, true, true);
if (savegame != nullptr)
{ {
//PNGHandle *png; FResourceLump *info = savegame->FindLump("info.json");
//char sig[16]; if (info == nullptr)
{
// savegame info not found. This is not a savegame so leave it alone.
delete savegame;
continue;
}
auto fr = info->NewReader();
FString title; FString title;
bool oldVer = true; int check = G_ValidateSavegame(fr, &title);
bool addIt = false; delete savegame;
bool missing = false; if (check != 0)
{
// ZDoom 1.23 betas 21-33 have the savesig first. FSaveGameNode *node = new FSaveGameNode;
// Earlier versions have the savesig second. node->Filename = filepath;
// Later versions have the savegame encapsulated inside a PNG. node->bOldVersion = check == -1;
// node->bMissingWads = check == -2;
// Old savegame versions are always added to the menu so node->Title = title;
// the user can easily delete them if desired. InsertSaveNode(node);
}
// Todo: Identify savegames here.
} }
} while (I_FindNext (filefirst, &c_file) == 0); } while (I_FindNext (filefirst, &c_file) == 0);
I_FindClose (filefirst); I_FindClose (filefirst);
@ -691,7 +701,7 @@ bool DLoadSaveMenu::Responder (event_t *ev)
{ {
FString EndString; FString EndString;
EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s",
GStrings("MNU_DELETESG"), SaveGames[Selected]->Title, GStrings("PRESSYN")); GStrings("MNU_DELETESG"), SaveGames[Selected]->Title.GetChars(), GStrings("PRESSYN"));
M_StartMessage (EndString, 0); M_StartMessage (EndString, 0);
} }
return true; return true;

View file

@ -1,6 +1,8 @@
/* /*
** savegame.cpp ** savegame.cpp
** **
** common savegame utilities for all front ends.
**
**--------------------------------------------------------------------------- **---------------------------------------------------------------------------
** Copyright 2019 Christoph Oelckers ** Copyright 2019 Christoph Oelckers
** All rights reserved. ** All rights reserved.
@ -29,21 +31,36 @@
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**--------------------------------------------------------------------------- **---------------------------------------------------------------------------
** **
** This is for keeping my sanity while working with the horrible mess
** that is the savegame code in Duke Nukem.
** Without handling this in global variables it is a losing proposition
** to save custom data along with the regular snapshot. :(
** With this the savegame code can mostly pretend to load from and write
** to files while really using a composite archive.
*/ */
#include "compositesaveame.h" #include "compositesaveame.h"
#include "savegamehelp.h" #include "savegamehelp.h"
#include "sjson.h"
#include "baselayer.h"
#include "gstrings.h"
#include "i_specialpaths.h"
#include "cmdlib.h"
#include "filesystem/filesystem.h"
#include "statistics.h"
#include "secrets.h"
static CompositeSavegameWriter savewriter; static CompositeSavegameWriter savewriter;
static FResourceFile *savereader; static FResourceFile *savereader;
//=============================================================================
//
// This is for keeping my sanity while working with the horrible mess
// that is the savegame code in Duke Nukem.
// Without handling this in global variables it is a losing proposition
// to save custom data along with the regular snapshot. :(
// With this the savegame code can mostly pretend to load from and write
// to files while really using a composite archive.
//
// All global non-game dependent state is also saved right here for convenience.
//
//=============================================================================
void OpenSaveGameForWrite(const char *name) void OpenSaveGameForWrite(const char *name)
{ {
savewriter.Clear(); savewriter.Clear();
@ -54,6 +71,13 @@ bool OpenSaveGameForRead(const char *name)
{ {
if (savereader) delete savereader; if (savereader) delete savereader;
savereader = FResourceFile::OpenResourceFile(name, true, true); savereader = FResourceFile::OpenResourceFile(name, true, true);
if (savereader != nullptr)
{
ReadStatistics();
SECRET_Load();
}
return savereader != nullptr; return savereader != nullptr;
} }
@ -80,3 +104,174 @@ void FinishSavegameRead()
delete savereader; delete savereader;
savereader = nullptr; savereader = nullptr;
} }
//=============================================================================
//
// Writes the header which is used to display the savegame in the menu.
//
//=============================================================================
void G_WriteSaveHeader(const char *name, const char*mapname, const char *maptitle)
{
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (!ctx)
{
return;
}
sjson_node* root = sjson_mkobject(ctx);
auto savesig = gi->GetSaveSig();
sjson_put_int(ctx, root, "Save Version", savesig.currentsavever);
sjson_put_string(ctx, root, "Engine", savesig.savesig);
sjson_put_string(ctx, root, "Game Resource", fileSystem.GetResourceFileName(1));
sjson_put_string(ctx, root, "map", mapname);
sjson_put_string(ctx, root, "Title", maptitle);
if (*mapname == '/') mapname++;
sjson_put_string(ctx, root, "Map Resource", mapname);
char* encoded = sjson_stringify(ctx, root, " ");
FileWriter* fil = WriteSavegameChunk("info.json");
if (!fil)
{
sjson_destroy_context(ctx);
return;
}
fil->Write(encoded, strlen(encoded));
sjson_free_string(ctx, encoded);
sjson_destroy_context(ctx);
SaveStatistics();
SECRET_Save();
}
//=============================================================================
//
//
//
//=============================================================================
static bool CheckSingleFile (const char *name, bool &printRequires, bool printwarn)
{
if (name == NULL)
{
return true;
}
if (fileSystem.CheckIfResourceFileLoaded(name) < 0)
{
if (printwarn)
{
if (!printRequires)
{
Printf ("%s:\n%s", GStrings("TXT_SAVEGAMENEEDS"), name);
}
else
{
Printf (", %s", name);
}
}
printRequires = true;
return false;
}
return true;
}
//=============================================================================
//
// Return false if not all the needed wads have been loaded.
//
//=============================================================================
bool G_CheckSaveGameWads (sjson_node* root, bool printwarn)
{
bool printRequires = false;
auto text = sjson_get_string(root, "Game Resource", "");
CheckSingleFile (text, printRequires, printwarn);
text = sjson_get_string(root, "MAP Resource", "");
CheckSingleFile (text, printRequires, printwarn);
if (printRequires)
{
if (printwarn)
{
Printf ("\n");
}
return false;
}
return true;
}
//=============================================================================
//
// Checks if the savegame is valid. Gets a reader to the included info.json
// Returns 1 if valid, 0 if invalid and -1 if old and -2 if content missing
//
//=============================================================================
int G_ValidateSavegame(FileReader &fr, FString *savetitle)
{
auto data = fr.ReadPadded(1);
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (ctx)
{
sjson_node* root = sjson_decode(ctx, (const char*)data.Data());
int savever = sjson_get_int(root, "Save Version", -1);
FString engine = sjson_get_string(root, "Engine", "");
FString gamegrp = sjson_get_string(root, "Game Resource", "");
FString title = sjson_get_string(root, "Title", "");
auto savesig = gi->GetSaveSig();
sjson_destroy_context(ctx);
if (savetitle) *savetitle = title;
if (engine.Compare(savesig.savesig) != 0 || savever > savesig.currentsavever)
{
// different engine or newer version:
// not our business. Leave it alone.
return 0;
}
if (savever < savesig.minsavever)
{
// old, incompatible savegame. List as not usable.
return -1;
}
else if (gamegrp.CompareNoCase(fileSystem.GetResourceFileName(1)) == 0)
{
return G_CheckSaveGameWads(root, false)? 0 : -2;
}
else
{
// different game. Skip this.
return 0;
}
}
}
//=============================================================================
//
//
//
//=============================================================================
FString G_BuildSaveName (const char *prefix)
{
FString name = M_GetSavegamesPath();
size_t len = name.Len();
if (name[0] != '\0' && name[len-1] != '\\' && name[len-1] != '/')
{
name << "/";
}
name << prefix;
if (!strchr(prefix, '.')) name << SAVEGAME_EXT; // only add an extension if the prefix doesn't have one already.
name = NicePath(name);
name.Substitute("\\", "/");
CreatePath(name);
return name;
}

View file

@ -10,3 +10,13 @@ FileReader ReadSavegameChunk(const char *name);
bool FinishSavegameWrite(); bool FinishSavegameWrite();
void FinishSavegameRead(); void FinishSavegameRead();
// Savegame utilities
class FileReader;
FString G_BuildSaveName (const char *prefix);
bool G_CheckSaveGameWads (struct sjson_node* root, bool printwarn);
int G_ValidateSavegame(FileReader &fr, FString *savetitle);
void G_WriteSaveHeader(const char *name, const char*mapname, const char *title);
#define SAVEGAME_EXT ".dsave"

View file

@ -41,11 +41,11 @@ const char *GetVersionString();
/** Lots of different version numbers **/ /** Lots of different version numbers **/
#define VERSIONSTR "0.0.1" #define VERSIONSTR "0.1.0"
// The version as seen in the Windows resource // The version as seen in the Windows resource
#define RC_FILEVERSION 0,0,1,0 #define RC_FILEVERSION 0,1,0,0
#define RC_PRODUCTVERSION 0,0,1,0 #define RC_PRODUCTVERSION 0,1,0,0
#define RC_PRODUCTVERSION2 VERSIONSTR #define RC_PRODUCTVERSION2 VERSIONSTR
// These are for content versioning. // These are for content versioning.
#define VER_MAJOR 0 #define VER_MAJOR 0
@ -59,6 +59,21 @@ const char *GetVersionString();
#define FORUM_URL "http://forum.zdoom.org/" #define FORUM_URL "http://forum.zdoom.org/"
//#define BUGS_FORUM_URL "http://forum.zdoom.org/viewforum.php?f=2" //#define BUGS_FORUM_URL "http://forum.zdoom.org/viewforum.php?f=2"
#define SAVESIG_DN3D "Demolition.Duke"
#define SAVESIG_BLD "Demolition.Blood"
#define SAVESIG_RR "Demolition.Redneck"
#define SAVESIG_SW "Demolition.SW"
#define MINSAVEVER_DN3D 1
#define MINSAVEVER_BLD 1
#define MINSAVEVER_RR 1
#define MINSAVEVER_SW 1
#define SAVEVER_DN3D 1
#define SAVEVER_BLD 1
#define SAVEVER_RR 1
#define SAVEVER_SW 1
#if defined(__APPLE__) || defined(_WIN32) #if defined(__APPLE__) || defined(_WIN32)
#define GAME_DIR GAMENAME #define GAME_DIR GAMENAME
#else #else

View file

@ -36,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "c_bind.h" #include "c_bind.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "gstrings.h" #include "gstrings.h"
#include "version.h"
#include "../../glbackend/glbackend.h" #include "../../glbackend/glbackend.h"
BEGIN_DUKE_NS BEGIN_DUKE_NS
@ -465,6 +466,12 @@ void GameInterface::StartGame(FGameStartup& gs)
} }
FSavegameInfo GameInterface::GetSaveSig()
{
return { SAVESIG_DN3D, MINSAVEVER_DN3D, SAVEVER_DN3D };
}
END_DUKE_NS END_DUKE_NS
static TMenuClassDescriptor<Duke::MainMenu> _mm("Duke.MainMenu"); static TMenuClassDescriptor<Duke::MainMenu> _mm("Duke.MainMenu");

View file

@ -163,6 +163,7 @@ struct GameInterface : ::GameInterface
bool CanSave() override; bool CanSave() override;
void CustomMenuSelection(int menu, int item) override; void CustomMenuSelection(int menu, int item) override;
void StartGame(FGameStartup& gs) override; void StartGame(FGameStartup& gs) override;
FSavegameInfo GetSaveSig() override;
}; };

View file

@ -5497,7 +5497,7 @@ static void G_FreeHashAnim(const char * /*string*/, intptr_t key)
static void G_Cleanup(void) static void G_Cleanup(void)
{ {
ReadSaveGameHeaders(); // for culling //ReadSaveGameHeaders(); // for culling
int32_t i; int32_t i;
@ -6189,8 +6189,6 @@ int GameInterface::app_main()
Menu_Init(); Menu_Init();
} }
ReadSaveGameHeaders();
FX_StopAllSounds(); FX_StopAllSounds();
S_ClearSoundLocks(); S_ClearSoundLocks();

View file

@ -2543,31 +2543,6 @@ static void Menu_PreDraw(MenuID_t cm, MenuEntry_t *entry, const vec2_t origin)
} }
static void Menu_ReadSaveGameHeaders();
static void Menu_LoadReadHeaders()
{
Menu_ReadSaveGameHeaders();
for (int i = 0; i < g_nummenusaves; ++i)
{
menusave_t const & msv = g_menusaves[i];
// MenuEntry_LookDisabledOnCondition(&ME_LOAD[i], msv.isOldVer && msv.brief.isExt);
MenuEntry_DisableOnCondition(&ME_LOAD[i], msv.isOldVer && !msv.brief.isExt);
}
}
static void Menu_SaveReadHeaders()
{
Menu_ReadSaveGameHeaders();
for (int i = 0; i < g_nummenusaves; ++i)
{
menusave_t const & msv = g_menusaves[i];
MenuEntry_LookDisabledOnCondition(&ME_SAVE[i], msv.isOldVer && !msv.brief.isExt);
}
}
static void Menu_PreInput(MenuEntry_t *entry) static void Menu_PreInput(MenuEntry_t *entry)
{ {
switch (g_currentMenu) switch (g_currentMenu)
@ -2862,7 +2837,6 @@ static void Menu_EntryLinkActivate(MenuEntry_t *entry)
} }
else if (entry == &ME_SAVESETUP_CLEANUP) else if (entry == &ME_SAVESETUP_CLEANUP)
{ {
g_oldSaveCnt = G_CountOldSaves();
Menu_Change(MENU_SAVECLEANVERIFY); Menu_Change(MENU_SAVECLEANVERIFY);
} }
else if (entry == &ME_NETHOST_LAUNCH) else if (entry == &ME_NETHOST_LAUNCH)
@ -3247,7 +3221,6 @@ static void Menu_Verify(int32_t input)
case MENU_LOADDELVERIFY: case MENU_LOADDELVERIFY:
if (input) if (input)
{ {
G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief);
Menu_LoadReadHeaders(); Menu_LoadReadHeaders();
M_LOAD.currentEntry = clamp(M_LOAD.currentEntry, 0, (int32_t)g_nummenusaves-1); M_LOAD.currentEntry = clamp(M_LOAD.currentEntry, 0, (int32_t)g_nummenusaves-1);
} }
@ -3255,7 +3228,6 @@ static void Menu_Verify(int32_t input)
case MENU_SAVEDELVERIFY: case MENU_SAVEDELVERIFY:
if (input) if (input)
{ {
G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief);
Menu_SaveReadHeaders(); Menu_SaveReadHeaders();
M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves); M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves);
} }
@ -3506,42 +3478,6 @@ static void Menu_FileSelect(int32_t input)
} }
static void Menu_ReadSaveGameHeaders()
{
ReadSaveGameHeaders();
int const numloaditems = max<int>(g_nummenusaves, 1), numsaveitems = g_nummenusaves+1;
ME_LOAD = (MenuEntry_t *)Xrealloc(ME_LOAD, g_nummenusaves * sizeof(MenuEntry_t));
MEL_LOAD = (MenuEntry_t **)Xrealloc(MEL_LOAD, numloaditems * sizeof(MenuEntry_t *));
MEO_SAVE = (MenuString_t *)Xrealloc(MEO_SAVE, g_nummenusaves * sizeof(MenuString_t));
ME_SAVE = (MenuEntry_t *)Xrealloc(ME_SAVE, g_nummenusaves * sizeof(MenuEntry_t));
MEL_SAVE = (MenuEntry_t **)Xrealloc(MEL_SAVE, numsaveitems * sizeof(MenuEntry_t *));
MEL_SAVE[0] = &ME_SAVE_NEW;
ME_SAVE_NEW.name = s_NewSaveGame;
for (int i = 0; i < g_nummenusaves; ++i)
{
MEL_LOAD[i] = &ME_LOAD[i];
MEL_SAVE[i+1] = &ME_SAVE[i];
ME_LOAD[i] = ME_LOAD_TEMPLATE;
ME_SAVE[i] = ME_SAVE_TEMPLATE;
ME_SAVE[i].entry = &MEO_SAVE[i];
MEO_SAVE[i] = MEO_SAVE_TEMPLATE;
ME_LOAD[i].name = g_menusaves[i].brief.name;
MEO_SAVE[i].variable = g_menusaves[i].brief.name;
}
if (g_nummenusaves == 0)
MEL_LOAD[0] = &ME_LOAD_EMPTY;
M_LOAD.entrylist = MEL_LOAD;
M_LOAD.numEntries = numloaditems;
M_SAVE.entrylist = MEL_SAVE;
M_SAVE.numEntries = numsaveitems;
// lexicographical sorting?
}
static void Menu_AboutToStartDisplaying(Menu_t * m) static void Menu_AboutToStartDisplaying(Menu_t * m)
{ {

View file

@ -913,12 +913,6 @@ static int osdcmd_kickban(osdcmdptr_t parm)
} }
#endif #endif
static int osdcmd_purgesaves(osdcmdptr_t UNUSED(parm))
{
UNREFERENCED_CONST_PARAMETER(parm);
G_DeleteOldSaves();
return OSDCMD_OK;
}
static int osdcmd_printtimes(osdcmdptr_t UNUSED(parm)) static int osdcmd_printtimes(osdcmdptr_t UNUSED(parm))
{ {
@ -1025,8 +1019,6 @@ int32_t registerosdcommands(void)
OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes); OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes);
OSD_RegisterFunction("purgesaves", "purgesaves: deletes obsolete and unreadable save files", osdcmd_purgesaves);
OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave); OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave);
OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload); OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload);

View file

@ -30,8 +30,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "i_specialpaths.h" #include "i_specialpaths.h"
#include "gamecontrol.h" #include "gamecontrol.h"
#include "version.h" #include "version.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h" #include "savegamehelp.h"
#include "menu/menu.h" #include "menu/menu.h"
@ -147,11 +145,6 @@ int32_t g_lastAutoSaveArbitraryID = -1;
bool g_saveRequested; bool g_saveRequested;
savebrief_t * g_quickload; savebrief_t * g_quickload;
menusave_t * g_menusaves;
uint16_t g_nummenusaves;
static menusave_t * g_internalsaves;
static uint16_t g_numinternalsaves;
static FileReader *OpenSavegame(const char *fn) static FileReader *OpenSavegame(const char *fn)
{ {
@ -159,12 +152,17 @@ static FileReader *OpenSavegame(const char *fn)
{ {
return nullptr; return nullptr;
} }
auto file = ReadSavegameChunk("DEMOLITION_ED"); auto file = ReadSavegameChunk("info.json");
if (!file.isOpen()) if (!file.isOpen())
{ {
FinishSavegameRead(); FinishSavegameRead();
return nullptr; return nullptr;
} }
if (G_ValidateSavegame(file, nullptr) <= 0)
{
FinishSavegameRead();
return nullptr;
}
file = ReadSavegameChunk("snapshot.dat"); file = ReadSavegameChunk("snapshot.dat");
if (!file.isOpen()) if (!file.isOpen())
{ {
@ -174,145 +172,6 @@ static FileReader *OpenSavegame(const char *fn)
return new FileReader(std::move(file)); return new FileReader(std::move(file));
} }
static void ReadSaveGameHeaders_CACHE1D(TArray<FString> &saves)
{
savehead_t h;
for (FString &save : saves)
{
auto fil = OpenSavegame(save);
if (!fil)
continue;
menusave_t & msv = g_internalsaves[g_numinternalsaves];
msv.brief.isExt = 0;
int32_t k = sv_loadheader(*fil, 0, &h);
delete fil;
if (k)
{
if (k < 0)
msv.isUnreadable = 1;
else
{
if (FURY)
{
auto extfil = ReadSavegameChunk("ext.json");
if (extfil.isOpen())
{
msv.brief.isExt = 1;
}
}
}
msv.isOldVer = 1;
}
else
msv.isOldVer = 0;
msv.isAutoSave = h.isAutoSave();
strncpy(msv.brief.path, save.GetChars(), ARRAY_SIZE(msv.brief.path));
++g_numinternalsaves;
if (k >= 0 && h.savename[0] != '\0')
{
memcpy(msv.brief.name, h.savename, ARRAY_SIZE(msv.brief.name));
}
else
msv.isUnreadable = 1;
}
FinishSavegameRead();
}
static void ReadSaveGameHeaders_Internal(void)
{
FString pattern = M_GetSavegamesPath() + "*.bsv";
TArray<FString> saves;
D_AddWildFile(saves, pattern);
// potentially overallocating but programmatically simple
int const numfiles = saves.Size();
size_t const internalsavesize = sizeof(menusave_t) * numfiles;
g_internalsaves = (menusave_t *)Xrealloc(g_internalsaves, internalsavesize);
for (int x = 0; x < numfiles; ++x)
g_internalsaves[x].clear();
g_numinternalsaves = 0;
ReadSaveGameHeaders_CACHE1D(saves);
g_nummenusaves = 0;
for (int x = g_numinternalsaves-1; x >= 0; --x)
{
menusave_t & msv = g_internalsaves[x];
if (!msv.isUnreadable)
{
++g_nummenusaves;
}
}
size_t const menusavesize = sizeof(menusave_t) * g_nummenusaves;
g_menusaves = (menusave_t *)Xrealloc(g_menusaves, menusavesize);
for (int x = 0; x < g_nummenusaves; ++x)
g_menusaves[x].clear();
for (int x = g_numinternalsaves-1, y = 0; x >= 0; --x)
{
menusave_t & msv = g_internalsaves[x];
if (!msv.isUnreadable)
{
g_menusaves[y++] = msv;
}
}
for (int x = g_numinternalsaves-1; x >= 0; --x)
{
char const * const path = g_internalsaves[x].brief.path;
int const pathlen = Bstrlen(path);
if (pathlen < 12)
continue;
char const * const fn = path + (pathlen-12);
if (fn[0] == 's' && fn[1] == 'a' && fn[2] == 'v' && fn[3] == 'e' &&
isdigit(fn[4]) && isdigit(fn[5]) && isdigit(fn[6]) && isdigit(fn[7]))
{
char number[5];
memcpy(number, fn+4, 4);
number[4] = '\0';
savecounter.count = Batoi(number)+1;
break;
}
}
}
void ReadSaveGameHeaders(void)
{
ReadSaveGameHeaders_Internal();
if (!cl_autosavedeletion)
return;
bool didDelete = false;
int numautosaves = 0;
for (int x = 0; x < g_nummenusaves; ++x)
{
menusave_t & msv = g_menusaves[x];
if (!msv.isAutoSave)
continue;
if (numautosaves >= cl_maxautosaves)
{
G_DeleteSave(msv.brief);
didDelete = true;
}
++numautosaves;
}
if (didDelete)
ReadSaveGameHeaders_Internal();
}
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh) int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
{ {
FileReader ssfil; FileReader ssfil;
@ -681,7 +540,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
if (status == 2) if (status == 2)
G_NewGame_EnterLevel(); G_NewGame_EnterLevel();
else if ((status = sv_loadsnapshot(*fil, 0, &h)) || !ReadStatistics() || !SECRET_Load()) // read the rest... else if ((status = sv_loadsnapshot(*fil, 0, &h))) // read the rest...
{ {
// in theory, we could load into an initial dump first and trivially // in theory, we could load into an initial dump first and trivially
// recover if things go wrong... // recover if things go wrong...
@ -722,49 +581,6 @@ static void G_RestoreTimers(void)
////////// //////////
void G_DeleteSave(savebrief_t const & sv)
{
if (!sv.isValid())
return;
char temp[BMAX_PATH];
if (snprintf(temp, sizeof(temp), "%s%s", M_GetSavegamesPath().GetChars(), sv.path))
{
OSD_Printf("G_SavePlayer: file name \"%s\" too long\n", sv.path);
return;
}
remove(temp);
}
void G_DeleteOldSaves(void)
{
ReadSaveGameHeaders();
for (int x = 0; x < g_numinternalsaves; ++x)
{
menusave_t const & msv = g_internalsaves[x];
if (msv.isOldVer || msv.isUnreadable)
G_DeleteSave(msv.brief);
}
}
uint16_t G_CountOldSaves(void)
{
ReadSaveGameHeaders();
int bad = 0;
for (int x = 0; x < g_numinternalsaves; ++x)
{
menusave_t const & msv = g_internalsaves[x];
if (msv.isOldVer || msv.isUnreadable)
++bad;
}
return bad;
}
int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave) int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
{ {
#ifdef __ANDROID__ #ifdef __ANDROID__
@ -783,29 +599,28 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
if (sv.isValid()) if (sv.isValid())
{ {
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path); fn = G_BuildSaveName(sv.path);
OpenSaveGameForWrite(fn); OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat"); fil = WriteSavegameChunk("snapshot.dat");
} }
else else
{ {
static char const SaveName[] = "save0000.bsv"; fn = G_BuildSaveName("save0000");
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), SaveName);
auto fnp = fn.LockBuffer(); auto fnp = fn.LockBuffer();
char* zeros = fnp + (fn.Len() - 8); char* zeros = strstr(fnp, "0000");
fil = savecounter.opennextfile(fnp, zeros); fil = savecounter.opennextfile(fnp, zeros); // fixme: Rewrite this so that it won't create the file.
fn.UnlockBuffer();
if (fil) if (fil)
{ {
delete fil; delete fil;
remove(fnp); remove(fn);
OpenSaveGameForWrite(fnp); OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat"); fil = WriteSavegameChunk("snapshot.dat");
} }
fn.UnlockBuffer();
savecounter.count++; savecounter.count++;
// don't copy the mod dir into sv.path // don't copy the mod dir into sv.path (G_BuildSaveName guarantees the presence of a slash.)
Bstrcpy(sv.path, fn + (fn.Len() - (ARRAY_SIZE(SaveName) - 1))); Bstrcpy(sv.path, strrchr(fn, '/') + 1);
} }
if (!fil) if (!fil)
@ -821,7 +636,6 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
} }
else else
{ {
WriteSavegameChunk("DEMOLITION_ED");
auto& fw = *fil; auto& fw = *fil;
sv.isExt = 0; sv.isExt = 0;
@ -835,8 +649,7 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
// SAVE! // SAVE!
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave); sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
SaveStatistics();
SECRET_Save();
fw.Close(); fw.Close();
FinishSavegameWrite(); FinishSavegameWrite();
@ -1712,6 +1525,8 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
Bstrncpyz(h.savename, name, sizeof(h.savename)); Bstrncpyz(h.savename, name, sizeof(h.savename));
auto fw = WriteSavegameChunk("header.dat"); auto fw = WriteSavegameChunk("header.dat");
fw->Write(&h, sizeof(savehead_t)); fw->Write(&h, sizeof(savehead_t));
G_WriteSaveHeader(name, currentboardfilename, g_mapInfo[(MAXLEVELS * ud.volume_number) + ud.level_number].name);
} }
else else
{ {

View file

@ -114,8 +114,6 @@ extern int32_t g_lastAutoSaveArbitraryID;
extern bool g_saveRequested; extern bool g_saveRequested;
extern savebrief_t * g_quickload; extern savebrief_t * g_quickload;
extern menusave_t * g_menusaves;
extern uint16_t g_nummenusaves;
int32_t sv_updatestate(int32_t frominit); int32_t sv_updatestate(int32_t frominit);
int32_t sv_readdiff(FileReader& fil); int32_t sv_readdiff(FileReader& fil);
@ -124,9 +122,6 @@ int32_t sv_loadheader(FileReader &fil, int32_t spot, savehead_t *h);
int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h); int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h);
int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, int8_t recdiffsp, int8_t diffcompress, int8_t synccompress, bool isAutoSave = false); int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, int8_t recdiffsp, int8_t diffcompress, int8_t synccompress, bool isAutoSave = false);
void sv_freemem(); void sv_freemem();
void G_DeleteSave(savebrief_t const & sv);
void G_DeleteOldSaves(void);
uint16_t G_CountOldSaves(void);
int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave); int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave);
int32_t G_LoadPlayer(savebrief_t & sv); int32_t G_LoadPlayer(savebrief_t & sv);
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh); int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh);

View file

@ -158,6 +158,7 @@ struct GameInterface : ::GameInterface
bool mouseInactiveConditional(bool condition) override; bool mouseInactiveConditional(bool condition) override;
FString statFPS() override; FString statFPS() override;
GameStats getStats() override; GameStats getStats() override;
FSavegameInfo GetSaveSig() override;
}; };
END_RR_NS END_RR_NS

View file

@ -31,6 +31,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "cheats.h" #include "cheats.h"
#include "gamecvars.h" #include "gamecvars.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "version.h"
#include "../../glbackend/glbackend.h" #include "../../glbackend/glbackend.h"
BEGIN_RR_NS BEGIN_RR_NS
@ -3446,7 +3447,6 @@ static void Menu_EntryLinkActivate(MenuEntry_t *entry)
} }
else if (entry == &ME_SAVESETUP_CLEANUP) else if (entry == &ME_SAVESETUP_CLEANUP)
{ {
g_oldSaveCnt = G_CountOldSaves();
Menu_Change(MENU_SAVECLEANVERIFY); Menu_Change(MENU_SAVECLEANVERIFY);
} }
else if (entry == &ME_COLCORR_RESET) else if (entry == &ME_COLCORR_RESET)
@ -3768,10 +3768,6 @@ static void Menu_Verify(int32_t input)
switch (g_currentMenu) switch (g_currentMenu)
{ {
case MENU_SAVECLEANVERIFY: case MENU_SAVECLEANVERIFY:
if (input)
{
G_DeleteOldSaves();
}
break; break;
case MENU_RESETPLAYER: case MENU_RESETPLAYER:
@ -3839,7 +3835,7 @@ static void Menu_Verify(int32_t input)
case MENU_LOADDELVERIFY: case MENU_LOADDELVERIFY:
if (input) if (input)
{ {
G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief); //G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief);
Menu_LoadReadHeaders(); Menu_LoadReadHeaders();
M_LOAD.currentEntry = clamp(M_LOAD.currentEntry, 0, (int32_t)g_nummenusaves-1); M_LOAD.currentEntry = clamp(M_LOAD.currentEntry, 0, (int32_t)g_nummenusaves-1);
} }
@ -3847,7 +3843,7 @@ static void Menu_Verify(int32_t input)
case MENU_SAVEDELVERIFY: case MENU_SAVEDELVERIFY:
if (input) if (input)
{ {
G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief); //G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief);
Menu_SaveReadHeaders(); Menu_SaveReadHeaders();
M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves); M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves);
} }
@ -7462,4 +7458,9 @@ bool GameInterface::mouseInactiveConditional(bool condition)
return MOUSEINACTIVECONDITIONAL(condition); return MOUSEINACTIVECONDITIONAL(condition);
} }
FSavegameInfo GameInterface::GetSaveSig()
{
return { SAVESIG_RR, MINSAVEVER_RR, SAVEVER_RR };
}
END_RR_NS END_RR_NS

View file

@ -791,13 +791,6 @@ static int osdcmd_kickban(osdcmdptr_t parm)
#endif #endif
#endif #endif
static int osdcmd_purgesaves(osdcmdptr_t UNUSED(parm))
{
UNREFERENCED_CONST_PARAMETER(parm);
G_DeleteOldSaves();
return OSDCMD_OK;
}
static int osdcmd_printtimes(osdcmdptr_t UNUSED(parm)) static int osdcmd_printtimes(osdcmdptr_t UNUSED(parm))
{ {
UNREFERENCED_CONST_PARAMETER(parm); UNREFERENCED_CONST_PARAMETER(parm);
@ -881,8 +874,6 @@ int32_t registerosdcommands(void)
OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes); OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes);
OSD_RegisterFunction("purgesaves", "purgesaves: deletes obsolete and unreadable save files", osdcmd_purgesaves);
OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave); OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave);
OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload); OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload);

View file

@ -28,8 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "i_specialpaths.h" #include "i_specialpaths.h"
#include "gamecontrol.h" #include "gamecontrol.h"
#include "version.h" #include "version.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h" #include "savegamehelp.h"
BEGIN_RR_NS BEGIN_RR_NS
@ -155,12 +154,17 @@ static FileReader *OpenSavegame(const char *fn)
{ {
return nullptr; return nullptr;
} }
auto file = ReadSavegameChunk("DEMOLITION_RN"); auto file = ReadSavegameChunk("info.json");
if (!file.isOpen()) if (!file.isOpen())
{ {
FinishSavegameRead(); FinishSavegameRead();
return nullptr; return nullptr;
} }
if (G_ValidateSavegame(file, nullptr) <= 0)
{
FinishSavegameRead();
return nullptr;
}
file = ReadSavegameChunk("snapshot.dat"); file = ReadSavegameChunk("snapshot.dat");
if (!file.isOpen()) if (!file.isOpen())
{ {
@ -170,130 +174,9 @@ static FileReader *OpenSavegame(const char *fn)
return new FileReader(std::move(file)); return new FileReader(std::move(file));
} }
static void ReadSaveGameHeaders_CACHE1D(TArray<FString>& saves)
{
savehead_t h;
for (FString &save : saves)
{
auto fil = OpenSavegame(save);
if (!fil)
continue;
menusave_t & msv = g_internalsaves[g_numinternalsaves];
int32_t k = sv_loadheader(*fil, 0, &h);
delete fil;
if (k)
{
if (k < 0)
msv.isUnreadable = 1;
msv.isOldVer = 1;
}
else
msv.isOldVer = 0;
msv.isAutoSave = h.isAutoSave();
strncpy(msv.brief.path, save.GetChars(), ARRAY_SIZE(msv.brief.path));
++g_numinternalsaves;
if (k >= 0 && h.savename[0] != '\0')
{
memcpy(msv.brief.name, h.savename, ARRAY_SIZE(msv.brief.name));
}
else
msv.isUnreadable = 1;
}
FinishSavegameRead();
}
static void ReadSaveGameHeaders_Internal(void)
{
FString pattern = M_GetSavegamesPath() + "*.bsv";
TArray<FString> saves;
D_AddWildFile(saves, pattern);
// potentially overallocating but programmatically simple
int const numfiles = saves.Size();
size_t const internalsavesize = sizeof(menusave_t) * numfiles;
g_internalsaves = (menusave_t *)Xrealloc(g_internalsaves, internalsavesize);
for (int x = 0; x < numfiles; ++x)
g_internalsaves[x].clear();
g_numinternalsaves = 0;
ReadSaveGameHeaders_CACHE1D(saves);
g_nummenusaves = 0;
for (int x = g_numinternalsaves-1; x >= 0; --x)
{
menusave_t & msv = g_internalsaves[x];
if (!msv.isUnreadable)
{
++g_nummenusaves;
}
}
size_t const menusavesize = sizeof(menusave_t) * g_nummenusaves;
g_menusaves = (menusave_t *)Xrealloc(g_menusaves, menusavesize);
for (int x = 0; x < g_nummenusaves; ++x)
g_menusaves[x].clear();
for (int x = g_numinternalsaves-1, y = 0; x >= 0; --x)
{
menusave_t & msv = g_internalsaves[x];
if (!msv.isUnreadable)
{
g_menusaves[y++] = msv;
}
}
for (int x = g_numinternalsaves-1; x >= 0; --x)
{
char const * const path = g_internalsaves[x].brief.path;
int const pathlen = Bstrlen(path);
if (pathlen < 12)
continue;
char const * const fn = path + (pathlen-12);
if (fn[0] == 's' && fn[1] == 'a' && fn[2] == 'v' && fn[3] == 'e' &&
isdigit(fn[4]) && isdigit(fn[5]) && isdigit(fn[6]) && isdigit(fn[7]))
{
char number[5];
memcpy(number, fn+4, 4);
number[4] = '\0';
savecounter.count = Batoi(number)+1;
break;
}
}
}
void ReadSaveGameHeaders(void) void ReadSaveGameHeaders(void)
{ {
ReadSaveGameHeaders_Internal();
if (!cl_autosavedeletion)
return;
bool didDelete = false;
int numautosaves = 0;
for (int x = 0; x < g_nummenusaves; ++x)
{
menusave_t & msv = g_menusaves[x];
if (!msv.isAutoSave)
continue;
if (numautosaves >= cl_maxautosaves)
{
G_DeleteSave(msv.brief);
didDelete = true;
}
++numautosaves;
}
if (didDelete)
ReadSaveGameHeaders_Internal();
} }
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh) int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
@ -415,7 +298,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
if (status == 2) if (status == 2)
G_NewGame_EnterLevel(); G_NewGame_EnterLevel();
else if ((status = sv_loadsnapshot(*fil, 0, &h)) || !ReadStatistics() || !SECRET_Load()) // read the rest... else if ((status = sv_loadsnapshot(*fil, 0, &h))) // read the rest...
{ {
// in theory, we could load into an initial dump first and trivially // in theory, we could load into an initial dump first and trivially
// recover if things go wrong... // recover if things go wrong...
@ -455,52 +338,6 @@ static void G_RestoreTimers(void)
lockclock = g_timers.lockclock; lockclock = g_timers.lockclock;
} }
//////////
void G_DeleteSave(savebrief_t const & sv)
{
if (!sv.isValid())
return;
char temp[BMAX_PATH];
if (snprintf(temp, sizeof(temp), "%s%s", M_GetSavegamesPath().GetChars(), sv.path))
{
OSD_Printf("G_SavePlayer: file name \"%s\" too long\n", sv.path);
return;
}
remove(temp);
}
void G_DeleteOldSaves(void)
{
ReadSaveGameHeaders();
for (int x = 0; x < g_numinternalsaves; ++x)
{
menusave_t const & msv = g_internalsaves[x];
if (msv.isOldVer || msv.isUnreadable)
G_DeleteSave(msv.brief);
}
}
uint16_t G_CountOldSaves(void)
{
ReadSaveGameHeaders();
int bad = 0;
for (int x = 0; x < g_numinternalsaves; ++x)
{
menusave_t const & msv = g_internalsaves[x];
if (msv.isOldVer || msv.isUnreadable)
++bad;
}
return bad;
}
int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave) int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
{ {
#ifdef __ANDROID__ #ifdef __ANDROID__
@ -519,29 +356,28 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
if (sv.isValid()) if (sv.isValid())
{ {
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path); fn = G_BuildSaveName(sv.path);
OpenSaveGameForWrite(fn); OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat"); fil = WriteSavegameChunk("snapshot.dat");
} }
else else
{ {
static char const SaveName[] = "save0000.bsv"; fn = G_BuildSaveName("save0000");
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), SaveName);
auto fnp = fn.LockBuffer(); auto fnp = fn.LockBuffer();
char* zeros = fnp + (fn.Len() - 8); char* zeros = strstr(fnp, "0000");
fil = savecounter.opennextfile(fnp, zeros); fil = savecounter.opennextfile(fnp, zeros); // fixme: Rewrite this so that it won't create the file.
fn.UnlockBuffer();
if (fil) if (fil)
{ {
delete fil; delete fil;
remove(fnp); remove(fn);
OpenSaveGameForWrite(fnp); OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat"); fil = WriteSavegameChunk("snapshot.dat");
} }
fn.UnlockBuffer();
savecounter.count++; savecounter.count++;
// don't copy the mod dir into sv.path // don't copy the mod dir into sv.path (G_BuildSaveName guarantees the presence of a slash.)
Bstrcpy(sv.path, fn + (fn.Len() - (ARRAY_SIZE(SaveName) - 1))); Bstrcpy(sv.path, strrchr(fn, '/') + 1);
} }
if (!fil) if (!fil)
@ -567,8 +403,7 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
// SAVE! // SAVE!
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave); sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
SaveStatistics();
SECRET_Save();
fw.Close(); fw.Close();
FinishSavegameWrite(); FinishSavegameWrite();
@ -1385,6 +1220,8 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
Bstrncpyz(h.savename, name, sizeof(h.savename)); Bstrncpyz(h.savename, name, sizeof(h.savename));
auto fw = WriteSavegameChunk("header.dat"); auto fw = WriteSavegameChunk("header.dat");
fw->Write(&h, sizeof(savehead_t)); fw->Write(&h, sizeof(savehead_t));
G_WriteSaveHeader(name, currentboardfilename, g_mapInfo[(MAXLEVELS * ud.volume_number) + ud.level_number].name);
} }
else else
{ {

View file

@ -117,9 +117,6 @@ int32_t sv_loadheader(FileReader &fil, int32_t spot, savehead_t *h);
int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h); int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h);
int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, int8_t recdiffsp, int8_t diffcompress, int8_t synccompress, bool isAutoSave = false); int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, int8_t recdiffsp, int8_t diffcompress, int8_t synccompress, bool isAutoSave = false);
void sv_freemem(); void sv_freemem();
void G_DeleteSave(savebrief_t const & sv);
void G_DeleteOldSaves(void);
uint16_t G_CountOldSaves(void);
int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave); int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave);
int32_t G_LoadPlayer(savebrief_t & sv); int32_t G_LoadPlayer(savebrief_t & sv);
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh); int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh);

View file

@ -2382,6 +2382,7 @@ struct GameInterface : ::GameInterface
void set_hud_layout(int size) override; void set_hud_layout(int size) override;
void set_hud_scale(int size) override; void set_hud_scale(int size) override;
bool mouseInactiveConditional(bool condition) override; bool mouseInactiveConditional(bool condition) override;
FSavegameInfo GetSaveSig() override;
}; };

View file

@ -53,6 +53,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
#include "fx_man.h" #include "fx_man.h"
#include "music.h" #include "music.h"
#include "text.h" #include "text.h"
#include "version.h"
#include "colormap.h" #include "colormap.h"
#include "config.h" #include "config.h"
@ -4737,5 +4738,10 @@ void ResetPalette(PLAYERp pp)
// vim:ts=4:sw=4:enc=utf-8: // vim:ts=4:sw=4:enc=utf-8:
FSavegameInfo GameInterface::GetSaveSig()
{
return { SAVESIG_SW, MINSAVEVER_SW, SAVEVER_SW };
}
END_SW_NS END_SW_NS

View file

@ -246,6 +246,15 @@ int SaveGame(short save_num)
Saveable_Init(); Saveable_Init();
#if 0 // A lot of work is needed here... (Thank God for all the macros around the load/save functions. :) )
FStringf base("save%04d", save_num);
auto game_name = G_BuildSaveName(base);
OpenSaveGameForWrite(game_name);
G_WriteSaveHeader(SaveGameDescr[save_num], LevelInfo[Level].LevelName, LevelInfo[Level].Description);
auto fil = WriteSavegameChunk("snapshot.sw");
#endif
snprintf(game_name, 256, "%sgame%d.sav", M_GetSavegamesPath().GetChars(), save_num); snprintf(game_name, 256, "%sgame%d.sav", M_GetSavegamesPath().GetChars(), save_num);
if ((fil = MOPEN_WRITE(game_name)) == MOPEN_WRITE_ERR) if ((fil = MOPEN_WRITE(game_name)) == MOPEN_WRITE_ERR)
return -1; return -1;