Rewrite to parts of the DTM code to remove the need for temporary files. Should fix files occasionally getting corrupted. DTM export dialog now asks before overwriting a file.

Saving a state that is not recording over one that was will now delete the old recording. Fixes trying to record with the wrong savestate.

Fixed some compiler warnings.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@7505 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
baby.lueshi 2011-05-03 00:06:44 +00:00
parent 263fc19d6f
commit 0601385271
6 changed files with 107 additions and 131 deletions

View File

@ -593,7 +593,7 @@ void VideoThrottle()
#else // Summary information #else // Summary information
std::string SFPS; std::string SFPS;
if (Frame::g_recordfd) if (Frame::IsPlayingInput() || Frame::IsRecordingInput())
SFPS = StringFromFormat("VI: %u - Frame: %u - FPS: %u - VPS: %u - SPEED: %u%%", Frame::g_frameCounter, Frame::g_InputCounter, FPS, VPS, Speed); SFPS = StringFromFormat("VI: %u - Frame: %u - FPS: %u - VPS: %u - SPEED: %u%%", Frame::g_frameCounter, Frame::g_InputCounter, FPS, VPS, Speed);
else else
SFPS = StringFromFormat("FPS: %u - VPS: %u - SPEED: %u%%", FPS, VPS, Speed); SFPS = StringFromFormat("FPS: %u - VPS: %u - SPEED: %u%%", FPS, VPS, Speed);

View File

@ -89,7 +89,7 @@ private:
// y, X, start for 3 seconds updates origin with current status // y, X, start for 3 seconds updates origin with current status
// Technically, the above is only on standard pad, wavebird does not support it for example // Technically, the above is only on standard pad, wavebird does not support it for example
// b, x, start for 3 seconds triggers reset (PI reset button interrupt) // b, x, start for 3 seconds triggers reset (PI reset button interrupt)
u32 m_TButtonComboStart, m_TButtonCombo; u64 m_TButtonComboStart, m_TButtonCombo;
// Type of button combo from the last/current poll // Type of button combo from the last/current poll
EButtonCombo m_LastButtonCombo; EButtonCombo m_LastButtonCombo;

View File

@ -36,6 +36,9 @@
#endif #endif
#include "State.h" #include "State.h"
// large enough for just over 24 hours of single-player recording
#define MAX_DTM_LENGTH (40 * 1024 * 1024)
std::mutex cs_frameSkip; std::mutex cs_frameSkip;
namespace Frame { namespace Frame {
@ -46,19 +49,19 @@ bool g_bReadOnly = true;
u32 g_rerecords = 0; u32 g_rerecords = 0;
PlayMode g_playMode = MODE_NONE; PlayMode g_playMode = MODE_NONE;
unsigned int g_framesToSkip = 0, g_frameSkipCounter = 0; u32 g_framesToSkip = 0, g_frameSkipCounter = 0;
int g_numPads = 0; u8 g_numPads = 0;
ControllerState g_padState; ControllerState g_padState;
File::IOFile g_recordfd; DTMHeader tmpHeader;
u8 *tmpInput = NULL;
u64 inputOffset = 0, tmpLength = 0;
u32 g_frameCounter = 0, g_lagCounter = 0, g_totalFrameCount = 0, g_InputCounter = 0; u64 g_frameCounter = 0, g_lagCounter = 0, g_totalFrameCount = 0, g_InputCounter = 0;
bool g_bRecordingFromSaveState = false; bool g_bRecordingFromSaveState = false;
bool g_bPolled = false; bool g_bPolled = false;
int g_numRerecords = 0; std::string tmpStateFilename = "dtm.sav";
std::string g_recordFile = "0.dtm";
std::string g_tmpRecordFile = "1.dtm";
std::string g_InputDisplay[4]; std::string g_InputDisplay[4];
@ -111,11 +114,6 @@ void SetFrameSkipping(unsigned int framesToSkip)
g_video_backend->Video_SetRendering(true); g_video_backend->Video_SetRendering(true);
} }
int FrameSkippingFactor()
{
return g_framesToSkip;
}
void SetPolledDevice() void SetPolledDevice()
{ {
g_bPolled = true; g_bPolled = true;
@ -199,37 +197,29 @@ void ChangeWiiPads()
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)
return false; return false;
if(File::Exists(g_recordFile))
File::Delete(g_recordFile);
if (Core::IsRunning()) if (Core::IsRunning())
{ {
const std::string stateFilename = g_recordFile + ".sav"; if(File::Exists(tmpStateFilename))
if(File::Exists(stateFilename)) File::Delete(tmpStateFilename);
File::Delete(stateFilename);
State::SaveAs(stateFilename.c_str()); State::SaveAs(tmpStateFilename.c_str());
g_bRecordingFromSaveState = true; g_bRecordingFromSaveState = true;
} }
if (!g_recordfd.Open(g_recordFile, "wb"))
{
PanicAlertT("Error opening file %s for recording", g_recordFile.c_str());
return false;
}
// Write initial empty header
DTMHeader dummy;
g_recordfd.WriteArray(&dummy, 1);
g_numPads = controllers; g_numPads = controllers;
g_frameCounter = 0; g_frameCounter = 0;
g_lagCounter = 0; g_lagCounter = 0;
g_InputCounter = 0; g_InputCounter = 0;
g_rerecords = 0;
g_playMode = MODE_RECORDING; g_playMode = MODE_RECORDING;
inputOffset = 0;
delete tmpInput;
tmpInput = new u8[MAX_DTM_LENGTH];
Core::DisplayMessage("Starting movie recording", 2000); Core::DisplayMessage("Starting movie recording", 2000);
@ -333,7 +323,8 @@ void RecordInput(SPADStatus *PadStatus, int controllerID)
g_padState.CStickX = PadStatus->substickX; g_padState.CStickX = PadStatus->substickX;
g_padState.CStickY = PadStatus->substickY; g_padState.CStickY = PadStatus->substickY;
g_recordfd.WriteArray(&g_padState, 1); memcpy(&(tmpInput[inputOffset]), &g_padState, 8);
inputOffset += 8;
SetInputDisplayString(g_padState, controllerID); SetInputDisplayString(g_padState, controllerID);
} }
@ -343,35 +334,33 @@ void RecordWiimote(int wiimote, u8 *data, s8 size)
if(!IsRecordingInput() || !IsUsingWiimote(wiimote)) if(!IsRecordingInput() || !IsUsingWiimote(wiimote))
return; return;
g_InputCounter++; g_InputCounter++;
g_recordfd.WriteArray(&size, 1); tmpInput[inputOffset++] = (u8) size;
g_recordfd.WriteArray(data, 1); memcpy(&(tmpInput[inputOffset]), data, size);
inputOffset += size;
} }
bool PlayInput(const char *filename) bool PlayInput(const char *filename)
{ {
if(!filename || g_playMode != MODE_NONE || g_recordfd) if(!filename || g_playMode != MODE_NONE)
return false; return false;
if(!File::Exists(filename)) if(!File::Exists(filename))
return false; return false;
DTMHeader header; File::IOFile g_recordfd;
File::Delete(g_recordFile); if (!g_recordfd.Open(filename, "rb"))
File::Copy(filename, g_recordFile);
if (!g_recordfd.Open(g_recordFile, "r+b"))
return false; return false;
g_recordfd.ReadArray(&header, 1); g_recordfd.ReadArray(&tmpHeader, 1);
if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A) { if(tmpHeader.filetype[0] != 'D' || tmpHeader.filetype[1] != 'T' || tmpHeader.filetype[2] != 'M' || tmpHeader.filetype[3] != 0x1A) {
PanicAlertT("Invalid recording file"); PanicAlertT("Invalid recording file");
goto cleanup; goto cleanup;
} }
// Load savestate (and skip to frame data) // Load savestate (and skip to frame data)
if(header.bFromSaveState) if(tmpHeader.bFromSaveState)
{ {
const std::string stateFilename = std::string(filename) + ".sav"; const std::string stateFilename = std::string(filename) + ".sav";
if(File::Exists(stateFilename)) if(File::Exists(stateFilename))
@ -381,25 +370,30 @@ bool PlayInput(const char *filename)
/* 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
// TODO: Replace with Unique ID // TODO: Replace with Unique ID
if(header.uniqueID != 0) { if(tmpHeader.uniqueID != 0) {
PanicAlert("Recording Unique ID Verification Failed"); PanicAlert("Recording Unique ID Verification Failed");
goto cleanup; goto cleanup;
} }
if(strncmp((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6)) { if(strncmp((char *)tmpHeader.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6)) {
PanicAlert("The recorded game (%s) is not the same as the selected game (%s)", header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str()); PanicAlert("The recorded game (%s) is not the same as the selected game (%s)", header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str());
goto cleanup; goto cleanup;
} }
*/ */
g_numPads = header.numControllers; g_numPads = tmpHeader.numControllers;
g_numRerecords = header.numRerecords; g_rerecords = tmpHeader.numRerecords;
g_totalFrameCount = header.frameCount; g_totalFrameCount = tmpHeader.frameCount;
ChangePads();
g_playMode = MODE_PLAYING; g_playMode = MODE_PLAYING;
tmpLength = g_recordfd.GetSize() - 256;
delete tmpInput;
tmpInput = new u8[MAX_DTM_LENGTH];
g_recordfd.ReadArray(tmpInput, tmpLength);
inputOffset = 0;
g_recordfd.Close();
return true; return true;
cleanup: cleanup:
@ -409,43 +403,41 @@ cleanup:
void LoadInput(const char *filename) void LoadInput(const char *filename)
{ {
File::IOFile t_record(filename, "rb"); File::IOFile t_record(filename, "r+b");
DTMHeader header; t_record.ReadArray(&tmpHeader, 1);
t_record.ReadArray(&header, 1); if(tmpHeader.filetype[0] != 'D' || tmpHeader.filetype[1] != 'T' || tmpHeader.filetype[2] != 'M' || tmpHeader.filetype[3] != 0x1A)
t_record.Close();
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);
EndPlayInput(false); EndPlayInput(false);
return; return;
} }
if (g_rerecords == 0) tmpHeader.numRerecords++;
g_rerecords = header.numRerecords;
g_frameCounter = header.frameCount; t_record.Seek(0, SEEK_SET);
g_totalFrameCount = header.frameCount; t_record.WriteArray(&tmpHeader, 1);
g_InputCounter = header.InputCount;
g_frameCounter = tmpHeader.frameCount;
g_totalFrameCount = tmpHeader.frameCount;
g_InputCounter = tmpHeader.InputCount;
g_numPads = header.numControllers; g_numPads = tmpHeader.numControllers;
ChangePads(true); ChangePads(true);
if (Core::g_CoreStartupParameter.bWii) if (Core::g_CoreStartupParameter.bWii)
ChangeWiiPads(); ChangeWiiPads();
inputOffset = t_record.GetSize() - 256;
delete tmpInput;
tmpInput = new u8[MAX_DTM_LENGTH];
t_record.ReadArray(tmpInput, inputOffset);
t_record.Close();
g_recordfd.Close(); g_rerecords = tmpHeader.numRerecords;
File::Delete(g_recordFile);
File::Copy(filename, g_recordFile);
g_recordfd.Open(g_recordFile, "r+b");
g_recordfd.Seek(0, SEEK_END);
g_rerecords++;
Core::DisplayMessage("Resuming movie recording", 2000); Core::DisplayMessage("Resuming movie recording", 2000);
@ -461,12 +453,15 @@ void PlayController(SPADStatus *PadStatus, int controllerID)
memset(PadStatus, 0, sizeof(SPADStatus)); memset(PadStatus, 0, sizeof(SPADStatus));
if (!g_recordfd.ReadArray(&g_padState, 1)) if (inputOffset + 8 > tmpLength)
{ {
Core::DisplayMessage("Movie End", 2000);
EndPlayInput(!g_bReadOnly); EndPlayInput(!g_bReadOnly);
return;
} }
memcpy(&g_padState, &(tmpInput[inputOffset]), 8);
inputOffset += 8;
PadStatus->triggerLeft = g_padState.TriggerL; PadStatus->triggerLeft = g_padState.TriggerL;
PadStatus->triggerRight = g_padState.TriggerR; PadStatus->triggerRight = g_padState.TriggerR;
@ -535,7 +530,6 @@ void PlayController(SPADStatus *PadStatus, int controllerID)
if (g_frameCounter >= g_totalFrameCount) if (g_frameCounter >= g_totalFrameCount)
{ {
Core::DisplayMessage("Movie End", 2000);
EndPlayInput(!g_bReadOnly); EndPlayInput(!g_bReadOnly);
} }
} }
@ -543,16 +537,33 @@ void PlayController(SPADStatus *PadStatus, int controllerID)
bool PlayWiimote(int wiimote, u8 *data, s8 &size) bool PlayWiimote(int wiimote, u8 *data, s8 &size)
{ {
s8 count = 0; s8 count = 0;
if(!IsPlayingInput() || !IsUsingWiimote(wiimote)) if(!IsPlayingInput() || !IsUsingWiimote(wiimote))
return false; return false;
g_InputCounter++;
g_recordfd.ReadArray(&count, 1); if (inputOffset > tmpLength)
{
EndPlayInput(!g_bReadOnly);
return false;
}
count = (s8) (tmpInput[inputOffset++]);
if (inputOffset + count > tmpLength)
{
EndPlayInput(!g_bReadOnly);
return false;
}
memcpy(data, &(tmpInput[inputOffset]), count);
inputOffset += count;
size = (count > size) ? size : count; size = (count > size) ? size : count;
g_InputCounter++;
// TODO: merge this with the above so that there's no duplicate code // TODO: merge this with the above so that there's no duplicate code
if (g_frameCounter >= g_totalFrameCount || !g_recordfd.ReadBytes(data, size)) if (g_frameCounter >= g_totalFrameCount)
{ {
Core::DisplayMessage("Movie End", 2000);
EndPlayInput(!g_bReadOnly); EndPlayInput(!g_bReadOnly);
} }
return true; return true;
@ -560,40 +571,31 @@ bool PlayWiimote(int wiimote, u8 *data, s8 &size)
void EndPlayInput(bool cont) void EndPlayInput(bool cont)
{ {
if (cont && g_recordfd) if (cont)
{ {
// The save and restore here is to resume rerecording
// from the exact point in playback we're at now
// if playback ends before the end of the file.
SaveRecording(g_tmpRecordFile.c_str());
g_recordfd.Close();
File::Delete(g_recordFile);
File::Copy(g_tmpRecordFile, g_recordFile);
g_recordfd.Open(g_recordFile, "r+b");
g_recordfd.Seek(0, SEEK_END);
g_playMode = MODE_RECORDING; g_playMode = MODE_RECORDING;
Core::DisplayMessage("Resuming movie recording", 2000); Core::DisplayMessage("Resuming movie recording", 2000);
} }
else else
{ {
g_recordfd.Close(); delete tmpInput;
tmpInput = NULL;
g_numPads = g_rerecords = 0; g_numPads = g_rerecords = 0;
g_totalFrameCount = g_frameCounter = g_lagCounter = 0; g_totalFrameCount = g_frameCounter = g_lagCounter = 0;
g_playMode = MODE_NONE; g_playMode = MODE_NONE;
Core::DisplayMessage("Movie End", 2000);
} }
} }
void SaveRecording(const char *filename) void SaveRecording(const char *filename)
{ {
const u64 size = g_recordfd.Tell(); File::IOFile save_record(filename, "wb");
// NOTE: Eventually this will not happen in // NOTE: Eventually this will not happen in
// read-only mode, but we need a way for the save state to // read-only mode, but we need a way for the save state to
// store the current point in the file first. // store the current point in the file first.
// if (!g_bReadOnly) // if (!g_bReadOnly)
{ {
rewind(g_recordfd.GetHandle());
// Create the real header now and write it // Create the real header now and write it
DTMHeader header; DTMHeader header;
memset(&header, 0, sizeof(DTMHeader)); memset(&header, 0, sizeof(DTMHeader));
@ -615,47 +617,21 @@ void SaveRecording(const char *filename)
// header.videoBackend; // header.videoBackend;
// header.audioEmulator; // header.audioEmulator;
g_recordfd.WriteArray(&header, 1); save_record.WriteArray(&header, 1);
} }
bool success = false; bool success = save_record.WriteArray(tmpInput, inputOffset);
g_recordfd.Close();
File::Delete(filename);
success = File::Copy(g_recordFile, filename);
if (success && g_bRecordingFromSaveState) if (success && g_bRecordingFromSaveState)
{ {
std::string tmpStateFilename = g_recordFile;
tmpStateFilename.append(".sav");
std::string stateFilename = filename; std::string stateFilename = filename;
stateFilename.append(".sav"); stateFilename.append(".sav");
success = File::Copy(tmpStateFilename, stateFilename); success = File::Copy(tmpStateFilename, stateFilename);
} }
if (success /* && !g_bReadOnly*/)
{
#ifdef WIN32
int fd;
if (!_sopen_s(&fd, filename, _O_RDWR, _SH_DENYNO, _S_IREAD | _S_IWRITE))
{
success = (_chsize_s(fd, size) == 0);
_close(fd);
}
else
{
success = false;
}
#else
success = !truncate(filename, size);
#endif
}
if (success) if (success)
Core::DisplayMessage(StringFromFormat("DTM %s saved", filename).c_str(), 2000); Core::DisplayMessage(StringFromFormat("DTM %s saved", filename).c_str(), 2000);
else else
Core::DisplayMessage(StringFromFormat("Failed to save %s", filename).c_str(), 2000); Core::DisplayMessage(StringFromFormat("Failed to save %s", filename).c_str(), 2000);
g_recordfd.Open(g_recordFile, "r+b");
g_recordfd.Seek(size, SEEK_SET);
} }
}; };

View File

@ -55,17 +55,16 @@ struct ControllerState {
extern bool g_bFrameStep, g_bPolled, g_bReadOnly; extern bool g_bFrameStep, g_bPolled, g_bReadOnly;
extern PlayMode g_playMode; extern PlayMode g_playMode;
extern unsigned int g_framesToSkip, g_frameSkipCounter; extern u32 g_framesToSkip, g_frameSkipCounter;
extern int g_numPads; extern u8 g_numPads;
extern ControllerState *g_padStates; extern ControllerState *g_padStates;
extern char g_playingFile[256]; extern char g_playingFile[256];
extern File::IOFile g_recordfd;
extern std::string g_recordFile; extern std::string g_recordFile;
extern u32 g_frameCounter, g_lagCounter, g_InputCounter; extern u64 g_frameCounter, g_lagCounter, g_InputCounter;
extern int g_numRerecords; extern u32 g_rerecords;
#pragma pack(push,1) #pragma pack(push,1)
struct DTMHeader { struct DTMHeader {
@ -112,7 +111,6 @@ void SetFrameStopping(bool bEnabled);
void SetReadOnly(bool bEnabled); void SetReadOnly(bool bEnabled);
void SetFrameSkipping(unsigned int framesToSkip); void SetFrameSkipping(unsigned int framesToSkip);
int FrameSkippingFactor();
void FrameSkipping(); void FrameSkipping();
bool BeginRecordingInput(int controllers); bool BeginRecordingInput(int controllers);

View File

@ -236,7 +236,9 @@ void SaveFileStateCallback(u64 userdata, int cyclesLate)
DoState(p); DoState(p);
if ((Frame::IsRecordingInput() || Frame::IsPlayingInput()) && !Frame::IsRecordingInputFromSaveState()) if ((Frame::IsRecordingInput() || Frame::IsPlayingInput()) && !Frame::IsRecordingInputFromSaveState())
Frame::SaveRecording(StringFromFormat("%s.dtm", g_current_filename.c_str()).c_str()); Frame::SaveRecording((g_current_filename + ".dtm").c_str());
else if (!Frame::IsRecordingInput() && !Frame::IsPlayingInput())
File::Delete(g_current_filename + ".dtm");
Core::DisplayMessage("Saving State...", 1000); Core::DisplayMessage("Saving State...", 1000);

View File

@ -1142,7 +1142,7 @@ void CFrame::DoRecordingSave()
wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString,
_("Dolphin TAS Movies (*.dtm)") + _("Dolphin TAS Movies (*.dtm)") +
wxString::Format(wxT("|*.dtm|%s"), wxGetTranslation(wxALL_FILES)), wxString::Format(wxT("|*.dtm|%s"), wxGetTranslation(wxALL_FILES)),
wxFD_SAVE | wxFD_PREVIEW, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT,
this); this);
if(path.IsEmpty()) if(path.IsEmpty())