Major overhaul to input recording, including fixing major desyncs during playback and a small bug in the .DTM file format. Like netplay, some emulator options (specifically dual core and idle skipping) can cause desyncs, and the more your plugin options are similar to the ones used during recording, the more likely playback will sync.
Also, input movies are now linked to savestates instead of just selecting a file to save to and running a game and are exported at a later time. This allows you to easily continue a recording across sessions and makes rerecording possible. git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@6154 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
parent
6a695eff49
commit
c1cac331a0
|
@ -24,6 +24,7 @@
|
|||
#include "../ConfigManager.h"
|
||||
#include "MemoryUtil.h"
|
||||
#include "FileUtil.h"
|
||||
#include "../OnFrame.h"
|
||||
|
||||
// english
|
||||
SRAM sram_dump = {{
|
||||
|
@ -415,7 +416,9 @@ u32 CEXIIPL::GetGCTime()
|
|||
// hack in some netplay stuff
|
||||
ltime = NetPlay_GetGCTime();
|
||||
#endif
|
||||
if (0 == ltime)
|
||||
if (Frame::IsRecordingInput() || Frame::IsPlayingInput())
|
||||
ltime = 1234567890; // TODO: Should you be able to set a custom time in movies?
|
||||
else if (0 == ltime)
|
||||
ltime = Common::Timer::GetLocalTimeSinceJan1970();
|
||||
|
||||
return ((u32)ltime - cJanuary2000);
|
||||
|
|
|
@ -31,20 +31,21 @@ bool g_bFrameStep = false;
|
|||
bool g_bFrameStop = false;
|
||||
bool g_bAutoFire = false;
|
||||
u32 g_autoFirstKey = 0, g_autoSecondKey = 0;
|
||||
u32 g_rerecords = 0;
|
||||
bool g_bFirstKey = true;
|
||||
PlayMode g_playMode = MODE_NONE;
|
||||
|
||||
unsigned int g_framesToSkip = 0, g_frameSkipCounter = 0;
|
||||
|
||||
int g_numPads = 0;
|
||||
ControllerState *g_padStates;
|
||||
ControllerState g_padState;
|
||||
FILE *g_recordfd = NULL;
|
||||
|
||||
u64 g_frameCounter = 0, g_lagCounter = 0;
|
||||
bool g_bPolled = false;
|
||||
|
||||
int g_numRerecords = 0;
|
||||
std::string g_recordFile;
|
||||
std::string g_recordFile = "0.dtm";
|
||||
|
||||
void FrameUpdate()
|
||||
{
|
||||
|
@ -67,17 +68,6 @@ void FrameUpdate()
|
|||
if (g_bAutoFire)
|
||||
g_bFirstKey = !g_bFirstKey;
|
||||
|
||||
// Dump/Read all controllers' states for this frame
|
||||
if(IsRecordingInput())
|
||||
fwrite(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd);
|
||||
else if(IsPlayingInput()) {
|
||||
fread(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd);
|
||||
|
||||
// End of recording
|
||||
if(feof(g_recordfd))
|
||||
EndPlayInput();
|
||||
}
|
||||
|
||||
g_bPolled = false;
|
||||
}
|
||||
|
||||
|
@ -196,11 +186,13 @@ bool IsPlayingInput()
|
|||
}
|
||||
|
||||
// TODO: Add BeginRecordingFromSavestate
|
||||
bool BeginRecordingInput(const char *filename, int controllers)
|
||||
bool BeginRecordingInput(int controllers)
|
||||
{
|
||||
if(!filename || g_playMode != MODE_NONE || g_recordfd)
|
||||
if(g_playMode != MODE_NONE || g_recordfd)
|
||||
return false;
|
||||
|
||||
const char *filename = g_recordFile.c_str();
|
||||
|
||||
if(File::Exists(filename))
|
||||
File::Delete(filename);
|
||||
|
||||
|
@ -215,79 +207,44 @@ bool BeginRecordingInput(const char *filename, int controllers)
|
|||
fwrite(&dummy, sizeof(DTMHeader), 1, g_recordfd);
|
||||
|
||||
g_numPads = controllers;
|
||||
g_padStates = new ControllerState[controllers];
|
||||
|
||||
g_frameCounter = 0;
|
||||
g_lagCounter = 0;
|
||||
|
||||
g_playMode = MODE_RECORDING;
|
||||
|
||||
g_recordFile = filename;
|
||||
Core::DisplayMessage("Starting movie recording", 2000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EndRecordingInput()
|
||||
{
|
||||
rewind(g_recordfd);
|
||||
|
||||
// Create the real header now and write it
|
||||
DTMHeader header;
|
||||
memset(&header, 0, sizeof(DTMHeader));
|
||||
|
||||
header.filetype[0] = 'D'; header.filetype[1] = 'T'; header.filetype[2] = 'M'; header.filetype[3] = 0x1A;
|
||||
strncpy((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6);
|
||||
header.bWii = Core::g_CoreStartupParameter.bWii;
|
||||
header.numControllers = g_numPads;
|
||||
|
||||
header.bFromSaveState = false; // TODO: add the case where it's true
|
||||
header.frameCount = g_frameCounter;
|
||||
header.lagCount = g_lagCounter;
|
||||
|
||||
// TODO
|
||||
header.uniqueID = 0;
|
||||
header.numRerecords = 0;
|
||||
// header.author;
|
||||
// header.videoPlugin;
|
||||
// header.audioPlugin;
|
||||
|
||||
fwrite(&header, sizeof(DTMHeader), 1, g_recordfd);
|
||||
|
||||
fclose(g_recordfd);
|
||||
g_recordfd = NULL;
|
||||
|
||||
delete[] g_padStates;
|
||||
|
||||
g_playMode = MODE_NONE;
|
||||
}
|
||||
|
||||
void RecordInput(SPADStatus *PadStatus, int controllerID)
|
||||
{
|
||||
if(!IsRecordingInput() || controllerID >= g_numPads || controllerID < 0)
|
||||
return;
|
||||
|
||||
g_padState.A = ((PadStatus->button & PAD_BUTTON_A) != 0);
|
||||
g_padState.B = ((PadStatus->button & PAD_BUTTON_B) != 0);
|
||||
g_padState.X = ((PadStatus->button & PAD_BUTTON_X) != 0);
|
||||
g_padState.Y = ((PadStatus->button & PAD_BUTTON_Y) != 0);
|
||||
g_padState.Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0);
|
||||
g_padState.Start = ((PadStatus->button & PAD_BUTTON_START) != 0);
|
||||
|
||||
g_padStates[controllerID].A = ((PadStatus->button & PAD_BUTTON_A) != 0);
|
||||
g_padStates[controllerID].B = ((PadStatus->button & PAD_BUTTON_B) != 0);
|
||||
g_padStates[controllerID].X = ((PadStatus->button & PAD_BUTTON_X) != 0);
|
||||
g_padStates[controllerID].Y = ((PadStatus->button & PAD_BUTTON_Y) != 0);
|
||||
g_padStates[controllerID].Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0);
|
||||
g_padStates[controllerID].Start = ((PadStatus->button & PAD_BUTTON_START) != 0);
|
||||
g_padState.DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0);
|
||||
g_padState.DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0);
|
||||
g_padState.DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0);
|
||||
g_padState.DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0);
|
||||
|
||||
g_padStates[controllerID].DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0);
|
||||
g_padStates[controllerID].DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0);
|
||||
g_padStates[controllerID].DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0);
|
||||
g_padStates[controllerID].DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0);
|
||||
g_padState.L = PadStatus->triggerLeft;
|
||||
g_padState.R = PadStatus->triggerRight;
|
||||
|
||||
g_padStates[controllerID].L = PadStatus->triggerLeft;
|
||||
g_padStates[controllerID].R = PadStatus->triggerRight;
|
||||
g_padState.AnalogStickX = PadStatus->stickX;
|
||||
g_padState.AnalogStickY = PadStatus->stickY;
|
||||
|
||||
g_padStates[controllerID].AnalogStickX = PadStatus->stickX;
|
||||
g_padStates[controllerID].AnalogStickY = PadStatus->stickY;
|
||||
g_padState.CStickX = PadStatus->substickX;
|
||||
g_padState.CStickY = PadStatus->substickY;
|
||||
|
||||
g_padStates[controllerID].CStickX = PadStatus->substickX;
|
||||
g_padStates[controllerID].CStickY = PadStatus->substickY;
|
||||
|
||||
PlayController(PadStatus, controllerID);
|
||||
fwrite(&g_padState, sizeof(ControllerState), 1, g_recordfd);
|
||||
}
|
||||
|
||||
bool PlayInput(const char *filename)
|
||||
|
@ -330,9 +287,7 @@ bool PlayInput(const char *filename)
|
|||
*/
|
||||
|
||||
g_numPads = header.numControllers;
|
||||
g_padStates = new ControllerState[g_numPads];
|
||||
g_numRerecords = header.numRerecords;
|
||||
g_recordFile = filename;
|
||||
|
||||
g_playMode = MODE_PLAYING;
|
||||
|
||||
|
@ -344,61 +299,146 @@ cleanup:
|
|||
return false;
|
||||
}
|
||||
|
||||
void LoadInput(const char *filename)
|
||||
{
|
||||
FILE *t_record = fopen(filename, "rb");
|
||||
|
||||
DTMHeader header;
|
||||
|
||||
fread(&header, sizeof(DTMHeader), 1, t_record);
|
||||
|
||||
if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A) {
|
||||
PanicAlert("Savestate movie %s is corrupted, movie recording stopping...", filename);
|
||||
fclose(t_record);
|
||||
EndPlayInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_rerecords == 0)
|
||||
g_rerecords = header.numRerecords;
|
||||
|
||||
g_numPads = header.numControllers;
|
||||
|
||||
fclose(t_record);
|
||||
|
||||
if (g_recordfd)
|
||||
fclose(g_recordfd);
|
||||
|
||||
File::Delete(g_recordFile.c_str());
|
||||
File::Copy(filename, g_recordFile.c_str());
|
||||
|
||||
g_recordfd = fopen(g_recordFile.c_str(), "r+b");
|
||||
fseek(g_recordfd, 0, SEEK_END);
|
||||
|
||||
g_rerecords++;
|
||||
|
||||
Core::DisplayMessage("Resuming movie recording", 2000);
|
||||
|
||||
g_playMode = MODE_RECORDING;
|
||||
}
|
||||
|
||||
void PlayController(SPADStatus *PadStatus, int controllerID)
|
||||
{
|
||||
// Correct playback is entirely dependent on the emulator polling the controllers
|
||||
// in the same order done during recording
|
||||
if(!IsPlayingInput() || controllerID >= g_numPads || controllerID < 0)
|
||||
return;
|
||||
|
||||
memset(PadStatus, 0, sizeof(SPADStatus));
|
||||
fread(&g_padState, sizeof(ControllerState), 1, g_recordfd);
|
||||
|
||||
PadStatus->button |= PAD_USE_ORIGIN;
|
||||
|
||||
if(g_padStates[controllerID].A) {
|
||||
if(g_padState.A) {
|
||||
PadStatus->button |= PAD_BUTTON_A;
|
||||
PadStatus->analogA = 0xFF;
|
||||
}
|
||||
if(g_padStates[controllerID].B) {
|
||||
if(g_padState.B) {
|
||||
PadStatus->button |= PAD_BUTTON_B;
|
||||
PadStatus->analogB = 0xFF;
|
||||
}
|
||||
if(g_padStates[controllerID].X)
|
||||
if(g_padState.X)
|
||||
PadStatus->button |= PAD_BUTTON_X;
|
||||
if(g_padStates[controllerID].Y)
|
||||
if(g_padState.Y)
|
||||
PadStatus->button |= PAD_BUTTON_Y;
|
||||
if(g_padStates[controllerID].Z)
|
||||
if(g_padState.Z)
|
||||
PadStatus->button |= PAD_TRIGGER_Z;
|
||||
if(g_padStates[controllerID].Start)
|
||||
if(g_padState.Start)
|
||||
PadStatus->button |= PAD_BUTTON_START;
|
||||
|
||||
if(g_padStates[controllerID].DPadUp)
|
||||
if(g_padState.DPadUp)
|
||||
PadStatus->button |= PAD_BUTTON_UP;
|
||||
if(g_padStates[controllerID].DPadDown)
|
||||
if(g_padState.DPadDown)
|
||||
PadStatus->button |= PAD_BUTTON_DOWN;
|
||||
if(g_padStates[controllerID].DPadLeft)
|
||||
if(g_padState.DPadLeft)
|
||||
PadStatus->button |= PAD_BUTTON_LEFT;
|
||||
if(g_padStates[controllerID].DPadRight)
|
||||
if(g_padState.DPadRight)
|
||||
PadStatus->button |= PAD_BUTTON_RIGHT;
|
||||
|
||||
PadStatus->triggerLeft = g_padStates[controllerID].L;
|
||||
PadStatus->triggerLeft = g_padState.L;
|
||||
if(PadStatus->triggerLeft > 230)
|
||||
PadStatus->button |= PAD_TRIGGER_L;
|
||||
PadStatus->triggerRight = g_padStates[controllerID].R;
|
||||
PadStatus->triggerRight = g_padState.R;
|
||||
if(PadStatus->triggerRight > 230)
|
||||
PadStatus->button |= PAD_TRIGGER_R;
|
||||
|
||||
PadStatus->stickX = g_padStates[controllerID].AnalogStickX;
|
||||
PadStatus->stickY = g_padStates[controllerID].AnalogStickY;
|
||||
PadStatus->stickX = g_padState.AnalogStickX;
|
||||
PadStatus->stickY = g_padState.AnalogStickY;
|
||||
|
||||
PadStatus->substickX = g_padStates[controllerID].CStickX;
|
||||
PadStatus->substickY = g_padStates[controllerID].CStickY;
|
||||
PadStatus->substickX = g_padState.CStickX;
|
||||
PadStatus->substickY = g_padState.CStickY;
|
||||
|
||||
if(feof(g_recordfd))
|
||||
{
|
||||
Core::DisplayMessage("Movie End", 2000);
|
||||
// TODO: read-only mode
|
||||
//EndPlayInput();
|
||||
g_playMode = MODE_RECORDING;
|
||||
}
|
||||
}
|
||||
|
||||
void EndPlayInput() {
|
||||
fclose(g_recordfd);
|
||||
if (g_recordfd)
|
||||
fclose(g_recordfd);
|
||||
g_recordfd = NULL;
|
||||
g_numPads = 0;
|
||||
delete[] g_padStates;
|
||||
g_numPads = g_rerecords = 0;
|
||||
g_frameCounter = g_lagCounter = 0;
|
||||
g_playMode = MODE_NONE;
|
||||
}
|
||||
|
||||
void SaveRecording(const char *filename)
|
||||
{
|
||||
rewind(g_recordfd);
|
||||
|
||||
// Create the real header now and write it
|
||||
DTMHeader header;
|
||||
memset(&header, 0, sizeof(DTMHeader));
|
||||
|
||||
header.filetype[0] = 'D'; header.filetype[1] = 'T'; header.filetype[2] = 'M'; header.filetype[3] = 0x1A;
|
||||
strncpy((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6);
|
||||
header.bWii = Core::g_CoreStartupParameter.bWii;
|
||||
header.numControllers = g_numPads;
|
||||
|
||||
header.bFromSaveState = false; // TODO: add the case where it's true
|
||||
header.frameCount = g_frameCounter;
|
||||
header.lagCount = g_lagCounter;
|
||||
header.numRerecords = g_rerecords;
|
||||
|
||||
// TODO
|
||||
header.uniqueID = 0;
|
||||
// header.author;
|
||||
// header.videoPlugin;
|
||||
// header.audioPlugin;
|
||||
|
||||
fwrite(&header, sizeof(DTMHeader), 1, g_recordfd);
|
||||
fclose(g_recordfd);
|
||||
|
||||
if (File::Copy(g_recordFile.c_str(), filename))
|
||||
Core::DisplayMessage(StringFromFormat("DTM %s saved", filename).c_str(), 2000);
|
||||
else
|
||||
Core::DisplayMessage(StringFromFormat("Failed to save %s", filename).c_str(), 2000);
|
||||
|
||||
g_recordfd = fopen(g_recordFile.c_str(), "r+b");
|
||||
fseek(g_recordfd, 0, SEEK_END);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,11 +40,12 @@ struct ControllerState {
|
|||
bool Start:1, A:1, B:1, X:1, Y:1, Z:1; // Binary buttons, 6 bits
|
||||
bool DPadUp:1, DPadDown:1, // Binary D-Pad buttons, 4 bits
|
||||
DPadLeft:1, DPadRight:1;
|
||||
bool reserved:6; // Reserved bits used for padding, 6 bits
|
||||
|
||||
u8 L, R; // Triggers, 16 bits
|
||||
u8 AnalogStickX, AnalogStickY; // Main Stick, 16 bits
|
||||
u8 CStickX, CStickY; // Sub-Stick, 16 bits
|
||||
|
||||
bool reserved:6; // Reserved bits, 6 bits
|
||||
}; // Total: 58 + 6 = 64 bits per frame
|
||||
#pragma pack(pop)
|
||||
|
||||
|
@ -110,13 +111,14 @@ void FrameSkipping();
|
|||
|
||||
void ModifyController(SPADStatus *PadStatus, int controllerID);
|
||||
|
||||
bool BeginRecordingInput(const char *filename, int controllers);
|
||||
bool BeginRecordingInput(int controllers);
|
||||
void RecordInput(SPADStatus *PadStatus, int controllerID);
|
||||
void EndRecordingInput();
|
||||
|
||||
bool PlayInput(const char *filename);
|
||||
void LoadInput(const char *filename);
|
||||
void PlayController(SPADStatus *PadStatus, int controllerID);
|
||||
void EndPlayInput();
|
||||
void SaveRecording(const char *filename);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "StringUtil.h"
|
||||
#include "Thread.h"
|
||||
#include "CoreTiming.h"
|
||||
#include "OnFrame.h"
|
||||
#include "HW/HW.h"
|
||||
#include "PowerPC/PowerPC.h"
|
||||
#include "PowerPC/JitCommon/JitBase.h"
|
||||
|
@ -255,6 +256,9 @@ void SaveStateCallback(u64 userdata, int cyclesLate)
|
|||
saveStruct *saveData = new saveStruct;
|
||||
saveData->buffer = buffer;
|
||||
saveData->size = sz;
|
||||
|
||||
if (Frame::IsRecordingInput())
|
||||
Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
|
||||
|
||||
Core::DisplayMessage("Saving State...", 1000);
|
||||
|
||||
|
@ -369,6 +373,11 @@ void LoadStateCallback(u64 userdata, int cyclesLate)
|
|||
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;
|
||||
|
||||
|
|
|
@ -246,6 +246,7 @@ EVT_MENU(IDM_STOP, CFrame::OnStop)
|
|||
EVT_MENU(IDM_RESET, CFrame::OnReset)
|
||||
EVT_MENU(IDM_RECORD, CFrame::OnRecord)
|
||||
EVT_MENU(IDM_PLAYRECORD, CFrame::OnPlayRecording)
|
||||
EVT_MENU(IDM_RECORDEXPORT, CFrame::OnRecordExport)
|
||||
EVT_MENU(IDM_FRAMESTEP, CFrame::OnFrameStep)
|
||||
EVT_MENU(IDM_LUA, CFrame::OnOpenLuaWindow)
|
||||
EVT_MENU(IDM_SCREENSHOT, CFrame::OnScreenshot)
|
||||
|
|
|
@ -117,6 +117,7 @@ class CFrame : public CRenderFrame
|
|||
void InitBitmaps();
|
||||
void DoPause();
|
||||
void DoStop();
|
||||
void DoRecordingSave();
|
||||
bool bRenderToMain;
|
||||
bool bNoWiimoteMsg;
|
||||
void UpdateGUI();
|
||||
|
@ -272,6 +273,7 @@ class CFrame : public CRenderFrame
|
|||
void OnReset(wxCommandEvent& event);
|
||||
void OnRecord(wxCommandEvent& event);
|
||||
void OnPlayRecording(wxCommandEvent& event);
|
||||
void OnRecordExport(wxCommandEvent& event);
|
||||
void OnChangeDisc(wxCommandEvent& event);
|
||||
void OnScreenshot(wxCommandEvent& event);
|
||||
void OnActive(wxActivateEvent& event);
|
||||
|
|
|
@ -131,8 +131,9 @@ void CFrame::CreateMenu()
|
|||
emulationMenu->AppendSeparator();
|
||||
emulationMenu->Append(IDM_TOGGLE_FULLSCREEN, GetMenuLabel(HK_FULLSCREEN));
|
||||
emulationMenu->AppendSeparator();
|
||||
emulationMenu->Append(IDM_RECORD, _T("Start Re&cording..."));
|
||||
emulationMenu->Append(IDM_RECORD, _T("Start Re&cording"));
|
||||
emulationMenu->Append(IDM_PLAYRECORD, _T("P&lay Recording..."));
|
||||
emulationMenu->Append(IDM_RECORDEXPORT, _T("Export Recording..."));
|
||||
emulationMenu->AppendSeparator();
|
||||
emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc"));
|
||||
|
||||
|
@ -627,23 +628,8 @@ void CFrame::OnChangeDisc(wxCommandEvent& WXUNUSED (event))
|
|||
|
||||
void CFrame::OnRecord(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
wxString path = wxFileSelector(
|
||||
_T("Select The Recording File"),
|
||||
wxEmptyString, wxEmptyString, wxEmptyString,
|
||||
wxString::Format
|
||||
(
|
||||
_T("Dolphin TAS Movies (*.dtm)|*.dtm|All files (%s)|%s"),
|
||||
wxFileSelectorDefaultWildcardStr,
|
||||
wxFileSelectorDefaultWildcardStr
|
||||
),
|
||||
wxFD_SAVE | wxFD_PREVIEW,
|
||||
this);
|
||||
|
||||
if(path.IsEmpty())
|
||||
return;
|
||||
|
||||
// TODO: Take controller settings from Gamecube Configuration menu
|
||||
if(Frame::BeginRecordingInput(path.mb_str(), 1))
|
||||
if(Frame::BeginRecordingInput(1))
|
||||
BootGame(std::string(""));
|
||||
}
|
||||
|
||||
|
@ -668,6 +654,11 @@ void CFrame::OnPlayRecording(wxCommandEvent& WXUNUSED (event))
|
|||
BootGame(std::string(""));
|
||||
}
|
||||
|
||||
void CFrame::OnRecordExport(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
DoRecordingSave();
|
||||
}
|
||||
|
||||
void CFrame::OnPlay(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
|
@ -901,8 +892,8 @@ void CFrame::DoStop()
|
|||
|
||||
// TODO: Show the author/description dialog here
|
||||
if(Frame::IsRecordingInput())
|
||||
Frame::EndRecordingInput();
|
||||
if(Frame::IsPlayingInput())
|
||||
DoRecordingSave();
|
||||
if(Frame::IsPlayingInput() || Frame::IsRecordingInput())
|
||||
Frame::EndPlayInput();
|
||||
|
||||
// These windows cause segmentation faults if they are open when the emulator
|
||||
|
@ -946,6 +937,34 @@ void CFrame::DoStop()
|
|||
}
|
||||
}
|
||||
|
||||
void CFrame::DoRecordingSave()
|
||||
{
|
||||
bool paused = (Core::GetState() == Core::CORE_PAUSE);
|
||||
|
||||
if (!paused)
|
||||
DoPause();
|
||||
|
||||
wxString path = wxFileSelector(
|
||||
_T("Select The Recording File"),
|
||||
wxEmptyString, wxEmptyString, wxEmptyString,
|
||||
wxString::Format
|
||||
(
|
||||
_T("Dolphin TAS Movies (*.dtm)|*.dtm|All files (%s)|%s"),
|
||||
wxFileSelectorDefaultWildcardStr,
|
||||
wxFileSelectorDefaultWildcardStr
|
||||
),
|
||||
wxFD_SAVE | wxFD_PREVIEW,
|
||||
this);
|
||||
|
||||
if(path.IsEmpty())
|
||||
return;
|
||||
|
||||
Frame::SaveRecording(path.mb_str());
|
||||
|
||||
if (!paused)
|
||||
DoPause();
|
||||
}
|
||||
|
||||
void CFrame::OnStop(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
m_bGameLoading = false;
|
||||
|
@ -1281,6 +1300,7 @@ void CFrame::UpdateGUI()
|
|||
GetMenuBar()->FindItem(IDM_RESET)->Enable(Running || Paused);
|
||||
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Initialized);
|
||||
GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!Initialized);
|
||||
GetMenuBar()->FindItem(IDM_RECORDEXPORT)->Enable(Frame::IsRecordingInput());
|
||||
GetMenuBar()->FindItem(IDM_FRAMESTEP)->Enable(Running || Paused);
|
||||
GetMenuBar()->FindItem(IDM_SCREENSHOT)->Enable(Running || Paused);
|
||||
GetMenuBar()->FindItem(IDM_TOGGLE_FULLSCREEN)->Enable(Running || Paused);
|
||||
|
|
|
@ -77,6 +77,7 @@ enum
|
|||
IDM_TOGGLE_FULLSCREEN,
|
||||
IDM_RECORD,
|
||||
IDM_PLAYRECORD,
|
||||
IDM_RECORDEXPORT,
|
||||
IDM_FRAMESTEP,
|
||||
IDM_SCREENSHOT,
|
||||
IDM_BROWSE,
|
||||
|
|
Loading…
Reference in New Issue