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)