all repos — mgba @ ff200093cab7f555f97cd28dda3c5091ba4c7f71

mGBA Game Boy Advance Emulator

src/gba/gba-rr.c (view raw)

  1#include "gba-rr.h"
  2
  3#include "gba.h"
  4#include "util/vfs.h"
  5
  6#define BINARY_EXT ".dat"
  7#define BINARY_MAGIC "GBAb"
  8#define METADATA_FILENAME "metadata" BINARY_EXT
  9
 10static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
 11static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
 12static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
 13static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
 14static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
 15static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
 16
 17void GBARRContextCreate(struct GBA* gba) {
 18	if (gba->rr) {
 19		return;
 20	}
 21
 22	gba->rr = calloc(1, sizeof(*gba->rr));
 23}
 24
 25void GBARRContextDestroy(struct GBA* gba) {
 26	if (!gba->rr) {
 27		return;
 28	}
 29
 30	if (GBARRIsPlaying(gba->rr)) {
 31		GBARRStopPlaying(gba->rr);
 32	}
 33	if (GBARRIsRecording(gba->rr)) {
 34		GBARRStopRecording(gba->rr);
 35	}
 36	if (gba->rr->metadataFile) {
 37		gba->rr->metadataFile->close(gba->rr->metadataFile);
 38	}
 39
 40	free(gba->rr);
 41	gba->rr = 0;
 42}
 43
 44bool GBARRSetStream(struct GBARRContext* rr, struct VDir* stream) {
 45	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
 46		return false;
 47	}
 48
 49	if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
 50		return false;
 51	}
 52
 53	rr->streamDir = stream;
 54	rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
 55	if (!_parseMetadata(rr, rr->metadataFile)) {
 56		rr->metadataFile->close(rr->metadataFile);
 57		rr->streamDir = 0;
 58		rr->metadataFile = 0;
 59		return false;
 60	}
 61	rr->movieStream = 0;
 62	rr->streamId = 1;
 63	rr->maxStreamId = 1;
 64	return true;
 65}
 66
 67bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
 68	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
 69		return false;
 70	}
 71	rr->movieStream = 0;
 72	rr->streamId = streamId;
 73	char buffer[14];
 74	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
 75	if (GBARRIsRecording(rr)) {
 76		int flags = O_CREAT | O_WRONLY;
 77		if (streamId > rr->maxStreamId) {
 78			flags |= O_TRUNC;
 79		} else {
 80			flags |= O_APPEND;
 81		}
 82		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
 83	} else if (GBARRIsPlaying(rr)) {
 84		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
 85		rr->peekedTag = TAG_INVALID;
 86		if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
 87			GBARRStopPlaying(rr);
 88		}
 89	}
 90	rr->frames = 0;
 91	rr->lagFrames = 0;
 92	return true;
 93}
 94
 95bool GBARRIncrementStream(struct GBARRContext* rr) {
 96	uint32_t newStreamId = rr->maxStreamId + 1;
 97	uint32_t oldStreamId = rr->streamId;
 98	if (GBARRIsRecording(rr) && rr->movieStream) {
 99		_emitTag(rr, rr->movieStream, TAG_END);
100		_emitTag(rr, rr->movieStream, TAG_FRAME_COUNT);
101		rr->movieStream->write(rr->movieStream, &rr->frames, sizeof(rr->frames));
102		_emitTag(rr, rr->movieStream, TAG_LAG_COUNT);
103		rr->movieStream->write(rr->movieStream, &rr->lagFrames, sizeof(rr->lagFrames));
104		_emitTag(rr, rr->movieStream, TAG_NEXT_TIME);
105		rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId));
106	}
107	if (!GBARRLoadStream(rr, newStreamId)) {
108		return false;
109	}
110	_emitMagic(rr, rr->movieStream);
111	rr->maxStreamId = newStreamId;
112	_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
113	rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
114	_emitTag(rr, rr->movieStream, TAG_BEGIN);
115
116	rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
117	rr->metadataFile->write(rr->movieStream, &rr->maxStreamId, sizeof(rr->maxStreamId));
118	return true;
119}
120
121bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
122	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
123		return false;
124	}
125
126	char buffer[14];
127	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
128	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
129	if (!rr->movieStream) {
130		return false;
131	}
132
133	rr->autorecord = autorecord;
134	if (!_verifyMagic(rr, rr->movieStream)) {
135		return false;
136	}
137	rr->peekedTag = TAG_INVALID;
138	_readTag(rr, rr->movieStream); // Discard the buffer
139	enum GBARRTag tag = _readTag(rr, rr->movieStream);
140	if (tag != TAG_BEGIN) {
141		rr->movieStream->close(rr->movieStream);
142		rr->movieStream = 0;
143		return false;
144	}
145
146	rr->isPlaying = true;
147	return true;
148}
149
150void GBARRStopPlaying(struct GBARRContext* rr) {
151	if (!GBARRIsPlaying(rr)) {
152		return;
153	}
154	rr->isPlaying = false;
155	if (rr->movieStream) {
156		rr->movieStream->close(rr->movieStream);
157		rr->movieStream = 0;
158	}
159}
160
161bool GBARRStartRecording(struct GBARRContext* rr) {
162	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
163		return false;
164	}
165
166	char buffer[14];
167	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
168	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_TRUNC | O_CREAT | O_WRONLY);
169	if (!rr->movieStream) {
170		return false;
171	}
172	_emitMagic(rr, rr->movieStream);
173	if (!_emitTag(rr, rr->movieStream, TAG_BEGIN)) {
174		rr->movieStream->close(rr->movieStream);
175		rr->movieStream = 0;
176		return false;
177	}
178
179	if (!rr->maxStreamIdOffset) {
180		_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
181		rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
182		rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
183	}
184
185	rr->isRecording = true;
186	return true;
187}
188
189void GBARRStopRecording(struct GBARRContext* rr) {
190	if (!GBARRIsRecording(rr)) {
191		return;
192	}
193	rr->isRecording = false;
194	if (rr->movieStream) {
195		_emitTag(rr, rr->movieStream, TAG_END);
196		rr->movieStream->close(rr->movieStream);
197		rr->movieStream = 0;
198	}
199}
200
201bool GBARRIsPlaying(struct GBARRContext* rr) {
202	return rr && rr->isPlaying;
203}
204
205bool GBARRIsRecording(struct GBARRContext* rr) {
206	return rr && rr->isRecording;
207}
208
209void GBARRNextFrame(struct GBARRContext* rr) {
210	if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
211		return;
212	}
213
214	++rr->frames;
215	if (!rr->inputThisFrame) {
216		++rr->lagFrames;
217	}
218
219	if (GBARRIsRecording(rr)) {
220		if (!rr->inputThisFrame) {
221			_emitTag(rr, rr->movieStream, TAG_LAG);
222		}
223		_emitTag(rr, rr->movieStream, TAG_FRAME);
224
225		rr->inputThisFrame = false;
226	} else {
227		if (rr->peekedTag == TAG_INPUT) {
228			GBALog(0, GBA_LOG_WARN, "RR desync detected!");
229		}
230		if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
231			uint32_t endStreamId = rr->streamId;
232			GBARRStopPlaying(rr);
233			if (rr->autorecord) {
234				rr->isRecording = true;
235				GBARRLoadStream(rr, endStreamId);
236				GBARRIncrementStream(rr);
237			}
238		}
239	}
240}
241
242void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
243	if (!GBARRIsRecording(rr)) {
244		return;
245	}
246
247	if (keys != rr->currentInput) {
248		_emitTag(rr, rr->movieStream, TAG_INPUT);
249		rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
250		rr->currentInput = keys;
251	}
252	rr->inputThisFrame = true;
253}
254
255uint16_t GBARRQueryInput(struct GBARRContext* rr) {
256	if (!GBARRIsPlaying(rr)) {
257		return 0;
258	}
259
260	if (rr->peekedTag == TAG_INPUT) {
261		_readTag(rr, rr->movieStream);
262	}
263	return rr->currentInput;
264}
265
266bool GBARRSkipSegment(struct GBARRContext* rr) {
267	rr->nextTime = 0;
268	while (_readTag(rr, rr->movieStream) != TAG_EOF);
269	if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
270		return false;
271	}
272	return true;
273}
274
275bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
276	UNUSED(rr);
277	return vf->write(vf, BINARY_MAGIC, 4) == 4;
278}
279
280bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
281	UNUSED(rr);
282	char buffer[4];
283	if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
284		return false;
285	}
286	if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
287		return false;
288	}
289	return true;
290}
291
292enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
293	if (!rr || !vf) {
294		return TAG_EOF;
295	}
296
297	enum GBARRTag tag = rr->peekedTag;
298	switch (tag) {
299	case TAG_INPUT:
300		vf->read(vf, &rr->currentInput, sizeof(uint16_t));
301		break;
302	case TAG_PREVIOUSLY:
303		vf->read(vf, &rr->previously, sizeof(rr->previously));
304		break;
305	case TAG_NEXT_TIME:
306		vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
307		break;
308	case TAG_MAX_STREAM:
309		vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
310		break;
311	case TAG_FRAME_COUNT:
312		vf->read(vf, &rr->frames, sizeof(rr->frames));
313		break;
314	case TAG_LAG_COUNT:
315		vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
316		break;
317
318	// To be spec'd
319	case TAG_RR_COUNT:
320	case TAG_INIT_TYPE:
321	case TAG_AUTHOR:
322	case TAG_COMMENT:
323		break;
324
325	// Empty markers
326	case TAG_FRAME:
327	case TAG_LAG:
328	case TAG_BEGIN:
329	case TAG_END:
330	case TAG_INVALID:
331	case TAG_EOF:
332		break;
333	}
334
335	uint8_t tagBuffer;
336	if (vf->read(vf, &tagBuffer, 1) != 1) {
337		rr->peekedTag = TAG_EOF;
338	} else {
339		rr->peekedTag = tagBuffer;
340	}
341	return tag;
342}
343
344bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
345	enum GBARRTag readTag;
346	while ((readTag = _readTag(rr, vf)) != tag) {
347		if (vf == rr->movieStream && readTag == TAG_END) {
348			if (!GBARRSkipSegment(rr)) {
349				return false;
350			}
351			vf = rr->movieStream;
352		} else if (readTag == TAG_EOF) {
353			return false;
354		}
355	}
356	return true;
357}
358
359bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
360	UNUSED(rr);
361	return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
362}
363
364bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
365	while (_readTag(rr, vf) != TAG_EOF) {
366		if (rr->peekedTag == TAG_MAX_STREAM) {
367			rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
368		}
369	}
370	rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_SET);
371	return true;
372}