2013-04-17 22:43:11 -04:00
|
|
|
// Copyright 2013 Dolphin Emulator Project
|
2015-05-18 01:08:10 +02:00
|
|
|
// Licensed under GPLv2+
|
2013-04-17 22:43:11 -04:00
|
|
|
// Refer to the license.txt file included.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-08-14 01:14:35 -04:00
|
|
|
#include <mutex>
|
2015-03-30 09:02:43 +02:00
|
|
|
#include <string>
|
2014-08-14 01:14:35 -04:00
|
|
|
#include <thread>
|
2010-03-09 22:17:33 +00:00
|
|
|
#include <lzo/lzo1x.h>
|
2014-02-17 05:18:15 -05:00
|
|
|
|
2014-09-07 20:06:58 -05:00
|
|
|
#include "Common/CommonTypes.h"
|
2014-04-14 01:15:23 +02:00
|
|
|
#include "Common/Event.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Common/StringUtil.h"
|
2015-05-10 20:16:05 -05:00
|
|
|
#include "Common/Thread.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Common/Timer.h"
|
|
|
|
|
|
|
|
#include "Core/ConfigManager.h"
|
2014-02-19 13:09:14 -05:00
|
|
|
#include "Core/Core.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Core/CoreTiming.h"
|
|
|
|
#include "Core/Movie.h"
|
|
|
|
#include "Core/State.h"
|
|
|
|
#include "Core/HW/CPU.h"
|
|
|
|
#include "Core/HW/DSP.h"
|
|
|
|
#include "Core/HW/HW.h"
|
|
|
|
#include "Core/HW/Memmap.h"
|
|
|
|
#include "Core/HW/SystemTimers.h"
|
|
|
|
#include "Core/HW/VideoInterface.h"
|
|
|
|
#include "Core/HW/Wiimote.h"
|
|
|
|
#include "Core/PowerPC/JitCommon/JitBase.h"
|
|
|
|
|
2014-10-04 03:28:01 -04:00
|
|
|
#include "VideoCommon/AVIDump.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "VideoCommon/VideoBackendBase.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
namespace State
|
|
|
|
{
|
2009-09-02 21:00:45 +00:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
#if defined(__LZO_STRICT_16BIT)
|
2011-03-17 10:17:45 +00:00
|
|
|
static const u32 IN_LEN = 8 * 1024u;
|
2008-12-08 05:30:24 +00:00
|
|
|
#elif defined(LZO_ARCH_I086) && !defined(LZO_HAVE_MM_HUGE_ARRAY)
|
2011-03-17 10:17:45 +00:00
|
|
|
static const u32 IN_LEN = 60 * 1024u;
|
2008-12-08 05:30:24 +00:00
|
|
|
#else
|
2011-03-17 10:17:45 +00:00
|
|
|
static const u32 IN_LEN = 128 * 1024u;
|
2008-12-08 05:30:24 +00:00
|
|
|
#endif
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static const u32 OUT_LEN = IN_LEN + (IN_LEN / 16) + 64 + 3;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static unsigned char __LZO_MMODEL out[OUT_LEN];
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
#define HEAP_ALLOC(var, size) \
|
|
|
|
lzo_align_t __LZO_MMODEL var[((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t)]
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
static std::string g_last_filename;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-03-09 21:14:26 +01:00
|
|
|
static CallbackFunc g_onAfterLoadCb = nullptr;
|
2011-12-18 01:15:59 -08:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// Temporary undo state buffer
|
|
|
|
static std::vector<u8> g_undo_load_buffer;
|
|
|
|
static std::vector<u8> g_current_buffer;
|
2011-12-17 16:49:24 -08:00
|
|
|
static int g_loadDepth = 0;
|
2009-06-28 21:11:51 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
static std::mutex g_cs_undo_load_buffer;
|
|
|
|
static std::mutex g_cs_current_buffer;
|
|
|
|
static Common::Event g_compressAndDumpStateSyncEvent;
|
2009-10-20 23:09:44 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
static std::thread g_save_thread;
|
2011-10-15 22:19:42 +11:00
|
|
|
|
2013-02-01 23:27:49 -06:00
|
|
|
// Don't forget to increase this after doing changes on the savestate system
|
2015-03-30 09:02:43 +02:00
|
|
|
static const u32 STATE_VERSION = 43; // Last changed in PR 2232
|
|
|
|
|
|
|
|
// Maps savestate versions to Dolphin versions.
|
|
|
|
// Versions after 42 don't need to be added to this list,
|
|
|
|
// beacuse they save the exact Dolphin version to savestates.
|
|
|
|
static const std::map<u32, std::pair<std::string, std::string>> s_old_versions =
|
|
|
|
{
|
|
|
|
// The 16 -> 17 change modified the size of StateHeader,
|
|
|
|
// so version older than that can't even be decompressed anymore
|
|
|
|
{ 17, { "3.5-1311", "3.5-1364" } },
|
|
|
|
{ 18, { "3.5-1366", "3.5-1371" } },
|
|
|
|
{ 19, { "3.5-1372", "3.5-1408" } },
|
|
|
|
{ 20, { "3.5-1409", "4.0-704" } },
|
|
|
|
{ 21, { "4.0-705", "4.0-889" } },
|
|
|
|
{ 22, { "4.0-905", "4.0-1871" } },
|
|
|
|
{ 23, { "4.0-1873", "4.0-1900" } },
|
|
|
|
{ 24, { "4.0-1902", "4.0-1919" } },
|
|
|
|
{ 25, { "4.0-1921", "4.0-1936" } },
|
|
|
|
{ 26, { "4.0-1939", "4.0-1959" } },
|
|
|
|
{ 27, { "4.0-1961", "4.0-2018" } },
|
|
|
|
{ 28, { "4.0-2020", "4.0-2291" } },
|
|
|
|
{ 29, { "4.0-2293", "4.0-2360" } },
|
|
|
|
{ 30, { "4.0-2362", "4.0-2628" } },
|
|
|
|
{ 31, { "4.0-2632", "4.0-3331" } },
|
|
|
|
{ 32, { "4.0-3334", "4.0-3340" } },
|
|
|
|
{ 33, { "4.0-3342", "4.0-3373" } },
|
|
|
|
{ 34, { "4.0-3376", "4.0-3402" } },
|
|
|
|
{ 35, { "4.0-3409", "4.0-3603" } },
|
|
|
|
{ 36, { "4.0-3610", "4.0-4480" } },
|
|
|
|
{ 37, { "4.0-4484", "4.0-4943" } },
|
|
|
|
{ 38, { "4.0-4963", "4.0-5267" } },
|
|
|
|
{ 39, { "4.0-5279", "4.0-5525" } },
|
|
|
|
{ 40, { "4.0-5531", "4.0-5809" } },
|
|
|
|
{ 41, { "4.0-5811", "4.0-5923" } },
|
|
|
|
{ 42, { "4.0-5925", "4.0-5946" } }
|
|
|
|
};
|
2009-10-20 23:09:44 +00:00
|
|
|
|
2011-10-15 22:19:42 +11:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
STATE_NONE = 0,
|
|
|
|
STATE_SAVE = 1,
|
|
|
|
STATE_LOAD = 2,
|
|
|
|
};
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static bool g_use_compression = true;
|
|
|
|
|
|
|
|
void EnableCompression(bool compression)
|
|
|
|
{
|
|
|
|
g_use_compression = compression;
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2015-03-30 09:02:43 +02:00
|
|
|
static std::string DoState(PointerWrap& p)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-12-14 04:03:05 -08:00
|
|
|
u32 version = STATE_VERSION;
|
2009-09-15 18:54:10 +00:00
|
|
|
{
|
2013-04-16 23:14:36 -04:00
|
|
|
static const u32 COOKIE_BASE = 0xBAADBABE;
|
2011-12-28 02:33:41 -06:00
|
|
|
u32 cookie = version + COOKIE_BASE;
|
2011-12-14 04:03:05 -08:00
|
|
|
p.Do(cookie);
|
2011-12-28 02:33:41 -06:00
|
|
|
version = cookie - COOKIE_BASE;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
2011-12-14 04:03:05 -08:00
|
|
|
|
2015-03-30 09:02:43 +02:00
|
|
|
std::string version_created_by = scm_rev_str;
|
|
|
|
if (version > 42)
|
|
|
|
p.Do(version_created_by);
|
|
|
|
else
|
|
|
|
version_created_by.clear();
|
|
|
|
|
2011-12-14 04:03:05 -08:00
|
|
|
if (version != STATE_VERSION)
|
|
|
|
{
|
2015-03-30 09:02:43 +02:00
|
|
|
if (version_created_by.empty() && s_old_versions.count(version))
|
|
|
|
{
|
|
|
|
// The savestate is from an old version that doesn't
|
|
|
|
// save the Dolphin version number to savestates, but
|
|
|
|
// by looking up the savestate version number, it is possible
|
|
|
|
// to know approximately which Dolphin version was used.
|
|
|
|
|
|
|
|
std::pair<std::string, std::string> version_range = s_old_versions.find(version)->second;
|
|
|
|
std::string oldest_version = version_range.first;
|
|
|
|
std::string newest_version = version_range.second;
|
|
|
|
|
|
|
|
version_created_by = "Dolphin " + oldest_version + " - " + newest_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
// because the version doesn't match, fail.
|
|
|
|
// this will trigger an OSD message like "Can't load state from other revisions"
|
2011-12-28 02:33:41 -06:00
|
|
|
// we could use the version numbers to maintain some level of backward compatibility, but currently don't.
|
|
|
|
p.SetMode(PointerWrap::MODE_MEASURE);
|
2015-03-30 09:02:43 +02:00
|
|
|
return version_created_by;
|
2011-12-14 04:03:05 -08:00
|
|
|
}
|
|
|
|
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("Version");
|
|
|
|
|
2015-03-30 09:02:43 +02:00
|
|
|
// Begin with video backend, so that it gets a chance to clear its caches and writeback modified things to RAM
|
2011-01-31 01:28:32 +00:00
|
|
|
g_video_backend->DoState(p);
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("video_backend");
|
2011-01-28 18:39:30 +00:00
|
|
|
|
2014-09-09 00:24:49 -04:00
|
|
|
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii)
|
2010-10-12 19:42:29 +00:00
|
|
|
Wiimote::DoState(p.GetPPtr(), p.GetMode());
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("Wiimote");
|
2011-01-30 21:20:33 +00:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
PowerPC::DoState(p);
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("PowerPC");
|
2008-12-08 05:30:24 +00:00
|
|
|
HW::DoState(p);
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("HW");
|
2008-12-08 05:30:24 +00:00
|
|
|
CoreTiming::DoState(p);
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("CoreTiming");
|
2011-12-28 02:33:41 -06:00
|
|
|
Movie::DoState(p);
|
2011-12-17 16:49:24 -08:00
|
|
|
p.DoMarker("Movie");
|
2015-03-30 09:02:43 +02:00
|
|
|
|
2014-10-04 03:28:01 -04:00
|
|
|
#if defined(HAVE_LIBAV) || defined (WIN32)
|
|
|
|
AVIDump::DoState();
|
|
|
|
#endif
|
2015-03-30 09:02:43 +02:00
|
|
|
|
|
|
|
return version_created_by;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
void LoadFromBuffer(std::vector<u8>& buffer)
|
2011-10-15 22:19:42 +11:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
2011-10-15 22:19:42 +11:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
u8* ptr = &buffer[0];
|
2009-06-28 21:11:51 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
|
|
|
DoState(p);
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
void SaveToBuffer(std::vector<u8>& buffer)
|
2009-06-28 21:11:51 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
|
|
|
|
2014-03-09 21:14:26 +01:00
|
|
|
u8* ptr = nullptr;
|
2009-06-28 21:11:51 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
DoState(p);
|
|
|
|
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
2011-12-30 20:16:12 -08:00
|
|
|
buffer.resize(buffer_size);
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
ptr = &buffer[0];
|
2009-06-28 21:11:51 +00:00
|
|
|
p.SetMode(PointerWrap::MODE_WRITE);
|
|
|
|
DoState(p);
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
void VerifyBuffer(std::vector<u8>& buffer)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
|
|
|
|
|
|
|
u8* ptr = &buffer[0];
|
2010-04-17 21:02:03 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
|
|
|
|
DoState(p);
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2012-11-08 08:40:49 +01:00
|
|
|
// return state number not in map
|
2014-07-08 14:29:26 +02:00
|
|
|
static int GetEmptySlot(std::map<double, int> m)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
2013-05-05 23:22:57 -05:00
|
|
|
for (int i = 1; i <= (int)NUM_STATES; i++)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
bool found = false;
|
2013-10-29 01:09:01 -04:00
|
|
|
for (auto& p : m)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
2013-10-29 01:09:01 -04:00
|
|
|
if (p.second == i)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-12-21 12:06:08 -06:00
|
|
|
if (!found)
|
|
|
|
return i;
|
2012-11-08 08:40:49 +01:00
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-05-05 23:22:57 -05:00
|
|
|
static std::string MakeStateFilename(int number);
|
|
|
|
|
2012-11-08 08:40:49 +01:00
|
|
|
// read state timestamps
|
2014-07-08 14:29:26 +02:00
|
|
|
static std::map<double, int> GetSavedStates()
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
StateHeader header;
|
|
|
|
std::map<double, int> m;
|
2013-05-05 23:22:57 -05:00
|
|
|
for (int i = 1; i <= (int)NUM_STATES; i++)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
2014-02-23 23:03:39 +01:00
|
|
|
std::string filename = MakeStateFilename(i);
|
|
|
|
if (File::Exists(filename))
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
2014-02-23 23:03:39 +01:00
|
|
|
if (ReadHeader(filename, header))
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
double d = Common::Timer::GetDoubleTime() - header.time;
|
|
|
|
// increase time until unique value is obtained
|
|
|
|
while (m.find(d) != m.end()) d += .001;
|
|
|
|
m.insert(std::pair<double,int>(d, i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
struct CompressAndDumpState_args
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
std::vector<u8>* buffer_vector;
|
|
|
|
std::mutex* buffer_mutex;
|
|
|
|
std::string filename;
|
2012-11-08 08:40:49 +01:00
|
|
|
bool wait;
|
2011-12-30 20:16:12 -08:00
|
|
|
};
|
|
|
|
|
2014-07-08 14:29:26 +02:00
|
|
|
static void CompressAndDumpState(CompressAndDumpState_args save_args)
|
2011-12-30 20:16:12 -08:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lk(*save_args.buffer_mutex);
|
2012-11-08 08:40:49 +01:00
|
|
|
if (!save_args.wait)
|
|
|
|
g_compressAndDumpStateSyncEvent.Set();
|
2011-12-30 20:16:12 -08:00
|
|
|
|
|
|
|
const u8* const buffer_data = &(*(save_args.buffer_vector))[0];
|
|
|
|
const size_t buffer_size = (save_args.buffer_vector)->size();
|
|
|
|
std::string& filename = save_args.filename;
|
2009-06-26 22:36:44 +00:00
|
|
|
|
2011-01-30 21:20:33 +00:00
|
|
|
// For easy debugging
|
|
|
|
Common::SetCurrentThreadName("SaveState thread");
|
|
|
|
|
2009-06-28 01:11:35 +00:00
|
|
|
// Moving to last overwritten save-state
|
2011-12-30 20:16:12 -08:00
|
|
|
if (File::Exists(filename))
|
2010-04-08 16:59:35 +00:00
|
|
|
{
|
2011-03-01 03:06:14 +00:00
|
|
|
if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
|
|
|
|
File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"));
|
2012-10-18 04:11:14 -04:00
|
|
|
if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm"))
|
|
|
|
File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm"));
|
2009-06-28 01:11:35 +00:00
|
|
|
|
2012-12-02 02:08:27 -05:00
|
|
|
if (!File::Rename(filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
|
2009-06-28 01:11:35 +00:00
|
|
|
Core::DisplayMessage("Failed to move previous state to state undo backup", 1000);
|
2013-10-29 01:23:17 -04:00
|
|
|
else
|
2012-12-02 02:08:27 -05:00
|
|
|
File::Rename(filename + ".dtm", File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm");
|
2009-06-28 01:11:35 +00:00
|
|
|
}
|
2013-04-16 23:14:36 -04:00
|
|
|
|
2014-09-06 23:44:25 -04:00
|
|
|
if ((Movie::IsMovieActive()) && !Movie::IsJustStartingRecordingInputFromSaveState())
|
2014-03-12 15:33:41 -04:00
|
|
|
Movie::SaveRecording(filename + ".dtm");
|
2014-09-06 23:44:25 -04:00
|
|
|
else if (!Movie::IsMovieActive())
|
2012-10-19 19:42:44 -04:00
|
|
|
File::Delete(filename + ".dtm");
|
2009-06-28 01:11:35 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
File::IOFile f(filename, "wb");
|
2011-03-11 10:21:46 +00:00
|
|
|
if (!f)
|
2010-04-08 16:59:35 +00:00
|
|
|
{
|
2008-12-08 05:30:24 +00:00
|
|
|
Core::DisplayMessage("Could not save state", 2000);
|
2012-11-08 08:40:49 +01:00
|
|
|
g_compressAndDumpStateSyncEvent.Set();
|
2011-01-27 20:47:58 +00:00
|
|
|
return;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2009-06-28 01:11:35 +00:00
|
|
|
// Setting up the header
|
2011-03-17 10:17:45 +00:00
|
|
|
StateHeader header;
|
2010-02-06 05:22:53 +00:00
|
|
|
memcpy(header.gameID, SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), 6);
|
2013-11-14 02:19:15 -05:00
|
|
|
header.size = g_use_compression ? (u32)buffer_size : 0;
|
2012-11-08 08:40:49 +01:00
|
|
|
header.time = Common::Timer::GetDoubleTime();
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-11 10:21:46 +00:00
|
|
|
f.WriteArray(&header, 1);
|
2011-03-17 10:17:45 +00:00
|
|
|
|
2014-02-16 23:51:41 -05:00
|
|
|
if (header.size != 0) // non-zero header size means the state is compressed
|
2010-04-08 16:59:35 +00:00
|
|
|
{
|
2009-07-22 22:50:52 +00:00
|
|
|
lzo_uint i = 0;
|
2011-03-17 10:17:45 +00:00
|
|
|
while (true)
|
2010-04-08 16:59:35 +00:00
|
|
|
{
|
2013-06-06 09:00:52 +02:00
|
|
|
lzo_uint32 cur_len = 0;
|
2011-03-17 10:17:45 +00:00
|
|
|
lzo_uint out_len = 0;
|
|
|
|
|
|
|
|
if ((i + IN_LEN) >= buffer_size)
|
2013-11-14 02:19:15 -05:00
|
|
|
{
|
|
|
|
cur_len = (lzo_uint32)(buffer_size - i);
|
|
|
|
}
|
2009-07-22 22:50:52 +00:00
|
|
|
else
|
2013-11-14 02:19:15 -05:00
|
|
|
{
|
2009-07-22 22:50:52 +00:00
|
|
|
cur_len = IN_LEN;
|
2013-11-14 02:19:15 -05:00
|
|
|
}
|
2009-07-22 22:50:52 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
if (lzo1x_1_compress(buffer_data + i, cur_len, out, &out_len, wrkmem) != LZO_E_OK)
|
2011-01-13 02:05:58 +00:00
|
|
|
PanicAlertT("Internal LZO Error - compression failed");
|
2009-07-22 22:50:52 +00:00
|
|
|
|
|
|
|
// The size of the data to write is 'out_len'
|
2013-06-06 09:00:52 +02:00
|
|
|
f.WriteArray((lzo_uint32*)&out_len, 1);
|
2011-03-11 10:21:46 +00:00
|
|
|
f.WriteBytes(out, out_len);
|
2009-07-22 22:50:52 +00:00
|
|
|
|
2009-09-15 18:54:10 +00:00
|
|
|
if (cur_len != IN_LEN)
|
2009-07-22 22:50:52 +00:00
|
|
|
break;
|
2011-03-17 10:17:45 +00:00
|
|
|
|
2009-07-22 22:50:52 +00:00
|
|
|
i += cur_len;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
2009-09-15 18:54:10 +00:00
|
|
|
}
|
2014-02-16 23:51:41 -05:00
|
|
|
else // uncompressed
|
2009-09-15 18:54:10 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
f.WriteBytes(buffer_data, buffer_size);
|
2009-09-15 18:54:10 +00:00
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-03-12 15:33:41 -04:00
|
|
|
Core::DisplayMessage(StringFromFormat("Saved State to %s", filename.c_str()), 2000);
|
2012-11-08 08:40:49 +01:00
|
|
|
g_compressAndDumpStateSyncEvent.Set();
|
2009-06-26 22:36:44 +00:00
|
|
|
}
|
|
|
|
|
2012-11-08 08:40:49 +01:00
|
|
|
void SaveAs(const std::string& filename, bool wait)
|
2009-06-26 22:36:44 +00:00
|
|
|
{
|
2011-01-30 21:20:33 +00:00
|
|
|
// Pause the core while we save the state
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
2009-09-15 18:54:10 +00:00
|
|
|
|
|
|
|
// Measure the size of the buffer.
|
2014-03-09 21:14:26 +01:00
|
|
|
u8 *ptr = nullptr;
|
2009-06-26 22:36:44 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
|
|
|
|
DoState(p);
|
2011-03-17 10:17:45 +00:00
|
|
|
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
2009-09-15 18:54:10 +00:00
|
|
|
|
|
|
|
// Then actually do the write.
|
2011-12-30 20:16:12 -08:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
|
|
|
|
g_current_buffer.resize(buffer_size);
|
|
|
|
ptr = &g_current_buffer[0];
|
|
|
|
p.SetMode(PointerWrap::MODE_WRITE);
|
|
|
|
DoState(p);
|
|
|
|
}
|
2009-06-26 22:36:44 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
if (p.GetMode() == PointerWrap::MODE_WRITE)
|
|
|
|
{
|
|
|
|
Core::DisplayMessage("Saving State...", 1000);
|
2009-06-26 22:36:44 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
CompressAndDumpState_args save_args;
|
|
|
|
save_args.buffer_vector = &g_current_buffer;
|
|
|
|
save_args.buffer_mutex = &g_cs_current_buffer;
|
|
|
|
save_args.filename = filename;
|
2012-11-08 08:40:49 +01:00
|
|
|
save_args.wait = wait;
|
2011-12-30 20:16:12 -08:00
|
|
|
|
|
|
|
Flush();
|
|
|
|
g_save_thread = std::thread(CompressAndDumpState, save_args);
|
|
|
|
g_compressAndDumpStateSyncEvent.Wait();
|
|
|
|
|
|
|
|
g_last_filename = filename;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// someone aborted the save by changing the mode?
|
2015-03-30 09:02:43 +02:00
|
|
|
Core::DisplayMessage("Unable to save: Internal DoState Error", 4000);
|
2011-12-30 20:16:12 -08:00
|
|
|
}
|
2010-07-30 09:24:23 +00:00
|
|
|
|
2011-01-30 21:20:33 +00:00
|
|
|
// Resume the core and disable stepping
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2014-02-23 23:03:39 +01:00
|
|
|
bool ReadHeader(const std::string& filename, StateHeader& header)
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
Flush();
|
|
|
|
File::IOFile f(filename, "rb");
|
|
|
|
if (!f)
|
|
|
|
{
|
|
|
|
Core::DisplayMessage("State not found", 2000);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
f.ReadArray(&header, 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-07-08 14:29:26 +02:00
|
|
|
static void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
Flush();
|
2011-03-17 10:17:45 +00:00
|
|
|
File::IOFile f(filename, "rb");
|
2009-09-15 18:54:10 +00:00
|
|
|
if (!f)
|
|
|
|
{
|
2008-12-08 05:30:24 +00:00
|
|
|
Core::DisplayMessage("State not found", 2000);
|
|
|
|
return;
|
|
|
|
}
|
2009-01-15 06:48:15 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
StateHeader header;
|
2011-03-11 10:21:46 +00:00
|
|
|
f.ReadArray(&header, 1);
|
2013-10-29 01:23:17 -04:00
|
|
|
|
|
|
|
if (memcmp(SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), header.gameID, 6))
|
2009-06-28 01:11:35 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
Core::DisplayMessage(StringFromFormat("State belongs to a different game (ID %.*s)",
|
|
|
|
6, header.gameID), 2000);
|
2009-06-28 01:11:35 +00:00
|
|
|
return;
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
std::vector<u8> buffer;
|
|
|
|
|
2014-02-16 23:51:41 -05:00
|
|
|
if (header.size != 0) // non-zero size means the state is compressed
|
2009-09-15 18:54:10 +00:00
|
|
|
{
|
2009-06-28 01:11:35 +00:00
|
|
|
Core::DisplayMessage("Decompressing State...", 500);
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
buffer.resize(header.size);
|
|
|
|
|
2009-07-22 22:50:52 +00:00
|
|
|
lzo_uint i = 0;
|
2009-09-15 18:54:10 +00:00
|
|
|
while (true)
|
|
|
|
{
|
2013-06-06 09:00:52 +02:00
|
|
|
lzo_uint32 cur_len = 0; // number of bytes to read
|
2010-04-08 16:59:35 +00:00
|
|
|
lzo_uint new_len = 0; // number of bytes to write
|
2011-03-11 10:21:46 +00:00
|
|
|
|
|
|
|
if (!f.ReadArray(&cur_len, 1))
|
2009-07-22 22:50:52 +00:00
|
|
|
break;
|
2011-03-11 10:21:46 +00:00
|
|
|
|
|
|
|
f.ReadBytes(out, cur_len);
|
2014-03-09 21:14:26 +01:00
|
|
|
const int res = lzo1x_decompress(out, cur_len, &buffer[i], &new_len, nullptr);
|
2009-09-15 18:54:10 +00:00
|
|
|
if (res != LZO_E_OK)
|
|
|
|
{
|
|
|
|
// This doesn't seem to happen anymore.
|
2011-01-13 02:05:58 +00:00
|
|
|
PanicAlertT("Internal LZO Error - decompression failed (%d) (%li, %li) \n"
|
2009-07-22 22:50:52 +00:00
|
|
|
"Try loading the state again", res, i, new_len);
|
|
|
|
return;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2009-07-22 22:50:52 +00:00
|
|
|
i += new_len;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
2009-09-15 18:54:10 +00:00
|
|
|
}
|
2014-02-16 23:51:41 -05:00
|
|
|
else // uncompressed
|
2009-09-15 18:54:10 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
const size_t size = (size_t)(f.GetSize() - sizeof(StateHeader));
|
|
|
|
buffer.resize(size);
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
if (!f.ReadBytes(&buffer[0], size))
|
|
|
|
{
|
|
|
|
PanicAlert("wtf? reading bytes: %i", (int)size);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-07-30 09:24:23 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// all good
|
|
|
|
ret_data.swap(buffer);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
void LoadAs(const std::string& filename)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2014-07-08 00:47:57 +02:00
|
|
|
if (!Core::IsRunning())
|
2014-06-20 21:22:54 -04:00
|
|
|
return;
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// Stop the core while we load the state
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
|
|
|
|
2011-12-17 16:49:24 -08:00
|
|
|
g_loadDepth++;
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// Save temp buffer for undo load state
|
2012-11-08 22:45:13 -05:00
|
|
|
if (!Movie::IsJustStartingRecordingInputFromSaveState())
|
2011-12-30 20:16:12 -08:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
|
|
|
SaveToBuffer(g_undo_load_buffer);
|
2014-09-06 23:44:25 -04:00
|
|
|
if (Movie::IsMovieActive())
|
2014-03-12 15:33:41 -04:00
|
|
|
Movie::SaveRecording(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
|
2013-11-02 23:47:10 -04:00
|
|
|
else if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) +"undo.dtm"))
|
|
|
|
File::Delete(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
|
2011-12-30 20:16:12 -08:00
|
|
|
}
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
bool loaded = false;
|
|
|
|
bool loadedSuccessfully = false;
|
2015-03-30 09:02:43 +02:00
|
|
|
std::string version_created_by;
|
2011-03-17 10:17:45 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
// brackets here are so buffer gets freed ASAP
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
std::vector<u8> buffer;
|
|
|
|
LoadFileStateData(filename, buffer);
|
|
|
|
|
|
|
|
if (!buffer.empty())
|
|
|
|
{
|
|
|
|
u8 *ptr = &buffer[0];
|
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
2015-03-30 09:02:43 +02:00
|
|
|
version_created_by = DoState(p);
|
2011-12-30 20:16:12 -08:00
|
|
|
loaded = true;
|
|
|
|
loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ);
|
|
|
|
}
|
|
|
|
}
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
if (loaded)
|
|
|
|
{
|
|
|
|
if (loadedSuccessfully)
|
2011-12-17 16:49:24 -08:00
|
|
|
{
|
2014-03-12 15:33:41 -04:00
|
|
|
Core::DisplayMessage(StringFromFormat("Loaded state from %s", filename.c_str()), 2000);
|
2011-12-30 20:16:12 -08:00
|
|
|
if (File::Exists(filename + ".dtm"))
|
2014-03-12 15:33:41 -04:00
|
|
|
Movie::LoadInput(filename + ".dtm");
|
2012-10-18 04:03:12 -04:00
|
|
|
else if (!Movie::IsJustStartingRecordingInputFromSaveState() && !Movie::IsJustStartingPlayingInputFromSaveState())
|
2011-12-17 16:49:24 -08:00
|
|
|
Movie::EndPlayInput(false);
|
|
|
|
}
|
2011-03-17 10:17:45 +00:00
|
|
|
else
|
2011-12-17 16:49:24 -08:00
|
|
|
{
|
|
|
|
// failed to load
|
2015-03-30 09:02:43 +02:00
|
|
|
Core::DisplayMessage("Unable to load: Can't load state from other versions!", 4000);
|
|
|
|
if (!version_created_by.empty())
|
|
|
|
Core::DisplayMessage("The savestate was created using " + version_created_by, 4000);
|
2011-12-17 16:49:24 -08:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
// since we could be in an inconsistent state now (and might crash or whatever), undo.
|
2011-12-28 02:33:41 -06:00
|
|
|
if (g_loadDepth < 2)
|
2011-12-17 16:49:24 -08:00
|
|
|
UndoLoadState();
|
|
|
|
}
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-12-18 01:15:59 -08:00
|
|
|
if (g_onAfterLoadCb)
|
|
|
|
g_onAfterLoadCb();
|
|
|
|
|
2011-12-17 16:49:24 -08:00
|
|
|
g_loadDepth--;
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// resume dat core
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2011-03-17 10:17:45 +00:00
|
|
|
}
|
2011-03-11 10:21:46 +00:00
|
|
|
|
2011-12-18 01:15:59 -08:00
|
|
|
void SetOnAfterLoadCallback(CallbackFunc callback)
|
|
|
|
{
|
|
|
|
g_onAfterLoadCb = callback;
|
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
void VerifyAt(const std::string& filename)
|
2011-03-17 10:17:45 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
bool wasUnpaused = Core::PauseAndLock(true);
|
2011-03-11 10:21:46 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
std::vector<u8> buffer;
|
2011-12-30 20:16:12 -08:00
|
|
|
LoadFileStateData(filename, buffer);
|
2011-03-17 10:17:45 +00:00
|
|
|
|
|
|
|
if (!buffer.empty())
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
u8 *ptr = &buffer[0];
|
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
|
|
|
|
DoState(p);
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-12-28 02:33:41 -06:00
|
|
|
if (p.GetMode() == PointerWrap::MODE_VERIFY)
|
2014-03-12 15:33:41 -04:00
|
|
|
Core::DisplayMessage(StringFromFormat("Verified state at %s", filename.c_str()), 2000);
|
2011-03-17 10:17:45 +00:00
|
|
|
else
|
|
|
|
Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000);
|
2011-03-11 10:21:46 +00:00
|
|
|
}
|
2011-12-28 02:33:41 -06:00
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
Core::PauseAndLock(false, wasUnpaused);
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 20:16:12 -08:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Init()
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2009-07-22 22:50:52 +00:00
|
|
|
if (lzo_init() != LZO_E_OK)
|
2011-01-13 02:05:58 +00:00
|
|
|
PanicAlertT("Internal LZO Error - lzo_init() failed");
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Shutdown()
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
Flush();
|
2009-06-28 21:11:51 +00:00
|
|
|
|
2011-03-18 23:10:56 +00:00
|
|
|
// swapping with an empty vector, rather than clear()ing
|
2011-12-30 20:16:12 -08:00
|
|
|
// this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually, never)
|
2011-03-18 23:10:56 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
|
|
|
|
std::vector<u8>().swap(g_current_buffer);
|
2011-03-18 23:10:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
|
|
|
std::vector<u8>().swap(g_undo_load_buffer);
|
2011-03-18 23:10:56 +00:00
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static std::string MakeStateFilename(int number)
|
2008-12-20 12:10:59 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
return StringFromFormat("%s%s.s%02i", File::GetUserPath(D_STATESAVES_IDX).c_str(),
|
|
|
|
SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), number);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2012-11-08 08:40:49 +01:00
|
|
|
void Save(int slot, bool wait)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2012-11-08 08:40:49 +01:00
|
|
|
SaveAs(MakeStateFilename(slot), wait);
|
2011-03-17 10:17:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Load(int slot)
|
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
LoadAs(MakeStateFilename(slot));
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Verify(int slot)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
VerifyAt(MakeStateFilename(slot));
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2012-11-08 08:40:49 +01:00
|
|
|
void LoadLastSaved(int i)
|
2009-06-28 01:11:35 +00:00
|
|
|
{
|
2012-11-08 08:40:49 +01:00
|
|
|
std::map<double, int> savedStates = GetSavedStates();
|
|
|
|
|
2013-05-05 23:22:57 -05:00
|
|
|
if (i > (int)savedStates.size())
|
2012-11-08 08:40:49 +01:00
|
|
|
Core::DisplayMessage("State doesn't exist", 2000);
|
2009-06-28 01:11:35 +00:00
|
|
|
else
|
2012-11-08 08:40:49 +01:00
|
|
|
{
|
|
|
|
std::map<double, int>::iterator it = savedStates.begin();
|
|
|
|
std::advance(it, i-1);
|
|
|
|
Load(it->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// must wait for state to be written because it must know if all slots are taken
|
|
|
|
void SaveFirstSaved()
|
|
|
|
{
|
|
|
|
std::map<double, int> savedStates = GetSavedStates();
|
|
|
|
|
|
|
|
// save to an empty slot
|
|
|
|
if (savedStates.size() < NUM_STATES)
|
|
|
|
Save(GetEmptySlot(savedStates), true);
|
|
|
|
// overwrite the oldest state
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::map<double, int>::iterator it = savedStates.begin();
|
|
|
|
std::advance(it, savedStates.size()-1);
|
|
|
|
Save(it->second, true);
|
|
|
|
}
|
2009-06-28 01:11:35 +00:00
|
|
|
}
|
2009-06-28 21:11:51 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Flush()
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
|
|
|
// If already saving state, wait for it to finish
|
2011-03-17 10:17:45 +00:00
|
|
|
if (g_save_thread.joinable())
|
|
|
|
g_save_thread.join();
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load the last state before loading the state
|
2011-03-17 10:17:45 +00:00
|
|
|
void UndoLoadState()
|
2009-06-28 21:11:51 +00:00
|
|
|
{
|
2011-12-30 20:16:12 -08:00
|
|
|
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
2011-03-17 10:17:45 +00:00
|
|
|
if (!g_undo_load_buffer.empty())
|
2012-10-18 04:11:14 -04:00
|
|
|
{
|
2014-09-06 23:44:25 -04:00
|
|
|
if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm") || (!Movie::IsMovieActive()))
|
2012-10-18 04:11:14 -04:00
|
|
|
{
|
|
|
|
LoadFromBuffer(g_undo_load_buffer);
|
2014-09-06 23:44:25 -04:00
|
|
|
if (Movie::IsMovieActive())
|
2014-03-12 15:33:41 -04:00
|
|
|
Movie::LoadInput(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
|
2012-10-18 04:11:14 -04:00
|
|
|
}
|
|
|
|
else
|
2013-04-16 23:14:36 -04:00
|
|
|
{
|
2012-10-18 04:11:14 -04:00
|
|
|
PanicAlert("No undo.dtm found, aborting undo load state to prevent movie desyncs");
|
2013-04-16 23:14:36 -04:00
|
|
|
}
|
2012-10-18 04:11:14 -04:00
|
|
|
}
|
2011-03-17 10:17:45 +00:00
|
|
|
else
|
2013-04-16 23:14:36 -04:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
PanicAlert("There is nothing to undo!");
|
2013-04-16 23:14:36 -04:00
|
|
|
}
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load the state that the last save state overwritten on
|
2011-03-17 10:17:45 +00:00
|
|
|
void UndoSaveState()
|
2009-06-28 21:11:51 +00:00
|
|
|
{
|
2014-03-12 15:33:41 -04:00
|
|
|
LoadAs(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav");
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
2009-11-07 20:01:39 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
} // namespace State
|