diff --git a/Common/Serialize/Serializer.cpp b/Common/Serialize/Serializer.cpp index d0ded74fd7..bcf53e5f92 100644 --- a/Common/Serialize/Serializer.cpp +++ b/Common/Serialize/Serializer.cpp @@ -601,6 +601,11 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const Path &filename, const s INFO_LOG(Log::SaveState, "Savestate: Compressed %i bytes into %i", (int)sz, (int)writeLen); } free(buffer); + // Buffered writes can still fail here (a full disk.) + if (!pFile.Close() && success) { + ERROR_LOG(Log::SaveState, "ChunkReader: Failed closing file"); + success = false; + } if (!success) { return ERROR_BAD_FILE; } diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 7138d1d7e0..e1e5db5272 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -414,6 +414,7 @@ int g_screenshotFailures; static bool QueueLoad(const Operation &op) { if (NetworkWarnUserIfOnlineAndCantSavestate()) { + CallbackRefused(op.callback); return false; } @@ -425,6 +426,7 @@ int g_screenshotFailures; static bool QueueSave(const Operation &op) { if (NetworkWarnUserIfOnlineAndCantSavestate()) { + CallbackRefused(op.callback); return false; } if (AttachToPendingSave(op.path, op.callback)) { @@ -646,6 +648,7 @@ int g_screenshotFailures; bool UndoLoad(std::string_view gamePrefix, Callback callback) { if (NetworkWarnUserIfOnlineAndCantSavestate()) { + CallbackRefused(callback); return false; } @@ -1159,7 +1162,10 @@ int g_screenshotFailures; } else { DeleteIfExists(job.path); } - File::Rename(job.writePath, job.path); + if (!File::Rename(job.writePath, job.path)) { + ERROR_LOG(Log::SaveState, "Failed to move '%s' into place", job.writePath.c_str()); + result = CChunkFileReader::ERROR_BAD_FILE; + } } PostCompletion([job, result](bool callCallbacks) { FinishSave(job, result, callCallbacks); @@ -1193,7 +1199,10 @@ int g_screenshotFailures; g_saveSnapshots--; if (result == CChunkFileReader::ERROR_NONE) { DeleteIfExists(backup); - File::Rename(temp, backup); + if (!File::Rename(temp, backup)) { + // The undo would load whatever was there before, maybe another game's state. + result = CChunkFileReader::ERROR_BAD_FILE; + } } // Not a callback, so it runs at shutdown too. PostCompletion([result, gamePrefix](bool) { @@ -1404,6 +1413,9 @@ int g_screenshotFailures; needsProcess = false; lock.unlock(); for (const Operation &op : operations) { + if (op.type == OperationType::Rewind && op.started) { + rewindStates.CancelRestore(); + } RefuseOperation(op); } return; diff --git a/Core/SaveState.h b/Core/SaveState.h index 4373b7c573..dfcdc30066 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -109,11 +109,11 @@ namespace SaveState { CChunkFileReader::Error LoadFromRam(std::vector &state, std::string *errorString); // For testing / automated tests. Runs a save state verification pass (async.) - // Warning: callback will be called on a different thread. + // The callback runs from Process(). bool Verify(Callback callback = Callback()); // To go back to a previous snapshot (only if enabled.) - // Warning: callback will be called on a different thread. + // The callback runs from Process(), once the snapshot has been applied. bool Rewind(Callback callback = Callback()); // Returns true if there are rewind snapshots available. diff --git a/Core/SaveStateRewind.cpp b/Core/SaveStateRewind.cpp index 47352e5f09..e54af38bf6 100644 --- a/Core/SaveStateRewind.cpp +++ b/Core/SaveStateRewind.cpp @@ -161,6 +161,16 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std:: return FinishRestore(errorString, metadata); } +void StateRingbuffer::CancelRestore() { + if (!restoreActive_) { + return; + } + // The decode may still be running into restoreBuffer_. + WaitForWorker(); + restoreActive_ = false; + std::vector().swap(restoreBuffer_); +} + // Only moves the ring back: the state's buffer may still be in use by a worker job, and is reused // by the next snapshot anyway. void StateRingbuffer::DropNewest() { diff --git a/Core/SaveStateRewind.h b/Core/SaveStateRewind.h index 190565af31..c7a7030d35 100644 --- a/Core/SaveStateRewind.h +++ b/Core/SaveStateRewind.h @@ -31,6 +31,8 @@ public: CChunkFileReader::Error FinishRestore(std::string *errorString, std::string *metadata); // Both halves, waiting for the worker in between. CChunkFileReader::Error Restore(std::string *errorString, std::string *metadata); + // For a rewind that was started and then dropped. + void CancelRestore(); void Clear(); bool Empty() const {