From f69d9db9d46322a71761c7f98da21a3da8c5dbe4 Mon Sep 17 00:00:00 2001
From: Jeffrey Pfau <jeffrey@endrift.com>
Date: Thu, 5 Mar 2015 14:10:23 -0800
Subject: [PATCH] GBA RR: Add way to play movies from startup

---
 src/gba/rr/mgm.c            | 17 ++++++-----------
 src/gba/rr/mgm.h            |  1 -
 src/gba/supervisor/rr.c     | 18 ++++++++++++++++--
 src/gba/supervisor/rr.h     |  7 +++++++
 src/gba/supervisor/thread.c | 33 +++++++++++++++++++++++++++++++++
 src/gba/supervisor/thread.h |  1 +
 src/platform/commandline.c  | 14 +++++++++++---
 src/platform/commandline.h  |  1 +
 8 files changed, 75 insertions(+), 17 deletions(-)

diff --git a/src/gba/rr/mgm.c b/src/gba/rr/mgm.c
index bc487a7b2..cf7f46c2b 100644
--- a/src/gba/rr/mgm.c
+++ b/src/gba/rr/mgm.c
@@ -17,6 +17,8 @@ enum {
 	INVALID_INPUT = 0x8000
 };
 
+static void GBAMGMContextDestroy(struct GBARRContext*);
+
 static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord);
 static void GBAMGMStopPlaying(struct GBARRContext*);
 static bool GBAMGMStartRecording(struct GBARRContext*);
@@ -56,6 +58,8 @@ static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags);
 void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
 	memset(mgm, 0, sizeof(*mgm));
 
+	mgm->d.destroy = GBAMGMContextDestroy;
+
 	mgm->d.startPlaying = GBAMGMStartPlaying;
 	mgm->d.stopPlaying = GBAMGMStopPlaying;
 	mgm->d.startRecording = GBAMGMStartRecording;
@@ -75,20 +79,11 @@ void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
 	mgm->d.openSavestate = GBAMGMOpenSavestate;
 }
 
-void GBAMGMContextDestroy(struct GBAMGMContext* mgm) {
-	if (mgm->d.isPlaying(&mgm->d)) {
-		mgm->d.stopPlaying(&mgm->d);
-	}
-	if (mgm->d.isRecording(&mgm->d)) {
-		mgm->d.stopRecording(&mgm->d);
-	}
+void GBAMGMContextDestroy(struct GBARRContext* rr) {
+	struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
 	if (mgm->metadataFile) {
 		mgm->metadataFile->close(mgm->metadataFile);
 	}
-	if (mgm->d.savedata) {
-		mgm->d.savedata->close(mgm->d.savedata);
-		mgm->d.savedata = 0;
-	}
 }
 
 bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) {
diff --git a/src/gba/rr/mgm.h b/src/gba/rr/mgm.h
index 3b08655ca..a0d0a860f 100644
--- a/src/gba/rr/mgm.h
+++ b/src/gba/rr/mgm.h
@@ -75,7 +75,6 @@ struct GBAMGMContext {
 };
 
 void GBAMGMContextCreate(struct GBAMGMContext*);
-void GBAMGMContextDestroy(struct GBAMGMContext*);
 
 bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream);
 bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom);
diff --git a/src/gba/supervisor/rr.c b/src/gba/supervisor/rr.c
index 06ffc07e0..d3b799df1 100644
--- a/src/gba/supervisor/rr.c
+++ b/src/gba/supervisor/rr.c
@@ -7,7 +7,7 @@
 
 #include "util/vfs.h"
 
-void GBARRSaveState(struct GBA* gba) {
+void GBARRInitRecord(struct GBA* gba) {
 	if (!gba || !gba->rr) {
 		return;
 	}
@@ -34,7 +34,7 @@ void GBARRSaveState(struct GBA* gba) {
 	}
 }
 
-void GBARRLoadState(struct GBA* gba) {
+void GBARRInitPlay(struct GBA* gba) {
 	if (!gba || !gba->rr) {
 		return;
 	}
@@ -57,3 +57,17 @@ void GBARRLoadState(struct GBA* gba) {
 		ARMReset(gba->cpu);
 	}
 }
+
+void GBARRDestroy(struct GBARRContext* rr) {
+	if (rr->isPlaying(rr)) {
+		rr->stopPlaying(rr);
+	}
+	if (rr->isRecording(rr)) {
+		rr->stopRecording(rr);
+	}
+	if (rr->savedata) {
+		rr->savedata->close(rr->savedata);
+		rr->savedata = 0;
+	}
+	rr->destroy(rr);
+}
diff --git a/src/gba/supervisor/rr.h b/src/gba/supervisor/rr.h
index ae0b2b832..940f9c425 100644
--- a/src/gba/supervisor/rr.h
+++ b/src/gba/supervisor/rr.h
@@ -20,6 +20,8 @@ enum GBARRInitFrom {
 };
 
 struct GBARRContext {
+	void (*destroy)(struct GBARRContext*);
+
 	bool (*startPlaying)(struct GBARRContext*, bool autorecord);
 	void (*stopPlaying)(struct GBARRContext*);
 	bool (*startRecording)(struct GBARRContext*);
@@ -47,4 +49,9 @@ struct GBARRContext {
 	struct VFile* savedata;
 };
 
+void GBARRDestroy(struct GBARRContext*);
+
+void GBARRInitRecord(struct GBA*);
+void GBARRInitPlay(struct GBA*);
+
 #endif
diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c
index e3b29627e..7c6a1677e 100644
--- a/src/gba/supervisor/thread.c
+++ b/src/gba/supervisor/thread.c
@@ -10,6 +10,7 @@
 #include "gba/cheats.h"
 #include "gba/serialize.h"
 #include "gba/supervisor/config.h"
+#include "gba/rr/mgm.h"
 
 #include "debugger/debugger.h"
 
@@ -117,6 +118,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
 	struct GBACheatDevice cheatDevice;
 	struct GBAThread* threadContext = context;
 	struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
+	struct GBARRContext* movie = 0;
 	int numComponents = GBA_COMPONENT_MAX;
 
 #if !defined(_WIN32) && defined(USE_PTHREADS)
@@ -170,7 +172,32 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
 		}
 	}
 
+	if (threadContext->movie) {
+		struct VDir* movieDir = VDirOpen(threadContext->movie);
+#ifdef ENABLE_LIBZIP
+		if (!movieDir) {
+			movieDir = VDirOpenZip(threadContext->movie, 0);
+		}
+#endif
+		if (movieDir) {
+			struct GBAMGMContext* mgm = malloc(sizeof(*mgm));
+			GBAMGMContextCreate(mgm);
+			if (!GBAMGMSetStream(mgm, movieDir)) {
+				mgm->d.destroy(&mgm->d);
+			} else {
+				movie = &mgm->d;
+			}
+		}
+	}
+
 	ARMReset(&cpu);
+
+	if (movie) {
+		gba.rr = movie;
+		movie->startPlaying(movie, false);
+		GBARRInitPlay(&gba);
+	}
+
 	if (threadContext->skipBios) {
 		GBASkipBIOS(&cpu);
 	}
@@ -256,6 +283,11 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
 		GBACheatDeviceDestroy(&cheatDevice);
 	}
 
+	if (movie) {
+		movie->destroy(movie);
+		free(movie);
+	}
+
 	threadContext->sync.videoFrameOn = false;
 	ConditionWake(&threadContext->sync.videoFrameAvailableCond);
 	ConditionWake(&threadContext->sync.audioRequiredCond);
@@ -309,6 +341,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
 	threadContext->fname = args->fname;
 	threadContext->patch = VFileOpen(args->patch, O_RDONLY);
 	threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
+	threadContext->movie = args->movie;
 }
 
 bool GBAThreadStart(struct GBAThread* threadContext) {
diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h
index 837deaae8..11b435d26 100644
--- a/src/gba/supervisor/thread.h
+++ b/src/gba/supervisor/thread.h
@@ -72,6 +72,7 @@ struct GBAThread {
 	struct VFile* patch;
 	struct VFile* cheatsFile;
 	const char* fname;
+	const char* movie;
 	int activeKeys;
 	struct GBAAVStream* stream;
 	struct Configuration* overrides;
diff --git a/src/platform/commandline.c b/src/platform/commandline.c
index bee0a608b..3a4456202 100644
--- a/src/platform/commandline.c
+++ b/src/platform/commandline.c
@@ -34,8 +34,8 @@
 
 static const struct option _options[] = {
 	{ "bios",      required_argument, 0, 'b' },
-	{ "cheats",      required_argument, 0, 'c' },
-	{ "dirmode",      required_argument, 0, 'D' },
+	{ "cheats",    required_argument, 0, 'c' },
+	{ "dirmode",   required_argument, 0, 'D' },
 	{ "frameskip", required_argument, 0, 's' },
 #ifdef USE_CLI_DEBUGGER
 	{ "debug",     no_argument, 0, 'd' },
@@ -43,6 +43,7 @@ static const struct option _options[] = {
 #ifdef USE_GDB_STUB
 	{ "gdb",       no_argument, 0, 'g' },
 #endif
+	{ "movie",     required_argument, 0, 'v' },
 	{ "patch",     required_argument, 0, 'p' },
 	{ 0, 0, 0, 0 }
 };
@@ -52,7 +53,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
 bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
 	int ch;
 	char options[64] =
-		"b:c:Dl:p:s:"
+		"b:c:Dl:p:s:v:"
 #ifdef USE_CLI_DEBUGGER
 		"d"
 #endif
@@ -101,6 +102,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
 		case 's':
 			GBAConfigSetDefaultValue(config, "frameskip", optarg);
 			break;
+		case 'v':
+			opts->movie = strdup(optarg);
+			break;
 		default:
 			if (subparser) {
 				if (!subparser->parse(subparser, config, ch, optarg)) {
@@ -125,6 +129,9 @@ void freeArguments(struct GBAArguments* opts) {
 
 	free(opts->patch);
 	opts->patch = 0;
+
+	free(opts->movie);
+	opts->movie = 0;
 }
 
 void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
@@ -211,6 +218,7 @@ void usage(const char* arg0, const char* extraOptions) {
 #ifdef USE_GDB_STUB
 	puts("  -g, --gdb           Start GDB session (default port 2345)");
 #endif
+	puts("  -v, --movie FILE    Play back a movie of recorded input");
 	puts("  -p, --patch FILE    Apply a specified patch file when running");
 	puts("  -s, --frameskip N   Skip every N frames");
 	if (extraOptions) {
diff --git a/src/platform/commandline.h b/src/platform/commandline.h
index a360c335d..97ac22240 100644
--- a/src/platform/commandline.h
+++ b/src/platform/commandline.h
@@ -26,6 +26,7 @@ struct GBAArguments {
 	char* patch;
 	char* cheatsFile;
 	bool dirmode;
+	char* movie;
 
 	enum DebuggerType debuggerType;
 	bool debugAtStart;