~ppsspp/ppsspp/ppsspp_1.3.0

« back to all changes in this revision

Viewing changes to Core/SaveState.cpp

  • Committer: Sérgio Benjamim
  • Date: 2017-01-02 00:12:05 UTC
  • Revision ID: sergio_br2@yahoo.com.br-20170102001205-cxbta9za203nmjwm
1.3.0 source (from ppsspp_1.3.0-r160.p5.l1762.a165.t83~56~ubuntu16.04.1.tar.xz).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright (c) 2012- PPSSPP Project.
 
2
 
 
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.
 
6
 
 
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.
 
11
 
 
12
// A copy of the GPL 2.0 should have been included with the program.
 
13
// If not, see http://www.gnu.org/licenses/
 
14
 
 
15
// Official git repository and contact information can be found at
 
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
 
17
 
 
18
#include <algorithm>
 
19
#include <vector>
 
20
 
 
21
#include "base/mutex.h"
 
22
#include "base/timeutil.h"
 
23
#include "i18n/i18n.h"
 
24
 
 
25
#include "Common/FileUtil.h"
 
26
#include "Common/ChunkFile.h"
 
27
 
 
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"
 
46
 
 
47
namespace SaveState
 
48
{
 
49
        struct SaveStart
 
50
        {
 
51
                void DoState(PointerWrap &p);
 
52
        };
 
53
 
 
54
        enum OperationType
 
55
        {
 
56
                SAVESTATE_SAVE,
 
57
                SAVESTATE_LOAD,
 
58
                SAVESTATE_VERIFY,
 
59
                SAVESTATE_REWIND,
 
60
                SAVESTATE_SAVE_SCREENSHOT,
 
61
        };
 
62
 
 
63
        struct Operation
 
64
        {
 
65
                Operation(OperationType t, const std::string &f, Callback cb, void *cbUserData_)
 
66
                        : type(t), filename(f), callback(cb), cbUserData(cbUserData_)
 
67
                {
 
68
                }
 
69
 
 
70
                OperationType type;
 
71
                std::string filename;
 
72
                Callback callback;
 
73
                void *cbUserData;
 
74
        };
 
75
 
 
76
        CChunkFileReader::Error SaveToRam(std::vector<u8> &data) {
 
77
                SaveStart state;
 
78
                size_t sz = CChunkFileReader::MeasurePtr(state);
 
79
                if (data.size() < sz)
 
80
                        data.resize(sz);
 
81
                return CChunkFileReader::SavePtr(&data[0], state);
 
82
        }
 
83
 
 
84
        CChunkFileReader::Error LoadFromRam(std::vector<u8> &data) {
 
85
                SaveStart state;
 
86
                return CChunkFileReader::LoadPtr(&data[0], state);
 
87
        }
 
88
 
 
89
        struct StateRingbuffer
 
90
        {
 
91
                StateRingbuffer(int size) : first_(0), next_(0), size_(size), base_(-1)
 
92
                {
 
93
                        states_.resize(size);
 
94
                        baseMapping_.resize(size);
 
95
                }
 
96
 
 
97
                CChunkFileReader::Error Save()
 
98
                {
 
99
                        int n = next_++ % size_;
 
100
                        if ((next_ % size_) == first_)
 
101
                                ++first_;
 
102
 
 
103
                        static std::vector<u8> buffer;
 
104
                        std::vector<u8> *compressBuffer = &buffer;
 
105
                        CChunkFileReader::Error err;
 
106
 
 
107
                        if (base_ == -1 || ++baseUsage_ > BASE_USAGE_INTERVAL)
 
108
                        {
 
109
                                base_ = (base_ + 1) % ARRAY_SIZE(bases_);
 
110
                                baseUsage_ = 0;
 
111
                                err = SaveToRam(bases_[base_]);
 
112
                                // Let's not bother savestating twice.
 
113
                                compressBuffer = &bases_[base_];
 
114
                        }
 
115
                        else
 
116
                                err = SaveToRam(buffer);
 
117
 
 
118
                        if (err == CChunkFileReader::ERROR_NONE)
 
119
                                Compress(states_[n], *compressBuffer, bases_[base_]);
 
120
                        else
 
121
                                states_[n].clear();
 
122
                        baseMapping_[n] = base_;
 
123
                        return err;
 
124
                }
 
125
 
 
126
                CChunkFileReader::Error Restore()
 
127
                {
 
128
                        // No valid states left.
 
129
                        if (Empty())
 
130
                                return CChunkFileReader::ERROR_BAD_FILE;
 
131
 
 
132
                        int n = (--next_ + size_) % size_;
 
133
                        if (states_[n].empty())
 
134
                                return CChunkFileReader::ERROR_BAD_FILE;
 
135
 
 
136
                        static std::vector<u8> buffer;
 
137
                        Decompress(buffer, states_[n], bases_[baseMapping_[n]]);
 
138
                        return LoadFromRam(buffer);
 
139
                }
 
140
 
 
141
                void Compress(std::vector<u8> &result, const std::vector<u8> &state, const std::vector<u8> &base)
 
142
                {
 
143
                        result.clear();
 
144
                        for (size_t i = 0; i < state.size(); i += BLOCK_SIZE)
 
145
                        {
 
146
                                int blockSize = std::min(BLOCK_SIZE, (int)(state.size() - i));
 
147
                                if (i + blockSize > base.size() || memcmp(&state[i], &base[i], blockSize) != 0)
 
148
                                {
 
149
                                        result.push_back(1);
 
150
                                        result.insert(result.end(), state.begin() + i, state.begin() +i + blockSize);
 
151
                                }
 
152
                                else
 
153
                                        result.push_back(0);
 
154
                        }
 
155
                }
 
156
 
 
157
                void Decompress(std::vector<u8> &result, const std::vector<u8> &compressed, const std::vector<u8> &base)
 
158
                {
 
159
                        result.clear();
 
160
                        result.reserve(base.size());
 
161
                        auto basePos = base.begin();
 
162
                        for (size_t i = 0; i < compressed.size(); )
 
163
                        {
 
164
                                if (compressed[i] == 0)
 
165
                                {
 
166
                                        ++i;
 
167
                                        int blockSize = std::min(BLOCK_SIZE, (int)(base.size() - result.size()));
 
168
                                        result.insert(result.end(), basePos, basePos + blockSize);
 
169
                                        basePos += blockSize;
 
170
                                }
 
171
                                else
 
172
                                {
 
173
                                        ++i;
 
174
                                        int blockSize = std::min(BLOCK_SIZE, (int)(compressed.size() - i));
 
175
                                        result.insert(result.end(), compressed.begin() + i, compressed.begin() + i + blockSize);
 
176
                                        i += blockSize;
 
177
                                        basePos += blockSize;
 
178
                                }
 
179
                        }
 
180
                }
 
181
 
 
182
                void Clear()
 
183
                {
 
184
                        first_ = 0;
 
185
                        next_ = 0;
 
186
                }
 
187
 
 
188
                bool Empty() const
 
189
                {
 
190
                        return next_ == first_;
 
191
                }
 
192
 
 
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;
 
197
                int first_;
 
198
                int next_;
 
199
                int size_;
 
200
                std::vector<StateBuffer> states_;
 
201
                StateBuffer bases_[2];
 
202
                std::vector<int> baseMapping_;
 
203
                int base_;
 
204
                int baseUsage_;
 
205
        };
 
206
 
 
207
        static bool needsProcess = false;
 
208
        static std::vector<Operation> pending;
 
209
        static recursive_mutex mutex;
 
210
        static bool hasLoadedState = false;
 
211
 
 
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;
 
220
 
 
221
        void SaveStart::DoState(PointerWrap &p)
 
222
        {
 
223
                auto s = p.Section("SaveStart", 1);
 
224
                if (!s)
 
225
                        return;
 
226
 
 
227
                // Gotta do CoreTiming first since we'll restore into it.
 
228
                CoreTiming::DoState(p);
 
229
 
 
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)
 
233
                {
 
234
                        std::vector<u32> savedBlocks;
 
235
                        savedBlocks = MIPSComp::jit->SaveAndClearEmuHackOps();
 
236
                        Memory::DoState(p);
 
237
                        MIPSComp::jit->RestoreSavedEmuHackOps(savedBlocks);
 
238
                }
 
239
                else
 
240
                        Memory::DoState(p);
 
241
                RestoreSavedReplacements(savedReplacements);
 
242
 
 
243
                MemoryStick_DoState(p);
 
244
                currentMIPS->DoState(p);
 
245
                HLEDoState(p);
 
246
                __KernelDoState(p);
 
247
                // Kernel object destructors might close open files, so do the filesystem last.
 
248
                pspFileSystem.DoState(p);
 
249
        }
 
250
 
 
251
        void Enqueue(SaveState::Operation op)
 
252
        {
 
253
                lock_guard guard(mutex);
 
254
                pending.push_back(op);
 
255
 
 
256
                // Don't actually run it until next frame.
 
257
                // It's possible there might be a duplicate but it won't hurt us.
 
258
                needsProcess = true;
 
259
                Core_UpdateSingleStep();
 
260
        }
 
261
 
 
262
        void Load(const std::string &filename, Callback callback, void *cbUserData)
 
263
        {
 
264
                Enqueue(Operation(SAVESTATE_LOAD, filename, callback, cbUserData));
 
265
        }
 
266
 
 
267
        void Save(const std::string &filename, Callback callback, void *cbUserData)
 
268
        {
 
269
                Enqueue(Operation(SAVESTATE_SAVE, filename, callback, cbUserData));
 
270
        }
 
271
 
 
272
        void Verify(Callback callback, void *cbUserData)
 
273
        {
 
274
                Enqueue(Operation(SAVESTATE_VERIFY, std::string(""), callback, cbUserData));
 
275
        }
 
276
 
 
277
        void Rewind(Callback callback, void *cbUserData)
 
278
        {
 
279
                Enqueue(Operation(SAVESTATE_REWIND, std::string(""), callback, cbUserData));
 
280
        }
 
281
 
 
282
        void SaveScreenshot(const std::string &filename, Callback callback, void *cbUserData)
 
283
        {
 
284
                Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, callback, cbUserData));
 
285
        }
 
286
 
 
287
        bool CanRewind()
 
288
        {
 
289
                return !rewindStates.Empty();
 
290
        }
 
291
 
 
292
        // Slot utilities
 
293
 
 
294
        std::string AppendSlotTitle(const std::string &filename, const std::string &title) {
 
295
                if (!endsWith(filename, std::string(".") + STATE_EXTENSION)) {
 
296
                        return title + " (" + filename + ")";
 
297
                }
 
298
 
 
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 + ")";
 
303
                }
 
304
 
 
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 + ")";
 
309
                }
 
310
 
 
311
                std::string slot = filename.substr(slotNumPos + 1, 1);
 
312
                if (slot[0] < '0' || slot[0] > '8') {
 
313
                        return title + " (" + filename + ")";
 
314
                }
 
315
 
 
316
                // Change from zero indexed to human friendly.
 
317
                slot[0]++;
 
318
                return title + " (" + slot + ")";
 
319
        }
 
320
 
 
321
        std::string GetTitle(const std::string &filename) {
 
322
                std::string title;
 
323
                if (CChunkFileReader::GetFileTitle(filename, &title) == CChunkFileReader::ERROR_NONE) {
 
324
                        if (title.empty()) {
 
325
                                return File::GetFilename(filename);
 
326
                        }
 
327
 
 
328
                        return AppendSlotTitle(filename, title);
 
329
                }
 
330
 
 
331
                // The file can't be loaded - let's note that.
 
332
                I18NCategory *sy = GetI18NCategory("System");
 
333
                return File::GetFilename(filename) + " " + sy->T("(broken)");
 
334
        }
 
335
 
 
336
        std::string GenerateSaveSlotFilename(const std::string &gameFilename, int slot, const char *extension)
 
337
        {
 
338
                std::string discId = g_paramSFO.GetValueString("DISC_ID");
 
339
                std::string fullDiscId;
 
340
                if (discId.size()) {
 
341
                        fullDiscId = StringFromFormat("%s_%s",
 
342
                                g_paramSFO.GetValueString("DISC_ID").c_str(),
 
343
                                g_paramSFO.GetValueString("DISC_VERSION").c_str());
 
344
                } else {
 
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);
 
352
                                fullDiscId = path;
 
353
                        } else {
 
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);
 
359
                                } else {
 
360
                                        fullDiscId = "elf";  // Fallback
 
361
                                }
 
362
                        }
 
363
                }
 
364
 
 
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)) {
 
368
                        return hostPath;
 
369
                } else {
 
370
                        return "";
 
371
                }
 
372
        }
 
373
 
 
374
        int GetCurrentSlot()
 
375
        {
 
376
                return g_Config.iCurrentStateSlot;
 
377
        }
 
378
 
 
379
        void NextSlot()
 
380
        {
 
381
                g_Config.iCurrentStateSlot = (g_Config.iCurrentStateSlot + 1) % NUM_SLOTS;
 
382
        }
 
383
 
 
384
        void LoadSlot(const std::string &gameFilename, int slot, Callback callback, void *cbUserData)
 
385
        {
 
386
                std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
 
387
                if (!fn.empty()) {
 
388
                        Load(fn, callback, cbUserData);
 
389
                } else {
 
390
                        I18NCategory *sy = GetI18NCategory("System");
 
391
                        if (callback)
 
392
                                callback(false, sy->T("Failed to load state. Error in the file system."), cbUserData);
 
393
                }
 
394
        }
 
395
 
 
396
        void SaveSlot(const std::string &gameFilename, int slot, Callback callback, void *cbUserData)
 
397
        {
 
398
                std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
 
399
                std::string shot = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION);
 
400
                if (!fn.empty()) {
 
401
                        auto renameCallback = [=](bool status, const std::string &message, void *data) {
 
402
                                if (status) {
 
403
                                        if (File::Exists(fn)) {
 
404
                                                File::Delete(fn);
 
405
                                        }
 
406
                                        File::Rename(fn + ".tmp", fn);
 
407
                                }
 
408
                                if (callback) {
 
409
                                        callback(status, message, data);
 
410
                                }
 
411
                        };
 
412
                        // Let's also create a screenshot.
 
413
                        SaveScreenshot(shot, Callback(), 0);
 
414
                        Save(fn + ".tmp", renameCallback, cbUserData);
 
415
                } else {
 
416
                        I18NCategory *sy = GetI18NCategory("System");
 
417
                        if (callback)
 
418
                                callback(false, sy->T("Failed to save state. Error in the file system."), cbUserData);
 
419
                }
 
420
        }
 
421
 
 
422
        bool HasSaveInSlot(const std::string &gameFilename, int slot)
 
423
        {
 
424
                std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
 
425
                return File::Exists(fn);
 
426
        }
 
427
 
 
428
        bool HasScreenshotInSlot(const std::string &gameFilename, int slot)
 
429
        {
 
430
                std::string fn = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION);
 
431
                return File::Exists(fn);
 
432
        }
 
433
 
 
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;
 
447
                return false;
 
448
        }
 
449
 
 
450
        int GetNewestSlot(const std::string &gameFilename) {
 
451
                int newestSlot = -1;
 
452
                tm newestDate = {0};
 
453
                for (int i = 0; i < NUM_SLOTS; i++) {
 
454
                        std::string fn = GenerateSaveSlotFilename(gameFilename, i, STATE_EXTENSION);
 
455
                        if (File::Exists(fn)) {
 
456
                                tm time;
 
457
                                bool success = File::GetModifTime(fn, time);
 
458
                                if (success && newestDate < time) {
 
459
                                        newestDate = time;
 
460
                                        newestSlot = i;
 
461
                                }
 
462
                        }
 
463
                }
 
464
                return newestSlot;
 
465
        }
 
466
 
 
467
        std::string GetSlotDateAsString(const std::string &gameFilename, int slot) {
 
468
                std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
 
469
                if (File::Exists(fn)) {
 
470
                        tm time;
 
471
                        if (File::GetModifTime(fn, time)) {
 
472
                                char buf[256];
 
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);
 
476
                        }
 
477
                }
 
478
                return "";
 
479
        }
 
480
 
 
481
        std::vector<Operation> Flush()
 
482
        {
 
483
                lock_guard guard(mutex);
 
484
                std::vector<Operation> copy = pending;
 
485
                pending.clear();
 
486
 
 
487
                return copy;
 
488
        }
 
489
 
 
490
        bool HandleFailure()
 
491
        {
 
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;
 
495
                do
 
496
                        result = rewindStates.Restore();
 
497
                while (result == CChunkFileReader::ERROR_BROKEN_STATE);
 
498
 
 
499
                if (result == CChunkFileReader::ERROR_NONE) {
 
500
                        return true;
 
501
                }
 
502
 
 
503
                // We tried, our only remaining option is to reset the game.
 
504
                PSP_Shutdown();
 
505
                std::string resetError;
 
506
                if (!PSP_Init(PSP_CoreParameter(), &resetError))
 
507
                {
 
508
                        ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str());
 
509
                        // TODO: This probably doesn't clean up well enough.
 
510
                        Core_Stop();
 
511
                        return false;
 
512
                }
 
513
                host->BootDone();
 
514
                host->UpdateDisassembly();
 
515
                return false;
 
516
        }
 
517
 
 
518
        static inline void CheckRewindState()
 
519
        {
 
520
                if (gpuStats.numFlips % g_Config.iRewindFlipFrequency != 0)
 
521
                        return;
 
522
 
 
523
                // For fast-forwarding, otherwise they may be useless and too close.
 
524
                time_update();
 
525
                float diff = time_now() - rewindLastTime;
 
526
                if (diff < rewindMaxWallFrequency)
 
527
                        return;
 
528
 
 
529
                rewindLastTime = time_now();
 
530
                DEBUG_LOG(BOOT, "saving rewind state");
 
531
                rewindStates.Save();
 
532
        }
 
533
 
 
534
        bool HasLoadedState()
 
535
        {
 
536
                return hasLoadedState;
 
537
        }
 
538
 
 
539
        void Process()
 
540
        {
 
541
#ifndef MOBILE_DEVICE
 
542
                if (g_Config.iRewindFlipFrequency != 0 && gpuStats.numFlips != 0)
 
543
                        CheckRewindState();
 
544
#endif
 
545
 
 
546
                if (!needsProcess)
 
547
                        return;
 
548
                needsProcess = false;
 
549
 
 
550
                if (!__KernelIsRunning())
 
551
                {
 
552
                        ERROR_LOG(COMMON, "Savestate failure: Unable to load without kernel, this should never happen.");
 
553
                        return;
 
554
                }
 
555
 
 
556
                std::vector<Operation> operations = Flush();
 
557
                SaveStart state;
 
558
 
 
559
                for (size_t i = 0, n = operations.size(); i < n; ++i)
 
560
                {
 
561
                        Operation &op = operations[i];
 
562
                        CChunkFileReader::Error result;
 
563
                        bool callbackResult;
 
564
                        std::string callbackMessage;
 
565
                        std::string reason;
 
566
 
 
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");
 
574
 
 
575
                        switch (op.type)
 
576
                        {
 
577
                        case SAVESTATE_LOAD:
 
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) {
 
585
                                        HandleFailure();
 
586
                                        callbackMessage = i18nLoadFailure;
 
587
                                        ERROR_LOG(COMMON, "Load state failure: %s", reason.c_str());
 
588
                                        callbackResult = false;
 
589
                                } else {
 
590
                                        callbackMessage = sc->T(reason.c_str(), i18nLoadFailure);
 
591
                                        callbackResult = false;
 
592
                                }
 
593
                                break;
 
594
 
 
595
                        case SAVESTATE_SAVE:
 
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) {
 
602
                                        HandleFailure();
 
603
                                        callbackMessage = i18nSaveFailure;
 
604
                                        ERROR_LOG(COMMON, "Save state failure: %s", reason.c_str());
 
605
                                        callbackResult = false;
 
606
                                } else {
 
607
                                        callbackMessage = i18nSaveFailure;
 
608
                                        callbackResult = false;
 
609
                                }
 
610
                                break;
 
611
 
 
612
                        case SAVESTATE_VERIFY:
 
613
                                callbackResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
 
614
                                if (callbackResult) {
 
615
                                        INFO_LOG(COMMON, "Verified save state system");
 
616
                                } else {
 
617
                                        ERROR_LOG(COMMON, "Save state system verification failed");
 
618
                                }
 
619
                                break;
 
620
 
 
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;
 
635
                                        } else {
 
636
                                                callbackMessage = i18nLoadFailure;
 
637
                                                callbackResult = false;
 
638
                                        }
 
639
                                } else {
 
640
                                        callbackMessage = i18nLoadFailure;
 
641
                                        callbackResult = false;
 
642
                                }
 
643
                                break;
 
644
 
 
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());
 
649
                                }
 
650
                                break;
 
651
 
 
652
                        default:
 
653
                                ERROR_LOG(COMMON, "Savestate failure: unknown operation type %d", op.type);
 
654
                                callbackResult = false;
 
655
                                break;
 
656
                        }
 
657
 
 
658
                        if (op.callback)
 
659
                                op.callback(callbackResult, callbackMessage, op.cbUserData);
 
660
                }
 
661
                if (operations.size()) {
 
662
                        // Avoid triggering frame skipping due to slowdown
 
663
                        __DisplaySetWasPaused();
 
664
                }
 
665
        }
 
666
 
 
667
        void Init()
 
668
        {
 
669
                // Make sure there's a directory for save slots
 
670
                pspFileSystem.MkDir("ms0:/PSP/PPSSPP_STATE");
 
671
 
 
672
                lock_guard guard(mutex);
 
673
                rewindStates.Clear();
 
674
 
 
675
                hasLoadedState = false;
 
676
        }
 
677
}