From 06013852711c18f7b8ce31b6b6bbe0162abe7c33 Mon Sep 17 00:00:00 2001 From: "baby.lueshi" Date: Tue, 3 May 2011 00:06:44 +0000 Subject: [PATCH] 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 --- Source/Core/Core/Src/Core.cpp | 2 +- .../Core/Core/Src/HW/SI_DeviceGCController.h | 2 +- Source/Core/Core/Src/OnFrame.cpp | 218 ++++++++---------- Source/Core/Core/Src/OnFrame.h | 10 +- Source/Core/Core/Src/State.cpp | 4 +- Source/Core/DolphinWX/Src/FrameTools.cpp | 2 +- 6 files changed, 107 insertions(+), 131 deletions(-) diff --git a/Source/Core/Core/Src/Core.cpp b/Source/Core/Core/Src/Core.cpp index a6d668f791..263b1a4f81 100644 --- a/Source/Core/Core/Src/Core.cpp +++ b/Source/Core/Core/Src/Core.cpp @@ -593,7 +593,7 @@ void VideoThrottle() #else // Summary information 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); else SFPS = StringFromFormat("FPS: %u - VPS: %u - SPEED: %u%%", FPS, VPS, Speed); diff --git a/Source/Core/Core/Src/HW/SI_DeviceGCController.h b/Source/Core/Core/Src/HW/SI_DeviceGCController.h index 257f7b8104..5493417793 100644 --- a/Source/Core/Core/Src/HW/SI_DeviceGCController.h +++ b/Source/Core/Core/Src/HW/SI_DeviceGCController.h @@ -89,7 +89,7 @@ private: // 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 // 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 EButtonCombo m_LastButtonCombo; diff --git a/Source/Core/Core/Src/OnFrame.cpp b/Source/Core/Core/Src/OnFrame.cpp index c58ba9ccc8..f4f67e926a 100644 --- a/Source/Core/Core/Src/OnFrame.cpp +++ b/Source/Core/Core/Src/OnFrame.cpp @@ -36,6 +36,9 @@ #endif #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; namespace Frame { @@ -46,19 +49,19 @@ bool g_bReadOnly = true; u32 g_rerecords = 0; 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; -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_bPolled = false; -int g_numRerecords = 0; -std::string g_recordFile = "0.dtm"; -std::string g_tmpRecordFile = "1.dtm"; +std::string tmpStateFilename = "dtm.sav"; std::string g_InputDisplay[4]; @@ -111,11 +114,6 @@ void SetFrameSkipping(unsigned int framesToSkip) g_video_backend->Video_SetRendering(true); } -int FrameSkippingFactor() -{ - return g_framesToSkip; -} - void SetPolledDevice() { g_bPolled = true; @@ -199,37 +197,29 @@ void ChangeWiiPads() bool BeginRecordingInput(int controllers) { - if(g_playMode != MODE_NONE || controllers == 0 || g_recordfd != NULL) + if(g_playMode != MODE_NONE || controllers == 0) return false; - if(File::Exists(g_recordFile)) - File::Delete(g_recordFile); - if (Core::IsRunning()) { - const std::string stateFilename = g_recordFile + ".sav"; - if(File::Exists(stateFilename)) - File::Delete(stateFilename); - State::SaveAs(stateFilename.c_str()); + if(File::Exists(tmpStateFilename)) + File::Delete(tmpStateFilename); + + State::SaveAs(tmpStateFilename.c_str()); 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_frameCounter = 0; g_lagCounter = 0; g_InputCounter = 0; + g_rerecords = 0; g_playMode = MODE_RECORDING; + inputOffset = 0; + delete tmpInput; + tmpInput = new u8[MAX_DTM_LENGTH]; + Core::DisplayMessage("Starting movie recording", 2000); @@ -333,7 +323,8 @@ void RecordInput(SPADStatus *PadStatus, int controllerID) g_padState.CStickX = PadStatus->substickX; g_padState.CStickY = PadStatus->substickY; - g_recordfd.WriteArray(&g_padState, 1); + memcpy(&(tmpInput[inputOffset]), &g_padState, 8); + inputOffset += 8; SetInputDisplayString(g_padState, controllerID); } @@ -343,35 +334,33 @@ void RecordWiimote(int wiimote, u8 *data, s8 size) if(!IsRecordingInput() || !IsUsingWiimote(wiimote)) return; g_InputCounter++; - g_recordfd.WriteArray(&size, 1); - g_recordfd.WriteArray(data, 1); + tmpInput[inputOffset++] = (u8) size; + memcpy(&(tmpInput[inputOffset]), data, size); + inputOffset += size; } bool PlayInput(const char *filename) { - if(!filename || g_playMode != MODE_NONE || g_recordfd) + if(!filename || g_playMode != MODE_NONE) return false; - + if(!File::Exists(filename)) return false; - DTMHeader header; + File::IOFile g_recordfd; - File::Delete(g_recordFile); - File::Copy(filename, g_recordFile); - - if (!g_recordfd.Open(g_recordFile, "r+b")) + if (!g_recordfd.Open(filename, "rb")) 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"); goto cleanup; } // Load savestate (and skip to frame data) - if(header.bFromSaveState) + if(tmpHeader.bFromSaveState) { const std::string stateFilename = std::string(filename) + ".sav"; 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: Replace with Unique ID - if(header.uniqueID != 0) { + if(tmpHeader.uniqueID != 0) { PanicAlert("Recording Unique ID Verification Failed"); 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()); goto cleanup; } */ - g_numPads = header.numControllers; - g_numRerecords = header.numRerecords; - g_totalFrameCount = header.frameCount; - - ChangePads(); + g_numPads = tmpHeader.numControllers; + g_rerecords = tmpHeader.numRerecords; + g_totalFrameCount = tmpHeader.frameCount; 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; cleanup: @@ -409,43 +403,41 @@ cleanup: 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); - t_record.Close(); - - 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("Savestate movie %s is corrupted, movie recording stopping...", filename); EndPlayInput(false); return; } - if (g_rerecords == 0) - g_rerecords = header.numRerecords; + tmpHeader.numRerecords++; - g_frameCounter = header.frameCount; - g_totalFrameCount = header.frameCount; - g_InputCounter = header.InputCount; + t_record.Seek(0, SEEK_SET); + t_record.WriteArray(&tmpHeader, 1); + + g_frameCounter = tmpHeader.frameCount; + g_totalFrameCount = tmpHeader.frameCount; + g_InputCounter = tmpHeader.InputCount; - g_numPads = header.numControllers; + g_numPads = tmpHeader.numControllers; ChangePads(true); if (Core::g_CoreStartupParameter.bWii) 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(); - - File::Delete(g_recordFile); - File::Copy(filename, g_recordFile); - - g_recordfd.Open(g_recordFile, "r+b"); - g_recordfd.Seek(0, SEEK_END); - - g_rerecords++; + g_rerecords = tmpHeader.numRerecords; Core::DisplayMessage("Resuming movie recording", 2000); @@ -461,12 +453,15 @@ void PlayController(SPADStatus *PadStatus, int controllerID) memset(PadStatus, 0, sizeof(SPADStatus)); - if (!g_recordfd.ReadArray(&g_padState, 1)) + if (inputOffset + 8 > tmpLength) { - Core::DisplayMessage("Movie End", 2000); EndPlayInput(!g_bReadOnly); + return; } + memcpy(&g_padState, &(tmpInput[inputOffset]), 8); + inputOffset += 8; + PadStatus->triggerLeft = g_padState.TriggerL; PadStatus->triggerRight = g_padState.TriggerR; @@ -535,7 +530,6 @@ void PlayController(SPADStatus *PadStatus, int controllerID) if (g_frameCounter >= g_totalFrameCount) { - Core::DisplayMessage("Movie End", 2000); EndPlayInput(!g_bReadOnly); } } @@ -543,16 +537,33 @@ void PlayController(SPADStatus *PadStatus, int controllerID) bool PlayWiimote(int wiimote, u8 *data, s8 &size) { s8 count = 0; + if(!IsPlayingInput() || !IsUsingWiimote(wiimote)) 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; + g_InputCounter++; + // 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); } return true; @@ -560,40 +571,31 @@ bool PlayWiimote(int wiimote, u8 *data, s8 &size) 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; Core::DisplayMessage("Resuming movie recording", 2000); } else { - g_recordfd.Close(); + delete tmpInput; + tmpInput = NULL; g_numPads = g_rerecords = 0; g_totalFrameCount = g_frameCounter = g_lagCounter = 0; g_playMode = MODE_NONE; + Core::DisplayMessage("Movie End", 2000); } } void SaveRecording(const char *filename) { - const u64 size = g_recordfd.Tell(); - + File::IOFile save_record(filename, "wb"); + // NOTE: Eventually this will not happen in // read-only mode, but we need a way for the save state to // store the current point in the file first. // if (!g_bReadOnly) { - rewind(g_recordfd.GetHandle()); - // Create the real header now and write it DTMHeader header; memset(&header, 0, sizeof(DTMHeader)); @@ -615,47 +617,21 @@ void SaveRecording(const char *filename) // header.videoBackend; // header.audioEmulator; - g_recordfd.WriteArray(&header, 1); + save_record.WriteArray(&header, 1); } - bool success = false; - g_recordfd.Close(); - File::Delete(filename); - success = File::Copy(g_recordFile, filename); + bool success = save_record.WriteArray(tmpInput, inputOffset); if (success && g_bRecordingFromSaveState) { - std::string tmpStateFilename = g_recordFile; - tmpStateFilename.append(".sav"); std::string stateFilename = filename; stateFilename.append(".sav"); 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) Core::DisplayMessage(StringFromFormat("DTM %s saved", filename).c_str(), 2000); else Core::DisplayMessage(StringFromFormat("Failed to save %s", filename).c_str(), 2000); - - g_recordfd.Open(g_recordFile, "r+b"); - g_recordfd.Seek(size, SEEK_SET); } }; diff --git a/Source/Core/Core/Src/OnFrame.h b/Source/Core/Core/Src/OnFrame.h index f80d182886..aa926b707e 100644 --- a/Source/Core/Core/Src/OnFrame.h +++ b/Source/Core/Core/Src/OnFrame.h @@ -55,17 +55,16 @@ struct ControllerState { extern bool g_bFrameStep, g_bPolled, g_bReadOnly; 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 char g_playingFile[256]; -extern File::IOFile g_recordfd; 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) struct DTMHeader { @@ -112,7 +111,6 @@ void SetFrameStopping(bool bEnabled); void SetReadOnly(bool bEnabled); void SetFrameSkipping(unsigned int framesToSkip); -int FrameSkippingFactor(); void FrameSkipping(); bool BeginRecordingInput(int controllers); diff --git a/Source/Core/Core/Src/State.cpp b/Source/Core/Core/Src/State.cpp index 14dd4bc855..305c6bdb76 100644 --- a/Source/Core/Core/Src/State.cpp +++ b/Source/Core/Core/Src/State.cpp @@ -236,7 +236,9 @@ void SaveFileStateCallback(u64 userdata, int cyclesLate) DoState(p); 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); diff --git a/Source/Core/DolphinWX/Src/FrameTools.cpp b/Source/Core/DolphinWX/Src/FrameTools.cpp index ca60de90d6..0a06f09d31 100644 --- a/Source/Core/DolphinWX/Src/FrameTools.cpp +++ b/Source/Core/DolphinWX/Src/FrameTools.cpp @@ -1142,7 +1142,7 @@ void CFrame::DoRecordingSave() wxEmptyString, wxEmptyString, wxEmptyString, _("Dolphin TAS Movies (*.dtm)") + wxString::Format(wxT("|*.dtm|%s"), wxGetTranslation(wxALL_FILES)), - wxFD_SAVE | wxFD_PREVIEW, + wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT, this); if(path.IsEmpty())