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:
parent
cf21251802
commit
fb4c82fb48
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue