From ba117554b0073755599705a164f089b1dff1e344 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 14 Nov 2019 21:07:43 +0100 Subject: [PATCH] - 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. --- source/CMakeLists.txt | 2 + source/blood/src/loadsave.cpp | 16 ++- source/common/compositesaveame.h | 30 ++++- source/common/compositesavegame.cpp | 19 ++-- source/common/gamecontrol.cpp | 2 + source/common/savegamehelp.cpp | 82 ++++++++++++++ source/common/savegamehelp.h | 12 ++ source/common/secrets.cpp | 16 ++- source/common/secrets.h | 4 +- source/common/statistics.cpp | 25 ++-- source/common/statistics.h | 5 +- source/duke3d/src/savegame.cpp | 170 +++++++++++++++------------- source/rr/src/savegame.cpp | 153 ++++++++++++------------- 13 files changed, 339 insertions(+), 197 deletions(-) create mode 100644 source/common/savegamehelp.cpp create mode 100644 source/common/savegamehelp.h diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 648c07705..4766ac268 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -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 diff --git a/source/blood/src/loadsave.cpp b/source/blood/src/loadsave.cpp index 08adbebf5..5d4149828 100644 --- a/source/blood/src/loadsave.cpp +++ b/source/blood/src/loadsave.cpp @@ -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; } diff --git a/source/common/compositesaveame.h b/source/common/compositesaveame.h index c76e59273..00adeefed 100644 --- a/source/common/compositesaveame.h +++ b/source/common/compositesaveame.h @@ -1,19 +1,39 @@ #pragma once +#include #include "files.h" #include "zstring.h" #include "tarray.h" +#include "filesystem/resourcefile.h" class CompositeSavegameWriter { + FString filename; TDeletingArray subfiles; TArray subfilenames; TArray isCompressed; - - FCompressedBuffer CompressElement(BufferWriter *element, bool compress); + + 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(); }; diff --git a/source/common/compositesavegame.cpp b/source/common/compositesavegame.cpp index b306466b3..e90a4afea 100644 --- a/source/common/compositesavegame.cpp +++ b/source/common/compositesavegame.cpp @@ -34,15 +34,15 @@ */ #include -#include "compositesavegame.h" +#include "compositesaveame.h" #include "file_zip.h" -#include "resourcefiles.h" +#include "resourcefile.h" bool WriteZip(const char *filename, TArray &filenames, TArray &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 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; } diff --git a/source/common/gamecontrol.cpp b/source/common/gamecontrol.cpp index ad0b368f4..e44395379 100644 --- a/source/common/gamecontrol.cpp +++ b/source/common/gamecontrol.cpp @@ -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(); diff --git a/source/common/savegamehelp.cpp b/source/common/savegamehelp.cpp new file mode 100644 index 000000000..3a3cdffa7 --- /dev/null +++ b/source/common/savegamehelp.cpp @@ -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; +} diff --git a/source/common/savegamehelp.h b/source/common/savegamehelp.h new file mode 100644 index 000000000..9dec6d617 --- /dev/null +++ b/source/common/savegamehelp.h @@ -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(); diff --git a/source/common/secrets.cpp b/source/common/secrets.cpp index bbdbb628e..e1235e712 100644 --- a/source/common/secrets.cpp +++ b/source/common/secrets.cpp @@ -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); diff --git a/source/common/secrets.h b/source/common/secrets.h index bc44ddca6..2377075c4 100644 --- a/source/common/secrets.h +++ b/source/common/secrets.h @@ -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); diff --git a/source/common/statistics.cpp b/source/common/statistics.cpp index 2619bc42d..d1cc4f4c5 100644 --- a/source/common/statistics.cpp +++ b/source/common/statistics.cpp @@ -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 &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); diff --git a/source/common/statistics.h b/source/common/statistics.h index 2d0e49ca6..3193fa3b7 100644 --- a/source/common/statistics.h +++ b/source/common/statistics.h @@ -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(); diff --git a/source/duke3d/src/savegame.cpp b/source/duke3d/src/savegame.cpp index c78d38acd..9bbbb4008 100644 --- a/source/duke3d/src/savegame.cpp +++ b/source/duke3d/src/savegame.cpp @@ -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 &saves) @@ -182,14 +180,15 @@ static void ReadSaveGameHeaders_CACHE1D(TArray &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 &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 &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); - return 0; + 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,18 +381,22 @@ 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) { - return -1; + delete fil; + FinishSavegameRead(); + return -1; } sjson_context * ctx = sjson_create_context(0, 0, NULL); @@ -405,7 +412,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) if (volume == -1 || level == -1 || skill == -1) { sjson_destroy_context(ctx); - return -1; + delete fil; + FinishSavegameRead(); + return -1; } sjson_node * players = sjson_find_member(root, "players"); @@ -417,7 +426,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) P_DoQuote(QUOTE_SAVE_BAD_PLAYERS, g_player[myconnectindex].ps); sjson_destroy_context(ctx); - return 1; + 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); - return 0; + 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,7 +624,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) ototalclock = totalclock; ready2send = 1; - return 1; + delete fil; + FinishSavegameRead(); + return 1; } VM_OnEvent(EVENT_PRELOADGAME, g_player[screenpeek].ps->i, screenpeek); @@ -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,7 +691,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) sv_postudload(); // ud.m_XXX = ud.XXX VM_OnEvent(EVENT_LOADGAME, g_player[screenpeek].ps->i, screenpeek); - return 0; + delete fil; + FinishSavegameRead(); + return 0; } ////////// TIMER SAVING/RESTORING ////////// @@ -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,7 +1709,9 @@ 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 { // demo @@ -1698,28 +1723,18 @@ 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)) { - - int v = 64000; - fil.Write(&v, 4); - // write the screenshot compressed - fil.Write(tileData(TILE_SAVESHOT), 320*200); + auto fw = WriteSavegameChunk("screenshot.dat"); + fw->Write(tileData(TILE_SAVESHOT), 320*200); } - else - { - int v = 0; - fil.Write(&v, 4); - } if (spot >= 0) @@ -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) diff --git a/source/rr/src/savegame.cpp b/source/rr/src/savegame.cpp index 9b7431ec6..0501c6f7a 100644 --- a/source/rr/src/savegame.cpp +++ b/source/rr/src/savegame.cpp @@ -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,44 +149,41 @@ 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& saves) { savehead_t h; - for (FString& save : saves) - { - char const* fn = save; - auto fil = OpenSavegame(fn); - if (!fil.isOpen()) + 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); + int32_t k = sv_loadheader(*fil, 0, &h); + delete fil; if (k) { if (k < 0) @@ -198,7 +195,7 @@ static void ReadSaveGameHeaders_CACHE1D(TArray& 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& 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); - return 0; + 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,7 +364,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) ototalclock = totalclock; ready2send = 1; - return 1; + delete fil; + FinishSavegameRead(); + return 1; } // some setup first @@ -411,8 +415,8 @@ 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... Bsprintf(tempbuf, "Loading save game file \"%s\" failed (code %d), cannot recover.", sv.path, status); @@ -421,7 +425,9 @@ int32_t G_LoadPlayer(savebrief_t & sv) sv_postudload(); // ud.m_XXX = ud.XXX - return 0; + delete fil; + FinishSavegameRead(); + return 0; } ////////// TIMER SAVING/RESTORING ////////// @@ -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,7 +1383,9 @@ 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 { // demo @@ -1379,28 +1397,18 @@ 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)) { - - int v = 64000; - fil.Write(&v, 4); - // write the screenshot compressed - fil.Write(tileData(TILE_SAVESHOT), 320*200); + auto fw = WriteSavegameChunk("screenshot.dat"); + fw->Write(tileData(TILE_SAVESHOT), 320*200); } - else - { - int v = 0; - fil.Write(&v, 4); - } if (spot >= 0) @@ -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)