Merge pull request #22666 from hrydgard/savestate-fixes

Savestate fixes: rewind ring, slot operations, load failure messages
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-19 18:46:20 +02:00
commit e2e0e0c748
54 files changed
+319 -131

No files matched your search

+12 -7
View File
@@ -383,7 +383,12 @@ CChunkFileReader::Error CChunkFileReader::LoadFileHeader(File::IOFile &pFile, SC
return ERROR_NONE;
}
CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std::string *title) {
// Not necessarily null-terminated.
static std::string HeaderGitVersion(const char (&gitVersion)[32]) {
return std::string(gitVersion, strnlen(gitVersion, sizeof(gitVersion)));
}
CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std::string *title, std::string *gitVersion) {
if (!File::Exists(filename)) {
ERROR_LOG(Log::SaveState, "ChunkReader: File doesn't exist");
return ERROR_BAD_FILE;
@@ -391,7 +396,11 @@ CChunkFileReader::Error CChunkFileReader::GetFileTitle(const Path &filename, std
File::IOFile pFile(filename, "rb");
SChunkHeader header;
return LoadFileHeader(pFile, header, title);
Error err = LoadFileHeader(pFile, header, title);
if (err == ERROR_NONE && gitVersion) {
*gitVersion = HeaderGitVersion(header.GitVersion);
}
return err;
}
CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::string *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) {
@@ -460,11 +469,7 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const Path &filename, std::st
_buffer = buffer;
}
if (header.GitVersion[31]) {
*gitVersion = std::string(header.GitVersion, 32);
} else {
*gitVersion = header.GitVersion;
}
*gitVersion = HeaderGitVersion(header.GitVersion);
return ERROR_NONE;
}
+13 -8
View File
@@ -200,7 +200,8 @@ public:
ERROR_BAD_ALLOC,
};
// May fail badly if ptr doesn't point to valid data.
// May fail badly if ptr doesn't point to valid data. On failure, errorString is the title of the
// section that failed.
template<class T>
static Error LoadPtr(u8 *ptr, size_t size, T &_class, std::string *errorString)
{
@@ -211,8 +212,7 @@ public:
if (p.error != p.ERROR_FAILURE) {
return ERROR_NONE;
} else {
std::string badSectionTitle = p.GetBadSectionTitle() ? p.GetBadSectionTitle() : "(unknown bad section)";
*errorString = std::string("Failure at ") + badSectionTitle;
*errorString = p.GetBadSectionTitle() ? p.GetBadSectionTitle() : "unknown section";
return ERROR_BROKEN_STATE;
}
}
@@ -292,7 +292,9 @@ public:
template<class T>
static Error Load(const Path &filename, std::string *gitVersion, T& _class, std::string *failureReason)
{
*failureReason = "LoadStateWrongVersion";
// LoadFile names the failures it can tell apart. A truncated or corrupt file isn't one of them,
// and isn't necessarily from another version either.
failureReason->clear();
u8 *ptr = nullptr;
size_t sz;
@@ -334,13 +336,16 @@ public:
std::vector<u8> buffer(sz);
// Step 2: Dump the state.
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
p.RewindForWrite(buffer.data());
_class.DoState(p);
if (!p.CheckAfterWrite()) {
return ERROR_BROKEN_STATE;
}
// Step 3: Verify the state.
ptr = &buffer[0];
ptr = buffer.data();
p.SetMode(PointerWrap::MODE_VERIFY);
p.SetReadEnd(buffer.data() + sz);
_class.DoState(p);
if (p.error == PointerWrap::ERROR_FAILURE) {
@@ -349,7 +354,7 @@ public:
return ERROR_NONE;
}
static Error GetFileTitle(const Path &filename, std::string *title);
static Error GetFileTitle(const Path &filename, std::string *title, std::string *gitVersion = nullptr);
private:
struct SChunkHeader
+64 -18
View File
@@ -57,10 +57,18 @@ struct SaveStateResult {
bool done = false;
SaveState::Status status = SaveState::Status::FAILURE;
std::string message;
std::string metadata;
};
enum class StateOp {
LOAD,
SAVE,
REWIND,
VERIFY,
};
struct WebSocketGameState : public DebuggerSubscriber {
void StateLoadSave(DebuggerRequest &req, bool save);
void StateOperation(DebuggerRequest &req, StateOp op);
void Broadcast(net::WebSocketServer *ws) override;
private:
@@ -74,23 +82,33 @@ private:
} // namespace
// Load a savestate file (game.state.load), or save one (game.state.save)
// Load a savestate file (game.state.load), save one (game.state.save), rewind to the newest
// rewind snapshot (game.state.rewind), or check that saving and reading back the current state
// agree (game.state.verify)
//
// The operation is queued, and happens at the end of the next frame the CPU runs, so follow this
// with something that runs it, like cpu.runUntilTime.
//
// Rewind snapshots are only taken with the RewindSnapshotInterval setting on. A rewind restores the
// newest snapshot and keeps it, so a later rewind comes back to it, but one within 0.8 seconds of
// the last steps further back.
//
// Parameters:
// - path: string, path to the .ppst file.
// - path: string, path to the .ppst file (only for load and save).
// - wait: optional boolean, respond when the operation has finished rather than when queued.
//
// Response (same event name) once queued, or an error if the network or achievements hardcore
// mode refuses it. With wait, it's sent once done instead, with a message string, or an error
// if the load or save failed.
void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) {
const char *event = save ? "game.state.save" : "game.state.load";
// mode refuses it, or there's nothing to rewind to. With wait, it's sent once done instead, with
// message and metadata strings (for a rewind, how long ago the snapshot was taken), or an error
// if the operation failed.
void WebSocketGameState::StateOperation(DebuggerRequest &req, StateOp op) {
static const char *const events[] = { "game.state.load", "game.state.save", "game.state.rewind", "game.state.verify" };
const char *event = events[(int)op];
std::string path;
bool wait = false;
if (!req.ParamString("path", &path) || !req.ParamBool("wait", &wait, DebuggerParamType::OPTIONAL))
if ((op == StateOp::LOAD || op == StateOp::SAVE) && !req.ParamString("path", &path))
return;
if (!req.ParamBool("wait", &wait, DebuggerParamType::OPTIONAL))
return;
// The queued operation holds the only other reference, so Broadcast() can also tell when it's
@@ -98,7 +116,7 @@ void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) {
std::shared_ptr<SaveStateResult> result;
if (wait)
result = std::make_shared<SaveStateResult>();
auto callback = [event, result](SaveState::Status status, std::string_view message, std::string_view) {
auto callback = [event, result](SaveState::Status status, std::string_view message, std::string_view metadata) {
if (status == SaveState::Status::FAILURE) {
ERROR_LOG(Log::System, "%s: %.*s", event, STR_VIEW(message));
}
@@ -106,22 +124,43 @@ void WebSocketGameState::StateLoadSave(DebuggerRequest &req, bool save) {
std::lock_guard<std::mutex> guard(result->lock);
result->status = status;
result->message = message;
result->metadata = metadata;
result->done = true;
}
};
bool running = false;
bool canRewind = true;
bool queued = false;
Core_RunOnCPUThread([&] {
running = PSP_GetBootState() == BootState::Complete;
if (running) {
queued = save ? SaveState::Save(Path(path), -1, callback) : SaveState::Load(Path(path), -1, callback);
if (!running) {
return;
}
switch (op) {
case StateOp::LOAD:
queued = SaveState::Load(Path(path), -1, callback);
break;
case StateOp::SAVE:
queued = SaveState::Save(Path(path), -1, callback);
break;
case StateOp::REWIND:
canRewind = SaveState::CanRewind();
if (canRewind) {
queued = SaveState::Rewind(callback);
}
break;
case StateOp::VERIFY:
queued = SaveState::Verify(callback);
break;
}
});
if (!running)
return req.Fail("Game not running");
if (!canRewind)
return req.Fail("No rewind snapshots");
if (!queued) {
return req.Fail(save ? "Could not queue savestate save" : "Could not queue savestate load");
return req.Fail(std::string("Could not queue ") + event);
}
if (!wait) {
@@ -136,17 +175,22 @@ void WebSocketGameState::Broadcast(net::WebSocketServer *ws) {
for (auto it = waiting_.begin(); it != waiting_.end(); ) {
// Check this first: if the callback ran, it was done before letting go.
bool dropped = it->result.use_count() == 1;
std::lock_guard<std::mutex> guard(it->result->lock);
if (!it->result->done && !dropped) {
// Our own reference, since erasing the entry below must not free the mutex we're holding.
std::shared_ptr<SaveStateResult> result = it->result;
std::lock_guard<std::mutex> guard(result->lock);
if (!result->done && !dropped) {
++it;
continue;
}
bool failed = !it->result->done || it->result->status == SaveState::Status::FAILURE;
bool failed = !result->done || result->status == SaveState::Status::FAILURE;
JsonWriter j;
j.begin();
j.writeString("event", failed ? "error" : it->event);
j.writeString("message", it->result->done ? it->result->message : "Savestate operation was dropped");
j.writeString("message", result->done ? result->message : "Savestate operation was dropped");
if (result->done) {
j.writeString("metadata", result->metadata);
}
if (failed) {
j.writeInt("level", (int)LogLevel::LERROR);
}
@@ -173,8 +217,10 @@ DebuggerSubscriber *WebSocketGameInit(DebuggerEventHandlerMap &map) {
auto p = new WebSocketGameState();
map["game.reset"] = &WebSocketGameReset;
map["game.status"] = &WebSocketGameStatus;
map["game.state.load"] = [p](DebuggerRequest &req) { p->StateLoadSave(req, false); };
map["game.state.save"] = [p](DebuggerRequest &req) { p->StateLoadSave(req, true); };
map["game.state.load"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::LOAD); };
map["game.state.save"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::SAVE); };
map["game.state.rewind"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::REWIND); };
map["game.state.verify"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::VERIFY); };
map["game.speed.get"] = &WebSocketGameSpeedGet;
map["game.speed.set"] = &WebSocketGameSpeedSet;
map["version"] = &WebSocketVersion;
+77 -16
View File
@@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <atomic>
#include <vector>
#include <thread>
#include <mutex>
@@ -98,9 +99,13 @@ static StateRingbuffer rewindStates;
// on some Android devices when we allow many save states.
static double g_lastRescan = 0.0;
static std::map<std::string, uint64_t, std::less<>> g_files; // the value is the mod-time.
// Set when a slot save finishes. That's on the emu thread, so the rescan is left to the next reader.
static std::atomic<bool> g_filesStale;
struct SaveStart {
void DoState(PointerWrap &p);
// Rewind snapshots and verification don't count towards IsStale().
bool countsAsSave = true;
};
enum class OperationType {
@@ -127,6 +132,7 @@ int g_screenshotFailures;
CChunkFileReader::Error SaveToRam(std::vector<u8> &data) {
SaveStart state;
state.countsAsSave = false;
return CChunkFileReader::MeasureAndSavePtr(state, &data);
}
@@ -154,7 +160,7 @@ int g_screenshotFailures;
if (s >= 2) {
// This only increments on save, of course (once, not also in the measuring pass.)
if (p.mode == p.MODE_WRITE) {
if (p.mode == p.MODE_WRITE && countsAsSave) {
++saveStateGeneration;
}
Do(p, saveStateGeneration);
@@ -201,16 +207,26 @@ int g_screenshotFailures;
pspFileSystem.DoState(p);
}
// For an operation that was refused after a warning was already shown, so there's no message.
// The callback still has to hear about it (auto-load tracks failure that way).
static void CallbackRefused(const Callback &callback) {
if (callback) {
callback(Status::FAILURE, "", "");
}
}
bool Enqueue(const SaveState::Operation &op) {
// These two both refuse the operation, and both say so - dropping it silently just
// looks like the hotkey didn't work. Callers that ask the same questions themselves
// return before getting here, so nothing shows a message twice.
if (NetworkWarnUserIfOnlineAndCantSavestate()) {
CallbackRefused(op.callback);
return false;
}
// Hardcore mode bans loading savestates outright, and saving too unless the user opted
// back into that.
if (Achievements::WarnUserIfHardcoreModeActive(op.type == OperationType::Save)) {
CallbackRefused(op.callback);
return false;
}
@@ -245,14 +261,17 @@ int g_screenshotFailures;
return Enqueue(Operation(OperationType::Save, filename, slot, callback));
}
void Verify(Callback callback) {
Enqueue(Operation(OperationType::Verify, Path(), -1, callback));
bool Verify(Callback callback) {
return Enqueue(Operation(OperationType::Verify, Path(), -1, callback));
}
void Rewind(Callback callback) {
bool Rewind(Callback callback) {
// Or a snapshot can be taken just before the rewind runs (after a long pause, say), and the
// rewind then restores that.
rewindStates.NotifyState();
if (coreState == CoreState::CORE_RUNTIME_ERROR)
Core_Break(BreakReason::SavestateRewind, 0);
Enqueue(Operation(OperationType::Rewind, Path(), -1, callback));
return Enqueue(Operation(OperationType::Rewind, Path(), -1, callback));
}
bool CanRewind() {
@@ -407,11 +426,20 @@ int g_screenshotFailures;
}
void LoadSlot(std::string_view gamePrefix, int slot, Callback callback) {
if (NetworkWarnUserIfOnlineAndCantSavestate()) {
// Enqueue() would refuse too, but only after the load undo backup below has replaced the old one.
if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(false)) {
CallbackRefused(callback);
return;
}
Path fn = GenerateSaveSlotPath(gamePrefix, slot, STATE_EXTENSION);
if (!fn.empty() && !File::Exists(fn)) {
if (callback) {
auto sc = GetI18NCategory(I18NCat::SCREEN);
callback(Status::FAILURE, sc->T("LoadStateDoesntExist", sc->T("Failed to load state")), "");
}
return;
}
if (!fn.empty()) {
// This add only 1 extra state, should we just always enable it?
if (g_Config.bEnableStateUndo) {
@@ -474,7 +502,9 @@ int g_screenshotFailures;
}
void SaveSlot(std::string_view gamePrefix, int slot, Callback callback) {
if (NetworkWarnUserIfOnlineAndCantSavestate()) {
// Enqueue() would refuse too, but only after the screenshots below have been rotated.
if (NetworkWarnUserIfOnlineAndCantSavestate() || Achievements::WarnUserIfHardcoreModeActive(true)) {
CallbackRefused(callback);
return;
}
Path fn = GenerateSaveSlotPath(gamePrefix, slot, STATE_EXTENSION);
@@ -494,6 +524,7 @@ int g_screenshotFailures;
DeleteIfExists(fn);
}
File::Rename(fn.WithExtraExtension(".tmp"), fn);
g_filesStale = true;
}
if (callback) {
callback(status, message, "");
@@ -531,6 +562,7 @@ int g_screenshotFailures;
// Swap them so they can undo again to redo. Mistakes happen.
SwapIfExists(shotUndo, shot);
SwapIfExists(fnUndo, fn);
Rescan(gamePrefix);
return true;
}
@@ -585,7 +617,14 @@ int g_screenshotFailures;
g_lastRescan = time_now_d();
}
static void RescanIfStale(std::string_view gamePrefix) {
if (g_filesStale.exchange(false)) {
Rescan(gamePrefix);
}
}
static bool SaveStateFileExists(std::string_view gamePrefix, int slot, const char *extension) {
RescanIfStale(gamePrefix);
return g_files.find(GenerateSaveSlotFilename(gamePrefix, slot, extension)) != g_files.end();
}
@@ -651,6 +690,7 @@ int g_screenshotFailures;
}
int GetNewestSlot(std::string_view gamePrefix) {
RescanIfStale(gamePrefix);
int newestSlot = -1;
int64_t newestTime = 0;
for (int i = 0; i < g_Config.iSaveStateSlotCount; i++) {
@@ -667,6 +707,7 @@ int g_screenshotFailures;
}
int GetOldestSlot(std::string_view gamePrefix) {
RescanIfStale(gamePrefix);
int oldestSlot = -1;
int64_t oldestTime = INT64_MAX;
for (int i = 0; i < g_Config.iSaveStateSlotCount; i++) {
@@ -763,6 +804,17 @@ int g_screenshotFailures;
return copy;
}
// Which build last saved the file, from its header. Shown when a load fails, since a state from
// another version is the usual suspect.
static std::string SavedByVersion(const Path &path) {
std::string title;
std::string gitVersion;
if (CChunkFileReader::GetFileTitle(path, &title, &gitVersion) != CChunkFileReader::ERROR_NONE || gitVersion.empty()) {
return "";
}
return "PPSSPP " + gitVersion;
}
static bool HandleLoadFailure(bool wasRewinding, std::string *metadata) {
if (wasRewinding) {
WARN_LOG(Log::SaveState, "HandleLoadFailure - trying a rewind state.");
@@ -857,6 +909,10 @@ int g_screenshotFailures;
if (!__KernelIsRunning()) {
ERROR_LOG(Log::SaveState, "Savestate failure: Unable to load without kernel, this should never happen.");
// needsProcess is already clear, so anything queued would only run after the next Enqueue.
for (const auto &op : Flush()) {
CallbackRefused(op.callback);
}
return;
}
@@ -871,6 +927,7 @@ int g_screenshotFailures;
// identification has finished and hardcore mode has come up.
if (Achievements::WarnUserIfHardcoreModeActive(op.type == OperationType::Save)) {
WARN_LOG(Log::SaveState, "Dropping queued savestate operation - hardcore mode is active");
CallbackRefused(op.callback);
continue;
}
@@ -917,11 +974,16 @@ int g_screenshotFailures;
g_lastSaveTime = time_now_d();
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
HandleLoadFailure(false, &callbackMetadata);
callbackMessage = std::string(i18nLoadFailure) + ": " + errorString;
ERROR_LOG(Log::SaveState, "Load state failure: %s", errorString.c_str());
// The file was fine, its contents weren't: almost always a state from another version.
// errorString is the section that failed.
callbackMessage = sc->T("LoadStateWrongVersion", i18nLoadFailure);
const std::string version = SavedByVersion(op.path);
callbackMetadata = version.empty() ? "(" + errorString + ")" : version + " (" + errorString + ")";
ERROR_LOG(Log::SaveState, "Load state failure at %s (saved by %s)", errorString.c_str(), version.c_str());
callbackResult = Status::FAILURE;
} else {
callbackMessage = sc->T(errorString, i18nLoadFailure);
callbackMessage = errorString.empty() ? std::string(i18nLoadFailure) : std::string(sc->T(errorString, i18nLoadFailure));
callbackMetadata = SavedByVersion(op.path);
callbackResult = Status::FAILURE;
}
break;
@@ -964,10 +1026,9 @@ int g_screenshotFailures;
case OperationType::Verify:
{
// Its write pass counts as a save, which it isn't.
const int generation = saveStateGeneration;
int tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
saveStateGeneration = generation;
SaveStart verifyState;
verifyState.countsAsSave = false;
int tempResult = CChunkFileReader::Verify(verifyState) == CChunkFileReader::ERROR_NONE;
callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE;
if (tempResult) {
INFO_LOG(Log::SaveState, "Verified save state system");
@@ -994,11 +1055,11 @@ int g_screenshotFailures;
hasLoadedState = true;
Core_ResetException();
} else {
callbackMessage = std::string(i18nLoadFailure) + ": " + errorString;
callbackMessage = std::string(i18nLoadFailure) + " (" + errorString + ")";
callbackResult = Status::FAILURE;
}
} else {
callbackMessage = std::string(i18nLoadFailure) + ": " + errorString;
callbackMessage = i18nLoadFailure;
callbackResult = Status::FAILURE;
}
break;
+2 -2
View File
@@ -100,11 +100,11 @@ namespace SaveState {
// For testing / automated tests. Runs a save state verification pass (async.)
// Warning: callback will be called on a different thread.
void Verify(Callback callback = Callback());
bool Verify(Callback callback = Callback());
// To go back to a previous snapshot (only if enabled.)
// Warning: callback will be called on a different thread.
void Rewind(Callback callback = Callback());
bool Rewind(Callback callback = Callback());
// Returns true if there are rewind snapshots available.
bool CanRewind();
+71 -21
View File
@@ -4,6 +4,7 @@
#include "Core/SaveState.h"
#include "Core/SaveStateRewind.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Config.h"
namespace SaveState {
@@ -18,9 +19,7 @@ CChunkFileReader::Error StateRingbuffer::Save() {
std::lock_guard<std::mutex> guard(lock_);
int n = next_++ % size_;
if ((next_ % size_) == first_)
++first_;
const int n = next_;
std::vector<u8> *compressBuffer = &buffer_;
CChunkFileReader::Error err;
@@ -38,15 +37,35 @@ CChunkFileReader::Error StateRingbuffer::Save() {
if (err == CChunkFileReader::ERROR_NONE) {
ScheduleCompress(&states_[n].stateBuffer, compressBuffer, &bases_[base_]);
states_[n].savedTime = time_now_d();
states_[n].emuTime = CoreTiming::GetGlobalTimeUs() / 1000000.0;
baseMapping_[n] = baseGeneration_[base_];
next_ = (next_ + 1) % size_;
if (count_ < size_) {
count_++;
}
} else {
states_[n].clear();
}
// The state a rewind restored isn't the newest any more, so the next rewind comes back to this one.
newestRestored_ = false;
baseMapping_[n] = baseGeneration_[base_];
// States compressed against a base that has since been replaced can't be decoded any more.
// They're the oldest ones, so drop them from the end.
while (count_ > 0) {
const int oldest = (next_ - count_ + size_) % size_;
if (BaseAlive(baseMapping_[oldest])) {
break;
}
states_[oldest].clear();
count_--;
}
return err;
}
bool StateRingbuffer::BaseAlive(int generation) const {
return generation >= 0 && baseGeneration_[generation % ARRAY_SIZE(bases_)] == generation;
}
CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std::string *metadata) {
// The newest state may still be compressing.
if (compressThread_.joinable())
@@ -58,37 +77,66 @@ CChunkFileReader::Error StateRingbuffer::Restore(std::string *errorString, std::
if (Empty())
return CChunkFileReader::ERROR_BAD_FILE;
int n = (--next_ + size_) % size_;
if (states_[n].empty())
return CChunkFileReader::ERROR_BAD_FILE;
auto pa = GetI18NCategory(I18NCat::PAUSE);
// Restoring a state doesn't use it up: rewind, play on a bit, rewind again, and you're back at the
// same state. Rewinding again quickly steps further back instead (except from the oldest state).
const double now = time_now_d();
const double emuNow = CoreTiming::GetGlobalTimeUs() / 1000000.0;
const bool quick = newestRestored_ && now - lastRestoreTime_ < STEP_BACK_SECONDS;
if (quick) {
if (count_ > 1) {
DropNewest();
}
} else if (!retryingAfterFailure_) {
rewindFromEmuTime_ = emuNow;
}
retryingAfterFailure_ = false;
const int n = (next_ - 1 + size_) % size_;
const int generation = baseMapping_[n];
const int baseSlot = generation < 0 ? -1 : generation % (int)ARRAY_SIZE(bases_);
if (baseSlot < 0 || baseGeneration_[baseSlot] != generation) {
// The base this state was compressed against has since been overwritten, so it can't be
// decoded any more. Only two bases are kept, but the state ring is longer.
WARN_LOG(Log::SaveState, "Rewind: state %d was compressed against a base that's gone", n);
if (states_[n].empty() || !BaseAlive(generation)) {
// Save() drops states whose base is gone, so this shouldn't happen.
WARN_LOG(Log::SaveState, "Rewind: state %d can't be decoded", n);
DropNewest();
return CChunkFileReader::ERROR_BAD_FILE;
}
const int baseSlot = generation % (int)ARRAY_SIZE(bases_);
static std::vector<u8> buffer;
LockedDecompress(buffer, states_[n].stateBuffer, bases_[baseSlot]);
CChunkFileReader::Error error = LoadFromRam(buffer, errorString);
*metadata = pa->T("Rewind");
if (error != CChunkFileReader::ERROR_NONE) {
// Don't come back to it. HandleLoadFailure() tries the next one, and after a partial load the
// clock is meaningless, so that keeps rewindFromEmuTime_.
DropNewest();
retryingAfterFailure_ = true;
return error;
}
newestRestored_ = true;
lastRestoreTime_ = now;
if (states_[n].savedTime) {
auto pa = GetI18NCategory(I18NCat::PAUSE);
*metadata = pa->T("Rewind");
// In emulated time, from where the rewinding started: wall-clock time would count pauses,
// fast-forward, and the time spent in states rewound to earlier.
const double secondsBack = rewindFromEmuTime_ - states_[n].emuTime;
if (secondsBack >= 0.0) {
auto di = GetI18NCategory(I18NCat::DIALOG);
metadata->append(" (");
metadata->append(ApplySafeSubstitutions(di->T("%1 seconds ago"), static_cast<int>(time_now_d() - states_[n].savedTime)));
metadata->append(ApplySafeSubstitutions(di->T("%1 seconds ago"), (int)(secondsBack + 0.5)));
metadata->append(")");
}
rewindLastTime_ = time_now_d();
rewindLastTime_ = now;
return error;
}
void StateRingbuffer::DropNewest() {
next_ = (next_ - 1 + size_) % size_;
states_[next_].clear();
count_--;
newestRestored_ = false;
}
void StateRingbuffer::ScheduleCompress(std::vector<u8> *result, const std::vector<u8> *state, const std::vector<u8> *base) {
if (compressThread_.joinable())
compressThread_.join();
@@ -103,7 +151,7 @@ void StateRingbuffer::ScheduleCompress(std::vector<u8> *result, const std::vecto
void StateRingbuffer::Compress(std::vector<u8> &result, const std::vector<u8> &state, const std::vector<u8> &base) {
std::lock_guard<std::mutex> guard(lock_);
// Bail if we were cleared before locking.
if (first_ == 0 && next_ == 0)
if (count_ == 0)
return;
double start_time = time_now_d();
@@ -159,8 +207,8 @@ void StateRingbuffer::Clear() {
// This lock is mainly for shutdown.
std::lock_guard<std::mutex> guard(lock_);
first_ = 0;
next_ = 0;
count_ = 0;
for (auto &b : bases_) {
b.clear();
}
@@ -176,6 +224,8 @@ void StateRingbuffer::Clear() {
buffer_.clear();
base_ = -1;
baseUsage_ = 0;
newestRestored_ = false;
retryingAfterFailure_ = false;
rewindLastTime_ = time_now_d();
}
+18 -4
View File
@@ -35,7 +35,7 @@ public:
void Clear();
bool Empty() const {
return next_ == first_;
return count_ == 0;
}
void Process();
@@ -44,23 +44,30 @@ public:
double NextStateTimestamp() const;
private:
bool BaseAlive(int generation) const;
void DropNewest();
const int BLOCK_SIZE = 8192;
const int REWIND_NUM_STATES = 20;
// TODO: Instead, based on size of compressed state?
const int BASE_USAGE_INTERVAL = 15;
// A rewind this soon after the last one steps back past the state that one restored.
const double STEP_BACK_SECONDS = 0.8;
typedef std::vector<u8> StateBuffer;
struct RewindState {
StateBuffer stateBuffer;
double savedTime;
// CoreTiming's clock when the state was taken, in seconds.
double emuTime = 0.0;
bool empty() const { return stateBuffer.empty(); }
void clear() { stateBuffer.clear(); savedTime = 0.0; }
void clear() { stateBuffer.clear(); emuTime = 0.0; }
};
int first_ = 0;
// The slot the next save goes in, and how many slots before it hold states we can restore.
int next_ = 0;
int count_ = 0;
int size_;
std::vector<RewindState> states_;
@@ -78,6 +85,13 @@ private:
int baseUsage_ = 0;
double rewindLastTime_ = 0.0f;
// Whether the newest state is the one the last rewind restored, and when that was.
bool newestRestored_ = false;
double lastRestoreTime_ = 0.0;
// CoreTiming's clock when the current run of rewinds started, for "seconds ago".
double rewindFromEmuTime_ = 0.0;
bool retryingAfterFailure_ = false;
};
} // namespace SaveState
+11 -8
View File
@@ -53,6 +53,13 @@ struct PendingScreenshot {
PendingScreenshot g_pendingScreenshot;
// The callback may schedule a retry, so the pending screenshot has to be out of the way before it runs.
static void FailPendingScreenshot() {
PendingScreenshot pending = std::move(g_pendingScreenshot);
g_pendingScreenshot = {};
pending.callback(ScreenshotResult::ScreenshotNotPossible);
}
class JPEGFileStream : public jpge::output_stream {
public:
JPEGFileStream(const Path &filename) {
@@ -329,16 +336,14 @@ bool ScreenshotNotifyEndOfFrame(Draw::DrawContext *draw) {
if (g_display.rotation != DisplayRotation::ROTATE_0) {
GPUDebugBuffer temp;
if (!::GetOutputFramebuffer(draw, temp)) {
g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible);
g_pendingScreenshot = {};
FailPendingScreenshot();
return false;
}
_dbg_assert_msg_(temp.IsBackBuffer(), "temp isn't backbuffer??");
buf = ApplyRotation(temp, g_display.rotation);
} else {
if (!::GetOutputFramebuffer(draw, buf)) {
g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible);
g_pendingScreenshot = {};
FailPendingScreenshot();
return false;
}
}
@@ -359,16 +364,14 @@ bool ScreenshotNotifyPostGameRender(Draw::DrawContext *draw) {
GPUDebugBuffer buf;
if (!gpu) {
ERROR_LOG(Log::System, "Can't take screenshots when GPU not running");
g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible);
g_pendingScreenshot = {};
FailPendingScreenshot();
return false;
}
const int maxRes = g_pendingScreenshot.maxRes;
if (!gpu->GetCurrentFramebuffer(buf, g_pendingScreenshot.type == ScreenshotType::Render ? GPU_DBG_FRAMEBUF_RENDER : GPU_DBG_FRAMEBUF_DISPLAY, maxRes)) {
g_pendingScreenshot.callback(ScreenshotResult::ScreenshotNotPossible);
g_pendingScreenshot = {};
FailPendingScreenshot();
return false;
}
+1 -1
View File
@@ -1768,7 +1768,7 @@ ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) {
} else if (PSP_CoreParameter().frozen) {
std::string errorString;
if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
ERROR_LOG(Log::SaveState, "Failed to load freeze state (failure at %s). Unfreezing.", errorString.c_str());
PSP_CoreParameter().frozen = false;
}
}
+1 -1
View File
@@ -1295,7 +1295,7 @@ Loaded. Game may refuse to save over different savedata. = تم التحميل.
Loaded. Game may refuse to save over newer savedata. = تم التحميل. قد ترفض اللعبة الحفظ فوق بيانات حفظ أحدث. # AI translated
Loaded. Save in game, restart, and load for less bugs. = تم التحميل. احفظ داخل اللعبة، ثم أعد التشغيل وحمّل الحفظ لتقليل المشاكل. # AI translated
LoadStateDoesntExist = Failed to load state: Savestate doesn't exist!
LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP!
LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP!
norewind = No rewind save states available.
Playing = جاري اللعب
PressESC = ‎إضغط خروج للدخول علي قائمة التوقف المؤقت.
+1 -1
View File
@@ -1283,7 +1283,7 @@ Loaded. Game may refuse to save over different savedata. = Yükləndi. Oyun, ba
Loaded. Game may refuse to save over newer savedata. = Yükləndi. Oyun, yeni qorunuş veriləni üzərinə qorumağı rədd edə bilər.
Loaded. Save in game, restart, and load for less bugs. = Yükləndi. Daha az yanlış üçün, oyunda qorunun, yenidən başladın və yükləyin.
LoadStateDoesntExist = Durum yüklənişi uğursuz oldu: Qorunmuş durum yoxdur!
LoadStateWrongVersion = Durum yüklənişi uğursuz oldu: Qorunmuş durum köhnə PPSSPP sürümü üçündür!
LoadStateWrongVersion = Durum yüklənişi uğursuz oldu: Qorunmuş durum daha yeni PPSSPP sürümündəndir!
norewind = Geri çəkiş durum qorunuşu əlçatan deyil.
Playing = Oynayır
PressESC = Ara seçməsini açmaq üçün ESC'ni basın.
+1 -1
View File
@@ -1282,7 +1282,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r
Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata.
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
LoadStateDoesntExist = Failed to load state: Savestate doesn't exist!
LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP!
LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP!
norewind = No rewind save states available.
Playing = Playing
PressESC = Press ESC to open the pause menu.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Неуспех при зареждане на състояние: Записа не съществува!
LoadStateWrongVersion = Неуспех при зареждане на състояние: Записа е за по-стара версия на PPSSPP!
LoadStateWrongVersion = Неуспех при зареждане на състояние: Записа е от по-нова версия на PPSSPP!
norewind = No rewind save states available.
Playing = Playing
PressESC = Натиснете ESC за да отворите pause menu-то.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Carregat. El joc pot
Loaded. Game may refuse to save over newer savedata. = Carregat. El joc pot no guardar sobre dades més actuals.
Loaded. Save in game, restart, and load for less bugs. = Carregat. Guarda la partida, reinicia, i recarrega per a menys errors.
LoadStateDoesntExist = Error en carregar l'estat: el fitxer no existeix.
LoadStateWrongVersion = Error en carregar l'estat: el fitxer és d'una versió anterior del PPSSPP.
LoadStateWrongVersion = Error en carregar l'estat: el fitxer és d'una versió més nova del PPSSPP.
norewind = No hi ha estats desats de rebobinatge disponibles.
Playing = Reproduint...
PressESC = Prem Esc per obrir el menú de pausa.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Nepodařilo se načíst stav: Uložený stav neexistuje!
LoadStateWrongVersion = Nepodařilo se načíst stav: Stav je pro starší verzi PPSSPP!
LoadStateWrongVersion = Nepodařilo se načíst stav: Stav je z novější verze PPSSPP!
norewind = Nejsou dostupné žádné uložené stavy pro přetočení.
Playing = Playing
PressESC = Stiskněte ESC k otevření nabídky při pozastavení.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Kunne ikke hente status: Spil-status eksisterer ikke!
LoadStateWrongVersion = Kunne ikke hente stauts: Spil-status er for en ældre version af PPSSPP!
LoadStateWrongVersion = Kunne ikke hente stauts: Spil-status er fra en nyere version af PPSSPP!
norewind = Ingen status for fortrydelse er tilgængelig.
Playing = Playing
PressESC = Tryk ESC for at åbne pause menu.
+1 -1
View File
@@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Geladen. Das Spiel ka
Loaded. Game may refuse to save over newer savedata. = Geladen. Das Spiel weigert sich möglicherweise, über neuere gespeicherte Daten zu speichern.
Loaded. Save in game, restart, and load for less bugs. = Geladen. Im Spiel speichern, neu starten und laden, um Fehler zu vermeiden.
LoadStateDoesntExist = Fehler beim Laden des Spielstands: Spielstand existiert nicht!
LoadStateWrongVersion = Fehler beim Laden des Spielstands: Spielstand ist für eine alte Version von PPSSPP!
LoadStateWrongVersion = Fehler beim Laden des Spielstands: Spielstand stammt von einer neueren Version von PPSSPP!
norewind = Keine Rückspulfunktion verfügbar
Playing = Spiele
PressESC = Drücke ESC, um das Pausemenü zu öffnen
+1 -1
View File
@@ -1313,7 +1313,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r
Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata.
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
LoadStateDoesntExist = Failed to load state: Savestate doesn't exist!
LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP!
LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP!
norewind = No rewind save states available.
Playing = Playing
PressESC = Press ESC to open the pause menu.
+1 -1
View File
@@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = Cargado. El juego pue
Loaded. Game may refuse to save over newer savedata. = Cargado. El juego puede negarse a guardar datos guardados más recientes.
Loaded. Save in game, restart, and load for less bugs. = Cargado. Guarda el juego, reinicia y carga para evitar errores.
LoadStateDoesntExist = Error al cargar el estado: ¡El estado guardado no existe!
LoadStateWrongVersion = Error al cargar el estado: ¡El estado guardado es para una versión anterior de PPSSPP!
LoadStateWrongVersion = Error al cargar el estado: ¡El estado guardado es de una versión más reciente de PPSSPP!
norewind = No hay estados de rebobinado guardados disponibles.
Playing = Reproduciendo...
PressESC = Presiona Esc para abrir el menú de pausa.
+1 -1
View File
@@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = ‎!خطا در بارگیری: هیچ ذخیره ی سریعی پیدا نشد
LoadStateWrongVersion = ‎!است PPSSPP خطا در بارگیری: ذخیره موجود برای ورژن‌های پایین تر
LoadStateWrongVersion = ‎!است PPSSPP خطا در بارگیری: ذخیره موجود برای ورژن‌های بالاتر
norewind = No rewind save states available.
Playing = Playing
PressESC = ‎.را برای منوی سریع فشار دهید ESC دکمه
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Ladattu. Peli saattaa
Loaded. Game may refuse to save over newer savedata. = Ladattu. Peli saattaa kieltäytyä tallentamasta uudelle tallennustiedolle.
Loaded. Save in game, restart, and load for less bugs. = Ladattu. Tallenna pelissä, käynnistä uudelleen ja lataa peli, vähentääksesi virheitä.
LoadStateDoesntExist = Tilatallennuksen lataaminen epäonnistui: Tallennusta ei ole olemassa!
LoadStateWrongVersion = Tilatallennuksen lataaminen epäonnistui: Tallennus on vanhemmalle PPSSPP-versiolle!
LoadStateWrongVersion = Tilatallennuksen lataaminen epäonnistui: Tallennus on uudemmasta PPSSPP-versiosta!
norewind = Takaisinkelauksen tilatallennuksia ei ole saatavilla.
Playing = Playing
PressESC = Press ESC to open the pause menu.
+1 -1
View File
@@ -1314,7 +1314,7 @@ Loaded. Game may refuse to save over different savedata. = Chargé. Le jeu peut
Loaded. Game may refuse to save over newer savedata. = Chargé. Le jeu peut refuser d'écraser les données de sauvegarde plus récentes.
Loaded. Save in game, restart, and load for less bugs. = Chargé.\nSauvegarder dans le jeu, redémarrer,\net charger la sauvegarde pour moins de bugs.
LoadStateDoesntExist = Échec du chargement de l'état :\nl'état n'existe pas !
LoadStateWrongVersion = Échec du chargement de l'état :\nl'état appartient à une ancienne version de PPSSPP !
LoadStateWrongVersion = Échec du chargement de l'état :\nl'état provient d'une version plus récente de PPSSPP !
norewind = Pas d'instantané de rembobinage disponible.
Playing = En jeu
PressESC = Appuyer sur "Échap" pour ouvrir le menu Pause.
+1 -1
View File
@@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Erro ó cargar o estado: non existe o arquivo.
LoadStateWrongVersion = Erro ó cargar o estado: o arquivo é dunha versión anterior de PPSSPP.
LoadStateWrongVersion = Erro ó cargar o estado: o arquivo é dunha versión máis nova de PPSSPP.
norewind = Non hai estados gardados de rebobinado dispoñibles.
Playing = Playing
PressESC = Presiona Esc para abrir o menú de pausa.
+1 -1
View File
@@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Φορτώθηκε. Αποθηκεύστε στο παιχνίδι, κάντε επανεκκίνηση και φορτώστε για λιγότερα σφάλματα.
LoadStateDoesntExist = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης δεν υπάρχει!
LoadStateWrongVersion = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης είναι για μία παλαιότερη έκδοση του PPSSPP!
LoadStateWrongVersion = Σφάλμα φόρτωσης σημείου: Το σημείο αποθήκευσης είναι από μία νεότερη έκδοση του PPSSPP!
norewind = Κανένα σημείο αποθήκευσης rewind διαθέσιμο.
Playing = Παίζει
PressESC = Πατήστε το ESC για να ανοίξετε το μενού παύσης.
+1 -1
View File
@@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = לא הצליח לטעון את המצב: המצב שמור אינו קיים!
LoadStateWrongVersion = לא הצליח לטעון את המצב: המצב הוא שמירה שמיועדת לגרסה ישנה יותר של PPSSPP!
LoadStateWrongVersion = לא הצליח לטעון את המצב: המצב הוא שמירה מגרסה חדשה יותר של PPSSPP!
norewind = No rewind save states available.
Playing = Playing
PressESC = לחץ על ESC כדי לפתוח את התפריט.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Učitano. Spremi u igri, ponovo pokreni, i učitaj za manje bugova.
LoadStateDoesntExist = Nije moguće učitati state: Savestate ne postoji!
LoadStateWrongVersion = Nije moguće učitati state: Savestate je za stariju verziju PPSSPP-a!
LoadStateWrongVersion = Nije moguće učitati state: Savestate je iz novije verzije PPSSPP-a!
norewind = Rewind save state-ovi nisu dostupni.
Playing = Playing
PressESC = Pritisni ESC za otvaranje izbornika.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Betöltve. Lehet, hog
Loaded. Game may refuse to save over newer savedata. = Betöltve. Lehet, hogy a játék nem fog felülírni újabb mentéseket.
Loaded. Save in game, restart, and load for less bugs. = Betöltve. Ments a játékban, indítsd újra, és töltsd be a mentést kevesebb hibáért!
LoadStateDoesntExist = Hiba az állapotmentés betöltése közben: Az állapotmentés nem létezik!
LoadStateWrongVersion = Hiba az állapotmentés betöltése közben: Az állapotmentés egy régebbi PPSSPP verzióhoz való!
LoadStateWrongVersion = Hiba az állapotmentés betöltése közben: Az állapotmentés egy újabb PPSSPP verzióból származik!
norewind = Nem érhető el visszatekerési állapotmentés.
Playing = Lejátszás
PressESC = Nyomd meg az ESC-et a menühöz.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Termuat. Game mungkin
Loaded. Game may refuse to save over newer savedata. = Termuat. Game mungkin menolak untuk menyimpan lebih dari simpanan data yang lebih baru.
Loaded. Save in game, restart, and load for less bugs. = Termuat. Simpan dalam game, mulai ulang, dan muat untuk mengurangi kesalahan.
LoadStateDoesntExist = Gagal memuat save state game: Savestate tidak ada!
LoadStateWrongVersion = Gagal memuat save state game: Savestate ini untuk versi lama PPSSPP!
LoadStateWrongVersion = Gagal memuat save state game: Savestate ini dari versi PPSSPP yang lebih baru!
norewind = Tidak ada save state lama yang tersedia.
Playing = Bermain
PressESC = Tekan ESC untuk membuka menu jeda.
+1 -1
View File
@@ -1290,7 +1290,7 @@ GLToolsWarning = AVVISO: rilevato GLTools, potrebbe causare problemi
In menu = Nei menu
Loaded State = Stato Caricato
LoadStateDoesntExist = Caricamento stato fallito: lo stato non esiste!
LoadStateWrongVersion = Caricamento stato fallito: lo stato è di una versione precedente di PPSSPP!
LoadStateWrongVersion = Caricamento stato fallito: lo stato è di una versione più recente di PPSSPP!
norewind = Nessun riavvolgimento degli stati salvati disponibile.
Playing = Sta giocando
PressESC = Premere ESC per aprire il Menu Pausa
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = ロードしました
Loaded. Game may refuse to save over newer savedata. = ロードしました。最新のセーブデータがある場合セーブが拒否される場合があります。
Loaded. Save in game, restart, and load for less bugs. = ロードしました。バグの発生を減らすためにゲーム中でセーブを行い、再起動してロードしてください。
LoadStateDoesntExist = ステートのロードに失敗しました: セーブステートがありません。
LoadStateWrongVersion = ステートのロードに失敗しました: 古いバージョンのセーブステートです。
LoadStateWrongVersion = ステートのロードに失敗しました: 新しいバージョンのセーブステートです。
norewind = 巻き戻すセーブステートがありません。
Playing = プレイ中
PressESC = ESCを押すと一時停止メニューを開きます。
+1 -1
View File
@@ -1282,7 +1282,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Gagal memuat: File simpen-cepet ora kasedhiya!
LoadStateWrongVersion = Gagal memuat: File simpen-cepet iki kanggi versi lawas PPSSPP!
LoadStateWrongVersion = Gagal memuat: File simpen-cepet iki saka versi PPSSPP sing luwih anyar!
norewind = No rewind save states available.
Playing = Playing
PressESC = Tekan ESC kanggo ing Menu.
+1 -1
View File
@@ -1280,7 +1280,7 @@ Loaded. Save in game, restart, and load for less bugs. =ផ្ទុក។ រ
Loaded. Game may refuse to save over different savedata. =ផ្ទុក។ ហ្គេមអាចបដិសេធមិនរក្សាទុកទិន្នន័យរក្សាទុកផ្សេងៗគ្នា។
Loaded. Game may refuse to save over newer savedata. =ផ្ទុក។ ហ្គេមអាចបដិសេធមិនរក្សាទុកទិន្នន័យរក្សាទុកថ្មីជាងនេះ។
LoadStateDoesntExist =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate មិនមានទេ!
LoadStateWrongVersion =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate គឺសម្រាប់កំណែចាស់របស់ PPSSPP!
LoadStateWrongVersion =បរាជ័យក្នុងការផ្ទុកស្ថានភាព៖ Savestate គឺមកពីកំណែថ្មីជាងរបស់ PPSSPP!
norewind =មិន​មាន​ស្ថានភាព​រក្សា​ទុក​ថយក្រោយ​ទេ។
Playing =កំពុងលេង
PressESC =ចុច ESC ដើម្បីបើកម៉ឺនុយផ្អាក។
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Save in game, restart, and load for less bugs. = 불러왔습니다. 버
Loaded. Game may refuse to save over different savedata. = 불러왔습니다. 게임이 다른 저장 데이터에 대한 저장을 거부할 수 있습니다.
Loaded. Game may refuse to save over newer savedata. = 불러왔습니다. 게임이 최신 저장 데이터에 대한 저장을 거부할 수 있습니다.
LoadStateDoesntExist = 상태 불러오기 실패: 저장상태가 존재하지 않습니다!
LoadStateWrongVersion = 상태 불러오기 실패: 저장상태는 이전 버전의 PPSSPP용입니다!
LoadStateWrongVersion = 상태 불러오기 실패: 저장상태는 더 새로운 버전의 PPSSPP에서 만들어졌습니다!
norewind = 되감기 저장 상태를 사용할 수 없습니다.
Playing = 게임 중
PressESC = 일시 중지 메뉴를 열려면 ESC를 누르세요.
+1 -1
View File
@@ -1296,7 +1296,7 @@ Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, r
Loaded. Game may refuse to save over different savedata. = Loaded. Game may refuse to save over different savedata.
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
LoadStateDoesntExist = Failed to load state: Savestate doesn't exist!
LoadStateWrongVersion = Failed to load state: Savestate is for an older version of PPSSPP!
LoadStateWrongVersion = Failed to load state: Savestate is from a newer version of PPSSPP!
norewind = No rewind save states available.
Playing = Playing
PressESC = Press ESC to open the pause menu.
+1 -1
View File
@@ -1282,7 +1282,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກບໍ່ຖືກຕ້ອງ!
LoadStateWrongVersion = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກແມ່ນເວີຊັ່ນເກົ່າຂອງ PPSSPP!
LoadStateWrongVersion = ລົ້ມເຫຼວໃນການໂຫຼດໄຟລ໌ບັນທຶກ: ໄຟລ໌ບັນທຶກແມ່ນມາຈາກເວີຊັ່ນໃໝ່ກວ່າຂອງ PPSSPP!
norewind = ບໍ່ມີຂໍ້ມູນໄຟລ໌ບັນທຶກແບບຍ້ອນຫຼັງ.
Playing = Playing
PressESC = ກົດ ESC ເພື່ອເປີດເມນູຢຸດຊົ່ວຄາວ
+1 -1
View File
@@ -1284,7 +1284,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Nepavyko pakrauti statuso: išsaugojimo statusas neegzistuoja!
LoadStateWrongVersion = Nepavyko pakrauti statuso: išsaugojimo statusas skirtas senesniai PPSSPP versijai!
LoadStateWrongVersion = Nepavyko pakrauti statuso: išsaugojimo statusas yra iš naujesnės PPSSPP versijos!
norewind = No rewind save states available.
Playing = Playing
PressESC = Paspauskite ESC, kad atidarytumėte pauzės meniu.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Dimuatkan. Permainan
Loaded. Game may refuse to save over newer savedata. = Dimuatkan. Permainan mungkin enggan menyimpan di atas data simpanan baharu.
Loaded. Save in game, restart, and load for less bugs. = Dimuatkan. Simpan dalam permainan, mula semula, dan muat untuk mengurangkan bug.
LoadStateDoesntExist = Gagal memuatkan state: Savestate tidak ditemui!
LoadStateWrongVersion = Gagal memuatkan state: Savestate adalah untuk versi PPSSPP yang lebih lama!
LoadStateWrongVersion = Gagal memuatkan state: Savestate adalah daripada versi PPSSPP yang lebih baharu!
norewind = Tiada keadaan simpanan putar semula tersedia.
Playing = Dimainkan
PressESC = Tekan ESC untuk membuka menu jeda.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Laden mislukt: Savestate bestaat niet!
LoadStateWrongVersion = Laden mislukt: Savestate is voor een oudere versie van PPSSPP!
LoadStateWrongVersion = Laden mislukt: Savestate is van een nieuwere versie van PPSSPP!
norewind = Geen savestates beschikbaar voor terugspoelen.
Playing = Playing
PressESC = Druk op ESC om het pauzemenu te openen
+1 -1
View File
@@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Lasta inn. Lagre i spel
Loaded. Game may refuse to save over different savedata. = Lasta inn. Spelet kan nekte å lagre over andre lagringsdata.
Loaded. Game may refuse to save over newer savedata. = Lasta inn. Spelet kan nekte å lagre over nyare lagringsdata.
LoadStateDoesntExist = Klarte ikkje å laste inn tilstand: Lagringstilstanden finst ikkje!
LoadStateWrongVersion = Klarte ikkje å laste inn tilstand: Lagringstilstanden er for ein eldre versjon av PPSSPP!
LoadStateWrongVersion = Klarte ikkje å laste inn tilstand: Lagringstilstanden er frå ein nyare versjon av PPSSPP!
norewind = Ingen tilbakespolingstilstandar tilgjengelege.
Playing = Spelar
PressESC = Trykk ESC for å opne pausemenyen.
+1 -1
View File
@@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Lastet inn. Lagre i spi
Loaded. Game may refuse to save over different savedata. = Lastet inn. Spillet kan nekte å lagre over andre lagringsdata.
Loaded. Game may refuse to save over newer savedata. = Lastet inn. Spillet kan nekte å lagre over nyere lagringsdata.
LoadStateDoesntExist = Mislyktes å laste: Lagret fil eksisterer ikke!
LoadStateWrongVersion = Mislyktes å laste: Lagret fil er for en eldre versjon av PPSSPP!
LoadStateWrongVersion = Mislyktes å laste: Lagret fil er fra en nyere versjon av PPSSPP!
norewind = Ingen tilbakespolingstilstander tilgjengelig.
Playing = Spiller
PressESC = Trykk ESC for å åpne pausemenyen.
+1 -1
View File
@@ -1295,7 +1295,7 @@ Loaded. Game may refuse to save over different savedata. = Wczytano. Gra może o
Loaded. Game may refuse to save over newer savedata. = Wczytano. Gra może odmówić nadpisywania nowszych zapisów.
Loaded. Save in game, restart, and load for less bugs. = Wczytano. By zmniejszyć ryzyko potencjalnych problemów, zapisz stan w grze, zrestartuj ją i wczytaj.
LoadStateDoesntExist = Nie można wczytać stanu: Zapis stanu nie istnieje!
LoadStateWrongVersion = Nie można wczytać stanu: Zapis stanu przeznaczony jest dla starszej wersji PPSSPP!
LoadStateWrongVersion = Nie można wczytać stanu: Zapis stanu pochodzi z nowszej wersji PPSSPP!
norewind = Brak zapisów stanów potrzebnych do przewinięcia.
Playing = W grze
PressESC = Naciśnij klawisz ESC, aby otworzyć menu pauzy.
+1 -1
View File
@@ -1310,7 +1310,7 @@ Loaded. Save in game, restart, and load for less bugs. = Carregado. Salve no jog
Loaded. Game may refuse to save over different savedata. = Carregado. O jogo pode recusar a salvar sobre os dados diferentes dos salvamentos.
Loaded. Game may refuse to save over newer savedata. = Carregado. O jogo pode recusar a salvar sobre os dados mais novos dos salvamentos.
LoadStateDoesntExist = Falhou em carregar o state: O state salvo não existe!
LoadStateWrongVersion = Falhou em carregar o state: O state salvo é de uma versão mais velha do PPSSPP!
LoadStateWrongVersion = Falhou em carregar o state: O state salvo é de uma versão mais nova do PPSSPP!
norewind = Não há states salvos disponíveis pra retroceder.
Playing = Jogando
PressESC = Pressione ESC pra abrir o menu da pausa.
+1 -1
View File
@@ -1312,7 +1312,7 @@ Loaded. Save in game, restart, and load for less bugs. = Carregado. Salva no jog
Loaded. Game may refuse to save over different savedata. = Carregado. O jogo poderá recusar-se a salvar sobre dados salvos diferentes.
Loaded. Game may refuse to save over newer savedata. = Carregado. O jogo poderá recusar-se a salvar sobre dados salvos mais novos.
LoadStateDoesntExist = Erro ao carregar o estado: O Estado salvo não existe!
LoadStateWrongVersion = Erro ao carregar o estado: O Estado salvo é de uma versão mais antiga da PPSSPP!
LoadStateWrongVersion = Erro ao carregar o estado: O Estado salvo é de uma versão mais recente da PPSSPP!
norewind = Não há estados salvos disponíveis para retroceder.
Playing = A jogar
PressESC = Pressione a tecla Esc para abrir o menu de pausa.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Loaded. Save in game, restart, and load for less bugs.
LoadStateDoesntExist = Eroare la încărcare de stare: Starea nu există!
LoadStateWrongVersion = Eroare la încărcare de stare: Starea e pt. o versiune PPSSPP mai veche!
LoadStateWrongVersion = Eroare la încărcare de stare: Starea e de la o versiune PPSSPP mai nouă!
norewind = Fără stări salvare disponibile.
Playing = Playing
PressESC = Apasă ESC pt. a deschide meniul pauză.
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Game may refuse to save over different savedata. = Загружено.
Loaded. Game may refuse to save over newer savedata. = Загружено. Игра может отказаться сохранять поверх более новых сохранений.
Loaded. Save in game, restart, and load for less bugs. = Загружено. Сохранитесь в игре, перезапустите игру и загрузите для исправления ошибок.
LoadStateDoesntExist = Не удалось загрузить состояние: файл не существует!
LoadStateWrongVersion = Не удалось загрузить состояние: файл состояния от старой версии PPSSPP!
LoadStateWrongVersion = Не удалось загрузить состояние: файл состояния от более новой версии PPSSPP!
norewind = Нет доступных состояний для возврата.
Playing = В игре
PressESC = Нажмите ESC, чтобы открыть меню паузы.
+1 -1
View File
@@ -1289,7 +1289,7 @@ Loaded. Game may refuse to save over different savedata. = Inläst. Spelet kan v
Loaded. Game may refuse to save over newer savedata. = Inläst. Spelet kan vägra att skriva över nyare sparade data.
Loaded. Save in game, restart, and load for less bugs. = Inläst. Spara i spelet, starta om och läs in igen för att minska risken för fel.
LoadStateDoesntExist = Tillståndet kunde inte läsas in: det sparade tillståndet finns inte!
LoadStateWrongVersion = Tillståndet kunde inte läsas in: det är från en annan PPSSPP-version!
LoadStateWrongVersion = Tillståndet kunde inte läsas in: det är från en nyare PPSSPP-version!
norewind = Det finns inga sparade tillstånd att spola tillbaka till.
Playing = Spelar
PressESC = Tryck på Esc för att öppna pausmenyn.
+1 -1
View File
@@ -1285,7 +1285,7 @@ Loaded. Game may refuse to save over different savedata. = Nakarga. Maaaring tum
Loaded. Game may refuse to save over newer savedata. = Nakarga. Maaaring tumangging mag-save ang laro sa mas bagong savedata.
Loaded. Save in game, restart, and load for less bugs. = Nakarga. Mag-save sa laro, mag-restart, at mag-load para maiwasan ang mga bugs.
LoadStateDoesntExist = May Problema sa load state: Walang Savestate!
LoadStateWrongVersion = May Problema sa load state: Ang savestate na eto ay para sa lumang bersiyon ng PPSSPP!
LoadStateWrongVersion = May Problema sa load state: Ang savestate na eto ay mula sa mas bagong bersiyon ng PPSSPP!
norewind = Walang available na rewind save states.
Playing = Naglalaro
PressESC = Pindutin ang ESC para mabuksan ang pause menu.
+1 -1
View File
@@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = Yüklendi. Oyun belki
Loaded. Game may refuse to save over newer savedata. = Yüklendi. Oyun belki yeni verilerin üzerine kaydetmeyi reddedebilir.
Loaded. Save in game, restart, and load for less bugs. = Yüklendi. Daha az bug için oyunda kaydedin, yeniden başlatın ve yükleyin.
LoadStateDoesntExist = Durum yüklenemedi: Durum kaydı yerinde yok!
LoadStateWrongVersion = Durum yüklenemedi: Durum kaydı eski bir PPSSPP versiyonu için uygun!
LoadStateWrongVersion = Durum yüklenemedi: Durum kaydı daha yeni bir PPSSPP versiyonundan!
norewind = Geriye sarma durumu bulunamadı.
Playing = Oynatılıyor...
PressESC = PPSSPP menüsünü açmak için ESC'ye basın.
+1 -1
View File
@@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = Завантажено. Збережіться в грі, перезапустіть гру і завантажте для виправлення помилок.
LoadStateDoesntExist = Не вдається завантажити стан: файла не існує!
LoadStateWrongVersion = Не вдається завантажити стан: файл стану від старої версії PPSSPP!
LoadStateWrongVersion = Не вдається завантажити стан: файл стану від новішої версії PPSSPP!
norewind = Немає доступних збережень для повернення.
Playing = У грі
PressESC = Натисніть ESC для переходу в меню
+1 -1
View File
@@ -1286,7 +1286,7 @@ Loaded. Game may refuse to save over different savedata. = Loaded. Game may refu
Loaded. Game may refuse to save over newer savedata. = Loaded. Game may refuse to save over newer savedata.
Loaded. Save in game, restart, and load for less bugs. = loaded. lưu trong game, khởi động lại và tải để ít lỗi hơn.
LoadStateDoesntExist = Không load được: không có file save!
LoadStateWrongVersion = Không load được: file save là của một phiên bản cũ của PPSSPP!
LoadStateWrongVersion = Không load được: file save là của một phiên bản mới hơn của PPSSPP!
norewind = Không tua lại được, trạng thái không có sẵn..
Playing = Playing
PressESC = Nhấn ESC để mở menu.
+1 -1
View File
@@ -1288,7 +1288,7 @@ Loaded. Game may refuse to save over different savedata. = 已读取,游戏可
Loaded. Game may refuse to save over newer savedata. = 已读取,游戏可能无法保存在进度提前的存档。
Loaded. Save in game, restart, and load for less bugs. = 已读取,请在游戏中存档并重启, 再次读档以减少bug。
LoadStateDoesntExist = 即时存档读取失败:存档不存在
LoadStateWrongVersion = 即时存档读取失败:存档是旧版本PPSSPP的
LoadStateWrongVersion = 即时存档读取失败:存档来自较新版本的PPSSPP
norewind = 没有可用的倒带自动即时存档。
Playing = 游戏中
PressESC = 按下ESC键打开暂停菜单。
+1 -1
View File
@@ -1287,7 +1287,7 @@ Loaded. Save in game, restart, and load for less bugs. = 已載入,在遊戲
Loaded. Game may refuse to save over different savedata. = 已載入,遊戲可能會拒絕覆寫不同的存檔
Loaded. Game may refuse to save over newer savedata. = 已載入,遊戲可能會拒絕覆寫較新的存檔
LoadStateDoesntExist = 無法載入存檔:存檔不存在!
LoadStateWrongVersion = 無法載入存檔:存檔僅適用於舊版 PPSSPP!
LoadStateWrongVersion = 無法載入存檔:存檔來自較新版本的 PPSSPP!
norewind = 沒有可用的倒轉存檔
Playing = 正在遊玩
PressESC = 按下 ESC 以開啟暫停選單
+1 -1
View File
@@ -271,7 +271,7 @@ file - this is just an index.
| Category | Events | File |
|---|---|---|
| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` |
| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file, applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.state.rewind` (the same for the newest rewind snapshot, which it keeps, so only a rewind within 0.8 seconds of the last steps further back; needs `RewindSnapshotInterval` set, and the `wait` response's `metadata` says how far back that is in emulated time), `game.state.verify` (the same for checking that saving the current state and reading it back agree), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `version` | `GameSubscriber.cpp` |
| CPU core | `cpu.stepping`, `cpu.resume`, `cpu.status` (reports `ticks` plus `us`, emulated microseconds, and `clockHz` - use `us` to line up with wall-clock timings, since games change the clock frequency and the ticks-per-second ratio isn't fixed), `cpu.getAllRegs`, `cpu.getReg`, `cpu.setReg`, `cpu.evaluate` | `CPUCoreSubscriber.cpp` |
| Stepping | `cpu.stepInto`, `cpu.stepOver`, `cpu.stepOut`, `cpu.runUntil`, `cpu.runUntilTime` (run until a point in emulated time - `us` absolute or `relativeUs` from now - and break there; this is how to get a scripted repro reproducibly "N seconds into the game" instead of polling `cpu.status` in a loop), `cpu.nextHLE` | `SteppingSubscriber.cpp` |
| Breakpoints | `cpu.breakpoint.add/update/remove/list`, `memory.breakpoint.add/update/remove/list`, `cpu.regBreakpoint.add/update/remove/list` (break when a register is written to, by any instruction anywhere - currently GPRs only; interpreter-only, no effect under a JIT backend) | `BreakpointSubscriber.cpp` |
+6 -2
View File
@@ -1390,10 +1390,14 @@ int main(int argc, const char* argv[]) {
if (stateToLoad) {
g_stateLoadPending = true;
// Queued now, actually applied by SaveState::Process() once the game is up and running.
SaveState::Load(Path(stateToLoad), -1, [](SaveState::Status status, std::string_view message, std::string_view) {
SaveState::Load(Path(stateToLoad), -1, [](SaveState::Status status, std::string_view message, std::string_view metadata) {
// The message already reads as a full sentence, e.g. "Failed to load state: <reason>".
fprintf(stderr, "%.*s\n", (int)message.size(), message.data());
fprintf(stderr, "%.*s\n", STR_VIEW(message));
if (status == SaveState::Status::FAILURE) {
// Which version saved the state.
if (!metadata.empty()) {
fprintf(stderr, "%.*s\n", STR_VIEW(metadata));
}
g_stateLoadFailed = true;
}
g_stateLoadPending = false;