dolphin/Source/Core/Core/Src/State.cpp
smelenchuk 0d110c41d9 Updates to rerecording behaviour:
* Frame counter is restored properly post-load.
* "Read-only mode" menu option added.  Currently this either causes the movie file to be closed at the end of playback (if enabled) or continues recording past end of playback (if disabled). 
* Can now properly resume recording from a state saved during movie playback.


git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@7142 8ced0084-cf51-0410-be5f-012b33b47a6e
2011-02-12 02:14:20 +00:00

628 lines
15 KiB
C++

// Copyright (C) 2003 Dolphin 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.
// 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"
#include "ConfigManager.h"
#include "StringUtil.h"
#include "Thread.h"
#include "CoreTiming.h"
#include "OnFrame.h"
#include "HW/Wiimote.h"
#include "HW/DSP.h"
#include "HW/HW.h"
#include "HW/CPU.h"
#include "PowerPC/JitCommon/JitBase.h"
#include "VideoBackendBase.h"
#include <string>
#include <lzo/lzo1x.h>
// TODO: Move to namespace
// TODO: Investigate a memory leak on save/load state
#if defined(__LZO_STRICT_16BIT)
#define IN_LEN (8*1024u)
#elif defined(LZO_ARCH_I086) && !defined(LZO_HAVE_MM_HUGE_ARRAY)
#define IN_LEN (60*1024u)
#else
#define IN_LEN (128*1024ul)
#endif
#define OUT_LEN (IN_LEN + IN_LEN / 16 + 64 + 3)
static unsigned char __LZO_MMODEL out [ OUT_LEN ];
#define HEAP_ALLOC(var,size) \
lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]
static HEAP_ALLOC(wrkmem,LZO1X_1_MEM_COMPRESS);
static bool state_op_in_progress = false;
static int ev_Save, ev_BufferSave;
static int ev_Load, ev_BufferLoad;
static int ev_Verify, ev_BufferVerify;
static std::string cur_filename, lastFilename;
static u8 **cur_buffer = NULL;
// Temporary undo state buffers
static u8 *undoLoad = NULL;
static bool const bCompressed = true;
static std::thread saveThread;
// Don't forget to increase this after doing changes on the savestate system
#define STATE_VERSION 4
void DoState(PointerWrap &p)
{
u32 cookie = 0xBAADBABE + STATE_VERSION;
p.Do(cookie);
if (cookie != 0xBAADBABE + STATE_VERSION)
{
//PanicAlert("Savestate version mismatch !\nSorry, you can't load states from other revisions.");
p.SetMode(PointerWrap::MODE_MEASURE);
return;
}
// Begin with video plugin, so that it gets a chance to clear it's caches and writeback modified things to RAM
// Pause the video thread in multi-threaded mode
g_video_backend->RunLoop(false);
g_video_backend->DoState(p);
if (Core::g_CoreStartupParameter.bWii)
Wiimote::DoState(p.GetPPtr(), p.GetMode());
PowerPC::DoState(p);
HW::DoState(p);
CoreTiming::DoState(p);
// Resume the video thread
g_video_backend->RunLoop(true);
}
void LoadBufferStateCallback(u64 userdata, int cyclesLate)
{
if (!cur_buffer || !*cur_buffer) {
Core::DisplayMessage("State does not exist", 1000);
return;
}
u8 *ptr = *cur_buffer;
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
Core::DisplayMessage("Loaded state", 2000);
state_op_in_progress = false;
}
void SaveBufferStateCallback(u64 userdata, int cyclesLate)
{
if (!cur_buffer) {
Core::DisplayMessage("Error saving state", 1000);
return;
}
u8 *ptr = NULL;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
if (!*cur_buffer)
{
// if we got passed an empty buffer,
// allocate it with new[]
// (and the caller is responsible for delete[]ing it later)
DoState(p);
size_t sz = (size_t)ptr;
*cur_buffer = new u8[sz];
}
else
{
// otherwise the caller is telling us that they have already allocated it with enough space
}
ptr = *cur_buffer;
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
state_op_in_progress = false;
}
void VerifyBufferStateCallback(u64 userdata, int cyclesLate)
{
if (!cur_buffer || !*cur_buffer) {
Core::DisplayMessage("State does not exist", 1000);
return;
}
u8 *ptr = *cur_buffer;
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
DoState(p);
Core::DisplayMessage("Verified state", 2000);
state_op_in_progress = false;
}
void CompressAndDumpState(saveStruct* saveArg)
{
u8 *buffer = saveArg->buffer;
size_t sz = saveArg->size;
lzo_uint out_len = 0;
state_header header;
std::string filename = cur_filename;
delete saveArg;
// For easy debugging
Common::SetCurrentThreadName("SaveState thread");
// Moving to last overwritten save-state
if (File::Exists(cur_filename.c_str()))
{
if (File::Exists((std::string(File::GetUserPath(D_STATESAVES_IDX)) + "lastState.sav").c_str()))
File::Delete((std::string(File::GetUserPath(D_STATESAVES_IDX)) + "lastState.sav").c_str());
if (!File::Rename(cur_filename.c_str(), (std::string(File::GetUserPath(D_STATESAVES_IDX)) + "lastState.sav").c_str()))
Core::DisplayMessage("Failed to move previous state to state undo backup", 1000);
}
FILE *f = fopen(filename.c_str(), "wb");
if (f == NULL)
{
Core::DisplayMessage("Could not save state", 2000);
delete[] buffer;
return;
}
// Setting up the header
memcpy(header.gameID, SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), 6);
header.sz = bCompressed ? sz : 0;
fwrite(&header, sizeof(state_header), 1, f);
if (bCompressed)
{
lzo_uint cur_len = 0;
lzo_uint i = 0;
for (;;)
{
if ((i + IN_LEN) >= sz)
cur_len = sz - i;
else
cur_len = IN_LEN;
if (lzo1x_1_compress((buffer + i), cur_len, out, &out_len, wrkmem) != LZO_E_OK)
PanicAlertT("Internal LZO Error - compression failed");
// The size of the data to write is 'out_len'
fwrite(&out_len, sizeof(int), 1, f);
fwrite(out, out_len, 1, f);
if (cur_len != IN_LEN)
break;
i += cur_len;
}
}
else
{
fwrite(buffer, sz, 1, f);
}
fclose(f);
delete[] buffer;
Core::DisplayMessage(StringFromFormat("Saved State to %s",
filename.c_str()).c_str(), 2000);
state_op_in_progress = false;
}
void SaveStateCallback(u64 userdata, int cyclesLate)
{
// Pause the core while we save the state
CCPU::EnableStepping(true);
// Wait for the other threaded sub-systems to stop too
SLEEP(100);
State_Flush();
// Measure the size of the buffer.
u8 *ptr = 0;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
size_t sz = (size_t)ptr;
// Then actually do the write.
u8 *buffer = new u8[sz];
ptr = buffer;
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
saveStruct *saveData = new saveStruct;
saveData->buffer = buffer;
saveData->size = sz;
if (Frame::IsRecordingInput() || Frame::IsPlayingInput())
Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
Core::DisplayMessage("Saving State...", 1000);
saveThread = std::thread(CompressAndDumpState, saveData);
// Resume the core and disable stepping
CCPU::EnableStepping(false);
}
void LoadStateCallback(u64 userdata, int cyclesLate)
{
bool bCompressedState;
// Stop the core while we load the state
CCPU::EnableStepping(true);
// Wait for the other threaded sub-systems to stop too
SLEEP(100);
State_Flush();
// 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.
{
delete[] undoLoad;
undoLoad = NULL;
cur_buffer = &undoLoad;
SaveBufferStateCallback(userdata, cyclesLate);
}
FILE *f = fopen(cur_filename.c_str(), "rb");
if (!f)
{
Core::DisplayMessage("State not found", 2000);
// Resume the clock
CCPU::EnableStepping(false);
return;
}
u8 *buffer = NULL;
state_header header;
size_t sz;
fread(&header, sizeof(state_header), 1, f);
if (memcmp(SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), header.gameID, 6))
{
char gameID[7] = {0};
memcpy(gameID, header.gameID, 6);
Core::DisplayMessage(StringFromFormat("State belongs to a different game (ID %s)",
gameID), 2000);
fclose(f);
// Resume the clock
CCPU::EnableStepping(false);
return;
}
sz = header.sz;
bCompressedState = (sz != 0);
if (bCompressedState)
{
Core::DisplayMessage("Decompressing State...", 500);
lzo_uint i = 0;
buffer = new u8[sz];
if (!buffer)
{
PanicAlertT("Error allocating buffer");
// Resume the clock
CCPU::EnableStepping(false);
return;
}
while (true)
{
lzo_uint cur_len = 0; // number of bytes to read
lzo_uint new_len = 0; // number of bytes to write
if (fread(&cur_len, 1, sizeof(int), f) == 0)
break;
if (feof(f))
break; // don't know if this happens.
fread(out, 1, cur_len, f);
int res = lzo1x_decompress(out, cur_len, (buffer + i), &new_len, NULL);
if (res != LZO_E_OK)
{
// This doesn't seem to happen anymore.
PanicAlertT("Internal LZO Error - decompression failed (%d) (%li, %li) \n"
"Try loading the state again", res, i, new_len);
fclose(f);
delete[] buffer;
// Resume the clock
CCPU::EnableStepping(false);
return;
}
i += new_len;
}
}
else
{
sz = (int)(File::GetSize(f) - sizeof(state_header));
buffer = new u8[sz];
int x;
if ((x = (int)fread(buffer, 1, sz, f)) != (int)sz)
PanicAlert("wtf? %d %lu", x, (unsigned long)sz);
}
fclose(f);
u8 *ptr = buffer;
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
if (p.GetMode() == PointerWrap::MODE_READ)
Core::DisplayMessage(StringFromFormat("Loaded state from %s", cur_filename.c_str()).c_str(), 2000);
else
Core::DisplayMessage("Unable to Load : Can't load state from other revisions !", 4000);
delete[] buffer;
if (File::Exists(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()))
Frame::LoadInput(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
else
Frame::EndPlayInput();
state_op_in_progress = false;
// Resume the clock
CCPU::EnableStepping(false);
}
void VerifyStateCallback(u64 userdata, int cyclesLate)
{
bool bCompressedState;
State_Flush();
FILE *f = fopen(cur_filename.c_str(), "rb");
if (!f)
{
Core::DisplayMessage("State not found", 2000);
return;
}
u8 *buffer = NULL;
state_header header;
size_t sz;
fread(&header, sizeof(state_header), 1, f);
if (memcmp(SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), header.gameID, 6))
{
char gameID[7] = {0};
memcpy(gameID, header.gameID, 6);
Core::DisplayMessage(StringFromFormat("State belongs to a different game (ID %s)",
gameID), 2000);
fclose(f);
return;
}
sz = header.sz;
bCompressedState = (sz != 0);
if (bCompressedState)
{
Core::DisplayMessage("Decompressing State...", 500);
lzo_uint i = 0;
buffer = new u8[sz];
if (!buffer) {
PanicAlertT("Error allocating buffer");
return;
}
while (true)
{
lzo_uint cur_len = 0;
lzo_uint new_len = 0;
if (fread(&cur_len, 1, sizeof(int), f) == 0)
break;
if (feof(f))
break; // don't know if this happens.
fread(out, 1, cur_len, f);
int res = lzo1x_decompress(out, cur_len, (buffer + i), &new_len, NULL);
if (res != LZO_E_OK)
{
// This doesn't seem to happen anymore.
PanicAlertT("Internal LZO Error - decompression failed (%d) (%ld, %ld) \n"
"Try verifying the state again", res, i, new_len);
fclose(f);
delete [] buffer;
return;
}
// The size of the data to read to our buffer is 'new_len'
i += new_len;
}
}
else
{
sz = (int)(File::GetSize(f) - sizeof(int));
buffer = new u8[sz];
int x;
if ((x = (int)fread(buffer, 1, sz, f)) != (int)sz)
PanicAlert("wtf? %d %lu", x, (unsigned long)sz);
}
fclose(f);
u8 *ptr = buffer;
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
DoState(p);
if (p.GetMode() == PointerWrap::MODE_READ)
Core::DisplayMessage(StringFromFormat("Verified state at %s", cur_filename.c_str()).c_str(), 2000);
else
Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000);
delete [] buffer;
}
void State_Init()
{
ev_Load = CoreTiming::RegisterEvent("LoadState", &LoadStateCallback);
ev_Save = CoreTiming::RegisterEvent("SaveState", &SaveStateCallback);
ev_Verify = CoreTiming::RegisterEvent("VerifyState", &VerifyStateCallback);
ev_BufferLoad = CoreTiming::RegisterEvent("LoadBufferState", &LoadBufferStateCallback);
ev_BufferSave = CoreTiming::RegisterEvent("SaveBufferState", &SaveBufferStateCallback);
ev_BufferVerify = CoreTiming::RegisterEvent("VerifyBufferState", &VerifyBufferStateCallback);
if (lzo_init() != LZO_E_OK)
PanicAlertT("Internal LZO Error - lzo_init() failed");
}
void State_Shutdown()
{
State_Flush();
if (undoLoad)
{
delete[] undoLoad;
undoLoad = NULL;
}
}
static std::string MakeStateFilename(int state_number)
{
return StringFromFormat("%s%s.s%02i", File::GetUserPath(D_STATESAVES_IDX), SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), state_number);
}
void State_SaveAs(const std::string &filename)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_filename = filename;
lastFilename = filename;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Save);
}
void State_Save(int slot)
{
State_SaveAs(MakeStateFilename(slot));
}
void State_LoadAs(const std::string &filename)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_filename = filename;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Load);
}
void State_Load(int slot)
{
State_LoadAs(MakeStateFilename(slot));
}
void State_VerifyAt(const std::string &filename)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_filename = filename;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Verify);
}
void State_Verify(int slot)
{
State_VerifyAt(MakeStateFilename(slot));
}
void State_LoadLastSaved()
{
if (lastFilename.empty())
Core::DisplayMessage("There is no last saved state", 2000);
else
State_LoadAs(lastFilename);
}
void State_LoadFromBuffer(u8 **buffer)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_buffer = buffer;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferLoad);
}
void State_SaveToBuffer(u8 **buffer)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_buffer = buffer;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferSave);
}
void State_VerifyBuffer(u8 **buffer)
{
if (state_op_in_progress)
return;
state_op_in_progress = true;
cur_buffer = buffer;
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferVerify);
}
void State_Flush()
{
// If already saving state, wait for it to finish
if (saveThread.joinable())
{
saveThread.join();
}
}
// Load the last state before loading the state
void State_UndoLoadState()
{
State_LoadFromBuffer(&undoLoad);
}
// Load the state that the last save state overwritten on
void State_UndoSaveState()
{
State_LoadAs((std::string(File::GetUserPath(D_STATESAVES_IDX)) + "lastState.sav").c_str());
}
size_t State_GetSize()
{
// Measure the size of the buffer.
u8 *ptr = 0;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
return (size_t)ptr;
}