all repos — mgba @ 484a7f299a966cb8fbd90c9e06b332235073bae8

mGBA Game Boy Advance Emulator

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

  1#include "gba-rr.h"
  2
  3#include "gba.h"
  4#include "gba-serialize.h"
  5#include "util/vfs.h"
  6
  7#define BINARY_EXT ".dat"
  8#define BINARY_MAGIC "GBAb"
  9#define METADATA_FILENAME "metadata" BINARY_EXT
 10
 11enum {
 12	INVALID_INPUT = 0x8000
 13};
 14
 15static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
 16static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
 17static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
 18static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
 19static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
 20static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf);
 21
 22static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
 23
 24static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive);
 25
 26static struct VFile* _openSavedata(struct GBARRContext* rr, int flags);
 27static struct VFile* _openSavestate(struct GBARRContext* rr, int flags);
 28
 29void GBARRContextCreate(struct GBA* gba) {
 30	if (gba->rr) {
 31		return;
 32	}
 33
 34	gba->rr = calloc(1, sizeof(*gba->rr));
 35}
 36
 37void GBARRContextDestroy(struct GBA* gba) {
 38	if (!gba->rr) {
 39		return;
 40	}
 41
 42	if (GBARRIsPlaying(gba->rr)) {
 43		GBARRStopPlaying(gba->rr);
 44	}
 45	if (GBARRIsRecording(gba->rr)) {
 46		GBARRStopRecording(gba->rr);
 47	}
 48	if (gba->rr->metadataFile) {
 49		gba->rr->metadataFile->close(gba->rr->metadataFile);
 50	}
 51	if (gba->rr->savedata) {
 52		gba->rr->savedata->close(gba->rr->savedata);
 53	}
 54
 55	free(gba->rr);
 56	gba->rr = 0;
 57}
 58
 59void GBARRSaveState(struct GBA* gba) {
 60	if (!gba || !gba->rr) {
 61		return;
 62	}
 63
 64	if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
 65		if (gba->rr->savedata) {
 66			gba->rr->savedata->close(gba->rr->savedata);
 67		}
 68		gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
 69		GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
 70		gba->rr->savedata->close(gba->rr->savedata);
 71		gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
 72		GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
 73	} else {
 74		GBASavedataMask(&gba->memory.savedata, 0);
 75	}
 76
 77	if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
 78		struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
 79		GBASaveStateNamed(gba, vf, false);
 80		vf->close(vf);
 81	} else {
 82		ARMReset(gba->cpu);
 83	}
 84}
 85
 86void GBARRLoadState(struct GBA* gba) {
 87	if (!gba || !gba->rr) {
 88		return;
 89	}
 90
 91	if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
 92		if (gba->rr->savedata) {
 93			gba->rr->savedata->close(gba->rr->savedata);
 94		}
 95		gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
 96		GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
 97	} else {
 98		GBASavedataMask(&gba->memory.savedata, 0);
 99	}
100
101	if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
102		struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
103		GBALoadStateNamed(gba, vf);
104		vf->close(vf);
105	} else {
106		ARMReset(gba->cpu);
107	}
108}
109
110bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
111	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
112		return false;
113	}
114
115	if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
116		return false;
117	}
118
119	rr->streamDir = stream;
120	rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
121	rr->currentInput = INVALID_INPUT;
122	if (!_parseMetadata(rr, rr->metadataFile)) {
123		rr->metadataFile->close(rr->metadataFile);
124		rr->metadataFile = 0;
125		rr->maxStreamId = 0;
126	}
127	rr->streamId = 1;
128	rr->movieStream = 0;
129	return true;
130}
131
132bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
133	if (!rr) {
134		return false;
135	}
136
137	if (rr->metadataFile) {
138		rr->metadataFile->truncate(rr->metadataFile, 0);
139	} else {
140		rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
141	}
142	_emitMagic(rr, rr->metadataFile);
143
144	rr->initFrom = initFrom;
145	rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
146	_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
147
148	rr->streamId = 0;
149	rr->maxStreamId = 0;
150	_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
151	rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
152	rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
153
154	rr->rrCount = 0;
155	_emitTag(rr, rr->metadataFile, TAG_RR_COUNT);
156	rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
157	rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
158	return true;
159}
160
161bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
162	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
163		return false;
164	}
165	rr->movieStream = 0;
166	rr->streamId = streamId;
167	rr->currentInput = INVALID_INPUT;
168	char buffer[14];
169	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
170	if (GBARRIsRecording(rr)) {
171		int flags = O_CREAT | O_RDWR;
172		if (streamId > rr->maxStreamId) {
173			flags |= O_TRUNC;
174		}
175		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
176	} else if (GBARRIsPlaying(rr)) {
177		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
178		rr->peekedTag = TAG_INVALID;
179		if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
180			GBARRStopPlaying(rr);
181		}
182	}
183	GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
184	rr->frames = 0;
185	rr->lagFrames = 0;
186	return true;
187}
188
189bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) {
190	uint32_t newStreamId = rr->maxStreamId + 1;
191	uint32_t oldStreamId = rr->streamId;
192	if (GBARRIsRecording(rr) && rr->movieStream) {
193		if (!_markStreamNext(rr, newStreamId, recursive)) {
194			return false;
195		}
196	}
197	if (!GBARRLoadStream(rr, newStreamId)) {
198		return false;
199	}
200	GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
201	_emitMagic(rr, rr->movieStream);
202	rr->maxStreamId = newStreamId;
203	_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
204	rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
205	_emitTag(rr, rr->movieStream, TAG_BEGIN);
206
207	rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
208	rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
209	rr->previously = oldStreamId;
210	return true;
211}
212
213bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
214	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
215		return false;
216	}
217
218	rr->isPlaying = true;
219	if (!GBARRLoadStream(rr, 1)) {
220		rr->isPlaying = false;
221		return false;
222	}
223	rr->autorecord = autorecord;
224	return true;
225}
226
227void GBARRStopPlaying(struct GBARRContext* rr) {
228	if (!GBARRIsPlaying(rr)) {
229		return;
230	}
231	rr->isPlaying = false;
232	if (rr->movieStream) {
233		rr->movieStream->close(rr->movieStream);
234		rr->movieStream = 0;
235	}
236}
237
238bool GBARRStartRecording(struct GBARRContext* rr) {
239	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
240		return false;
241	}
242
243	if (!rr->maxStreamIdOffset) {
244		_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
245		rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
246		rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
247	}
248
249	rr->isRecording = true;
250	return GBARRIncrementStream(rr, false);
251}
252
253void GBARRStopRecording(struct GBARRContext* rr) {
254	if (!GBARRIsRecording(rr)) {
255		return;
256	}
257	rr->isRecording = false;
258	if (rr->movieStream) {
259		_emitEnd(rr, rr->movieStream);
260		rr->movieStream->close(rr->movieStream);
261		rr->movieStream = 0;
262	}
263}
264
265bool GBARRIsPlaying(struct GBARRContext* rr) {
266	return rr && rr->isPlaying;
267}
268
269bool GBARRIsRecording(struct GBARRContext* rr) {
270	return rr && rr->isRecording;
271}
272
273void GBARRNextFrame(struct GBARRContext* rr) {
274	if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
275		return;
276	}
277
278	if (GBARRIsPlaying(rr)) {
279		while (rr->peekedTag == TAG_INPUT) {
280			_readTag(rr, rr->movieStream);
281			GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
282		}
283		if (rr->peekedTag == TAG_LAG) {
284			GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
285			if (rr->inputThisFrame) {
286				GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
287			}
288		}
289	}
290
291	++rr->frames;
292	GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
293	if (!rr->inputThisFrame) {
294		++rr->lagFrames;
295		GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
296	}
297
298	if (GBARRIsRecording(rr)) {
299		if (!rr->inputThisFrame) {
300			_emitTag(rr, rr->movieStream, TAG_LAG);
301		}
302		_emitTag(rr, rr->movieStream, TAG_FRAME);
303		rr->inputThisFrame = false;
304	} else {
305		if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
306			uint32_t endStreamId = rr->streamId;
307			GBARRStopPlaying(rr);
308			if (rr->autorecord) {
309				rr->isRecording = true;
310				GBARRLoadStream(rr, endStreamId);
311				GBARRIncrementStream(rr, false);
312			}
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		return false;
361	}
362	return true;
363}
364
365bool GBARRMarkRerecord(struct GBARRContext* rr) {
366	++rr->rrCount;
367	rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
368	rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
369	return true;
370}
371
372bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
373	UNUSED(rr);
374	return vf->write(vf, BINARY_MAGIC, 4) == 4;
375}
376
377bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
378	UNUSED(rr);
379	char buffer[4];
380	if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
381		return false;
382	}
383	if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
384		return false;
385	}
386	return true;
387}
388
389enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
390	if (!rr || !vf) {
391		return TAG_EOF;
392	}
393
394	enum GBARRTag tag = rr->peekedTag;
395	switch (tag) {
396	case TAG_INPUT:
397		vf->read(vf, &rr->currentInput, sizeof(uint16_t));
398		break;
399	case TAG_PREVIOUSLY:
400		vf->read(vf, &rr->previously, sizeof(rr->previously));
401		break;
402	case TAG_NEXT_TIME:
403		vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
404		break;
405	case TAG_MAX_STREAM:
406		vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
407		break;
408	case TAG_FRAME_COUNT:
409		vf->read(vf, &rr->frames, sizeof(rr->frames));
410		break;
411	case TAG_LAG_COUNT:
412		vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
413		break;
414	case TAG_RR_COUNT:
415		vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
416		break;
417
418	case TAG_INIT_EX_NIHILO:
419		rr->initFrom = INIT_EX_NIHILO;
420		break;
421	case TAG_INIT_FROM_SAVEGAME:
422		rr->initFrom = INIT_FROM_SAVEGAME;
423		break;
424	case TAG_INIT_FROM_SAVESTATE:
425		rr->initFrom = INIT_FROM_SAVESTATE;
426	case TAG_INIT_FROM_BOTH:
427		rr->initFrom = INIT_FROM_BOTH;
428		break;
429
430	// To be spec'd
431	case TAG_AUTHOR:
432	case TAG_COMMENT:
433		break;
434
435	// Empty markers
436	case TAG_FRAME:
437	case TAG_LAG:
438	case TAG_BEGIN:
439	case TAG_END:
440	case TAG_INVALID:
441	case TAG_EOF:
442		break;
443	}
444
445	uint8_t tagBuffer;
446	if (vf->read(vf, &tagBuffer, 1) != 1) {
447		rr->peekedTag = TAG_EOF;
448	} else {
449		rr->peekedTag = tagBuffer;
450	}
451
452	if (rr->peekedTag == TAG_END) {
453		GBARRSkipSegment(rr);
454	}
455	return tag;
456}
457
458bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
459	enum GBARRTag readTag;
460	while ((readTag = _readTag(rr, vf)) != tag) {
461		if (readTag == TAG_EOF) {
462			return false;
463		}
464	}
465	return true;
466}
467
468bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
469	UNUSED(rr);
470	return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
471}
472
473bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
474	if (!_verifyMagic(rr, vf)) {
475		return false;
476	}
477	while (_readTag(rr, vf) != TAG_EOF) {
478		switch (rr->peekedTag) {
479		case TAG_MAX_STREAM:
480			rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
481			break;
482		case TAG_INIT_EX_NIHILO:
483		case TAG_INIT_FROM_SAVEGAME:
484		case TAG_INIT_FROM_SAVESTATE:
485		case TAG_INIT_FROM_BOTH:
486			rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
487			break;
488		case TAG_RR_COUNT:
489			rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
490			break;
491		default:
492			break;
493		}
494	}
495	rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_SET);
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
551struct VFile* _openSavedata(struct GBARRContext* rr, int flags) {
552	return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags);
553}
554
555struct VFile* _openSavestate(struct GBARRContext* rr, int flags) {
556	return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags);
557}