all repos — mgba @ ef65d185a3dec0bd89d085e49759c42db210c1ed

mGBA Game Boy Advance Emulator

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

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "rr.h"
  7
  8#include "gba/gba.h"
  9#include "gba/serialize.h"
 10#include "util/vfs.h"
 11
 12#define BINARY_EXT ".dat"
 13#define BINARY_MAGIC "GBAb"
 14#define METADATA_FILENAME "metadata" BINARY_EXT
 15
 16enum {
 17	INVALID_INPUT = 0x8000
 18};
 19
 20static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
 21static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
 22static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
 23static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
 24static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
 25static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf);
 26
 27static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
 28
 29static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive);
 30static void _streamEndReached(struct GBARRContext* rr);
 31
 32static struct VFile* _openSavedata(struct GBARRContext* rr, int flags);
 33static struct VFile* _openSavestate(struct GBARRContext* rr, int flags);
 34
 35void GBARRContextCreate(struct GBA* gba) {
 36	if (gba->rr) {
 37		return;
 38	}
 39
 40	gba->rr = calloc(1, sizeof(*gba->rr));
 41}
 42
 43void GBARRContextDestroy(struct GBA* gba) {
 44	if (!gba->rr) {
 45		return;
 46	}
 47
 48	if (GBARRIsPlaying(gba->rr)) {
 49		GBARRStopPlaying(gba->rr);
 50	}
 51	if (GBARRIsRecording(gba->rr)) {
 52		GBARRStopRecording(gba->rr);
 53	}
 54	if (gba->rr->metadataFile) {
 55		gba->rr->metadataFile->close(gba->rr->metadataFile);
 56	}
 57	if (gba->rr->savedata) {
 58		gba->rr->savedata->close(gba->rr->savedata);
 59	}
 60
 61	free(gba->rr);
 62	gba->rr = 0;
 63}
 64
 65void GBARRSaveState(struct GBA* gba) {
 66	if (!gba || !gba->rr) {
 67		return;
 68	}
 69
 70	if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
 71		if (gba->rr->savedata) {
 72			gba->rr->savedata->close(gba->rr->savedata);
 73		}
 74		gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
 75		GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
 76		gba->rr->savedata->close(gba->rr->savedata);
 77		gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
 78		GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
 79	} else {
 80		GBASavedataMask(&gba->memory.savedata, 0);
 81	}
 82
 83	if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
 84		struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
 85		GBASaveStateNamed(gba, vf, false);
 86		vf->close(vf);
 87	} else {
 88		ARMReset(gba->cpu);
 89	}
 90}
 91
 92void GBARRLoadState(struct GBA* gba) {
 93	if (!gba || !gba->rr) {
 94		return;
 95	}
 96
 97	if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
 98		if (gba->rr->savedata) {
 99			gba->rr->savedata->close(gba->rr->savedata);
100		}
101		gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
102		GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
103	} else {
104		GBASavedataMask(&gba->memory.savedata, 0);
105	}
106
107	if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
108		struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
109		GBALoadStateNamed(gba, vf);
110		vf->close(vf);
111	} else {
112		ARMReset(gba->cpu);
113	}
114}
115
116bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
117	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
118		return false;
119	}
120
121	if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
122		return false;
123	}
124
125	rr->streamDir = stream;
126	rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
127	rr->currentInput = INVALID_INPUT;
128	if (!_parseMetadata(rr, rr->metadataFile)) {
129		rr->metadataFile->close(rr->metadataFile);
130		rr->metadataFile = 0;
131		rr->maxStreamId = 0;
132	}
133	rr->streamId = 1;
134	rr->movieStream = 0;
135	return true;
136}
137
138bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
139	if (!rr) {
140		return false;
141	}
142
143	if (rr->metadataFile) {
144		rr->metadataFile->truncate(rr->metadataFile, 0);
145	} else {
146		rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
147	}
148	_emitMagic(rr, rr->metadataFile);
149
150	rr->initFrom = initFrom;
151	rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
152	_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
153
154	rr->streamId = 0;
155	rr->maxStreamId = 0;
156	_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
157	rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
158	rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
159
160	rr->rrCount = 0;
161	_emitTag(rr, rr->metadataFile, TAG_RR_COUNT);
162	rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
163	rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
164	return true;
165}
166
167bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
168	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
169		return false;
170	}
171	rr->movieStream = 0;
172	rr->streamId = streamId;
173	rr->currentInput = INVALID_INPUT;
174	char buffer[14];
175	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
176	if (GBARRIsRecording(rr)) {
177		int flags = O_CREAT | O_RDWR;
178		if (streamId > rr->maxStreamId) {
179			flags |= O_TRUNC;
180		}
181		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
182	} else if (GBARRIsPlaying(rr)) {
183		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
184		rr->peekedTag = TAG_INVALID;
185		if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
186			GBARRStopPlaying(rr);
187		}
188	}
189	GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
190	rr->frames = 0;
191	rr->lagFrames = 0;
192	return true;
193}
194
195bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) {
196	uint32_t newStreamId = rr->maxStreamId + 1;
197	uint32_t oldStreamId = rr->streamId;
198	if (GBARRIsRecording(rr) && rr->movieStream) {
199		if (!_markStreamNext(rr, newStreamId, recursive)) {
200			return false;
201		}
202	}
203	if (!GBARRLoadStream(rr, newStreamId)) {
204		return false;
205	}
206	GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
207	_emitMagic(rr, rr->movieStream);
208	rr->maxStreamId = newStreamId;
209	_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
210	rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
211	_emitTag(rr, rr->movieStream, TAG_BEGIN);
212
213	rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
214	rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
215	rr->previously = oldStreamId;
216	return true;
217}
218
219bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
220	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
221		return false;
222	}
223
224	rr->isPlaying = true;
225	if (!GBARRLoadStream(rr, 1)) {
226		rr->isPlaying = false;
227		return false;
228	}
229	rr->autorecord = autorecord;
230	return true;
231}
232
233void GBARRStopPlaying(struct GBARRContext* rr) {
234	if (!GBARRIsPlaying(rr)) {
235		return;
236	}
237	rr->isPlaying = false;
238	if (rr->movieStream) {
239		rr->movieStream->close(rr->movieStream);
240		rr->movieStream = 0;
241	}
242}
243
244bool GBARRStartRecording(struct GBARRContext* rr) {
245	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
246		return false;
247	}
248
249	if (!rr->maxStreamIdOffset) {
250		_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
251		rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
252		rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
253	}
254
255	rr->isRecording = true;
256	return GBARRIncrementStream(rr, false);
257}
258
259void GBARRStopRecording(struct GBARRContext* rr) {
260	if (!GBARRIsRecording(rr)) {
261		return;
262	}
263	rr->isRecording = false;
264	if (rr->movieStream) {
265		_emitEnd(rr, rr->movieStream);
266		rr->movieStream->close(rr->movieStream);
267		rr->movieStream = 0;
268	}
269}
270
271bool GBARRIsPlaying(struct GBARRContext* rr) {
272	return rr && rr->isPlaying;
273}
274
275bool GBARRIsRecording(struct GBARRContext* rr) {
276	return rr && rr->isRecording;
277}
278
279void GBARRNextFrame(struct GBARRContext* rr) {
280	if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
281		return;
282	}
283
284	if (GBARRIsPlaying(rr)) {
285		while (rr->peekedTag == TAG_INPUT) {
286			_readTag(rr, rr->movieStream);
287			GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
288		}
289		if (rr->peekedTag == TAG_LAG) {
290			GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
291			if (rr->inputThisFrame) {
292				GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
293			}
294		}
295	}
296
297	++rr->frames;
298	GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
299	if (!rr->inputThisFrame) {
300		++rr->lagFrames;
301		GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
302	}
303
304	if (GBARRIsRecording(rr)) {
305		if (!rr->inputThisFrame) {
306			_emitTag(rr, rr->movieStream, TAG_LAG);
307		}
308		_emitTag(rr, rr->movieStream, TAG_FRAME);
309		rr->inputThisFrame = false;
310	} else {
311		if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
312			_streamEndReached(rr);
313		}
314	}
315}
316
317void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
318	if (!GBARRIsRecording(rr)) {
319		return;
320	}
321
322	if (keys != rr->currentInput) {
323		_emitTag(rr, rr->movieStream, TAG_INPUT);
324		rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
325		rr->currentInput = keys;
326	}
327	GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput);
328	rr->inputThisFrame = true;
329}
330
331uint16_t GBARRQueryInput(struct GBARRContext* rr) {
332	if (!GBARRIsPlaying(rr)) {
333		return 0;
334	}
335
336	if (rr->peekedTag == TAG_INPUT) {
337		_readTag(rr, rr->movieStream);
338	}
339	rr->inputThisFrame = true;
340	if (rr->currentInput == INVALID_INPUT) {
341		GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
342	}
343	GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput);
344	return rr->currentInput;
345}
346
347bool GBARRFinishSegment(struct GBARRContext* rr) {
348	if (rr->movieStream) {
349		if (!_emitEnd(rr, rr->movieStream)) {
350			return false;
351		}
352	}
353	return GBARRIncrementStream(rr, false);
354}
355
356bool GBARRSkipSegment(struct GBARRContext* rr) {
357	rr->nextTime = 0;
358	while (_readTag(rr, rr->movieStream) != TAG_EOF);
359	if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
360		_streamEndReached(rr);
361		return false;
362	}
363	return true;
364}
365
366bool GBARRMarkRerecord(struct GBARRContext* rr) {
367	++rr->rrCount;
368	rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
369	rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
370	return true;
371}
372
373bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
374	UNUSED(rr);
375	return vf->write(vf, BINARY_MAGIC, 4) == 4;
376}
377
378bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
379	UNUSED(rr);
380	char buffer[4];
381	if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
382		return false;
383	}
384	if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
385		return false;
386	}
387	return true;
388}
389
390enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
391	if (!rr || !vf) {
392		return TAG_EOF;
393	}
394
395	enum GBARRTag tag = rr->peekedTag;
396	switch (tag) {
397	case TAG_INPUT:
398		vf->read(vf, &rr->currentInput, sizeof(uint16_t));
399		break;
400	case TAG_PREVIOUSLY:
401		vf->read(vf, &rr->previously, sizeof(rr->previously));
402		break;
403	case TAG_NEXT_TIME:
404		vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
405		break;
406	case TAG_MAX_STREAM:
407		vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
408		break;
409	case TAG_FRAME_COUNT:
410		vf->read(vf, &rr->frames, sizeof(rr->frames));
411		break;
412	case TAG_LAG_COUNT:
413		vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
414		break;
415	case TAG_RR_COUNT:
416		vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
417		break;
418
419	case TAG_INIT_EX_NIHILO:
420		rr->initFrom = INIT_EX_NIHILO;
421		break;
422	case TAG_INIT_FROM_SAVEGAME:
423		rr->initFrom = INIT_FROM_SAVEGAME;
424		break;
425	case TAG_INIT_FROM_SAVESTATE:
426		rr->initFrom = INIT_FROM_SAVESTATE;
427	case TAG_INIT_FROM_BOTH:
428		rr->initFrom = INIT_FROM_BOTH;
429		break;
430
431	// To be spec'd
432	case TAG_AUTHOR:
433	case TAG_COMMENT:
434		break;
435
436	// Empty markers
437	case TAG_FRAME:
438	case TAG_LAG:
439	case TAG_BEGIN:
440	case TAG_END:
441	case TAG_INVALID:
442	case TAG_EOF:
443		break;
444	}
445
446	uint8_t tagBuffer;
447	if (vf->read(vf, &tagBuffer, 1) != 1) {
448		rr->peekedTag = TAG_EOF;
449	} else {
450		rr->peekedTag = tagBuffer;
451	}
452
453	if (rr->peekedTag == TAG_END) {
454		GBARRSkipSegment(rr);
455	}
456	return tag;
457}
458
459bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
460	enum GBARRTag readTag;
461	while ((readTag = _readTag(rr, vf)) != tag) {
462		if (readTag == TAG_EOF) {
463			return false;
464		}
465	}
466	return true;
467}
468
469bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
470	UNUSED(rr);
471	return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
472}
473
474bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
475	if (!_verifyMagic(rr, vf)) {
476		return false;
477	}
478	while (_readTag(rr, vf) != TAG_EOF) {
479		switch (rr->peekedTag) {
480		case TAG_MAX_STREAM:
481			rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
482			break;
483		case TAG_INIT_EX_NIHILO:
484		case TAG_INIT_FROM_SAVEGAME:
485		case TAG_INIT_FROM_SAVESTATE:
486		case TAG_INIT_FROM_BOTH:
487			rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
488			break;
489		case TAG_RR_COUNT:
490			rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
491			break;
492		default:
493			break;
494		}
495	}
496	return true;
497}
498
499bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) {
500	// TODO: Error check
501	_emitTag(rr, vf, TAG_END);
502	_emitTag(rr, vf, TAG_FRAME_COUNT);
503	vf->write(vf, &rr->frames, sizeof(rr->frames));
504	_emitTag(rr, vf, TAG_LAG_COUNT);
505	vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames));
506	_emitTag(rr, vf, TAG_NEXT_TIME);
507
508	uint32_t newStreamId = 0;
509	vf->write(vf, &newStreamId, sizeof(newStreamId));
510	return true;
511}
512
513bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) {
514	if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
515		return false;
516	}
517
518	uint8_t tagBuffer;
519	if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) {
520		return false;
521	}
522	if (tagBuffer != TAG_NEXT_TIME) {
523		return false;
524	}
525	if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
526		return false;
527	}
528	if (recursive) {
529		if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) {
530			return false;
531		}
532		if (!_verifyMagic(rr, rr->movieStream)) {
533			return false;
534		}
535		_readTag(rr, rr->movieStream);
536		if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) {
537			return false;
538		}
539		if (rr->previously == 0) {
540			return true;
541		}
542		uint32_t currentStreamId = rr->streamId;
543		if (!GBARRLoadStream(rr, rr->previously)) {
544			return false;
545		}
546		return _markStreamNext(rr, currentStreamId, rr->previously);
547	}
548	return true;
549}
550
551void _streamEndReached(struct GBARRContext* rr) {
552	if (!GBARRIsPlaying(rr)) {
553		return;
554	}
555
556	uint32_t endStreamId = rr->streamId;
557	GBARRStopPlaying(rr);
558	if (rr->autorecord) {
559		rr->isRecording = true;
560		GBARRLoadStream(rr, endStreamId);
561		GBARRIncrementStream(rr, false);
562	}
563}
564
565struct VFile* _openSavedata(struct GBARRContext* rr, int flags) {
566	return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags);
567}
568
569struct VFile* _openSavestate(struct GBARRContext* rr, int flags) {
570	return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags);
571}