mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-05-22 11:02:38 +00:00
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
8 changed files with 191 additions and 113 deletions
|
@ -24,6 +24,7 @@
|
||||||
#include "../ConfigManager.h"
|
#include "../ConfigManager.h"
|
||||||
#include "MemoryUtil.h"
|
#include "MemoryUtil.h"
|
||||||
#include "FileUtil.h"
|
#include "FileUtil.h"
|
||||||
|
#include "../OnFrame.h"
|
||||||
|
|
||||||
// english
|
// english
|
||||||
SRAM sram_dump = {{
|
SRAM sram_dump = {{
|
||||||
|
@ -415,7 +416,9 @@ u32 CEXIIPL::GetGCTime()
|
||||||
// hack in some netplay stuff
|
// hack in some netplay stuff
|
||||||
ltime = NetPlay_GetGCTime();
|
ltime = NetPlay_GetGCTime();
|
||||||
#endif
|
#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();
|
ltime = Common::Timer::GetLocalTimeSinceJan1970();
|
||||||
|
|
||||||
return ((u32)ltime - cJanuary2000);
|
return ((u32)ltime - cJanuary2000);
|
||||||
|
|
|
@ -31,20 +31,21 @@ bool g_bFrameStep = false;
|
||||||
bool g_bFrameStop = false;
|
bool g_bFrameStop = false;
|
||||||
bool g_bAutoFire = false;
|
bool g_bAutoFire = false;
|
||||||
u32 g_autoFirstKey = 0, g_autoSecondKey = 0;
|
u32 g_autoFirstKey = 0, g_autoSecondKey = 0;
|
||||||
|
u32 g_rerecords = 0;
|
||||||
bool g_bFirstKey = true;
|
bool g_bFirstKey = true;
|
||||||
PlayMode g_playMode = MODE_NONE;
|
PlayMode g_playMode = MODE_NONE;
|
||||||
|
|
||||||
unsigned int g_framesToSkip = 0, g_frameSkipCounter = 0;
|
unsigned int g_framesToSkip = 0, g_frameSkipCounter = 0;
|
||||||
|
|
||||||
int g_numPads = 0;
|
int g_numPads = 0;
|
||||||
ControllerState *g_padStates;
|
ControllerState g_padState;
|
||||||
FILE *g_recordfd = NULL;
|
FILE *g_recordfd = NULL;
|
||||||
|
|
||||||
u64 g_frameCounter = 0, g_lagCounter = 0;
|
u64 g_frameCounter = 0, g_lagCounter = 0;
|
||||||
bool g_bPolled = false;
|
bool g_bPolled = false;
|
||||||
|
|
||||||
int g_numRerecords = 0;
|
int g_numRerecords = 0;
|
||||||
std::string g_recordFile;
|
std::string g_recordFile = "0.dtm";
|
||||||
|
|
||||||
void FrameUpdate()
|
void FrameUpdate()
|
||||||
{
|
{
|
||||||
|
@ -67,17 +68,6 @@ void FrameUpdate()
|
||||||
if (g_bAutoFire)
|
if (g_bAutoFire)
|
||||||
g_bFirstKey = !g_bFirstKey;
|
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;
|
g_bPolled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,11 +186,13 @@ bool IsPlayingInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add BeginRecordingFromSavestate
|
// 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;
|
return false;
|
||||||
|
|
||||||
|
const char *filename = g_recordFile.c_str();
|
||||||
|
|
||||||
if(File::Exists(filename))
|
if(File::Exists(filename))
|
||||||
File::Delete(filename);
|
File::Delete(filename);
|
||||||
|
|
||||||
|
@ -215,79 +207,44 @@ bool BeginRecordingInput(const char *filename, int controllers)
|
||||||
fwrite(&dummy, sizeof(DTMHeader), 1, g_recordfd);
|
fwrite(&dummy, sizeof(DTMHeader), 1, g_recordfd);
|
||||||
|
|
||||||
g_numPads = controllers;
|
g_numPads = controllers;
|
||||||
g_padStates = new ControllerState[controllers];
|
|
||||||
|
|
||||||
g_frameCounter = 0;
|
g_frameCounter = 0;
|
||||||
g_lagCounter = 0;
|
g_lagCounter = 0;
|
||||||
|
|
||||||
g_playMode = MODE_RECORDING;
|
g_playMode = MODE_RECORDING;
|
||||||
|
|
||||||
g_recordFile = filename;
|
Core::DisplayMessage("Starting movie recording", 2000);
|
||||||
|
|
||||||
return true;
|
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)
|
void RecordInput(SPADStatus *PadStatus, int controllerID)
|
||||||
{
|
{
|
||||||
if(!IsRecordingInput() || controllerID >= g_numPads || controllerID < 0)
|
if(!IsRecordingInput() || controllerID >= g_numPads || controllerID < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
g_padStates[controllerID].A = ((PadStatus->button & PAD_BUTTON_A) != 0);
|
g_padState.A = ((PadStatus->button & PAD_BUTTON_A) != 0);
|
||||||
g_padStates[controllerID].B = ((PadStatus->button & PAD_BUTTON_B) != 0);
|
g_padState.B = ((PadStatus->button & PAD_BUTTON_B) != 0);
|
||||||
g_padStates[controllerID].X = ((PadStatus->button & PAD_BUTTON_X) != 0);
|
g_padState.X = ((PadStatus->button & PAD_BUTTON_X) != 0);
|
||||||
g_padStates[controllerID].Y = ((PadStatus->button & PAD_BUTTON_Y) != 0);
|
g_padState.Y = ((PadStatus->button & PAD_BUTTON_Y) != 0);
|
||||||
g_padStates[controllerID].Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0);
|
g_padState.Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0);
|
||||||
g_padStates[controllerID].Start = ((PadStatus->button & PAD_BUTTON_START) != 0);
|
g_padState.Start = ((PadStatus->button & PAD_BUTTON_START) != 0);
|
||||||
|
|
||||||
g_padStates[controllerID].DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0);
|
g_padState.DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0);
|
||||||
g_padStates[controllerID].DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0);
|
g_padState.DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0);
|
||||||
g_padStates[controllerID].DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0);
|
g_padState.DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0);
|
||||||
g_padStates[controllerID].DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0);
|
g_padState.DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0);
|
||||||
|
|
||||||
g_padStates[controllerID].L = PadStatus->triggerLeft;
|
g_padState.L = PadStatus->triggerLeft;
|
||||||
g_padStates[controllerID].R = PadStatus->triggerRight;
|
g_padState.R = PadStatus->triggerRight;
|
||||||
|
|
||||||
g_padStates[controllerID].AnalogStickX = PadStatus->stickX;
|
g_padState.AnalogStickX = PadStatus->stickX;
|
||||||
g_padStates[controllerID].AnalogStickY = PadStatus->stickY;
|
g_padState.AnalogStickY = PadStatus->stickY;
|
||||||
|
|
||||||
g_padStates[controllerID].CStickX = PadStatus->substickX;
|
g_padState.CStickX = PadStatus->substickX;
|
||||||
g_padStates[controllerID].CStickY = PadStatus->substickY;
|
g_padState.CStickY = PadStatus->substickY;
|
||||||
|
|
||||||
PlayController(PadStatus, controllerID);
|
fwrite(&g_padState, sizeof(ControllerState), 1, g_recordfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlayInput(const char *filename)
|
bool PlayInput(const char *filename)
|
||||||
|
@ -330,9 +287,7 @@ bool PlayInput(const char *filename)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
g_numPads = header.numControllers;
|
g_numPads = header.numControllers;
|
||||||
g_padStates = new ControllerState[g_numPads];
|
|
||||||
g_numRerecords = header.numRerecords;
|
g_numRerecords = header.numRerecords;
|
||||||
g_recordFile = filename;
|
|
||||||
|
|
||||||
g_playMode = MODE_PLAYING;
|
g_playMode = MODE_PLAYING;
|
||||||
|
|
||||||
|
@ -344,61 +299,146 @@ cleanup:
|
||||||
return false;
|
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)
|
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)
|
if(!IsPlayingInput() || controllerID >= g_numPads || controllerID < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
memset(PadStatus, 0, sizeof(SPADStatus));
|
memset(PadStatus, 0, sizeof(SPADStatus));
|
||||||
|
fread(&g_padState, sizeof(ControllerState), 1, g_recordfd);
|
||||||
|
|
||||||
PadStatus->button |= PAD_USE_ORIGIN;
|
PadStatus->button |= PAD_USE_ORIGIN;
|
||||||
|
|
||||||
if(g_padStates[controllerID].A) {
|
if(g_padState.A) {
|
||||||
PadStatus->button |= PAD_BUTTON_A;
|
PadStatus->button |= PAD_BUTTON_A;
|
||||||
PadStatus->analogA = 0xFF;
|
PadStatus->analogA = 0xFF;
|
||||||
}
|
}
|
||||||
if(g_padStates[controllerID].B) {
|
if(g_padState.B) {
|
||||||
PadStatus->button |= PAD_BUTTON_B;
|
PadStatus->button |= PAD_BUTTON_B;
|
||||||
PadStatus->analogB = 0xFF;
|
PadStatus->analogB = 0xFF;
|
||||||
}
|
}
|
||||||
if(g_padStates[controllerID].X)
|
if(g_padState.X)
|
||||||
PadStatus->button |= PAD_BUTTON_X;
|
PadStatus->button |= PAD_BUTTON_X;
|
||||||
if(g_padStates[controllerID].Y)
|
if(g_padState.Y)
|
||||||
PadStatus->button |= PAD_BUTTON_Y;
|
PadStatus->button |= PAD_BUTTON_Y;
|
||||||
if(g_padStates[controllerID].Z)
|
if(g_padState.Z)
|
||||||
PadStatus->button |= PAD_TRIGGER_Z;
|
PadStatus->button |= PAD_TRIGGER_Z;
|
||||||
if(g_padStates[controllerID].Start)
|
if(g_padState.Start)
|
||||||
PadStatus->button |= PAD_BUTTON_START;
|
PadStatus->button |= PAD_BUTTON_START;
|
||||||
|
|
||||||
if(g_padStates[controllerID].DPadUp)
|
if(g_padState.DPadUp)
|
||||||
PadStatus->button |= PAD_BUTTON_UP;
|
PadStatus->button |= PAD_BUTTON_UP;
|
||||||
if(g_padStates[controllerID].DPadDown)
|
if(g_padState.DPadDown)
|
||||||
PadStatus->button |= PAD_BUTTON_DOWN;
|
PadStatus->button |= PAD_BUTTON_DOWN;
|
||||||
if(g_padStates[controllerID].DPadLeft)
|
if(g_padState.DPadLeft)
|
||||||
PadStatus->button |= PAD_BUTTON_LEFT;
|
PadStatus->button |= PAD_BUTTON_LEFT;
|
||||||
if(g_padStates[controllerID].DPadRight)
|
if(g_padState.DPadRight)
|
||||||
PadStatus->button |= PAD_BUTTON_RIGHT;
|
PadStatus->button |= PAD_BUTTON_RIGHT;
|
||||||
|
|
||||||
PadStatus->triggerLeft = g_padStates[controllerID].L;
|
PadStatus->triggerLeft = g_padState.L;
|
||||||
if(PadStatus->triggerLeft > 230)
|
if(PadStatus->triggerLeft > 230)
|
||||||
PadStatus->button |= PAD_TRIGGER_L;
|
PadStatus->button |= PAD_TRIGGER_L;
|
||||||
PadStatus->triggerRight = g_padStates[controllerID].R;
|
PadStatus->triggerRight = g_padState.R;
|
||||||
if(PadStatus->triggerRight > 230)
|
if(PadStatus->triggerRight > 230)
|
||||||
PadStatus->button |= PAD_TRIGGER_R;
|
PadStatus->button |= PAD_TRIGGER_R;
|
||||||
|
|
||||||
PadStatus->stickX = g_padStates[controllerID].AnalogStickX;
|
PadStatus->stickX = g_padState.AnalogStickX;
|
||||||
PadStatus->stickY = g_padStates[controllerID].AnalogStickY;
|
PadStatus->stickY = g_padState.AnalogStickY;
|
||||||
|
|
||||||
PadStatus->substickX = g_padStates[controllerID].CStickX;
|
PadStatus->substickX = g_padState.CStickX;
|
||||||
PadStatus->substickY = g_padStates[controllerID].CStickY;
|
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() {
|
void EndPlayInput() {
|
||||||
fclose(g_recordfd);
|
if (g_recordfd)
|
||||||
|
fclose(g_recordfd);
|
||||||
g_recordfd = NULL;
|
g_recordfd = NULL;
|
||||||
g_numPads = 0;
|
g_numPads = g_rerecords = 0;
|
||||||
delete[] g_padStates;
|
g_frameCounter = g_lagCounter = 0;
|
||||||
g_playMode = MODE_NONE;
|
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 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
|
bool DPadUp:1, DPadDown:1, // Binary D-Pad buttons, 4 bits
|
||||||
DPadLeft:1, DPadRight:1;
|
DPadLeft:1, DPadRight:1;
|
||||||
|
bool reserved:6; // Reserved bits used for padding, 6 bits
|
||||||
|
|
||||||
u8 L, R; // Triggers, 16 bits
|
u8 L, R; // Triggers, 16 bits
|
||||||
u8 AnalogStickX, AnalogStickY; // Main Stick, 16 bits
|
u8 AnalogStickX, AnalogStickY; // Main Stick, 16 bits
|
||||||
u8 CStickX, CStickY; // Sub-Stick, 16 bits
|
u8 CStickX, CStickY; // Sub-Stick, 16 bits
|
||||||
|
|
||||||
bool reserved:6; // Reserved bits, 6 bits
|
|
||||||
}; // Total: 58 + 6 = 64 bits per frame
|
}; // Total: 58 + 6 = 64 bits per frame
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
@ -110,13 +111,14 @@ void FrameSkipping();
|
||||||
|
|
||||||
void ModifyController(SPADStatus *PadStatus, int controllerID);
|
void ModifyController(SPADStatus *PadStatus, int controllerID);
|
||||||
|
|
||||||
bool BeginRecordingInput(const char *filename, int controllers);
|
bool BeginRecordingInput(int controllers);
|
||||||
void RecordInput(SPADStatus *PadStatus, int controllerID);
|
void RecordInput(SPADStatus *PadStatus, int controllerID);
|
||||||
void EndRecordingInput();
|
|
||||||
|
|
||||||
bool PlayInput(const char *filename);
|
bool PlayInput(const char *filename);
|
||||||
|
void LoadInput(const char *filename);
|
||||||
void PlayController(SPADStatus *PadStatus, int controllerID);
|
void PlayController(SPADStatus *PadStatus, int controllerID);
|
||||||
void EndPlayInput();
|
void EndPlayInput();
|
||||||
|
void SaveRecording(const char *filename);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "StringUtil.h"
|
#include "StringUtil.h"
|
||||||
#include "Thread.h"
|
#include "Thread.h"
|
||||||
#include "CoreTiming.h"
|
#include "CoreTiming.h"
|
||||||
|
#include "OnFrame.h"
|
||||||
#include "HW/HW.h"
|
#include "HW/HW.h"
|
||||||
#include "PowerPC/PowerPC.h"
|
#include "PowerPC/PowerPC.h"
|
||||||
#include "PowerPC/JitCommon/JitBase.h"
|
#include "PowerPC/JitCommon/JitBase.h"
|
||||||
|
@ -256,6 +257,9 @@ void SaveStateCallback(u64 userdata, int cyclesLate)
|
||||||
saveData->buffer = buffer;
|
saveData->buffer = buffer;
|
||||||
saveData->size = sz;
|
saveData->size = sz;
|
||||||
|
|
||||||
|
if (Frame::IsRecordingInput())
|
||||||
|
Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str());
|
||||||
|
|
||||||
Core::DisplayMessage("Saving State...", 1000);
|
Core::DisplayMessage("Saving State...", 1000);
|
||||||
|
|
||||||
saveThread = new Common::Thread(CompressAndDumpState, saveData);
|
saveThread = new Common::Thread(CompressAndDumpState, saveData);
|
||||||
|
@ -370,6 +374,11 @@ void LoadStateCallback(u64 userdata, int cyclesLate)
|
||||||
|
|
||||||
delete[] buffer;
|
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;
|
state_op_in_progress = false;
|
||||||
|
|
||||||
// Resume the clock
|
// Resume the clock
|
||||||
|
|
|
@ -246,6 +246,7 @@ EVT_MENU(IDM_STOP, CFrame::OnStop)
|
||||||
EVT_MENU(IDM_RESET, CFrame::OnReset)
|
EVT_MENU(IDM_RESET, CFrame::OnReset)
|
||||||
EVT_MENU(IDM_RECORD, CFrame::OnRecord)
|
EVT_MENU(IDM_RECORD, CFrame::OnRecord)
|
||||||
EVT_MENU(IDM_PLAYRECORD, CFrame::OnPlayRecording)
|
EVT_MENU(IDM_PLAYRECORD, CFrame::OnPlayRecording)
|
||||||
|
EVT_MENU(IDM_RECORDEXPORT, CFrame::OnRecordExport)
|
||||||
EVT_MENU(IDM_FRAMESTEP, CFrame::OnFrameStep)
|
EVT_MENU(IDM_FRAMESTEP, CFrame::OnFrameStep)
|
||||||
EVT_MENU(IDM_LUA, CFrame::OnOpenLuaWindow)
|
EVT_MENU(IDM_LUA, CFrame::OnOpenLuaWindow)
|
||||||
EVT_MENU(IDM_SCREENSHOT, CFrame::OnScreenshot)
|
EVT_MENU(IDM_SCREENSHOT, CFrame::OnScreenshot)
|
||||||
|
|
|
@ -117,6 +117,7 @@ class CFrame : public CRenderFrame
|
||||||
void InitBitmaps();
|
void InitBitmaps();
|
||||||
void DoPause();
|
void DoPause();
|
||||||
void DoStop();
|
void DoStop();
|
||||||
|
void DoRecordingSave();
|
||||||
bool bRenderToMain;
|
bool bRenderToMain;
|
||||||
bool bNoWiimoteMsg;
|
bool bNoWiimoteMsg;
|
||||||
void UpdateGUI();
|
void UpdateGUI();
|
||||||
|
@ -272,6 +273,7 @@ class CFrame : public CRenderFrame
|
||||||
void OnReset(wxCommandEvent& event);
|
void OnReset(wxCommandEvent& event);
|
||||||
void OnRecord(wxCommandEvent& event);
|
void OnRecord(wxCommandEvent& event);
|
||||||
void OnPlayRecording(wxCommandEvent& event);
|
void OnPlayRecording(wxCommandEvent& event);
|
||||||
|
void OnRecordExport(wxCommandEvent& event);
|
||||||
void OnChangeDisc(wxCommandEvent& event);
|
void OnChangeDisc(wxCommandEvent& event);
|
||||||
void OnScreenshot(wxCommandEvent& event);
|
void OnScreenshot(wxCommandEvent& event);
|
||||||
void OnActive(wxActivateEvent& event);
|
void OnActive(wxActivateEvent& event);
|
||||||
|
|
|
@ -131,8 +131,9 @@ void CFrame::CreateMenu()
|
||||||
emulationMenu->AppendSeparator();
|
emulationMenu->AppendSeparator();
|
||||||
emulationMenu->Append(IDM_TOGGLE_FULLSCREEN, GetMenuLabel(HK_FULLSCREEN));
|
emulationMenu->Append(IDM_TOGGLE_FULLSCREEN, GetMenuLabel(HK_FULLSCREEN));
|
||||||
emulationMenu->AppendSeparator();
|
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_PLAYRECORD, _T("P&lay Recording..."));
|
||||||
|
emulationMenu->Append(IDM_RECORDEXPORT, _T("Export Recording..."));
|
||||||
emulationMenu->AppendSeparator();
|
emulationMenu->AppendSeparator();
|
||||||
emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc"));
|
emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc"));
|
||||||
|
|
||||||
|
@ -627,23 +628,8 @@ void CFrame::OnChangeDisc(wxCommandEvent& WXUNUSED (event))
|
||||||
|
|
||||||
void CFrame::OnRecord(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
|
// TODO: Take controller settings from Gamecube Configuration menu
|
||||||
if(Frame::BeginRecordingInput(path.mb_str(), 1))
|
if(Frame::BeginRecordingInput(1))
|
||||||
BootGame(std::string(""));
|
BootGame(std::string(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,6 +654,11 @@ void CFrame::OnPlayRecording(wxCommandEvent& WXUNUSED (event))
|
||||||
BootGame(std::string(""));
|
BootGame(std::string(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CFrame::OnRecordExport(wxCommandEvent& WXUNUSED (event))
|
||||||
|
{
|
||||||
|
DoRecordingSave();
|
||||||
|
}
|
||||||
|
|
||||||
void CFrame::OnPlay(wxCommandEvent& WXUNUSED (event))
|
void CFrame::OnPlay(wxCommandEvent& WXUNUSED (event))
|
||||||
{
|
{
|
||||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||||
|
@ -901,8 +892,8 @@ void CFrame::DoStop()
|
||||||
|
|
||||||
// TODO: Show the author/description dialog here
|
// TODO: Show the author/description dialog here
|
||||||
if(Frame::IsRecordingInput())
|
if(Frame::IsRecordingInput())
|
||||||
Frame::EndRecordingInput();
|
DoRecordingSave();
|
||||||
if(Frame::IsPlayingInput())
|
if(Frame::IsPlayingInput() || Frame::IsRecordingInput())
|
||||||
Frame::EndPlayInput();
|
Frame::EndPlayInput();
|
||||||
|
|
||||||
// These windows cause segmentation faults if they are open when the emulator
|
// 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))
|
void CFrame::OnStop(wxCommandEvent& WXUNUSED (event))
|
||||||
{
|
{
|
||||||
m_bGameLoading = false;
|
m_bGameLoading = false;
|
||||||
|
@ -1281,6 +1300,7 @@ void CFrame::UpdateGUI()
|
||||||
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(!Initialized);
|
||||||
GetMenuBar()->FindItem(IDM_PLAYRECORD)->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_FRAMESTEP)->Enable(Running || Paused);
|
||||||
GetMenuBar()->FindItem(IDM_SCREENSHOT)->Enable(Running || Paused);
|
GetMenuBar()->FindItem(IDM_SCREENSHOT)->Enable(Running || Paused);
|
||||||
GetMenuBar()->FindItem(IDM_TOGGLE_FULLSCREEN)->Enable(Running || Paused);
|
GetMenuBar()->FindItem(IDM_TOGGLE_FULLSCREEN)->Enable(Running || Paused);
|
||||||
|
|
|
@ -77,6 +77,7 @@ enum
|
||||||
IDM_TOGGLE_FULLSCREEN,
|
IDM_TOGGLE_FULLSCREEN,
|
||||||
IDM_RECORD,
|
IDM_RECORD,
|
||||||
IDM_PLAYRECORD,
|
IDM_PLAYRECORD,
|
||||||
|
IDM_RECORDEXPORT,
|
||||||
IDM_FRAMESTEP,
|
IDM_FRAMESTEP,
|
||||||
IDM_SCREENSHOT,
|
IDM_SCREENSHOT,
|
||||||
IDM_BROWSE,
|
IDM_BROWSE,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue