2012-12-27 18:34:22 +00:00
|
|
|
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
|
|
|
|
// Official git repository and contact information can be found at
|
|
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
#include <vector>
|
|
|
|
|
2013-05-22 16:00:06 +00:00
|
|
|
#include "Common/StdMutex.h"
|
|
|
|
#include "Common/FileUtil.h"
|
|
|
|
|
|
|
|
#include "Core/SaveState.h"
|
|
|
|
#include "Core/Core.h"
|
|
|
|
#include "Core/CoreTiming.h"
|
|
|
|
#include "Core/HLE/HLE.h"
|
|
|
|
#include "Core/HLE/sceKernel.h"
|
2012-12-27 19:58:15 +00:00
|
|
|
#include "HW/MemoryStick.h"
|
2013-05-22 16:00:06 +00:00
|
|
|
#include "Core/MemMap.h"
|
|
|
|
#include "Core/MIPS/MIPS.h"
|
|
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
|
|
#include "Core/System.h"
|
|
|
|
#include "UI/OnScreenDisplay.h"
|
2013-06-24 07:58:29 +00:00
|
|
|
#include "i18n/i18n.h"
|
2012-12-27 18:34:22 +00:00
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
namespace SaveState
|
2012-12-27 18:34:22 +00:00
|
|
|
{
|
2012-12-27 19:28:12 +00:00
|
|
|
struct SaveStart
|
|
|
|
{
|
|
|
|
void DoState(PointerWrap &p);
|
|
|
|
};
|
2012-12-27 18:34:22 +00:00
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
enum OperationType
|
|
|
|
{
|
|
|
|
SAVESTATE_SAVE,
|
|
|
|
SAVESTATE_LOAD,
|
|
|
|
SAVESTATE_VERIFY,
|
|
|
|
};
|
2012-12-27 18:34:22 +00:00
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
struct Operation
|
|
|
|
{
|
2013-01-02 20:00:10 +00:00
|
|
|
Operation(OperationType t, const std::string &f, Callback cb, void *cbUserData_)
|
|
|
|
: type(t), filename(f), callback(cb), cbUserData(cbUserData_)
|
2012-12-27 19:28:12 +00:00
|
|
|
{
|
|
|
|
}
|
2012-12-27 18:34:22 +00:00
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
OperationType type;
|
|
|
|
std::string filename;
|
|
|
|
Callback callback;
|
2013-01-02 20:00:10 +00:00
|
|
|
void *cbUserData;
|
2012-12-27 19:28:12 +00:00
|
|
|
};
|
|
|
|
|
2013-01-02 22:25:35 +00:00
|
|
|
static bool needsProcess = false;
|
2012-12-27 19:28:12 +00:00
|
|
|
static std::vector<Operation> pending;
|
|
|
|
static std::recursive_mutex mutex;
|
|
|
|
|
|
|
|
void SaveStart::DoState(PointerWrap &p)
|
|
|
|
{
|
2012-12-27 19:58:15 +00:00
|
|
|
// Gotta do CoreTiming first since we'll restore into it.
|
2012-12-27 21:08:58 +00:00
|
|
|
CoreTiming::DoState(p);
|
2012-12-27 19:58:15 +00:00
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
Memory::DoState(p);
|
2012-12-27 19:58:15 +00:00
|
|
|
MemoryStick_DoState(p);
|
2012-12-28 04:33:10 +00:00
|
|
|
currentMIPS->DoState(p);
|
2012-12-28 06:14:31 +00:00
|
|
|
HLEDoState(p);
|
2012-12-27 19:28:12 +00:00
|
|
|
__KernelDoState(p);
|
2013-02-08 16:10:20 +00:00
|
|
|
// Kernel object destructors might close open files, so do the filesystem last.
|
|
|
|
pspFileSystem.DoState(p);
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enqueue(SaveState::Operation op)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(mutex);
|
|
|
|
pending.push_back(op);
|
|
|
|
|
2013-09-15 01:43:23 +00:00
|
|
|
// Don't actually run it until next frame.
|
2012-12-27 19:28:12 +00:00
|
|
|
// It's possible there might be a duplicate but it won't hurt us.
|
2013-09-15 01:43:23 +00:00
|
|
|
needsProcess = true;
|
|
|
|
Core_UpdateSingleStep();
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 20:00:10 +00:00
|
|
|
void Load(const std::string &filename, Callback callback, void *cbUserData)
|
2012-12-27 19:28:12 +00:00
|
|
|
{
|
2013-01-02 20:00:10 +00:00
|
|
|
Enqueue(Operation(SAVESTATE_LOAD, filename, callback, cbUserData));
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 20:00:10 +00:00
|
|
|
void Save(const std::string &filename, Callback callback, void *cbUserData)
|
2012-12-27 19:28:12 +00:00
|
|
|
{
|
2013-01-02 20:00:10 +00:00
|
|
|
Enqueue(Operation(SAVESTATE_SAVE, filename, callback, cbUserData));
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
|
2013-04-13 08:13:28 +00:00
|
|
|
void Verify(Callback callback, void *cbUserData)
|
|
|
|
{
|
|
|
|
Enqueue(Operation(SAVESTATE_VERIFY, std::string(""), callback, cbUserData));
|
|
|
|
}
|
|
|
|
|
2013-01-02 20:00:10 +00:00
|
|
|
|
|
|
|
// Slot utilities
|
|
|
|
|
|
|
|
std::string GenerateSaveSlotFilename(int slot)
|
|
|
|
{
|
|
|
|
char discID[256];
|
|
|
|
char temp[256];
|
|
|
|
sprintf(discID, "%s_%s",
|
|
|
|
g_paramSFO.GetValueString("DISC_ID").c_str(),
|
|
|
|
g_paramSFO.GetValueString("DISC_VERSION").c_str());
|
|
|
|
sprintf(temp, "ms0:/PSP/PPSSPP_STATE/%s_%i.ppst", discID, slot);
|
|
|
|
std::string hostPath;
|
|
|
|
if (pspFileSystem.GetHostPath(std::string(temp), hostPath)) {
|
|
|
|
return hostPath;
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LoadSlot(int slot, Callback callback, void *cbUserData)
|
|
|
|
{
|
|
|
|
std::string fn = GenerateSaveSlotFilename(slot);
|
2013-07-17 05:33:26 +00:00
|
|
|
if (!fn.empty()) {
|
2013-01-02 20:00:10 +00:00
|
|
|
Load(fn, callback, cbUserData);
|
2013-07-17 05:33:26 +00:00
|
|
|
} else {
|
|
|
|
I18NCategory *s = GetI18NCategory("Screen");
|
|
|
|
osm.Show("Failed to load state. Error in the file system.", 2.0);
|
2013-09-11 20:21:15 +00:00
|
|
|
if (callback)
|
|
|
|
(*callback)(false, cbUserData);
|
2013-07-17 05:33:26 +00:00
|
|
|
}
|
2013-01-02 20:00:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SaveSlot(int slot, Callback callback, void *cbUserData)
|
|
|
|
{
|
|
|
|
std::string fn = GenerateSaveSlotFilename(slot);
|
2013-07-17 05:33:26 +00:00
|
|
|
if (!fn.empty()) {
|
2013-01-02 20:00:10 +00:00
|
|
|
Save(fn, callback, cbUserData);
|
2013-07-17 05:33:26 +00:00
|
|
|
} else {
|
|
|
|
I18NCategory *s = GetI18NCategory("Screen");
|
|
|
|
osm.Show("Failed to save state. Error in the file system.", 2.0);
|
2013-09-11 20:21:15 +00:00
|
|
|
if (callback)
|
|
|
|
(*callback)(false, cbUserData);
|
2013-07-17 05:33:26 +00:00
|
|
|
}
|
2013-01-02 20:00:10 +00:00
|
|
|
}
|
|
|
|
|
2013-04-13 08:13:28 +00:00
|
|
|
bool HasSaveInSlot(int slot)
|
2013-01-02 20:00:10 +00:00
|
|
|
{
|
|
|
|
std::string fn = GenerateSaveSlotFilename(slot);
|
2013-04-13 08:13:28 +00:00
|
|
|
return File::Exists(fn);
|
2013-01-02 20:00:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool operator < (const tm &t1, const tm &t2) {
|
|
|
|
if (t1.tm_year < t2.tm_year) return true;
|
|
|
|
if (t1.tm_year > t2.tm_year) return false;
|
|
|
|
if (t1.tm_mon < t2.tm_mon) return true;
|
|
|
|
if (t1.tm_mon > t2.tm_mon) return false;
|
|
|
|
if (t1.tm_mday < t2.tm_mday) return true;
|
|
|
|
if (t1.tm_mday > t2.tm_mday) return false;
|
|
|
|
if (t1.tm_hour < t2.tm_hour) return true;
|
|
|
|
if (t1.tm_hour > t2.tm_hour) return false;
|
|
|
|
if (t1.tm_min < t2.tm_min) return true;
|
|
|
|
if (t1.tm_min > t2.tm_min) return false;
|
|
|
|
if (t1.tm_sec < t2.tm_sec) return true;
|
|
|
|
if (t1.tm_sec > t2.tm_sec) return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetMostRecentSaveSlot() {
|
|
|
|
int newestSlot = -1;
|
|
|
|
tm newestDate = {0};
|
|
|
|
for (int i = 0; i < SAVESTATESLOTS; i++) {
|
|
|
|
std::string fn = GenerateSaveSlotFilename(i);
|
|
|
|
if (File::Exists(fn)) {
|
|
|
|
tm time = File::GetModifTime(fn);
|
|
|
|
if (newestDate < time) {
|
|
|
|
newestDate = time;
|
|
|
|
newestSlot = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newestSlot;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
std::vector<Operation> Flush()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(mutex);
|
|
|
|
std::vector<Operation> copy = pending;
|
|
|
|
pending.clear();
|
|
|
|
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
2013-08-07 06:00:20 +00:00
|
|
|
void Process()
|
2012-12-27 19:28:12 +00:00
|
|
|
{
|
2013-08-07 06:00:20 +00:00
|
|
|
if (!needsProcess)
|
|
|
|
return;
|
|
|
|
needsProcess = false;
|
|
|
|
|
2012-12-29 01:23:05 +00:00
|
|
|
if (!__KernelIsRunning())
|
|
|
|
{
|
|
|
|
ERROR_LOG(COMMON, "Savestate failure: Unable to load without kernel, this should never happen.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
std::vector<Operation> operations = Flush();
|
|
|
|
SaveStart state;
|
|
|
|
|
|
|
|
for (size_t i = 0, n = operations.size(); i < n; ++i)
|
|
|
|
{
|
|
|
|
Operation &op = operations[i];
|
2013-06-24 07:58:29 +00:00
|
|
|
bool result;
|
2013-06-24 08:24:34 +00:00
|
|
|
std::string reason;
|
|
|
|
|
2013-06-24 07:58:29 +00:00
|
|
|
I18NCategory *s = GetI18NCategory("Screen");
|
|
|
|
|
2012-12-27 19:28:12 +00:00
|
|
|
switch (op.type)
|
|
|
|
{
|
|
|
|
case SAVESTATE_LOAD:
|
2012-12-28 23:09:17 +00:00
|
|
|
if (MIPSComp::jit)
|
|
|
|
MIPSComp::jit->ClearCache();
|
2012-12-27 19:28:12 +00:00
|
|
|
INFO_LOG(COMMON, "Loading state from %s", op.filename.c_str());
|
2013-06-24 08:24:34 +00:00
|
|
|
result = CChunkFileReader::Load(op.filename, REVISION, state, &reason);
|
2013-09-11 20:21:15 +00:00
|
|
|
if (result) {
|
2013-06-24 09:09:45 +00:00
|
|
|
osm.Show(s->T("Loaded State"), 2.0);
|
2013-09-11 20:21:15 +00:00
|
|
|
} else {
|
2013-06-24 09:09:45 +00:00
|
|
|
osm.Show(s->T(reason.c_str(), "Load savestate failed"), 2.0);
|
2013-06-24 08:24:34 +00:00
|
|
|
}
|
2012-12-27 19:28:12 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SAVESTATE_SAVE:
|
2012-12-28 23:09:17 +00:00
|
|
|
if (MIPSComp::jit)
|
|
|
|
MIPSComp::jit->ClearCache();
|
2012-12-27 19:28:12 +00:00
|
|
|
INFO_LOG(COMMON, "Saving state to %s", op.filename.c_str());
|
|
|
|
result = CChunkFileReader::Save(op.filename, REVISION, state);
|
2013-09-11 20:21:15 +00:00
|
|
|
if (result) {
|
2013-06-24 09:09:45 +00:00
|
|
|
osm.Show(s->T("Saved State"), 2.0);
|
2013-09-11 20:21:15 +00:00
|
|
|
} else {
|
2013-06-24 09:09:45 +00:00
|
|
|
osm.Show(s->T("Save State Failed"), 2.0);
|
2013-09-11 20:21:15 +00:00
|
|
|
}
|
2012-12-27 19:28:12 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SAVESTATE_VERIFY:
|
|
|
|
INFO_LOG(COMMON, "Verifying save state system");
|
|
|
|
result = CChunkFileReader::Verify(state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ERROR_LOG(COMMON, "Savestate failure: unknown operation type %d", op.type);
|
2013-07-08 01:24:53 +00:00
|
|
|
result = false;
|
2012-12-27 19:28:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-09-11 20:21:15 +00:00
|
|
|
if (op.callback)
|
2013-06-24 08:00:10 +00:00
|
|
|
op.callback(result, op.cbUserData);
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
{
|
2013-01-02 20:00:10 +00:00
|
|
|
// Make sure there's a directory for save slots
|
|
|
|
pspFileSystem.MkDir("ms0:/PSP/PPSSPP_STATE");
|
2013-01-02 22:25:35 +00:00
|
|
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(mutex);
|
2012-12-27 19:28:12 +00:00
|
|
|
}
|
|
|
|
}
|