all repos — mgba @ f0053268003218ffc5252198c0d44ac234b02ff9

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