From a173f98965d03b74e9e2249db3e4e66c268304f1 Mon Sep 17 00:00:00 2001 From: Kevin Foley Date: Sat, 12 Aug 2023 14:56:50 -0700 Subject: [PATCH] When saving, write the save to a temporary file, then copy the temporary file into the final destination path. This way, if the game crashes while writing the save file, we don't have any risk of overwriting a valid save with a corrupted save. --- TheForceEngine/TFE_FileSystem/filestream.cpp | 8 ++++++++ TheForceEngine/TFE_FileSystem/filestream.h | 1 + TheForceEngine/TFE_Game/saveSystem.cpp | 12 +++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_FileSystem/filestream.cpp b/TheForceEngine/TFE_FileSystem/filestream.cpp index 3d4464197..2fbc871ff 100644 --- a/TheForceEngine/TFE_FileSystem/filestream.cpp +++ b/TheForceEngine/TFE_FileSystem/filestream.cpp @@ -30,6 +30,14 @@ bool FileStream::exists(const char* filename) return res; } +bool FileStream::renameFile(const char* oldName, const char* newName) +{ + remove(newName); + int status = rename(oldName, newName); + if (status < 0) { return false; } + return true; +} + bool FileStream::open(const char* filename, AccessMode mode) { const char* modeStrings[] = { "rb", "wb", "rb+" }; diff --git a/TheForceEngine/TFE_FileSystem/filestream.h b/TheForceEngine/TFE_FileSystem/filestream.h index dfc988987..b937d8cfe 100644 --- a/TheForceEngine/TFE_FileSystem/filestream.h +++ b/TheForceEngine/TFE_FileSystem/filestream.h @@ -16,6 +16,7 @@ class FileStream : public Stream ~FileStream(); bool exists(const char* filename); + bool renameFile(const char* oldName, const char* newName); bool open(const char* filename, AccessMode mode); bool open(const FilePath* filePath, AccessMode mode); void close(); diff --git a/TheForceEngine/TFE_Game/saveSystem.cpp b/TheForceEngine/TFE_Game/saveSystem.cpp index e9a65d943..3a8c8310c 100644 --- a/TheForceEngine/TFE_Game/saveSystem.cpp +++ b/TheForceEngine/TFE_Game/saveSystem.cpp @@ -213,17 +213,27 @@ namespace TFE_SaveSystem bool saveGame(const char* filename, const char* saveName) { + // We first write the new save to a temporary file, then rename the temporary file. + // This way, if we are saving into an existing slot and something goes wrong during + // the save process, we won't overwrite a valid save with a corrupted save. + + char tempFilePath[TFE_MAX_PATH]; char filePath[TFE_MAX_PATH]; + string tempFilename = string(filename) + ".tmp"; + sprintf(tempFilePath, "%s%s", s_gameSavePath, tempFilename.c_str()); sprintf(filePath, "%s%s", s_gameSavePath, filename); bool ret = false; FileStream stream; - if (stream.open(filePath, Stream::MODE_WRITE)) + if (stream.open(tempFilePath, Stream::MODE_WRITE)) { saveHeader(&stream, saveName); ret = s_game->serializeGameState(&stream, filename, true); stream.close(); } + + stream.renameFile(tempFilePath, filePath); + return ret; }