- fixed savegame issues.

Since the code is extremely volatile I changed the setup so that the save is a zip file with the regular snapshot plus all added data as separate entries.
This allows compressing everything properly without savegame breaking interference.

Blood does not yet load its savegames, need to check.
This commit is contained in:
Christoph Oelckers 2019-11-14 21:07:43 +01:00
parent 0ae9d3b93e
commit ba117554b0
13 changed files with 339 additions and 197 deletions

View file

@ -746,6 +746,8 @@ set (PCH_SOURCES
common/optionmenu/optionmenu.cpp
common/statistics.cpp
common/secrets.cpp
common/compositesavegame.cpp
common/savegamehelp.cpp
common/2d/v_2ddrawer.cpp
common/2d/v_draw.cpp

View file

@ -48,6 +48,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "view.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h"
BEGIN_BLD_NS
@ -117,7 +118,8 @@ void LoadSave::LoadGame(char *pzFile)
memset(sprite, 0, sizeof(spritetype)*kMaxSprites);
automapping = 1;
}
hLFile = fopenFileReader(pzFile, 0);
OpenSaveGameForRead(pzFile);
hLFile = ReadSavegameChunk("snapshot.bld");
if (!hLFile.isOpen())
ThrowError("Error loading save file.");
LoadSave *rover = head.next;
@ -126,10 +128,11 @@ void LoadSave::LoadGame(char *pzFile)
rover->Load();
rover = rover->next;
}
if (!ReadStatistics(hLFile) || !SECRET_Load(hLFile)) // read the rest...
if (!ReadStatistics() || !SECRET_Load()) // read the rest...
ThrowError("Error loading save file.");
hLFile.Close();
FinishSavegameRead();
if (!gGameStarted)
scrLoadPLUs();
InitSectorFX();
@ -193,7 +196,8 @@ void LoadSave::LoadGame(char *pzFile)
void LoadSave::SaveGame(char *pzFile)
{
hSFile = FileWriter::Open(pzFile);
OpenSaveGameForWrite(pzFile);
hSFile = WriteSavegameChunk("snapshot.bld");
if (hSFile == NULL)
ThrowError("File error #%d creating save file.", errno);
dword_27AA38 = 0;
@ -207,9 +211,9 @@ void LoadSave::SaveGame(char *pzFile)
dword_27AA38 = 0;
rover = rover->next;
}
SaveStatistics(*hSFile);
SECRET_Save(*hSFile);
delete hSFile;
SaveStatistics();
SECRET_Save();
FinishSavegameWrite();
hSFile = NULL;
}

View file

@ -1,19 +1,39 @@
#pragma once
#include <assert.h>
#include "files.h"
#include "zstring.h"
#include "tarray.h"
#include "filesystem/resourcefile.h"
class CompositeSavegameWriter
{
FString filename;
TDeletingArray<BufferWriter*> subfiles;
TArray<FString> subfilenames;
TArray<bool> isCompressed;
FCompressedBuffer CompressElement(BufferWriter* element, bool compress);
public:
FileWriter &NewEleemnt(const char *filename, bool compress = true);
bool WriteToFile(const char *filename);
void Clear()
{
subfiles.Reset();
subfilenames.Reset();
isCompressed.Reset();
}
void SetFileName(const char* fn)
{
filename = fn;
}
void SetFileName(const FString& fn)
{
filename = fn;
}
~CompositeSavegameWriter()
{
assert(subfiles.Size() == 0); // must be written out.
}
FileWriter& NewElement(const char* filename, bool compress = true);
bool WriteToFile();
};

View file

@ -34,15 +34,15 @@
*/
#include <zlib.h>
#include "compositesavegame.h"
#include "compositesaveame.h"
#include "file_zip.h"
#include "resourcefiles.h"
#include "resourcefile.h"
bool WriteZip(const char *filename, TArray<FString> &filenames, TArray<FCompressedBuffer> &content);
FileWriter &CompositeSavegameWriter::NewEleemnt(const char *filename, bool compress)
FileWriter &CompositeSavegameWriter::NewElement(const char *filename, bool compress)
{
subfilenames.Push(filename);
isCompressed.Push(compress);
@ -100,14 +100,15 @@ FCompressedBuffer CompositeSavegameWriter::CompressElement(BufferWriter *bw, boo
}
error:
memcpy(compressbuf, buffer->Data(), buff.mSize + 1);
if (buff.mSize) memcpy(compressbuf, buffer->Data(), buff.mSize + 1);
buff.mBuffer = (char*)compressbuf;
buff.mCompressedSize = buff.mSize;
buff.mMethod = METHOD_STORED;
return buff;
}
bool CompositeSavegameWriter::WriteToFile(const char *filename)
bool CompositeSavegameWriter::WriteToFile()
{
TArray<FCompressedBuffer> compressed(subfiles.Size(), 1);
for (unsigned i = 0; i < subfiles.Size(); i++)
@ -118,14 +119,16 @@ bool CompositeSavegameWriter::WriteToFile(const char *filename)
if (WriteZip(filename, subfilenames, compressed))
{
// Check whether the file is ok by trying to open it.
FResourceFile *test = FResourceFile::OpenResourceFile(filename, true);
if (test != nullptr)
//FResourceFile *test = FResourceFile::OpenResourceFile(filename, true);
//if (test != nullptr)
{
delete test;
Clear();
//delete test;
return true;
}
}
Clear();
return false;
}

View file

@ -22,6 +22,7 @@
#include "c_dispatch.h"
#include "i_specialpaths.h"
#include "z_music.h"
#include "statistics.h"
#ifndef NETCODE_DISABLE
#include "enet.h"
#endif
@ -379,6 +380,7 @@ int CONFIG_Init()
V_InitFonts();
buttonMap.SetGameAliases();
Mus_Init();
InitStatistics();

View file

@ -0,0 +1,82 @@
/*
** savegame.cpp
**
**---------------------------------------------------------------------------
** Copyright 2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OFf
** 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"
static CompositeSavegameWriter savewriter;
static FResourceFile *savereader;
void OpenSaveGameForWrite(const char *name)
{
savewriter.Clear();
savewriter.SetFileName(name);
}
bool OpenSaveGameForRead(const char *name)
{
if (savereader) delete savereader;
savereader = FResourceFile::OpenResourceFile(name, true, true);
return savereader != nullptr;
}
FileWriter *WriteSavegameChunk(const char *name)
{
return &savewriter.NewElement(name);
}
FileReader ReadSavegameChunk(const char *name)
{
if (!savereader) return FileReader();
auto lump = savereader->FindLump(name);
if (!lump) return FileReader();
return lump->NewReader();
}
bool FinishSavegameWrite()
{
return savewriter.WriteToFile();
}
void FinishSavegameRead()
{
delete savereader;
savereader = nullptr;
}

View file

@ -0,0 +1,12 @@
#pragma once
#include "filesystem/resourcefile.h"
void OpenSaveGameForWrite(const char *name);
bool OpenSaveGameForRead(const char *name);
FileWriter *WriteSavegameChunk(const char *name);
FileReader ReadSavegameChunk(const char *name);
bool FinishSavegameWrite();
void FinishSavegameRead();

View file

@ -6,6 +6,7 @@
#include "c_cvars.h"
#include "v_font.h"
#include "v_draw.h"
#include "savegamehelp.h"
// Unlike in GZDoom we have to maintain this list here, because we got different game frontents that all store this info differently.
// So the games will have to report the credited secrets so that this code can keep track of how to display them.
@ -106,20 +107,23 @@ CCMD(secret)
}
}
void SECRET_Save(FileWriter &fil)
void SECRET_Save()
{
fil.Write("SECR", 4);
auto fil = WriteSavegameChunk("secrets.dat");
fil->Write("SECR", 4);
unsigned count = discovered_secrets.Size();
fil.Write(&count, 4);
fil.Write(discovered_secrets.Data(), 4 * count);
fil.Write("RCES", 4);
fil->Write(&count, 4);
fil->Write(discovered_secrets.Data(), 4 * count);
fil->Write("RCES", 4);
}
bool SECRET_Load(FileReader &fil)
bool SECRET_Load()
{
char buf[4];
unsigned count;
auto fil = ReadSavegameChunk("secrets.dat");
if (!fil.isOpen()) return false;
fil.Read(buf, 4);
if (memcmp(buf, "SECR", 4)) return false;
fil.Read(&count, 4);

View file

@ -1,8 +1,8 @@
#pragma once
#include "files.h"
void SECRET_Save(FileWriter &fil);
bool SECRET_Load(FileReader &fil);
void SECRET_Save();
bool SECRET_Load();
void SECRET_SetMapName(const char *filename, const char *maptitle);
void SECRET_Trigger(int num);

View file

@ -45,6 +45,7 @@
#include "c_cvars.h"
#include "sc_man.h"
#include "baselayer.h"
#include "savegamehelp.h"
CVAR(Int, savestatistics, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(String, statfile, "demolitionstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
@ -171,7 +172,7 @@ static void ParseStatistics(const char *fn, TArray<FStatistics> &statlist)
//
// ====================================================================
void ReadStatistics()
void InitStatistics()
{
ParseStatistics(statfile, EpisodeStatistics);
}
@ -484,25 +485,27 @@ void ReadOneLevel(FileReader& fil, OneLevel& l)
l.Levelname.UnlockBuffer();
}
void SaveStatistics(FileWriter& fil)
void SaveStatistics()
{
fil.Write("STAT", 4);
fil.Write(LevelName, MAX_PATH);
fil.Write(&StartEpisode, MAX_PATH);
fil.Write(&StartSkill, 4);
auto fil = WriteSavegameChunk("statistics.dat");
fil->Write("STAT", 4);
fil->Write(LevelName, MAX_PATH);
fil->Write(&StartEpisode, MAX_PATH);
fil->Write(&StartSkill, 4);
int p = LevelData.Size();
fil.Write(&p, 4);
fil->Write(&p, 4);
for (auto& lev : LevelData)
{
SaveOneLevel(fil, lev);
SaveOneLevel(*fil, lev);
}
fil.Write("TATS", 4);
fil->Write("TATS", 4);
}
bool ReadStatistics(FileReader& fil)
bool ReadStatistics()
{
char id[4];
auto fil = ReadSavegameChunk("statistics.dat");
if (!fil.isOpen()) return false;
fil.Read(id, 4);
if (memcmp(id, "STAT", 4)) return false;
fil.Read(LevelName, MAX_PATH);

View file

@ -6,5 +6,6 @@ void STAT_NewLevel(const char* mapname);
void STAT_Update(bool endofgame);
void STAT_Cancel();
void SaveStatistics(FileWriter& fil);
bool ReadStatistics(FileReader& fil);
void InitStatistics();
void SaveStatistics();
bool ReadStatistics();

View file

@ -32,10 +32,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "version.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h"
BEGIN_DUKE_NS
static OutputFileCounter savecounter;
char previousboardfilename[BMAX_PATH];
@ -152,27 +152,25 @@ 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)
{
auto file = fopenFileReader(fn, 0);
if (!OpenSaveGameForRead(fn))
{
return nullptr;
}
auto file = ReadSavegameChunk("DEMOLITION_ED");
if (!file.isOpen())
return file;
char buffer[13];
file.Read(buffer, 13);
if (memcmp(buffer, "DEMOLITION_ED", 13))
return FileReader();
FileReader fr;
try
{
fr.OpenDecompressor(file, file.GetLength()-13, METHOD_DEFLATE, false, nullptr);
FinishSavegameRead();
return nullptr;
}
catch(std::runtime_error & err)
file = ReadSavegameChunk("snapshot.dat");
if (!file.isOpen())
{
Printf("%s: %s\n", fn, err.what());
FinishSavegameRead();
return nullptr;
}
return fr;
return new FileReader(std::move(file));
}
static void ReadSaveGameHeaders_CACHE1D(TArray<FString> &saves)
@ -182,14 +180,15 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString> &saves)
for (FString &save : saves)
{
auto fil = OpenSavegame(save);
if (!fil.isOpen())
if (!fil)
continue;
menusave_t & msv = g_internalsaves[g_numinternalsaves];
msv.brief.isExt = 0;
int32_t k = sv_loadheader(fil, 0, &h);
int32_t k = sv_loadheader(*fil, 0, &h);
delete fil;
if (k)
{
if (k < 0)
@ -198,8 +197,7 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString> &saves)
{
if (FURY)
{
FStringf extfn("%s.ext", save.GetChars());
auto extfil = fopenFileReader(extfn, 0);
auto extfil = ReadSavegameChunk("ext.json");
if (extfil.isOpen())
{
msv.brief.isExt = 1;
@ -224,6 +222,7 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString> &saves)
msv.isUnreadable = 1;
}
FinishSavegameRead();
}
static void ReadSaveGameHeaders_Internal(void)
@ -315,22 +314,21 @@ void ReadSaveGameHeaders(void)
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
{
FileReader ssfil;
auto fil = OpenSavegame(fn);
if (!fil.isOpen())
if (!fil)
return -1;
int32_t i = sv_loadheader(fil, 0, saveh);
int32_t i = sv_loadheader(*fil, 0, saveh);
if (i < 0)
goto corrupt;
int32_t screenshotofs;
if (fil.Read(&screenshotofs, 4) != 4)
goto corrupt;
ssfil = ReadSavegameChunk("screenshot.dat");
TileFiles.tileCreate(TILE_LOADSHOT, 200, 320);
if (screenshotofs)
if (ssfil.isOpen())
{
if (fil.Read(tileData(TILE_LOADSHOT), 320 * 200) != 320 * 200)
if (ssfil.Read(tileData(TILE_LOADSHOT), 320 * 200) != 320 * 200)
{
OSD_Printf("G_LoadSaveHeaderNew(): failed reading screenshot in \"%s\"\n", fn);
goto corrupt;
@ -340,11 +338,16 @@ int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
{
Bmemset(tileData(TILE_LOADSHOT), 0, 320*200);
}
ssfil.Close();
tileInvalidate(TILE_LOADSHOT, 0, 255);
delete fil;
FinishSavegameRead();
return 0;
corrupt:
delete fil;
FinishSavegameRead();
return 1;
}
@ -365,11 +368,11 @@ int32_t G_LoadPlayer(savebrief_t & sv)
int skill = -1;
auto fil = OpenSavegame(sv.path);
if (!fil) return -1;
if (fil.isOpen())
{
savehead_t h;
int status = sv_loadheader(fil, 0, &h);
int status = sv_loadheader(*fil, 0, &h);
if (status >= 0)
{
volume = h.volnum;
@ -378,17 +381,21 @@ int32_t G_LoadPlayer(savebrief_t & sv)
}
}
FStringf extfn("%s.ext", sv.path);
auto extfil = fopenFileReader(extfn, 0);
auto extfil = ReadSavegameChunk("ext.json");
if (!extfil.isOpen())
{
delete fil;
FinishSavegameRead();
return -1;
}
auto text = extfil.ReadPadded(1);
extfil.Close();
if (text.Size() == 0)
{
delete fil;
FinishSavegameRead();
return -1;
}
@ -405,6 +412,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
if (volume == -1 || level == -1 || skill == -1)
{
sjson_destroy_context(ctx);
delete fil;
FinishSavegameRead();
return -1;
}
@ -417,6 +426,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
P_DoQuote(QUOTE_SAVE_BAD_PLAYERS, g_player[myconnectindex].ps);
sjson_destroy_context(ctx);
delete fil;
FinishSavegameRead();
return 1;
}
@ -588,18 +599,20 @@ int32_t G_LoadPlayer(savebrief_t & sv)
VM_OnEvent(EVENT_LOADGAME, g_player[screenpeek].ps->i, screenpeek);
delete fil;
FinishSavegameRead();
return 0;
}
auto fil = OpenSavegame(sv.path);
if (!fil.isOpen())
if (!fil)
return -1;
ready2send = 0;
savehead_t h;
int status = sv_loadheader(fil, 0, &h);
int status = sv_loadheader(*fil, 0, &h);
if (status < 0 || h.numplayers != ud.multimode)
{
@ -611,6 +624,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
ototalclock = totalclock;
ready2send = 1;
delete fil;
FinishSavegameRead();
return 1;
}
@ -665,7 +680,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
if (status == 2)
G_NewGame_EnterLevel();
else if ((status = sv_loadsnapshot(fil, 0, &h)) || !ReadStatistics(fil) || !SECRET_Load(fil)) // read the rest...
else if ((status = sv_loadsnapshot(*fil, 0, &h)) || !ReadStatistics() || !SECRET_Load()) // read the rest...
{
// in theory, we could load into an initial dump first and trivially
// recover if things go wrong...
@ -676,6 +691,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
sv_postudload(); // ud.m_XXX = ud.XXX
VM_OnEvent(EVENT_LOADGAME, g_player[screenpeek].ps->i, screenpeek);
delete fil;
FinishSavegameRead();
return 0;
}
@ -718,8 +735,6 @@ void G_DeleteSave(savebrief_t const & sv)
}
remove(temp);
Bstrcat(temp, ".ext");
remove(temp);
}
void G_DeleteOldSaves(void)
@ -768,7 +783,8 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
if (sv.isValid())
{
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path);
fil = FileWriter::Open(fn);
OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat");
}
else
{
@ -778,6 +794,13 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
auto fnp = fn.LockBuffer();
char* zeros = fnp + (fn.Len() - 8);
fil = savecounter.opennextfile(fnp, zeros);
if (fil)
{
delete fil;
remove(fnp);
OpenSaveGameForWrite(fnp);
fil = WriteSavegameChunk("snapshot.dat");
}
fn.UnlockBuffer();
savecounter.count++;
// don't copy the mod dir into sv.path
@ -797,9 +820,8 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
}
else
{
fil->Write("DEMOLITION_ED", 13);
CompressedFileWriter fw(fil, true);
WriteSavegameChunk("DEMOLITION_ED");
auto& fw = *fil;
sv.isExt = 0;
@ -812,10 +834,11 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
// SAVE!
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
SaveStatistics(fw);
SECRET_Save(fw);
SaveStatistics();
SECRET_Save();
fw.Close();
FinishSavegameWrite();
if (!g_netServer && ud.multimode < 2)
{
@ -1686,6 +1709,8 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
{
// savegame
Bstrncpyz(h.savename, name, sizeof(h.savename));
auto fw = WriteSavegameChunk("header.dat");
fw->Write(&h, sizeof(savehead_t));
}
else
{
@ -1698,27 +1723,17 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
if (t>=0 && (st = localtime(&t)))
Bsnprintf(h.savename, sizeof(h.savename), "Demo %04d%02d%02d %s",
st->tm_year+1900, st->tm_mon+1, st->tm_mday, GetGitDescription());
fil.Write(&h, sizeof(savehead_t));
}
// write header
fil.Write(&h, sizeof(savehead_t));
// for savegames, the file offset after the screenshot goes here;
// for demos, we keep it 0 to signify that we didn't save one
if (spot >= 0 && tileData(TILE_SAVESHOT))
{
auto fw = WriteSavegameChunk("screenshot.dat");
fw->Write(tileData(TILE_SAVESHOT), 320*200);
int v = 64000;
fil.Write(&v, 4);
// write the screenshot compressed
fil.Write(tileData(TILE_SAVESHOT), 320*200);
}
else
{
int v = 0;
fil.Write(&v, 4);
}
@ -1745,11 +1760,18 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
}
// if file is not an EDuke32 savegame/demo, h->headerstr will be all zeros
int32_t sv_loadheader(FileReader &fil, int32_t spot, savehead_t *h)
int32_t sv_loadheader(FileReader &fill, int32_t spot, savehead_t *h)
{
FileReader filc;
FileReader* filp = &fill;
int32_t havedemo = (spot < 0);
if (!havedemo)
{
filc = ReadSavegameChunk("header.dat");
filp = &filc;
}
if (fil.Read(h, sizeof(savehead_t)) != sizeof(savehead_t))
if (filp->Read(h, sizeof(savehead_t)) != sizeof(savehead_t))
{
OSD_Printf("%s %d header corrupt.\n", havedemo ? "Demo":"Savegame", havedemo ? -spot : spot);
Bmemset(h->headerstr, 0, sizeof(h->headerstr));
@ -1825,20 +1847,6 @@ int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h)
OSD_Printf("sv_loadsnapshot: snapshot size: %d bytes.\n", h->snapsiz);
#endif
if (fil.Read(&i, 4) != 4)
{
OSD_Printf("sv_snapshot: couldn't read 4 bytes after header.\n");
return 7;
}
if (i > 0)
{
if (fil.Seek(i, FileReader::SeekCur) < 0)
{
OSD_Printf("sv_snapshot: failed skipping over the screenshot.\n");
return 8;
}
}
savegame_comprthres = h->comprthres;
if (spot >= 0)

View file

@ -30,7 +30,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "version.h"
#include "statistics.h"
#include "secrets.h"
#include "savegamehelp.h"
BEGIN_RR_NS
@ -149,28 +149,25 @@ 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)
{
auto file = fopenFileReader(fn, 0);
if (!OpenSaveGameForRead(fn))
{
return nullptr;
}
auto file = ReadSavegameChunk("DEMOLITION_RN");
if (!file.isOpen())
return file;
char buffer[13];
file.Read(buffer, 13);
if (memcmp(buffer, "DEMOLITION_RN", 13))
return FileReader();
FileReader fr;
try
{
fr.OpenDecompressor(file, file.GetLength()-13, METHOD_DEFLATE|METHOD_TRANSFEROWNER, false, nullptr);
FinishSavegameRead();
return nullptr;
}
catch(std::runtime_error & err)
file = ReadSavegameChunk("snapshot.dat");
if (!file.isOpen())
{
Printf("%s: %s\n", fn, err.what());
return FileReader();
FinishSavegameRead();
return nullptr;
}
return fr;
return new FileReader(std::move(file));
}
static void ReadSaveGameHeaders_CACHE1D(TArray<FString>& saves)
@ -179,14 +176,14 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString>& saves)
for (FString &save : saves)
{
char const* fn = save;
auto fil = OpenSavegame(fn);
if (!fil.isOpen())
auto fil = OpenSavegame(save);
if (!fil)
continue;
menusave_t & msv = g_internalsaves[g_numinternalsaves];
int32_t k = sv_loadheader(fil, 0, &h);
int32_t k = sv_loadheader(*fil, 0, &h);
delete fil;
if (k)
{
if (k < 0)
@ -198,7 +195,7 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString>& saves)
msv.isAutoSave = h.isAutoSave();
strncpy(msv.brief.path, fn, ARRAY_SIZE(msv.brief.path));
strncpy(msv.brief.path, save.GetChars(), ARRAY_SIZE(msv.brief.path));
++g_numinternalsaves;
if (k >= 0 && h.savename[0] != '\0')
@ -208,6 +205,7 @@ static void ReadSaveGameHeaders_CACHE1D(TArray<FString>& saves)
else
msv.isUnreadable = 1;
}
FinishSavegameRead();
}
static void ReadSaveGameHeaders_Internal(void)
@ -300,22 +298,21 @@ void ReadSaveGameHeaders(void)
int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
{
FileReader ssfil;
auto fil = OpenSavegame(fn);
if (!fil.isOpen())
if (!fil)
return -1;
int32_t i = sv_loadheader(fil, 0, saveh);
int32_t i = sv_loadheader(*fil, 0, saveh);
if (i < 0)
goto corrupt;
int32_t screenshotofs;
if (fil.Read(&screenshotofs, 4) != 4)
goto corrupt;
ssfil = ReadSavegameChunk("screenshot.dat");
TileFiles.tileCreate(TILE_LOADSHOT, 200, 320);
if (screenshotofs)
if (ssfil.isOpen())
{
if (fil.Read(tileData(TILE_LOADSHOT), 320 * 200) != 320 * 200)
if (ssfil.Read(tileData(TILE_LOADSHOT), 320 * 200) != 320 * 200)
{
OSD_Printf("G_LoadSaveHeaderNew(): failed reading screenshot in \"%s\"\n", fn);
goto corrupt;
@ -325,11 +322,16 @@ int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
{
Bmemset(tileData(TILE_LOADSHOT), 0, 320*200);
}
ssfil.Close();
tileInvalidate(TILE_LOADSHOT, 0, 255);
delete fil;
FinishSavegameRead();
return 0;
corrupt:
delete fil;
FinishSavegameRead();
return 1;
}
@ -344,13 +346,13 @@ int32_t G_LoadPlayer(savebrief_t & sv)
{
auto fil = OpenSavegame(sv.path);
if (!fil.isOpen())
if (!fil)
return -1;
ready2send = 0;
savehead_t h;
int status = sv_loadheader(fil, 0, &h);
int status = sv_loadheader(*fil, 0, &h);
if (status < 0 || h.numplayers != ud.multimode)
{
@ -362,6 +364,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
ototalclock = totalclock;
ready2send = 1;
delete fil;
FinishSavegameRead();
return 1;
}
@ -411,7 +415,7 @@ int32_t G_LoadPlayer(savebrief_t & sv)
if (status == 2)
G_NewGame_EnterLevel();
else if ((status = sv_loadsnapshot(fil, 0, &h)) || !ReadStatistics(fil) || !SECRET_Load(fil)) // read the rest...
else if ((status = sv_loadsnapshot(*fil, 0, &h)) || !ReadStatistics() || !SECRET_Load()) // read the rest...
{
// in theory, we could load into an initial dump first and trivially
// recover if things go wrong...
@ -421,6 +425,8 @@ int32_t G_LoadPlayer(savebrief_t & sv)
sv_postudload(); // ud.m_XXX = ud.XXX
delete fil;
FinishSavegameRead();
return 0;
}
@ -465,7 +471,7 @@ void G_DeleteSave(savebrief_t const & sv)
return;
}
unlink(temp);
remove(temp);
}
void G_DeleteOldSaves(void)
@ -514,7 +520,8 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
if (sv.isValid())
{
fn.Format("%s%s", M_GetSavegamesPath().GetChars(), sv.path);
fil = FileWriter::Open(fn);
OpenSaveGameForWrite(fn);
fil = WriteSavegameChunk("snapshot.dat");
}
else
{
@ -524,6 +531,13 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
auto fnp = fn.LockBuffer();
char* zeros = fnp + (fn.Len() - 8);
fil = savecounter.opennextfile(fnp, zeros);
if (fil)
{
delete fil;
remove(fnp);
OpenSaveGameForWrite(fnp);
fil = WriteSavegameChunk("snapshot.dat");
}
fn.UnlockBuffer();
savecounter.count++;
// don't copy the mod dir into sv.path
@ -543,8 +557,9 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
}
else
{
fil->Write("DEMOLITION_RN", 13);
CompressedFileWriter fw(fil, true);
WriteSavegameChunk("DEMOLITION_RN");
auto& fw = *fil;
//CompressedFileWriter fw(fil, true);
// temporary hack
ud.user_map = G_HaveUserMap();
@ -552,10 +567,11 @@ int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
// SAVE!
sv_saveandmakesnapshot(fw, sv.name, 0, 0, 0, 0, isAutoSave);
SaveStatistics(fw);
SECRET_Save(fw);
SaveStatistics();
SECRET_Save();
fw.Close();
FinishSavegameWrite();
if (!g_netServer && ud.multimode < 2)
{
@ -1367,6 +1383,8 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
{
// savegame
Bstrncpyz(h.savename, name, sizeof(h.savename));
auto fw = WriteSavegameChunk("header.dat");
fw->Write(&h, sizeof(savehead_t));
}
else
{
@ -1379,27 +1397,17 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
if (t>=0 && (st = localtime(&t)))
Bsnprintf(h.savename, sizeof(h.savename), "Demo %04d%02d%02d %s",
st->tm_year+1900, st->tm_mon+1, st->tm_mday, GetGitDescription());
fil.Write(&h, sizeof(savehead_t));
}
// write header
fil.Write(&h, sizeof(savehead_t));
// for savegames, the file offset after the screenshot goes here;
// for demos, we keep it 0 to signify that we didn't save one
if (spot >= 0 && tileData(TILE_SAVESHOT))
{
auto fw = WriteSavegameChunk("screenshot.dat");
fw->Write(tileData(TILE_SAVESHOT), 320*200);
int v = 64000;
fil.Write(&v, 4);
// write the screenshot compressed
fil.Write(tileData(TILE_SAVESHOT), 320*200);
}
else
{
int v = 0;
fil.Write(&v, 4);
}
@ -1426,11 +1434,18 @@ int32_t sv_saveandmakesnapshot(FileWriter &fil, char const *name, int8_t spot, i
}
// if file is not an EDuke32 savegame/demo, h->headerstr will be all zeros
int32_t sv_loadheader(FileReader &fil, int32_t spot, savehead_t *h)
int32_t sv_loadheader(FileReader &fill, int32_t spot, savehead_t *h)
{
FileReader filc;
FileReader* filp = &fill;
int32_t havedemo = (spot < 0);
if (!havedemo)
{
filc = ReadSavegameChunk("header.dat");
filp = &filc;
}
if (fil.Read(h, sizeof(savehead_t)) != sizeof(savehead_t))
if (filp->Read(h, sizeof(savehead_t)) != sizeof(savehead_t))
{
OSD_Printf("%s %d header corrupt.\n", havedemo ? "Demo":"Savegame", havedemo ? -spot : spot);
Bmemset(h->headerstr, 0, sizeof(h->headerstr));
@ -1506,20 +1521,6 @@ int32_t sv_loadsnapshot(FileReader &fil, int32_t spot, savehead_t *h)
OSD_Printf("sv_loadsnapshot: snapshot size: %d bytes.\n", h->snapsiz);
#endif
if (fil.Read(&i, 4) != 4)
{
OSD_Printf("sv_snapshot: couldn't read 4 bytes after header.\n");
return 7;
}
if (i > 0)
{
if (fil.Seek(i, FileReader::SeekCur) < 0)
{
OSD_Printf("sv_snapshot: failed skipping over the screenshot.\n");
return 8;
}
}
savegame_comprthres = h->comprthres;
if (spot >= 0)