1
// Copyright (c) 2012- PPSSPP Project.
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
21
#include "base/mutex.h"
22
#include "base/timeutil.h"
23
#include "i18n/i18n.h"
25
#include "Common/FileUtil.h"
26
#include "Common/ChunkFile.h"
28
#include "Core/SaveState.h"
29
#include "Core/Config.h"
30
#include "Core/Core.h"
31
#include "Core/CoreTiming.h"
32
#include "Core/Host.h"
33
#include "Core/Screenshot.h"
34
#include "Core/System.h"
35
#include "Core/FileSystems/MetaFileSystem.h"
36
#include "Core/ELF/ParamSFO.h"
37
#include "Core/HLE/HLE.h"
38
#include "Core/HLE/sceDisplay.h"
39
#include "Core/HLE/ReplaceTables.h"
40
#include "Core/HLE/sceKernel.h"
41
#include "Core/MemMap.h"
42
#include "Core/MIPS/MIPS.h"
43
#include "Core/MIPS/JitCommon/JitBlockCache.h"
44
#include "HW/MemoryStick.h"
45
#include "GPU/GPUState.h"
51
void DoState(PointerWrap &p);
60
SAVESTATE_SAVE_SCREENSHOT,
65
Operation(OperationType t, const std::string &f, Callback cb, void *cbUserData_)
66
: type(t), filename(f), callback(cb), cbUserData(cbUserData_)
76
CChunkFileReader::Error SaveToRam(std::vector<u8> &data) {
78
size_t sz = CChunkFileReader::MeasurePtr(state);
81
return CChunkFileReader::SavePtr(&data[0], state);
84
CChunkFileReader::Error LoadFromRam(std::vector<u8> &data) {
86
return CChunkFileReader::LoadPtr(&data[0], state);
89
struct StateRingbuffer
91
StateRingbuffer(int size) : first_(0), next_(0), size_(size), base_(-1)
94
baseMapping_.resize(size);
97
CChunkFileReader::Error Save()
99
int n = next_++ % size_;
100
if ((next_ % size_) == first_)
103
static std::vector<u8> buffer;
104
std::vector<u8> *compressBuffer = &buffer;
105
CChunkFileReader::Error err;
107
if (base_ == -1 || ++baseUsage_ > BASE_USAGE_INTERVAL)
109
base_ = (base_ + 1) % ARRAY_SIZE(bases_);
111
err = SaveToRam(bases_[base_]);
112
// Let's not bother savestating twice.
113
compressBuffer = &bases_[base_];
116
err = SaveToRam(buffer);
118
if (err == CChunkFileReader::ERROR_NONE)
119
Compress(states_[n], *compressBuffer, bases_[base_]);
122
baseMapping_[n] = base_;
126
CChunkFileReader::Error Restore()
128
// No valid states left.
130
return CChunkFileReader::ERROR_BAD_FILE;
132
int n = (--next_ + size_) % size_;
133
if (states_[n].empty())
134
return CChunkFileReader::ERROR_BAD_FILE;
136
static std::vector<u8> buffer;
137
Decompress(buffer, states_[n], bases_[baseMapping_[n]]);
138
return LoadFromRam(buffer);
141
void Compress(std::vector<u8> &result, const std::vector<u8> &state, const std::vector<u8> &base)
144
for (size_t i = 0; i < state.size(); i += BLOCK_SIZE)
146
int blockSize = std::min(BLOCK_SIZE, (int)(state.size() - i));
147
if (i + blockSize > base.size() || memcmp(&state[i], &base[i], blockSize) != 0)
150
result.insert(result.end(), state.begin() + i, state.begin() +i + blockSize);
157
void Decompress(std::vector<u8> &result, const std::vector<u8> &compressed, const std::vector<u8> &base)
160
result.reserve(base.size());
161
auto basePos = base.begin();
162
for (size_t i = 0; i < compressed.size(); )
164
if (compressed[i] == 0)
167
int blockSize = std::min(BLOCK_SIZE, (int)(base.size() - result.size()));
168
result.insert(result.end(), basePos, basePos + blockSize);
169
basePos += blockSize;
174
int blockSize = std::min(BLOCK_SIZE, (int)(compressed.size() - i));
175
result.insert(result.end(), compressed.begin() + i, compressed.begin() + i + blockSize);
177
basePos += blockSize;
190
return next_ == first_;
193
static const int BLOCK_SIZE;
194
// TODO: Instead, based on size of compressed state?
195
static const int BASE_USAGE_INTERVAL;
196
typedef std::vector<u8> StateBuffer;
200
std::vector<StateBuffer> states_;
201
StateBuffer bases_[2];
202
std::vector<int> baseMapping_;
207
static bool needsProcess = false;
208
static std::vector<Operation> pending;
209
static recursive_mutex mutex;
210
static bool hasLoadedState = false;
212
// TODO: Should this be configurable?
213
static const int REWIND_NUM_STATES = 20;
214
static StateRingbuffer rewindStates(REWIND_NUM_STATES);
215
// TODO: Any reason for this to be configurable?
216
const static float rewindMaxWallFrequency = 1.0f;
217
static float rewindLastTime = 0.0f;
218
const int StateRingbuffer::BLOCK_SIZE = 8192;
219
const int StateRingbuffer::BASE_USAGE_INTERVAL = 15;
221
void SaveStart::DoState(PointerWrap &p)
223
auto s = p.Section("SaveStart", 1);
227
// Gotta do CoreTiming first since we'll restore into it.
228
CoreTiming::DoState(p);
230
// Memory is a bit tricky when jit is enabled, since there's emuhacks in it.
231
auto savedReplacements = SaveAndClearReplacements();
232
if (MIPSComp::jit && p.mode == p.MODE_WRITE)
234
std::vector<u32> savedBlocks;
235
savedBlocks = MIPSComp::jit->SaveAndClearEmuHackOps();
237
MIPSComp::jit->RestoreSavedEmuHackOps(savedBlocks);
241
RestoreSavedReplacements(savedReplacements);
243
MemoryStick_DoState(p);
244
currentMIPS->DoState(p);
247
// Kernel object destructors might close open files, so do the filesystem last.
248
pspFileSystem.DoState(p);
251
void Enqueue(SaveState::Operation op)
253
lock_guard guard(mutex);
254
pending.push_back(op);
256
// Don't actually run it until next frame.
257
// It's possible there might be a duplicate but it won't hurt us.
259
Core_UpdateSingleStep();
262
void Load(const std::string &filename, Callback callback, void *cbUserData)
264
Enqueue(Operation(SAVESTATE_LOAD, filename, callback, cbUserData));
267
void Save(const std::string &filename, Callback callback, void *cbUserData)
269
Enqueue(Operation(SAVESTATE_SAVE, filename, callback, cbUserData));
272
void Verify(Callback callback, void *cbUserData)
274
Enqueue(Operation(SAVESTATE_VERIFY, std::string(""), callback, cbUserData));
277
void Rewind(Callback callback, void *cbUserData)
279
Enqueue(Operation(SAVESTATE_REWIND, std::string(""), callback, cbUserData));
282
void SaveScreenshot(const std::string &filename, Callback callback, void *cbUserData)
284
Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, callback, cbUserData));
289
return !rewindStates.Empty();
294
std::string AppendSlotTitle(const std::string &filename, const std::string &title) {
295
if (!endsWith(filename, std::string(".") + STATE_EXTENSION)) {
296
return title + " (" + filename + ")";
299
// Usually these are slots, let's check the slot # after the last '_'.
300
size_t slotNumPos = filename.find_last_of('_');
301
if (slotNumPos == filename.npos) {
302
return title + " (" + filename + ")";
305
const size_t extLength = strlen(STATE_EXTENSION) + 1;
306
// If we take out the extension, '_', etc. we should be left with only a single digit.
307
if (slotNumPos + 1 + extLength != filename.length() - 1) {
308
return title + " (" + filename + ")";
311
std::string slot = filename.substr(slotNumPos + 1, 1);
312
if (slot[0] < '0' || slot[0] > '8') {
313
return title + " (" + filename + ")";
316
// Change from zero indexed to human friendly.
318
return title + " (" + slot + ")";
321
std::string GetTitle(const std::string &filename) {
323
if (CChunkFileReader::GetFileTitle(filename, &title) == CChunkFileReader::ERROR_NONE) {
325
return File::GetFilename(filename);
328
return AppendSlotTitle(filename, title);
331
// The file can't be loaded - let's note that.
332
I18NCategory *sy = GetI18NCategory("System");
333
return File::GetFilename(filename) + " " + sy->T("(broken)");
336
std::string GenerateSaveSlotFilename(const std::string &gameFilename, int slot, const char *extension)
338
std::string discId = g_paramSFO.GetValueString("DISC_ID");
339
std::string fullDiscId;
341
fullDiscId = StringFromFormat("%s_%s",
342
g_paramSFO.GetValueString("DISC_ID").c_str(),
343
g_paramSFO.GetValueString("DISC_VERSION").c_str());
345
// Okay, no discId. Probably homebrew, let's use the last part of the path name.
346
if (File::IsDirectory(gameFilename)) {
347
// EBOOT.PBP directory, most likely.
348
std::string path = gameFilename;
349
size_t slash = path.rfind('/'); // Always '/', not '\\', as we're in a virtual directory
350
if (slash != std::string::npos && slash < path.size() - 1)
351
path = path.substr(slash + 1);
354
// Probably a loose elf.
355
std::string fn = File::GetFilename(gameFilename);
356
size_t dot = fn.rfind('.');
357
if (dot != std::string::npos) {
358
fullDiscId = fn.substr(0, dot);
360
fullDiscId = "elf"; // Fallback
365
std::string temp = StringFromFormat("ms0:/PSP/PPSSPP_STATE/%s_%i.%s", fullDiscId.c_str(), slot, extension);
366
std::string hostPath;
367
if (pspFileSystem.GetHostPath(temp, hostPath)) {
376
return g_Config.iCurrentStateSlot;
381
g_Config.iCurrentStateSlot = (g_Config.iCurrentStateSlot + 1) % NUM_SLOTS;
384
void LoadSlot(const std::string &gameFilename, int slot, Callback callback, void *cbUserData)
386
std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
388
Load(fn, callback, cbUserData);
390
I18NCategory *sy = GetI18NCategory("System");
392
callback(false, sy->T("Failed to load state. Error in the file system."), cbUserData);
396
void SaveSlot(const std::string &gameFilename, int slot, Callback callback, void *cbUserData)
398
std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
399
std::string shot = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION);
401
auto renameCallback = [=](bool status, const std::string &message, void *data) {
403
if (File::Exists(fn)) {
406
File::Rename(fn + ".tmp", fn);
409
callback(status, message, data);
412
// Let's also create a screenshot.
413
SaveScreenshot(shot, Callback(), 0);
414
Save(fn + ".tmp", renameCallback, cbUserData);
416
I18NCategory *sy = GetI18NCategory("System");
418
callback(false, sy->T("Failed to save state. Error in the file system."), cbUserData);
422
bool HasSaveInSlot(const std::string &gameFilename, int slot)
424
std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
425
return File::Exists(fn);
428
bool HasScreenshotInSlot(const std::string &gameFilename, int slot)
430
std::string fn = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION);
431
return File::Exists(fn);
434
bool operator < (const tm &t1, const tm &t2) {
435
if (t1.tm_year < t2.tm_year) return true;
436
if (t1.tm_year > t2.tm_year) return false;
437
if (t1.tm_mon < t2.tm_mon) return true;
438
if (t1.tm_mon > t2.tm_mon) return false;
439
if (t1.tm_mday < t2.tm_mday) return true;
440
if (t1.tm_mday > t2.tm_mday) return false;
441
if (t1.tm_hour < t2.tm_hour) return true;
442
if (t1.tm_hour > t2.tm_hour) return false;
443
if (t1.tm_min < t2.tm_min) return true;
444
if (t1.tm_min > t2.tm_min) return false;
445
if (t1.tm_sec < t2.tm_sec) return true;
446
if (t1.tm_sec > t2.tm_sec) return false;
450
int GetNewestSlot(const std::string &gameFilename) {
453
for (int i = 0; i < NUM_SLOTS; i++) {
454
std::string fn = GenerateSaveSlotFilename(gameFilename, i, STATE_EXTENSION);
455
if (File::Exists(fn)) {
457
bool success = File::GetModifTime(fn, time);
458
if (success && newestDate < time) {
467
std::string GetSlotDateAsString(const std::string &gameFilename, int slot) {
468
std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
469
if (File::Exists(fn)) {
471
if (File::GetModifTime(fn, time)) {
473
// TODO: Use local time format? Americans and some others might not like ISO standard :)
474
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);
475
return std::string(buf);
481
std::vector<Operation> Flush()
483
lock_guard guard(mutex);
484
std::vector<Operation> copy = pending;
492
// Okay, first, let's give the rewind state a shot - maybe we can at least not reset entirely.
493
// Even if this was a rewind, maybe we can still load a previous one.
494
CChunkFileReader::Error result;
496
result = rewindStates.Restore();
497
while (result == CChunkFileReader::ERROR_BROKEN_STATE);
499
if (result == CChunkFileReader::ERROR_NONE) {
503
// We tried, our only remaining option is to reset the game.
505
std::string resetError;
506
if (!PSP_Init(PSP_CoreParameter(), &resetError))
508
ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str());
509
// TODO: This probably doesn't clean up well enough.
514
host->UpdateDisassembly();
518
static inline void CheckRewindState()
520
if (gpuStats.numFlips % g_Config.iRewindFlipFrequency != 0)
523
// For fast-forwarding, otherwise they may be useless and too close.
525
float diff = time_now() - rewindLastTime;
526
if (diff < rewindMaxWallFrequency)
529
rewindLastTime = time_now();
530
DEBUG_LOG(BOOT, "saving rewind state");
534
bool HasLoadedState()
536
return hasLoadedState;
541
#ifndef MOBILE_DEVICE
542
if (g_Config.iRewindFlipFrequency != 0 && gpuStats.numFlips != 0)
548
needsProcess = false;
550
if (!__KernelIsRunning())
552
ERROR_LOG(COMMON, "Savestate failure: Unable to load without kernel, this should never happen.");
556
std::vector<Operation> operations = Flush();
559
for (size_t i = 0, n = operations.size(); i < n; ++i)
561
Operation &op = operations[i];
562
CChunkFileReader::Error result;
564
std::string callbackMessage;
567
I18NCategory *sc = GetI18NCategory("Screen");
568
const char *i18nLoadFailure = sc->T("Load savestate failed", "");
569
const char *i18nSaveFailure = sc->T("Save State Failed", "");
570
if (strlen(i18nLoadFailure) == 0)
571
i18nLoadFailure = sc->T("Failed to load state");
572
if (strlen(i18nSaveFailure) == 0)
573
i18nSaveFailure = sc->T("Failed to save state");
578
INFO_LOG(COMMON, "Loading state from %s", op.filename.c_str());
579
result = CChunkFileReader::Load(op.filename, PPSSPP_GIT_VERSION, state, &reason);
580
if (result == CChunkFileReader::ERROR_NONE) {
581
callbackMessage = sc->T("Loaded State");
582
callbackResult = true;
583
hasLoadedState = true;
584
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
586
callbackMessage = i18nLoadFailure;
587
ERROR_LOG(COMMON, "Load state failure: %s", reason.c_str());
588
callbackResult = false;
590
callbackMessage = sc->T(reason.c_str(), i18nLoadFailure);
591
callbackResult = false;
596
INFO_LOG(COMMON, "Saving state to %s", op.filename.c_str());
597
result = CChunkFileReader::Save(op.filename, g_paramSFO.GetValueString("TITLE"), PPSSPP_GIT_VERSION, state);
598
if (result == CChunkFileReader::ERROR_NONE) {
599
callbackMessage = sc->T("Saved State");
600
callbackResult = true;
601
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
603
callbackMessage = i18nSaveFailure;
604
ERROR_LOG(COMMON, "Save state failure: %s", reason.c_str());
605
callbackResult = false;
607
callbackMessage = i18nSaveFailure;
608
callbackResult = false;
612
case SAVESTATE_VERIFY:
613
callbackResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
614
if (callbackResult) {
615
INFO_LOG(COMMON, "Verified save state system");
617
ERROR_LOG(COMMON, "Save state system verification failed");
621
case SAVESTATE_REWIND:
622
INFO_LOG(COMMON, "Rewinding to recent savestate snapshot");
623
result = rewindStates.Restore();
624
if (result == CChunkFileReader::ERROR_NONE) {
625
callbackMessage = sc->T("Loaded State");
626
callbackResult = true;
627
hasLoadedState = true;
628
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
629
// Cripes. Good news is, we might have more. Let's try those too, better than a reset.
630
if (HandleFailure()) {
631
// Well, we did rewind, even if too much...
632
callbackMessage = sc->T("Loaded State");
633
callbackResult = true;
634
hasLoadedState = true;
636
callbackMessage = i18nLoadFailure;
637
callbackResult = false;
640
callbackMessage = i18nLoadFailure;
641
callbackResult = false;
645
case SAVESTATE_SAVE_SCREENSHOT:
646
callbackResult = TakeGameScreenshot(op.filename.c_str(), SCREENSHOT_JPG, SCREENSHOT_RENDER);
647
if (!callbackResult) {
648
ERROR_LOG(COMMON, "Failed to take a screenshot for the savestate! %s", op.filename.c_str());
653
ERROR_LOG(COMMON, "Savestate failure: unknown operation type %d", op.type);
654
callbackResult = false;
659
op.callback(callbackResult, callbackMessage, op.cbUserData);
661
if (operations.size()) {
662
// Avoid triggering frame skipping due to slowdown
663
__DisplaySetWasPaused();
669
// Make sure there's a directory for save slots
670
pspFileSystem.MkDir("ms0:/PSP/PPSSPP_STATE");
672
lock_guard guard(mutex);
673
rewindStates.Clear();
675
hasLoadedState = false;