2009-07-28 21:32:10 +00:00
|
|
|
// Copyright (C) 2003 Dolphin Project.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
// 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 SVN repository and contact information can be found at
|
|
|
|
// http://code.google.com/p/dolphin-emu/
|
|
|
|
|
|
|
|
#include "Common.h"
|
|
|
|
#include "State.h"
|
|
|
|
#include "Core.h"
|
2010-02-06 05:22:53 +00:00
|
|
|
#include "ConfigManager.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
#include "StringUtil.h"
|
2009-07-12 21:58:32 +00:00
|
|
|
#include "Thread.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
#include "CoreTiming.h"
|
2010-08-30 07:05:47 +00:00
|
|
|
#include "OnFrame.h"
|
2010-10-12 19:42:29 +00:00
|
|
|
#include "HW/Wiimote.h"
|
2011-01-28 18:39:30 +00:00
|
|
|
#include "HW/DSP.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
#include "HW/HW.h"
|
2011-01-30 21:20:33 +00:00
|
|
|
#include "HW/CPU.h"
|
2010-01-19 19:28:27 +00:00
|
|
|
#include "PowerPC/JitCommon/JitBase.h"
|
2011-01-31 01:28:32 +00:00
|
|
|
#include "VideoBackendBase.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-03-09 22:17:33 +00:00
|
|
|
#include <lzo/lzo1x.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-03-17 10:17:45 +00:00
|
|
|
static volatile bool g_op_in_progress = false;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static int ev_FileSave, ev_BufferSave, ev_FileLoad, ev_BufferLoad, ev_FileVerify, ev_BufferVerify;
|
2009-06-28 21:11:51 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static std::string g_current_filename, g_last_filename;
|
2008-12-08 05:30:24 +00: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;
|
2009-06-28 21:11:51 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
static std::thread g_save_thread;
|
2009-10-20 23:09:44 +00:00
|
|
|
|
|
|
|
// Don't forget to increase this after doing changes on the savestate system
|
2011-05-27 19:55:07 +00:00
|
|
|
static const int STATE_VERSION = 5;
|
2011-03-17 10:17:45 +00:00
|
|
|
|
|
|
|
struct StateHeader
|
|
|
|
{
|
|
|
|
u8 gameID[6];
|
|
|
|
size_t size;
|
|
|
|
};
|
2009-10-20 23:09:44 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
void DoState(PointerWrap &p)
|
|
|
|
{
|
2011-03-17 13:32:25 +00:00
|
|
|
u32 cookie = 0xBAADBABE + STATE_VERSION;
|
2008-12-08 05:30:24 +00:00
|
|
|
p.Do(cookie);
|
2011-03-17 13:32:25 +00:00
|
|
|
if (cookie != 0xBAADBABE + STATE_VERSION)
|
2009-09-15 18:54:10 +00:00
|
|
|
{
|
2009-11-01 01:24:30 +00:00
|
|
|
p.SetMode(PointerWrap::MODE_MEASURE);
|
2008-12-08 05:30:24 +00:00
|
|
|
return;
|
|
|
|
}
|
2011-02-14 02:18:03 +00:00
|
|
|
// Begin with video backend, so that it gets a chance to clear it's caches and writeback modified things to RAM
|
2011-02-08 10:37:47 +00:00
|
|
|
// Pause the video thread in multi-threaded mode
|
|
|
|
g_video_backend->RunLoop(false);
|
2011-01-31 01:28:32 +00:00
|
|
|
g_video_backend->DoState(p);
|
2011-01-28 18:39:30 +00:00
|
|
|
|
2010-04-08 16:59:35 +00:00
|
|
|
if (Core::g_CoreStartupParameter.bWii)
|
2010-10-12 19:42:29 +00:00
|
|
|
Wiimote::DoState(p.GetPPtr(), p.GetMode());
|
2011-01-30 21:20:33 +00:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
PowerPC::DoState(p);
|
|
|
|
HW::DoState(p);
|
|
|
|
CoreTiming::DoState(p);
|
2011-02-08 10:37:47 +00:00
|
|
|
|
|
|
|
// Resume the video thread
|
|
|
|
g_video_backend->RunLoop(true);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2009-06-28 21:11:51 +00:00
|
|
|
void LoadBufferStateCallback(u64 userdata, int cyclesLate)
|
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
u8* ptr = &g_current_buffer[0];
|
2009-06-28 21:11:51 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
|
|
|
DoState(p);
|
|
|
|
|
|
|
|
Core::DisplayMessage("Loaded state", 2000);
|
2011-03-17 10:17:45 +00:00
|
|
|
|
|
|
|
g_op_in_progress = false;
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SaveBufferStateCallback(u64 userdata, int cyclesLate)
|
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
u8* ptr = NULL;
|
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);
|
|
|
|
g_current_buffer.resize(buffer_size);
|
2009-07-22 22:50:52 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
ptr = &g_current_buffer[0];
|
2009-06-28 21:11:51 +00:00
|
|
|
p.SetMode(PointerWrap::MODE_WRITE);
|
|
|
|
DoState(p);
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
g_op_in_progress = false;
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
2010-04-17 21:02:03 +00:00
|
|
|
void VerifyBufferStateCallback(u64 userdata, int cyclesLate)
|
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
u8* ptr = &g_current_buffer[0];
|
2010-04-17 21:02:03 +00:00
|
|
|
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
|
|
|
|
DoState(p);
|
|
|
|
|
|
|
|
Core::DisplayMessage("Verified state", 2000);
|
2011-03-17 10:17:45 +00:00
|
|
|
|
|
|
|
g_op_in_progress = false;
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void CompressAndDumpState(const std::vector<u8>* save_arg)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
const u8* const buffer_data = &(*save_arg)[0];
|
|
|
|
const size_t buffer_size = save_arg->size();
|
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-03-17 10:17:45 +00:00
|
|
|
if (File::Exists(g_current_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"));
|
2009-06-28 01:11:35 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
if (!File::Rename(g_current_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);
|
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
File::IOFile f(g_current_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);
|
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);
|
2011-03-17 10:17:45 +00:00
|
|
|
header.size = g_use_compression ? buffer_size : 0;
|
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
|
|
|
|
|
|
|
if (0 != header.size) // 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
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
lzo_uint cur_len = 0;
|
|
|
|
lzo_uint out_len = 0;
|
|
|
|
|
|
|
|
if ((i + IN_LEN) >= buffer_size)
|
|
|
|
cur_len = buffer_size - i;
|
2009-07-22 22:50:52 +00:00
|
|
|
else
|
|
|
|
cur_len = IN_LEN;
|
|
|
|
|
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'
|
2011-03-11 10:21:46 +00:00
|
|
|
f.WriteArray(&out_len, 1);
|
|
|
|
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
|
|
|
}
|
2011-03-17 10:17:45 +00: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
|
|
|
|
2009-01-15 06:48:15 +00:00
|
|
|
Core::DisplayMessage(StringFromFormat("Saved State to %s",
|
2011-03-17 10:17:45 +00:00
|
|
|
g_current_filename.c_str()).c_str(), 2000);
|
2009-06-26 22:36:44 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
g_op_in_progress = false;
|
2009-06-26 22:36:44 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void SaveFileStateCallback(u64 userdata, int cyclesLate)
|
2009-06-26 22:36:44 +00:00
|
|
|
{
|
2011-01-30 21:20:33 +00:00
|
|
|
// Pause the core while we save the state
|
|
|
|
CCPU::EnableStepping(true);
|
2009-06-26 22:36:44 +00:00
|
|
|
|
2010-07-30 09:24:23 +00:00
|
|
|
// Wait for the other threaded sub-systems to stop too
|
2011-03-17 10:17:45 +00:00
|
|
|
// TODO: this is ugly
|
2010-07-30 11:58:18 +00:00
|
|
|
SLEEP(100);
|
2010-07-30 09:24:23 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
Flush();
|
2009-09-15 18:54:10 +00:00
|
|
|
|
|
|
|
// Measure the size of the buffer.
|
2011-03-17 10:17:45 +00:00
|
|
|
u8 *ptr = NULL;
|
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-03-17 10:17:45 +00:00
|
|
|
g_current_buffer.resize(buffer_size);
|
|
|
|
ptr = &g_current_buffer[0];
|
2009-06-26 22:36:44 +00:00
|
|
|
p.SetMode(PointerWrap::MODE_WRITE);
|
|
|
|
DoState(p);
|
2010-08-30 07:05:47 +00:00
|
|
|
|
2011-02-15 09:07:55 +00:00
|
|
|
if ((Frame::IsRecordingInput() || Frame::IsPlayingInput()) && !Frame::IsRecordingInputFromSaveState())
|
2011-05-03 00:06:44 +00:00
|
|
|
Frame::SaveRecording((g_current_filename + ".dtm").c_str());
|
|
|
|
else if (!Frame::IsRecordingInput() && !Frame::IsPlayingInput())
|
|
|
|
File::Delete(g_current_filename + ".dtm");
|
2009-06-26 22:36:44 +00:00
|
|
|
|
|
|
|
Core::DisplayMessage("Saving State...", 1000);
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
g_save_thread = std::thread(CompressAndDumpState, &g_current_buffer);
|
2010-07-30 09:24:23 +00:00
|
|
|
|
2011-01-30 21:20:33 +00:00
|
|
|
// Resume the core and disable stepping
|
|
|
|
CCPU::EnableStepping(false);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void LoadFileStateData(std::string& filename, std::vector<u8>& ret_data)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
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);
|
2009-06-28 01:11:35 +00:00
|
|
|
|
2010-02-06 05:22:53 +00: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;
|
|
|
|
|
|
|
|
if (0 != header.size) // 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)
|
|
|
|
{
|
2010-04-08 16:59:35 +00:00
|
|
|
lzo_uint cur_len = 0; // number of bytes to read
|
|
|
|
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);
|
2011-03-17 10:17:45 +00:00
|
|
|
const int res = lzo1x_decompress(out, cur_len, &buffer[i], &new_len, NULL);
|
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
|
|
|
}
|
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
|
|
|
}
|
2011-03-17 10:17:45 +00: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-03-17 10:17:45 +00:00
|
|
|
void LoadFileStateCallback(u64 userdata, int cyclesLate)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
// Stop the core while we load the state
|
|
|
|
CCPU::EnableStepping(true);
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// Wait for the other threaded sub-systems to stop too
|
|
|
|
// TODO: uglyyy
|
|
|
|
SLEEP(100);
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
Flush();
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// Save temp buffer for undo load state
|
|
|
|
// TODO: this should be controlled by a user option,
|
|
|
|
// because it slows down every savestate load to provide an often-unused feature.
|
|
|
|
SaveBufferStateCallback(userdata, cyclesLate);
|
|
|
|
g_undo_load_buffer.swap(g_current_buffer);
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
std::vector<u8> buffer;
|
|
|
|
LoadFileStateData(g_current_filename, buffer);
|
|
|
|
|
|
|
|
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_READ);
|
|
|
|
DoState(p);
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
if (p.GetMode() == PointerWrap::MODE_READ)
|
|
|
|
Core::DisplayMessage(StringFromFormat("Loaded state from %s", g_current_filename.c_str()).c_str(), 2000);
|
|
|
|
else
|
|
|
|
Core::DisplayMessage("Unable to Load : Can't load state from other revisions !", 4000);
|
|
|
|
|
|
|
|
if (File::Exists(g_current_filename + ".dtm"))
|
|
|
|
Frame::LoadInput((g_current_filename + ".dtm").c_str());
|
|
|
|
else if (!Frame::IsRecordingInputFromSaveState())
|
|
|
|
Frame::EndPlayInput(false);
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
g_op_in_progress = false;
|
2010-04-17 21:02:03 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
// resume dat core
|
|
|
|
CCPU::EnableStepping(false);
|
|
|
|
}
|
2011-03-11 10:21:46 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void VerifyFileStateCallback(u64 userdata, int cyclesLate)
|
|
|
|
{
|
|
|
|
Flush();
|
2011-03-11 10:21:46 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
std::vector<u8> buffer;
|
|
|
|
LoadFileStateData(g_current_filename, buffer);
|
|
|
|
|
|
|
|
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-03-17 10:17:45 +00:00
|
|
|
if (p.GetMode() == PointerWrap::MODE_READ)
|
|
|
|
Core::DisplayMessage(StringFromFormat("Verified state at %s", g_current_filename.c_str()).c_str(), 2000);
|
|
|
|
else
|
|
|
|
Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000);
|
2011-03-11 10:21:46 +00:00
|
|
|
}
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Init()
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
ev_FileLoad = CoreTiming::RegisterEvent("LoadState", &LoadFileStateCallback);
|
|
|
|
ev_FileSave = CoreTiming::RegisterEvent("SaveState", &SaveFileStateCallback);
|
|
|
|
ev_FileVerify = CoreTiming::RegisterEvent("VerifyState", &VerifyFileStateCallback);
|
|
|
|
|
2009-06-28 21:11:51 +00:00
|
|
|
ev_BufferLoad = CoreTiming::RegisterEvent("LoadBufferState", &LoadBufferStateCallback);
|
|
|
|
ev_BufferSave = CoreTiming::RegisterEvent("SaveBufferState", &SaveBufferStateCallback);
|
2010-04-17 21:02:03 +00:00
|
|
|
ev_BufferVerify = CoreTiming::RegisterEvent("VerifyBufferState", &VerifyBufferStateCallback);
|
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
|
|
|
|
// this gives a better guarantee to free the allocated memory right NOW
|
|
|
|
{
|
|
|
|
std::vector<u8> tmp;
|
|
|
|
g_current_buffer.swap(tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::vector<u8> tmp;
|
|
|
|
g_undo_load_buffer.swap(tmp);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void ScheduleFileEvent(const std::string &filename, int ev)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
if (g_op_in_progress)
|
2009-09-15 18:54:10 +00:00
|
|
|
return;
|
2011-03-17 10:17:45 +00:00
|
|
|
g_op_in_progress = true;
|
|
|
|
|
|
|
|
g_current_filename = filename;
|
|
|
|
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void SaveAs(const std::string &filename)
|
2009-06-28 01:11:35 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
g_last_filename = filename;
|
|
|
|
ScheduleFileEvent(filename, ev_FileSave);
|
2009-06-28 01:11:35 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void LoadAs(const std::string &filename)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
ScheduleFileEvent(filename, ev_FileLoad);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
2009-06-28 01:11:35 +00:00
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void VerifyAt(const std::string &filename)
|
2009-06-28 01:11:35 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
ScheduleFileEvent(filename, ev_FileVerify);
|
2009-06-28 01:11:35 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void Save(int slot)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
SaveAs(MakeStateFilename(slot));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Load(int slot)
|
|
|
|
{
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void LoadLastSaved()
|
2009-06-28 01:11:35 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
if (g_last_filename.empty())
|
2009-06-28 01:11:35 +00:00
|
|
|
Core::DisplayMessage("There is no last saved state", 2000);
|
|
|
|
else
|
2011-03-17 10:17:45 +00:00
|
|
|
LoadAs(g_last_filename);
|
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 ScheduleBufferEvent(std::vector<u8>& buffer, int ev)
|
2009-06-28 21:11:51 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
if (g_op_in_progress)
|
2009-09-15 18:54:10 +00:00
|
|
|
return;
|
2011-03-17 10:17:45 +00:00
|
|
|
g_op_in_progress = true;
|
|
|
|
|
|
|
|
g_current_buffer.swap(buffer);
|
|
|
|
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
|
2009-06-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void SaveToBuffer(std::vector<u8>& buffer)
|
2009-06-28 21:11:51 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
ScheduleBufferEvent(buffer, ev_BufferSave);
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void LoadFromBuffer(std::vector<u8>& buffer)
|
2010-04-17 21:02:03 +00:00
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
ScheduleBufferEvent(buffer, ev_BufferLoad);
|
2010-04-17 21:02:03 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 10:17:45 +00:00
|
|
|
void VerifyBuffer(std::vector<u8>& buffer)
|
|
|
|
{
|
|
|
|
ScheduleBufferEvent(buffer, ev_BufferVerify);
|
|
|
|
}
|
|
|
|
|
|
|
|
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-03-17 10:17:45 +00:00
|
|
|
if (!g_undo_load_buffer.empty())
|
|
|
|
LoadFromBuffer(g_undo_load_buffer);
|
|
|
|
else
|
|
|
|
PanicAlert("There is nothing to undo!");
|
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
|
|
|
{
|
2011-03-17 10:17:45 +00:00
|
|
|
LoadAs((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav").c_str());
|
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
|