Added the feature to allow creating a TAS movie from a save state. To activate this function, start the game and use the "Start recording" command. A save state will be created at that point in time and the emulator will start recording. This results in two files, a .dtm containing the movie and a .dtm.sav which is the save state.

Changes:
* Allow events to be scheduled when the emulator is not running.  This allows the save state event to be added before the emulator starts.
* Removed the Audio back-end init flag from the save state.  This value should not be saved as it is not data relevant to guest machine.
* Allow a recording to be started at any time (apart from when a recording is already being made).
* Updated the status bar and title bar when an on-screen message is shown
* Removed the saving of PEToken from the save state as the FIFO will save this information
* Added a couple Pixel Engine interrupt states to the save state
* Added the copyright notice to the GCPadStatus.h file.

This function is preliminary.  Let us know of any bugs you find or any UI quirks.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@7175 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
skidau 2011-02-15 09:07:55 +00:00
parent cf21251802
commit fb4c82fb48
11 changed files with 100 additions and 21 deletions

View File

@ -94,7 +94,7 @@ bool g_bStopping = false;
bool g_bHwInit = false; bool g_bHwInit = false;
bool g_bRealWiimote = false; bool g_bRealWiimote = false;
void *g_pWindowHandle = NULL; void *g_pWindowHandle = NULL;
std::string g_stateFileName;
std::thread g_EmuThread; std::thread g_EmuThread;
static std::thread cpuThread; static std::thread cpuThread;
@ -105,7 +105,8 @@ SCoreStartupParameter g_CoreStartupParameter;
Common::Event emuThreadGoing; Common::Event emuThreadGoing;
Common::Event cpuRunloopQuit; Common::Event cpuRunloopQuit;
std::string GetStateFileName() { return g_stateFileName; }
void SetStateFileName(std::string val) { g_stateFileName = val; }
// Display messages and return values // Display messages and return values
@ -125,12 +126,26 @@ bool PanicAlertToVideo(const char* text, bool yes_no)
void DisplayMessage(const std::string &message, int time_in_ms) void DisplayMessage(const std::string &message, int time_in_ms)
{ {
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
g_video_backend->Video_AddMessage(message.c_str(), time_in_ms); g_video_backend->Video_AddMessage(message.c_str(), time_in_ms);
if (_CoreParameter.bRenderToMain &&
SConfig::GetInstance().m_InterfaceStatusbar) {
Host_UpdateStatusBar(message.c_str());
} else
Host_UpdateTitle(message.c_str());
} }
void DisplayMessage(const char *message, int time_in_ms) void DisplayMessage(const char *message, int time_in_ms)
{ {
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
g_video_backend->Video_AddMessage(message, time_in_ms); g_video_backend->Video_AddMessage(message, time_in_ms);
if (_CoreParameter.bRenderToMain &&
SConfig::GetInstance().m_InterfaceStatusbar) {
Host_UpdateStatusBar(message);
} else
Host_UpdateTitle(message);
} }
void Callback_DebuggerBreak() void Callback_DebuggerBreak()
@ -158,6 +173,11 @@ bool IsRunningInCurrentThread()
return isRunning() && ((!cpuThread.joinable()) || cpuThread.get_id() == std::this_thread::get_id()); return isRunning() && ((!cpuThread.joinable()) || cpuThread.get_id() == std::this_thread::get_id());
} }
bool IsCPUThread()
{
return ((!cpuThread.joinable()) || cpuThread.get_id() == std::this_thread::get_id());
}
// This is called from the GUI thread. See the booting call schedule in // This is called from the GUI thread. See the booting call schedule in
// BootManager.cpp // BootManager.cpp
bool Init() bool Init()
@ -258,6 +278,9 @@ void CpuThread()
#endif #endif
} }
if (!g_stateFileName.empty())
State_LoadAs(g_stateFileName);
// Enter CPU run loop. When we leave it - we are done. // Enter CPU run loop. When we leave it - we are done.
CCPU::Run(); CCPU::Run();

View File

@ -55,7 +55,8 @@ void Callback_CoreMessage(int Id);
std::string StopMessage(bool, std::string); std::string StopMessage(bool, std::string);
bool isRunning(); bool isRunning();
bool IsRunningInCurrentThread(); // this tells us whether we are in the cpu thread. bool IsRunningInCurrentThread(); // this tells us whether we are running in the cpu thread.
bool IsCPUThread(); // this tells us whether we are the cpu thread.
void SetState(EState _State); void SetState(EState _State);
EState GetState(); EState GetState();
@ -79,6 +80,9 @@ void Callback_CoreMessage(int Id);
void DisplayMessage(const std::string &message, int time_in_ms); // This displays messages in a user-visible way. void DisplayMessage(const std::string &message, int time_in_ms); // This displays messages in a user-visible way.
void DisplayMessage(const char *message, int time_in_ms); // This displays messages in a user-visible way. void DisplayMessage(const char *message, int time_in_ms); // This displays messages in a user-visible way.
std::string GetStateFileName();
void SetStateFileName(std::string val);
int SyncTrace(); int SyncTrace();
void SetBlockStart(u32 addr); void SetBlockStart(u32 addr);
void StopTrace(); void StopTrace();
@ -100,7 +104,6 @@ void Callback_CoreMessage(int Id);
extern bool g_FrameStep; extern bool g_FrameStep;
#endif #endif
// --------------------------- // ---------------------------
} // namespace } // namespace
#endif #endif

View File

@ -241,11 +241,11 @@ void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata
externalEventSection.Leave(); externalEventSection.Leave();
} }
// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the main thread // Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread
// in which case the event will get handled immediately, before returning. // in which case the event will get handled immediately, before returning.
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata) void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata)
{ {
if(Core::IsRunningInCurrentThread()) if(Core::IsCPUThread())
{ {
externalEventSection.Enter(); externalEventSection.Enter();
event_types[event_type].callback(userdata, 0); event_types[event_type].callback(userdata, 0);

View File

@ -51,8 +51,6 @@ DSPLLE::DSPLLE() {
void DSPLLE::DoState(PointerWrap &p) void DSPLLE::DoState(PointerWrap &p)
{ {
p.Do(m_InitMixer);
p.Do(g_dsp.r); p.Do(g_dsp.r);
p.Do(g_dsp.pc); p.Do(g_dsp.pc);
#if PROFILE #if PROFILE
@ -96,7 +94,8 @@ void DSPLLE::dsp_thread(DSPLLE *lpParameter)
} }
Common::AtomicStore(dsp_lle->m_cycle_count, 0); Common::AtomicStore(dsp_lle->m_cycle_count, 0);
} }
Common::YieldCPU(); else
Common::YieldCPU();
} }
} }
@ -164,7 +163,7 @@ u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
UDSPControl Temp(_uFlag); UDSPControl Temp(_uFlag);
if (!m_InitMixer) if (!m_InitMixer)
{ {
if (!Temp.DSPHalt && Temp.DSPInit) if (!Temp.DSPHalt)
{ {
unsigned int AISampleRate, DACSampleRate; unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate); AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);

View File

@ -34,6 +34,7 @@
#else #else
#include <unistd.h> //truncate #include <unistd.h> //truncate
#endif #endif
#include "State.h"
Common::CriticalSection cs_frameSkip; Common::CriticalSection cs_frameSkip;
@ -53,6 +54,7 @@ char g_playingFile[256] = "\0";
FILE *g_recordfd = NULL; FILE *g_recordfd = NULL;
u64 g_frameCounter = 0, g_lagCounter = 0; u64 g_frameCounter = 0, g_lagCounter = 0;
bool g_bRecordingFromSaveState = false;
bool g_bPolled = false; bool g_bPolled = false;
int g_numRerecords = 0; int g_numRerecords = 0;
@ -141,6 +143,11 @@ bool IsRecordingInput()
return (g_playMode == MODE_RECORDING); return (g_playMode == MODE_RECORDING);
} }
bool IsRecordingInputFromSaveState()
{
return g_bRecordingFromSaveState;
}
bool IsPlayingInput() bool IsPlayingInput()
{ {
return (g_playMode == MODE_PLAYING); return (g_playMode == MODE_PLAYING);
@ -177,7 +184,6 @@ void ChangeWiiPads()
} }
} }
// TODO: Add BeginRecordingFromSavestate
bool BeginRecordingInput(int controllers) bool BeginRecordingInput(int controllers)
{ {
if(g_playMode != MODE_NONE || controllers == 0 || g_recordfd != NULL) if(g_playMode != MODE_NONE || controllers == 0 || g_recordfd != NULL)
@ -188,6 +194,17 @@ bool BeginRecordingInput(int controllers)
if(File::Exists(filename)) if(File::Exists(filename))
File::Delete(filename); File::Delete(filename);
if (Core::isRunning())
{
std::string tmpStateFilename = g_recordFile;
tmpStateFilename.append(".sav");
const char *stateFilename = tmpStateFilename.c_str();
if(File::Exists(stateFilename))
File::Delete(stateFilename);
State_SaveAs(stateFilename);
g_bRecordingFromSaveState = true;
}
g_recordfd = fopen(filename, "wb"); g_recordfd = fopen(filename, "wb");
if(!g_recordfd) { if(!g_recordfd) {
PanicAlertT("Error opening file %s for recording", filename); PanicAlertT("Error opening file %s for recording", filename);
@ -273,8 +290,13 @@ bool PlayInput(const char *filename)
} }
// Load savestate (and skip to frame data) // Load savestate (and skip to frame data)
if(header.bFromSaveState) { if(header.bFromSaveState)
// TODO {
std::string stateFilename = filename;
stateFilename.append(".sav");
if(File::Exists(stateFilename.c_str()))
Core::SetStateFileName(stateFilename);
g_bRecordingFromSaveState = true;
} }
/* TODO: Put this verification somewhere we have the gameID of the played game /* TODO: Put this verification somewhere we have the gameID of the played game
@ -316,7 +338,8 @@ void LoadInput(const char *filename)
fread(&header, sizeof(DTMHeader), 1, t_record); fread(&header, sizeof(DTMHeader), 1, t_record);
fclose(t_record); fclose(t_record);
if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A) { if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A)
{
PanicAlertT("Savestate movie %s is corrupted, movie recording stopping...", filename); PanicAlertT("Savestate movie %s is corrupted, movie recording stopping...", filename);
strncpy(g_playingFile, "\0", 256); strncpy(g_playingFile, "\0", 256);
EndPlayInput(); EndPlayInput();
@ -469,7 +492,7 @@ void SaveRecording(const char *filename)
header.bWii = Core::g_CoreStartupParameter.bWii; header.bWii = Core::g_CoreStartupParameter.bWii;
header.numControllers = g_numPads & (Core::g_CoreStartupParameter.bWii ? 0xFF : 0x0F); header.numControllers = g_numPads & (Core::g_CoreStartupParameter.bWii ? 0xFF : 0x0F);
header.bFromSaveState = false; // TODO: add the case where it's true header.bFromSaveState = g_bRecordingFromSaveState;
header.frameCount = g_frameCounter; header.frameCount = g_frameCounter;
header.lagCount = g_lagCounter; header.lagCount = g_lagCounter;
header.numRerecords = g_rerecords; header.numRerecords = g_rerecords;
@ -488,6 +511,15 @@ void SaveRecording(const char *filename)
File::Delete(filename); File::Delete(filename);
success = File::Copy(g_recordFile.c_str(), filename); success = File::Copy(g_recordFile.c_str(), filename);
if (success && g_bRecordingFromSaveState)
{
std::string tmpStateFilename = g_recordFile;
tmpStateFilename.append(".sav");
std::string stateFilename = filename;
stateFilename.append(".sav");
success = File::Copy(tmpStateFilename.c_str(), stateFilename.c_str());
}
if (success /* && !g_bReadOnly*/) if (success /* && !g_bReadOnly*/)
{ {
#ifdef WIN32 #ifdef WIN32

View File

@ -97,6 +97,7 @@ void SetPolledDevice();
bool IsAutoFiring(); bool IsAutoFiring();
bool IsRecordingInput(); bool IsRecordingInput();
bool IsRecordingInputFromSaveState();
bool IsPlayingInput(); bool IsPlayingInput();
bool IsUsingPad(int controller); bool IsUsingPad(int controller);

View File

@ -265,7 +265,7 @@ void SaveStateCallback(u64 userdata, int cyclesLate)
saveData->buffer = buffer; saveData->buffer = buffer;
saveData->size = sz; saveData->size = sz;
if (Frame::IsRecordingInput() || Frame::IsPlayingInput()) if ((Frame::IsRecordingInput() || Frame::IsPlayingInput()) && !Frame::IsRecordingInputFromSaveState())
Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()); Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
Core::DisplayMessage("Saving State...", 1000); Core::DisplayMessage("Saving State...", 1000);
@ -390,7 +390,7 @@ void LoadStateCallback(u64 userdata, int cyclesLate)
if (File::Exists(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str())) if (File::Exists(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()))
Frame::LoadInput(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()); Frame::LoadInput(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
else else if (!Frame::IsRecordingInputFromSaveState())
Frame::EndPlayInput(); Frame::EndPlayInput();
state_op_in_progress = false; state_op_in_progress = false;

View File

@ -1456,7 +1456,7 @@ void CFrame::UpdateGUI()
// Emulation // Emulation
GetMenuBar()->FindItem(IDM_STOP)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_STOP)->Enable(Running || Paused);
GetMenuBar()->FindItem(IDM_RESET)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_RESET)->Enable(Running || Paused);
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Initialized); GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Frame::IsRecordingInput());
GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!Initialized); GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!Initialized);
GetMenuBar()->FindItem(IDM_RECORDEXPORT)->Enable(Frame::IsRecordingInput()); GetMenuBar()->FindItem(IDM_RECORDEXPORT)->Enable(Frame::IsRecordingInput());
GetMenuBar()->FindItem(IDM_FRAMESTEP)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_FRAMESTEP)->Enable(Running || Paused);

View File

@ -572,6 +572,7 @@ void Host_UpdateTitle(const char* title)
wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_UPDATETITLE); wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_UPDATETITLE);
event.SetString(wxString::FromAscii(title)); event.SetString(wxString::FromAscii(title));
main_frame->GetEventHandler()->AddPendingEvent(event); main_frame->GetEventHandler()->AddPendingEvent(event);
Host_UpdateMainFrame();
} }
void Host_UpdateBreakPointView() void Host_UpdateBreakPointView()

View File

@ -1,6 +1,25 @@
// 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/
#ifndef _GCPAD_H_INCLUDED__ #ifndef _GCPAD_H_INCLUDED__
#define _GCPAD_H_INCLUDED__ #define _GCPAD_H_INCLUDED__
#include "CommonTypes.h"
#define PAD_ERR_NONE 0 #define PAD_ERR_NONE 0
#define PAD_ERR_NO_CONTROLLER -1 #define PAD_ERR_NO_CONTROLLER -1
#define PAD_ERR_NOT_READY -2 #define PAD_ERR_NOT_READY -2

View File

@ -135,10 +135,11 @@ void DoState(PointerWrap &p)
p.Do(m_AlphaModeConf); p.Do(m_AlphaModeConf);
p.Do(m_AlphaRead); p.Do(m_AlphaRead);
p.Do(m_Control); p.Do(m_Control);
p.Do(CommandProcessor::fifo.PEToken);
p.Do(g_bSignalTokenInterrupt); p.Do(g_bSignalTokenInterrupt);
p.Do(g_bSignalFinishInterrupt); p.Do(g_bSignalFinishInterrupt);
p.Do(interruptSetToken);
p.Do(interruptSetFinish);
p.Do(bbox); p.Do(bbox);
p.Do(bbox_active); p.Do(bbox_active);