mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-15 08:51:24 +00:00
- 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:
parent
3b7aa74c27
commit
723b210c95
25 changed files with 371 additions and 539 deletions
|
@ -92,6 +92,8 @@ struct GameInterface : ::GameInterface
|
|||
void set_hud_scale(int size) override;
|
||||
bool mouseInactiveConditional(bool condition) override;
|
||||
FString statFPS() override;
|
||||
FSavegameInfo GetSaveSig() override;
|
||||
|
||||
};
|
||||
|
||||
END_BLD_NS
|
||||
|
|
|
@ -46,8 +46,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "sound.h"
|
||||
#include "i_specialpaths.h"
|
||||
#include "view.h"
|
||||
#include "statistics.h"
|
||||
#include "secrets.h"
|
||||
#include "savegamehelp.h"
|
||||
|
||||
BEGIN_BLD_NS
|
||||
|
@ -102,7 +100,7 @@ void LoadSave::Write(void *pData, int nSize)
|
|||
ThrowError("File error #%d writing save file.", errno);
|
||||
}
|
||||
|
||||
void LoadSave::LoadGame(char *pzFile)
|
||||
void LoadSave::LoadGame(const char *pzFile)
|
||||
{
|
||||
bool demoWasPlayed = gDemo.at1;
|
||||
if (gDemo.at1)
|
||||
|
@ -128,8 +126,6 @@ void LoadSave::LoadGame(char *pzFile)
|
|||
rover->Load();
|
||||
rover = rover->next;
|
||||
}
|
||||
if (!ReadStatistics() || !SECRET_Load()) // read the rest...
|
||||
ThrowError("Error loading save file.");
|
||||
|
||||
hLFile.Close();
|
||||
FinishSavegameRead();
|
||||
|
@ -194,7 +190,7 @@ void LoadSave::LoadGame(char *pzFile)
|
|||
//sndPlaySong(gGameOptions.zLevelSong, 1);
|
||||
}
|
||||
|
||||
void LoadSave::SaveGame(char *pzFile)
|
||||
void LoadSave::SaveGame(const char *pzFile)
|
||||
{
|
||||
OpenSaveGameForWrite(pzFile);
|
||||
hSFile = WriteSavegameChunk("snapshot.bld");
|
||||
|
@ -211,8 +207,9 @@ void LoadSave::SaveGame(char *pzFile)
|
|||
dword_27AA38 = 0;
|
||||
rover = rover->next;
|
||||
}
|
||||
SaveStatistics();
|
||||
SECRET_Save();
|
||||
auto & li = gEpisodeInfo[gGameOptions.nEpisode].at28[gGameOptions.nLevel];
|
||||
G_WriteSaveHeader(gGameOptions.szUserGameName, li.at0, li.at90);
|
||||
|
||||
FinishSavegameWrite();
|
||||
hSFile = NULL;
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ public:
|
|||
virtual void Load(void);
|
||||
void Read(void *, int);
|
||||
void Write(void *, int);
|
||||
static void LoadGame(char *);
|
||||
static void SaveGame(char *);
|
||||
static void LoadGame(const char *);
|
||||
static void SaveGame(const char *);
|
||||
};
|
||||
|
||||
extern unsigned int gSavedOffset;
|
||||
|
|
|
@ -43,6 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "view.h"
|
||||
#include "cmdlib.h"
|
||||
#include "i_specialpaths.h"
|
||||
#include "savegamehelp.h"
|
||||
|
||||
EXTERN_CVAR(Bool, hud_powerupduration)
|
||||
|
||||
|
@ -2069,7 +2070,6 @@ short gQuickSaveSlot = -1;
|
|||
|
||||
void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
|
||||
{
|
||||
char strSaveGameName[BMAX_PATH];
|
||||
int nSlot = pItem->at28;
|
||||
if (gGameOptions.nGameType > 0 || !gGameStarted)
|
||||
return;
|
||||
|
@ -2078,21 +2078,21 @@ void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
|
|||
gGameMenuMgr.Deactivate();
|
||||
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]);
|
||||
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName);
|
||||
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName.GetChars());
|
||||
gGameOptions.nSaveGameSlot = nSlot;
|
||||
viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[nSlot]);
|
||||
videoNextPage();
|
||||
gSaveGameNum = nSlot;
|
||||
LoadSave::SaveGame(strSaveGameName);
|
||||
LoadSave::SaveGame(strSaveGameName.GetChars());
|
||||
gQuickSaveSlot = nSlot;
|
||||
gGameMenuMgr.Deactivate();
|
||||
}
|
||||
|
||||
void QuickSaveGame(void)
|
||||
{
|
||||
char strSaveGameName[BMAX_PATH];
|
||||
if (gGameOptions.nGameType > 0 || !gGameStarted)
|
||||
return;
|
||||
/*if (strSaveGameName[0])
|
||||
|
@ -2100,9 +2100,11 @@ void QuickSaveGame(void)
|
|||
gGameMenuMgr.Deactivate();
|
||||
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]);
|
||||
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName);
|
||||
sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName.GetChars());
|
||||
gGameOptions.nSaveGameSlot = gQuickSaveSlot;
|
||||
viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[gQuickSaveSlot]);
|
||||
videoNextPage();
|
||||
|
@ -2116,11 +2118,11 @@ void QuickSaveGame(void)
|
|||
void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(event);
|
||||
char strLoadGameName[BMAX_PATH];
|
||||
int nSlot = pItem->at28;
|
||||
if (gGameOptions.nGameType > 0)
|
||||
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))
|
||||
return;
|
||||
viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[nSlot]);
|
||||
|
@ -2132,10 +2134,11 @@ void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
|
|||
|
||||
void QuickLoadGame(void)
|
||||
{
|
||||
char strLoadGameName[BMAX_PATH];
|
||||
|
||||
if (gGameOptions.nGameType > 0)
|
||||
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))
|
||||
return;
|
||||
viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[gQuickLoadSlot]);
|
||||
|
@ -2270,4 +2273,9 @@ void drawLoadingScreen(void)
|
|||
viewLoadingScreen(2049, buffer, levelGetTitle(), NULL);
|
||||
}
|
||||
|
||||
FSavegameInfo GameInterface::GetSaveSig()
|
||||
{
|
||||
return { SAVESIG_BLD, MINSAVEVER_BLD, SAVEVER_BLD };
|
||||
}
|
||||
|
||||
END_BLD_NS
|
||||
|
|
|
@ -177,6 +177,13 @@ struct FGameStartup
|
|||
int CustomLevel2;
|
||||
};
|
||||
|
||||
struct FSavegameInfo
|
||||
{
|
||||
const char *savesig;
|
||||
int minsavever;
|
||||
int currentsavever;
|
||||
};
|
||||
|
||||
struct GameInterface
|
||||
{
|
||||
enum EMenuSounds
|
||||
|
@ -202,6 +209,7 @@ struct GameInterface
|
|||
virtual bool CanSave() { return true; }
|
||||
virtual void CustomMenuSelection(int menu, int item) {}
|
||||
virtual void StartGame(FGameStartup& gs) {}
|
||||
virtual FSavegameInfo GetSaveSig() { return { "", 0, 0}; }
|
||||
};
|
||||
|
||||
extern GameInterface* gi;
|
||||
|
|
|
@ -267,7 +267,7 @@ static void D_AddDirectory (TArray<FString> &wadfiles, const char *dir)
|
|||
{
|
||||
skindir[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)
|
||||
{
|
||||
stuffstart = savedstart;
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
#include "gstrings.h"
|
||||
#include "d_gui.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.
|
||||
|
||||
|
||||
|
@ -207,7 +212,7 @@ void DLoadSaveMenu::ReadSaveStrings ()
|
|||
|
||||
LastSaved = LastAccessed = -1;
|
||||
quickSaveSlot = NULL;
|
||||
filter = "";// G_BuildSaveName("*.zds", -1);
|
||||
filter = G_BuildSaveName("*");
|
||||
filefirst = I_FindFirst (filter.GetChars(), &c_file);
|
||||
if (filefirst != ((void *)(-1)))
|
||||
{
|
||||
|
@ -215,25 +220,30 @@ void DLoadSaveMenu::ReadSaveStrings ()
|
|||
{
|
||||
// I_FindName only returns the file's name and not its full path
|
||||
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;
|
||||
//char sig[16];
|
||||
FResourceLump *info = savegame->FindLump("info.json");
|
||||
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;
|
||||
bool oldVer = true;
|
||||
bool addIt = false;
|
||||
bool missing = false;
|
||||
|
||||
// ZDoom 1.23 betas 21-33 have the savesig first.
|
||||
// Earlier versions have the savesig second.
|
||||
// Later versions have the savegame encapsulated inside a PNG.
|
||||
//
|
||||
// Old savegame versions are always added to the menu so
|
||||
// the user can easily delete them if desired.
|
||||
|
||||
// Todo: Identify savegames here.
|
||||
int check = G_ValidateSavegame(fr, &title);
|
||||
delete savegame;
|
||||
if (check != 0)
|
||||
{
|
||||
FSaveGameNode *node = new FSaveGameNode;
|
||||
node->Filename = filepath;
|
||||
node->bOldVersion = check == -1;
|
||||
node->bMissingWads = check == -2;
|
||||
node->Title = title;
|
||||
InsertSaveNode(node);
|
||||
}
|
||||
}
|
||||
} while (I_FindNext (filefirst, &c_file) == 0);
|
||||
I_FindClose (filefirst);
|
||||
|
@ -691,7 +701,7 @@ bool DLoadSaveMenu::Responder (event_t *ev)
|
|||
{
|
||||
FString EndString;
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
** savegame.cpp
|
||||
**
|
||||
** common savegame utilities for all front ends.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2019 Christoph Oelckers
|
||||
|
@ -29,21 +31,36 @@
|
|||
** 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 "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 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)
|
||||
{
|
||||
savewriter.Clear();
|
||||
|
@ -54,6 +71,13 @@ bool OpenSaveGameForRead(const char *name)
|
|||
{
|
||||
if (savereader) delete savereader;
|
||||
savereader = FResourceFile::OpenResourceFile(name, true, true);
|
||||
|
||||
if (savereader != nullptr)
|
||||
{
|
||||
ReadStatistics();
|
||||
SECRET_Load();
|
||||
}
|
||||
|
||||
return savereader != nullptr;
|
||||
}
|
||||
|
||||
|
@ -80,3 +104,174 @@ void FinishSavegameRead()
|
|||
delete savereader;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,3 +10,13 @@ FileReader ReadSavegameChunk(const char *name);
|
|||
|
||||
bool FinishSavegameWrite();
|
||||
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"
|
||||
|
|
|
@ -41,11 +41,11 @@ const char *GetVersionString();
|
|||
|
||||
/** Lots of different version numbers **/
|
||||
|
||||
#define VERSIONSTR "0.0.1"
|
||||
#define VERSIONSTR "0.1.0"
|
||||
|
||||
// The version as seen in the Windows resource
|
||||
#define RC_FILEVERSION 0,0,1,0
|
||||
#define RC_PRODUCTVERSION 0,0,1,0
|
||||
#define RC_FILEVERSION 0,1,0,0
|
||||
#define RC_PRODUCTVERSION 0,1,0,0
|
||||
#define RC_PRODUCTVERSION2 VERSIONSTR
|
||||
// These are for content versioning.
|
||||
#define VER_MAJOR 0
|
||||
|
@ -59,6 +59,21 @@ const char *GetVersionString();
|
|||
#define FORUM_URL "http://forum.zdoom.org/"
|
||||
//#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)
|
||||
#define GAME_DIR GAMENAME
|
||||
#else
|
||||
|
|
|
@ -36,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "c_bind.h"
|
||||
#include "menu/menu.h"
|
||||
#include "gstrings.h"
|
||||
#include "version.h"
|
||||
#include "../../glbackend/glbackend.h"
|
||||
|
||||
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
|
||||
|
||||
static TMenuClassDescriptor<Duke::MainMenu> _mm("Duke.MainMenu");
|
||||
|
|
|
@ -163,6 +163,7 @@ struct GameInterface : ::GameInterface
|
|||
bool CanSave() override;
|
||||
void CustomMenuSelection(int menu, int item) override;
|
||||
void StartGame(FGameStartup& gs) override;
|
||||
FSavegameInfo GetSaveSig() override;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -5497,7 +5497,7 @@ static void G_FreeHashAnim(const char * /*string*/, intptr_t key)
|
|||
|
||||
static void G_Cleanup(void)
|
||||
{
|
||||
ReadSaveGameHeaders(); // for culling
|
||||
//ReadSaveGameHeaders(); // for culling
|
||||
|
||||
int32_t i;
|
||||
|
||||
|
@ -6189,8 +6189,6 @@ int GameInterface::app_main()
|
|||
Menu_Init();
|
||||
}
|
||||
|
||||
ReadSaveGameHeaders();
|
||||
|
||||
FX_StopAllSounds();
|
||||
S_ClearSoundLocks();
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
switch (g_currentMenu)
|
||||
|
@ -2862,7 +2837,6 @@ static void Menu_EntryLinkActivate(MenuEntry_t *entry)
|
|||
}
|
||||
else if (entry == &ME_SAVESETUP_CLEANUP)
|
||||
{
|
||||
g_oldSaveCnt = G_CountOldSaves();
|
||||
Menu_Change(MENU_SAVECLEANVERIFY);
|
||||
}
|
||||
else if (entry == &ME_NETHOST_LAUNCH)
|
||||
|
@ -3247,15 +3221,13 @@ static void Menu_Verify(int32_t input)
|
|||
case MENU_LOADDELVERIFY:
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case MENU_SAVEDELVERIFY:
|
||||
if (input)
|
||||
{
|
||||
G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief);
|
||||
Menu_SaveReadHeaders();
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -913,12 +913,6 @@ static int osdcmd_kickban(osdcmdptr_t parm)
|
|||
}
|
||||
#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))
|
||||
{
|
||||
|
@ -1025,8 +1019,6 @@ int32_t registerosdcommands(void)
|
|||
|
||||
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("quickload","quickload: performs a quick load", osdcmd_quickload);
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "i_specialpaths.h"
|
||||
#include "gamecontrol.h"
|
||||
#include "version.h"
|
||||
#include "statistics.h"
|
||||
#include "secrets.h"
|
||||
#include "savegamehelp.h"
|
||||
#include "menu/menu.h"
|
||||
|
||||
|
@ -147,11 +145,6 @@ int32_t g_lastAutoSaveArbitraryID = -1;
|
|||
bool g_saveRequested;
|
||||
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)
|
||||
{
|
||||
|
@ -159,12 +152,17 @@ static FileReader *OpenSavegame(const char *fn)
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto file = ReadSavegameChunk("DEMOLITION_ED");
|
||||
auto file = ReadSavegameChunk("info.json");
|
||||
if (!file.isOpen())
|
||||
{
|
||||
FinishSavegameRead();
|
||||
return nullptr;
|
||||
}
|
||||
if (G_ValidateSavegame(file, nullptr) <= 0)
|
||||
{
|
||||
FinishSavegameRead();
|
||||
return nullptr;
|
||||
}
|
||||
file = ReadSavegameChunk("snapshot.dat");
|
||||
if (!file.isOpen())
|
||||
{
|
||||
|
@ -174,145 +172,6 @@ static FileReader *OpenSavegame(const char *fn)
|
|||
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)
|
||||
{
|
||||
FileReader ssfil;
|
||||
|
@ -681,7 +540,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
|
|||
|
||||
if (status == 2)
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
|
@ -783,29 +599,28 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
|
|||
|
||||
if (sv.isValid())
|
||||
{
|
||||
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path);
|
||||
fn = G_BuildSaveName(sv.path);
|
||||
OpenSaveGameForWrite(fn);
|
||||
fil = WriteSavegameChunk("snapshot.dat");
|
||||
}
|
||||
else
|
||||
{
|
||||
static char const SaveName[] = "save0000.bsv";
|
||||
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), SaveName);
|
||||
fn = G_BuildSaveName("save0000");
|
||||
|
||||
auto fnp = fn.LockBuffer();
|
||||
char* zeros = fnp + (fn.Len() - 8);
|
||||
fil = savecounter.opennextfile(fnp, zeros);
|
||||
char* zeros = strstr(fnp, "0000");
|
||||
fil = savecounter.opennextfile(fnp, zeros); // fixme: Rewrite this so that it won't create the file.
|
||||
fn.UnlockBuffer();
|
||||
if (fil)
|
||||
{
|
||||
delete fil;
|
||||
remove(fnp);
|
||||
OpenSaveGameForWrite(fnp);
|
||||
remove(fn);
|
||||
OpenSaveGameForWrite(fn);
|
||||
fil = WriteSavegameChunk("snapshot.dat");
|
||||
}
|
||||
fn.UnlockBuffer();
|
||||
savecounter.count++;
|
||||
// don't copy the mod dir into sv.path
|
||||
Bstrcpy(sv.path, fn + (fn.Len() - (ARRAY_SIZE(SaveName) - 1)));
|
||||
// don't copy the mod dir into sv.path (G_BuildSaveName guarantees the presence of a slash.)
|
||||
Bstrcpy(sv.path, strrchr(fn, '/') + 1);
|
||||
}
|
||||
|
||||
if (!fil)
|
||||
|
@ -821,7 +636,6 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
|
|||
}
|
||||
else
|
||||
{
|
||||
WriteSavegameChunk("DEMOLITION_ED");
|
||||
auto& fw = *fil;
|
||||
|
||||
sv.isExt = 0;
|
||||
|
@ -835,8 +649,7 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
|
|||
|
||||
// SAVE!
|
||||
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
|
||||
SaveStatistics();
|
||||
SECRET_Save();
|
||||
|
||||
|
||||
fw.Close();
|
||||
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));
|
||||
auto fw = WriteSavegameChunk("header.dat");
|
||||
fw->Write(&h, sizeof(savehead_t));
|
||||
|
||||
G_WriteSaveHeader(name, currentboardfilename, g_mapInfo[(MAXLEVELS * ud.volume_number) + ud.level_number].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -114,8 +114,6 @@ extern int32_t g_lastAutoSaveArbitraryID;
|
|||
extern bool g_saveRequested;
|
||||
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_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_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 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_LoadPlayer(savebrief_t & sv);
|
||||
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh);
|
||||
|
|
|
@ -158,6 +158,7 @@ struct GameInterface : ::GameInterface
|
|||
bool mouseInactiveConditional(bool condition) override;
|
||||
FString statFPS() override;
|
||||
GameStats getStats() override;
|
||||
FSavegameInfo GetSaveSig() override;
|
||||
};
|
||||
|
||||
END_RR_NS
|
||||
|
|
|
@ -31,6 +31,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "cheats.h"
|
||||
#include "gamecvars.h"
|
||||
#include "menu/menu.h"
|
||||
#include "version.h"
|
||||
#include "../../glbackend/glbackend.h"
|
||||
|
||||
BEGIN_RR_NS
|
||||
|
@ -3446,7 +3447,6 @@ static void Menu_EntryLinkActivate(MenuEntry_t *entry)
|
|||
}
|
||||
else if (entry == &ME_SAVESETUP_CLEANUP)
|
||||
{
|
||||
g_oldSaveCnt = G_CountOldSaves();
|
||||
Menu_Change(MENU_SAVECLEANVERIFY);
|
||||
}
|
||||
else if (entry == &ME_COLCORR_RESET)
|
||||
|
@ -3768,10 +3768,6 @@ static void Menu_Verify(int32_t input)
|
|||
switch (g_currentMenu)
|
||||
{
|
||||
case MENU_SAVECLEANVERIFY:
|
||||
if (input)
|
||||
{
|
||||
G_DeleteOldSaves();
|
||||
}
|
||||
break;
|
||||
|
||||
case MENU_RESETPLAYER:
|
||||
|
@ -3839,7 +3835,7 @@ static void Menu_Verify(int32_t input)
|
|||
case MENU_LOADDELVERIFY:
|
||||
if (input)
|
||||
{
|
||||
G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief);
|
||||
//G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief);
|
||||
Menu_LoadReadHeaders();
|
||||
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:
|
||||
if (input)
|
||||
{
|
||||
G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief);
|
||||
//G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief);
|
||||
Menu_SaveReadHeaders();
|
||||
M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves);
|
||||
}
|
||||
|
@ -7462,4 +7458,9 @@ bool GameInterface::mouseInactiveConditional(bool condition)
|
|||
return MOUSEINACTIVECONDITIONAL(condition);
|
||||
}
|
||||
|
||||
FSavegameInfo GameInterface::GetSaveSig()
|
||||
{
|
||||
return { SAVESIG_RR, MINSAVEVER_RR, SAVEVER_RR };
|
||||
}
|
||||
|
||||
END_RR_NS
|
||||
|
|
|
@ -791,13 +791,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))
|
||||
{
|
||||
UNREFERENCED_CONST_PARAMETER(parm);
|
||||
|
@ -881,8 +874,6 @@ int32_t registerosdcommands(void)
|
|||
|
||||
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("quickload","quickload: performs a quick load", osdcmd_quickload);
|
||||
|
||||
|
|
|
@ -28,8 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
#include "i_specialpaths.h"
|
||||
#include "gamecontrol.h"
|
||||
#include "version.h"
|
||||
#include "statistics.h"
|
||||
#include "secrets.h"
|
||||
|
||||
#include "savegamehelp.h"
|
||||
BEGIN_RR_NS
|
||||
|
||||
|
@ -155,12 +154,17 @@ static FileReader *OpenSavegame(const char *fn)
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto file = ReadSavegameChunk("DEMOLITION_RN");
|
||||
auto file = ReadSavegameChunk("info.json");
|
||||
if (!file.isOpen())
|
||||
{
|
||||
FinishSavegameRead();
|
||||
return nullptr;
|
||||
}
|
||||
if (G_ValidateSavegame(file, nullptr) <= 0)
|
||||
{
|
||||
FinishSavegameRead();
|
||||
return nullptr;
|
||||
}
|
||||
file = ReadSavegameChunk("snapshot.dat");
|
||||
if (!file.isOpen())
|
||||
{
|
||||
|
@ -170,130 +174,9 @@ static FileReader *OpenSavegame(const char *fn)
|
|||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -415,7 +298,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
|
|||
|
||||
if (status == 2)
|
||||
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
|
||||
// recover if things go wrong...
|
||||
|
@ -455,52 +338,6 @@ static void G_RestoreTimers(void)
|
|||
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)
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
|
@ -519,29 +356,28 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
|
|||
|
||||
if (sv.isValid())
|
||||
{
|
||||
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path);
|
||||
fn = G_BuildSaveName(sv.path);
|
||||
OpenSaveGameForWrite(fn);
|
||||
fil = WriteSavegameChunk("snapshot.dat");
|
||||
}
|
||||
else
|
||||
{
|
||||
static char const SaveName[] = "save0000.bsv";
|
||||
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), SaveName);
|
||||
fn = G_BuildSaveName("save0000");
|
||||
|
||||
auto fnp = fn.LockBuffer();
|
||||
char* zeros = fnp + (fn.Len() - 8);
|
||||
fil = savecounter.opennextfile(fnp, zeros);
|
||||
char* zeros = strstr(fnp, "0000");
|
||||
fil = savecounter.opennextfile(fnp, zeros); // fixme: Rewrite this so that it won't create the file.
|
||||
fn.UnlockBuffer();
|
||||
if (fil)
|
||||
{
|
||||
delete fil;
|
||||
remove(fnp);
|
||||
OpenSaveGameForWrite(fnp);
|
||||
remove(fn);
|
||||
OpenSaveGameForWrite(fn);
|
||||
fil = WriteSavegameChunk("snapshot.dat");
|
||||
}
|
||||
fn.UnlockBuffer();
|
||||
savecounter.count++;
|
||||
// don't copy the mod dir into sv.path
|
||||
Bstrcpy(sv.path, fn + (fn.Len() - (ARRAY_SIZE(SaveName) - 1)));
|
||||
// don't copy the mod dir into sv.path (G_BuildSaveName guarantees the presence of a slash.)
|
||||
Bstrcpy(sv.path, strrchr(fn, '/') + 1);
|
||||
}
|
||||
|
||||
if (!fil)
|
||||
|
@ -567,8 +403,7 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
|
|||
|
||||
// SAVE!
|
||||
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
|
||||
SaveStatistics();
|
||||
SECRET_Save();
|
||||
|
||||
|
||||
fw.Close();
|
||||
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));
|
||||
auto fw = WriteSavegameChunk("header.dat");
|
||||
fw->Write(&h, sizeof(savehead_t));
|
||||
|
||||
G_WriteSaveHeader(name, currentboardfilename, g_mapInfo[(MAXLEVELS * ud.volume_number) + ud.level_number].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -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_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 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_LoadPlayer(savebrief_t & sv);
|
||||
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh);
|
||||
|
|
|
@ -2382,6 +2382,7 @@ struct GameInterface : ::GameInterface
|
|||
void set_hud_layout(int size) override;
|
||||
void set_hud_scale(int size) override;
|
||||
bool mouseInactiveConditional(bool condition) override;
|
||||
FSavegameInfo GetSaveSig() override;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
|
|||
#include "fx_man.h"
|
||||
#include "music.h"
|
||||
#include "text.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "colormap.h"
|
||||
#include "config.h"
|
||||
|
@ -4737,5 +4738,10 @@ void ResetPalette(PLAYERp pp)
|
|||
|
||||
// vim:ts=4:sw=4:enc=utf-8:
|
||||
|
||||
FSavegameInfo GameInterface::GetSaveSig()
|
||||
{
|
||||
return { SAVESIG_SW, MINSAVEVER_SW, SAVEVER_SW };
|
||||
}
|
||||
|
||||
|
||||
END_SW_NS
|
||||
|
|
|
@ -245,6 +245,15 @@ int SaveGame(short save_num)
|
|||
OrgTileP otp, next_otp;
|
||||
|
||||
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);
|
||||
if ((fil = MOPEN_WRITE(game_name)) == MOPEN_WRITE_ERR)
|
||||
|
|
Loading…
Reference in a new issue