From 693607f66b09f3d3bea9e41aea1399da15a2db13 Mon Sep 17 00:00:00 2001
From: aquanull <aquanull@gmail.com>
Date: Wed, 4 Apr 2018 19:32:29 +0800
Subject: [PATCH] Fixes the bug that extra frames could be saved in the movie
 file, when rerecording from middle of the movie. Also flushes the movie file
 more often.

---
 src/movie.cpp | 167 +++++++++++++++++++++++++++++++++-----------------
 src/movie.h   |   2 +-
 2 files changed, 113 insertions(+), 56 deletions(-)

diff --git a/src/movie.cpp b/src/movie.cpp
index 6bb763b0..c14158fb 100644
--- a/src/movie.cpp
+++ b/src/movie.cpp
@@ -478,7 +478,7 @@ void MovieData::installValue(std::string& key, std::string& val)
 	}
 }
 
-int MovieData::dump(EMUFILE *os, bool binary)
+int MovieData::dump(EMUFILE *os, bool binary, bool seekToCurrFramePos)
 {
 	int start = os->ftell();
 	os->fprintf("version %d\n", version);
@@ -516,19 +516,30 @@ int MovieData::dump(EMUFILE *os, bool binary)
 	if (this->loadFrameCount >= 0)
 		os->fprintf("length %d\n" , this->loadFrameCount);
 
+	int currFramePos = -1;
 	if(binary)
 	{
 		//put one | to start the binary dump
 		os->fputc('|');
-		for(int i=0;i<(int)records.size();i++)
+		for (int i = 0; i < (int)records.size(); i++)
+		{
+			if (seekToCurrFramePos && currFrameCounter == i)
+				currFramePos = os->ftell();
 			records[i].dumpBinary(this, os, i);
+		}
 	} else
 	{
-		for(int i=0;i<(int)records.size();i++)
+		for (int i = 0; i < (int)records.size(); i++)
+		{
+			if (seekToCurrFramePos && currFrameCounter == i)
+				currFramePos = os->ftell();
 			records[i].dump(this, os, i);
+		}
 	}
 
 	int end = os->ftell();
+	if (currFramePos >= 0)
+		os->fseek(currFramePos, SEEK_SET);
 	return end-start;
 }
 
@@ -735,53 +746,83 @@ bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader)
 	return true;
 }
 
-/// Stop movie playback.
-static void StopPlayback()
+static EMUFILE *openRecordingMovie(const char* fname)
 {
-	FCEU_DispMessageOnMovie("Movie playback stopped.");
-	movieMode = MOVIEMODE_INACTIVE;
-}
+	if (osRecordingMovie)
+		delete osRecordingMovie;
 
-// Stop movie playback without closing the movie.
-static void FinishPlayback()
-{
-	extern int closeFinishedMovie;
-	if (closeFinishedMovie)
-		StopPlayback();
-	else
-	{
-		FCEU_DispMessage("Movie finished playing.",0);
-		movieMode = MOVIEMODE_FINISHED;
+	osRecordingMovie = FCEUD_UTF8_fstream(fname, "wb");
+	if (!osRecordingMovie || osRecordingMovie->fail()) {
+		FCEU_PrintError("Error opening movie output file: %s", fname);
+		return NULL;
 	}
+	strcpy(curMovieFilename, fname);
+
+	return osRecordingMovie;
 }
 
 static void closeRecordingMovie()
 {
-	if(osRecordingMovie)
+	if (osRecordingMovie)
 	{
 		delete osRecordingMovie;
 		osRecordingMovie = 0;
 	}
 }
 
+// Callers shall set the approriate movieMode before calling this
+static void RedumpWholeMovieFile(bool justToggledRecording = false)
+{
+	bool recording = (movieMode == MOVIEMODE_RECORD);
+	assert((NULL != osRecordingMovie) == (recording != justToggledRecording) && "osRecordingMovie should be consistent with movie mode!");
+
+	if (NULL == openRecordingMovie(curMovieFilename))
+		return;
+
+	currMovieData.dump(osRecordingMovie, false/*currMovieData.binaryFlag*/, recording);
+	if (recording)
+		osRecordingMovie->fflush();
+	else
+		closeRecordingMovie();
+}
+
+/// Stop movie playback.
+static void StopPlayback()
+{
+	assert(movieMode != MOVIEMODE_RECORD && NULL == osRecordingMovie);
+
+	movieMode = MOVIEMODE_INACTIVE;
+	FCEU_DispMessageOnMovie("Movie playback stopped.");
+}
+
+// Stop movie playback without closing the movie.
+static void FinishPlayback()
+{
+	assert(movieMode != MOVIEMODE_RECORD);
+
+	extern int closeFinishedMovie;
+	if (closeFinishedMovie)
+		StopPlayback();
+	else
+	{
+		movieMode = MOVIEMODE_FINISHED;
+		FCEU_DispMessage("Movie finished playing.",0);
+	}
+}
+
 /// Stop movie recording
 static void StopRecording()
 {
-	FCEU_DispMessage("Movie recording stopped.",0);
-	movieMode = MOVIEMODE_INACTIVE;
+	assert(movieMode == MOVIEMODE_RECORD);
 
-	closeRecordingMovie();
+	movieMode = MOVIEMODE_INACTIVE;
+	RedumpWholeMovieFile(true);
+	FCEU_DispMessage("Movie recording stopped.",0);
 }
 
-void FCEUI_StopMovie()
+static void OnMovieClosed()
 {
-	if(suppressMovieStop)
-		return;
-
-	if(movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_FINISHED)
-		StopPlayback();
-	else if(movieMode == MOVIEMODE_RECORD)
-		StopRecording();
+	assert(movieMode == MOVIEMODE_INACTIVE);
 
 	curMovieFilename[0] = 0;			//No longer a current movie filename
 	freshMovie = false;					//No longer a fresh movie loaded
@@ -794,6 +835,19 @@ void FCEUI_StopMovie()
 
 bool bogorf;
 
+void FCEUI_StopMovie()
+{
+	if (suppressMovieStop)
+		return;
+
+	if (movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_FINISHED)
+		StopPlayback();
+	else if (movieMode == MOVIEMODE_RECORD)
+		StopRecording();
+
+	OnMovieClosed();
+}
+
 void poweron(bool shouldDisableBatteryLoading)
 {
 	//// make a for-movie-recording power-on clear the game's save data, too
@@ -1021,21 +1075,6 @@ bool FCEUI_LoadMovie(const char *fname, bool _read_only, int _pauseframe)
 	return true;
 }
 
-static void openRecordingMovie(const char* fname)
-{
-	osRecordingMovie = FCEUD_UTF8_fstream(fname, "wb");
-	if(!osRecordingMovie || osRecordingMovie->fail()) {
-		FCEU_PrintError("Error opening movie output file: %s",fname);
-		return;
-	}
-	strcpy(curMovieFilename, fname);
-
-#ifdef WIN32
-	//Add to the recent movie menu
-	AddRecentMovieFile(fname);
-#endif
-}
-
 
 //begin recording a new movie
 //TODO - BUG - the record-from-another-savestate doesnt work.
@@ -1048,11 +1087,14 @@ void FCEUI_SaveMovie(const char *fname, EMOVIE_FLAG flags, std::wstring author)
 
 	FCEUI_StopMovie();
 
-	openRecordingMovie(fname);
-
-	if(!osRecordingMovie || osRecordingMovie->fail())
+	if (NULL == openRecordingMovie(fname))
 		return;
 
+#ifdef WIN32
+	//Add to the recent movie menu
+	AddRecentMovieFile(fname);
+#endif
+
 	currFrameCounter = 0;
 	LagCounterReset();
 	FCEUMOV_CreateCleanMovie();
@@ -1224,6 +1266,9 @@ void FCEUMOV_AddCommand(int cmd)
 
 void FCEU_DrawMovies(uint8 *XBuf)
 {
+	// not the best place, but just working
+	assert((NULL != osRecordingMovie) == (movieMode == MOVIEMODE_RECORD));
+
 	if(frame_display)
 	{
 		char counterbuf[32] = {0};
@@ -1420,9 +1465,15 @@ bool FCEUMOV_ReadState(EMUFILE* is, uint32 size)
 			#endif
 		}
 
-		closeRecordingMovie();
 		if (movie_readonly)
 		{
+			if (movieMode == MOVIEMODE_RECORD)
+			{
+				movieMode = MOVIEMODE_PLAY;
+				RedumpWholeMovieFile(true);
+				closeRecordingMovie();
+			}
+
 			// currFrameCounter at this point represents the savestate framecount
 			int frame_of_mismatch = CheckTimelines(tempMovieData, currMovieData);
 			if (frame_of_mismatch >= 0)
@@ -1461,13 +1512,16 @@ bool FCEUMOV_ReadState(EMUFILE* is, uint32 size)
 		} else
 		{
 			//Read+Write mode
+			closeRecordingMovie();
+
 			if (currFrameCounter > (int)tempMovieData.records.size())
 			{
 				//This is a post movie savestate, handle it differently
 				//Replace movie contents but then switch to movie finished mode
 				currMovieData = tempMovieData;
-				openRecordingMovie(curMovieFilename);
-				currMovieData.dump(osRecordingMovie, false/*currMovieData.binaryFlag*/);
+				movieMode = MOVIEMODE_PLAY;
+				FCEUMOV_IncrementRerecordCount();
+				RedumpWholeMovieFile();
 				FinishPlayback();
 			} else
 			{
@@ -1477,11 +1531,9 @@ bool FCEUMOV_ReadState(EMUFILE* is, uint32 size)
 					tempMovieData.truncateAt(currFrameCounter);
 				
 				currMovieData = tempMovieData;
-				FCEUMOV_IncrementRerecordCount();
-				openRecordingMovie(curMovieFilename);
-				currMovieData.dump(osRecordingMovie, false/*currMovieData.binaryFlag*/);
 				movieMode = MOVIEMODE_RECORD;
-
+				FCEUMOV_IncrementRerecordCount();
+				RedumpWholeMovieFile(true);
 			}
 		}
 	}
@@ -1616,6 +1668,11 @@ void FCEUI_MoviePlayFromBeginning(void)
 #endif
 	} else if (movieMode != MOVIEMODE_INACTIVE)
 	{
+		if (movieMode == MOVIEMODE_RECORD)
+		{
+			movieMode = MOVIEMODE_PLAY;
+			RedumpWholeMovieFile(true);
+		}
 		if (currMovieData.savestate.empty())
 		{
 			movie_readonly = true;
diff --git a/src/movie.h b/src/movie.h
index a19d25ba..c1c41a17 100644
--- a/src/movie.h
+++ b/src/movie.h
@@ -234,7 +234,7 @@ public:
 
 	void truncateAt(int frame);
 	void installValue(std::string& key, std::string& val);
-	int dump(EMUFILE* os, bool binary);
+	int dump(EMUFILE* os, bool binary, bool seekToCurrFramePos = false);
 
 	void clearRecordRange(int start, int len);
 	void eraseRecords(int at, int frames = 1);