all repos — mgba @ 6291a62860376b1e4a5e0d3ee681b6c93eb10f68

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