all repos — mgba @ 220d836f13a99f81166217b8a498863c2e31d709

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 GBARRInitStream(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->metadataFile = 0;
 58		rr->maxStreamId = 1;
 59	}
 60	rr->streamId = 1;
 61	rr->movieStream = 0;
 62	return true;
 63}
 64
 65bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
 66	if (!rr) {
 67		return false;
 68	}
 69
 70	if (rr->metadataFile) {
 71		rr->metadataFile->truncate(rr->metadataFile, 0);
 72	} else {
 73		rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
 74	}
 75	_emitMagic(rr, rr->metadataFile);
 76
 77	rr->initFrom = initFrom;
 78	rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
 79	_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
 80
 81	rr->streamId = 1;
 82	rr->maxStreamId = 1;
 83	rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
 84	_emitTag(rr, rr->metadataFile, 1);
 85	return true;
 86}
 87
 88bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
 89	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
 90		return false;
 91	}
 92	rr->movieStream = 0;
 93	rr->streamId = streamId;
 94	char buffer[14];
 95	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
 96	if (GBARRIsRecording(rr)) {
 97		int flags = O_CREAT | O_WRONLY;
 98		if (streamId > rr->maxStreamId) {
 99			flags |= O_TRUNC;
100		} else {
101			flags |= O_APPEND;
102		}
103		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
104	} else if (GBARRIsPlaying(rr)) {
105		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
106		rr->peekedTag = TAG_INVALID;
107		if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
108			GBARRStopPlaying(rr);
109		}
110	}
111	rr->frames = 0;
112	rr->lagFrames = 0;
113	return true;
114}
115
116bool GBARRIncrementStream(struct GBARRContext* rr) {
117	uint32_t newStreamId = rr->maxStreamId + 1;
118	uint32_t oldStreamId = rr->streamId;
119	if (GBARRIsRecording(rr) && rr->movieStream) {
120		_emitTag(rr, rr->movieStream, TAG_END);
121		_emitTag(rr, rr->movieStream, TAG_FRAME_COUNT);
122		rr->movieStream->write(rr->movieStream, &rr->frames, sizeof(rr->frames));
123		_emitTag(rr, rr->movieStream, TAG_LAG_COUNT);
124		rr->movieStream->write(rr->movieStream, &rr->lagFrames, sizeof(rr->lagFrames));
125		_emitTag(rr, rr->movieStream, TAG_NEXT_TIME);
126		rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId));
127	}
128	if (!GBARRLoadStream(rr, newStreamId)) {
129		return false;
130	}
131	_emitMagic(rr, rr->movieStream);
132	rr->maxStreamId = newStreamId;
133	_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
134	rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
135	_emitTag(rr, rr->movieStream, TAG_BEGIN);
136
137	rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
138	rr->metadataFile->write(rr->movieStream, &rr->maxStreamId, sizeof(rr->maxStreamId));
139	return true;
140}
141
142bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
143	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
144		return false;
145	}
146
147	char buffer[14];
148	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
149	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
150	if (!rr->movieStream) {
151		return false;
152	}
153
154	rr->autorecord = autorecord;
155	if (!_verifyMagic(rr, rr->movieStream)) {
156		return false;
157	}
158	rr->peekedTag = TAG_INVALID;
159	_readTag(rr, rr->movieStream); // Discard the buffer
160	enum GBARRTag tag = _readTag(rr, rr->movieStream);
161	if (tag != TAG_BEGIN) {
162		rr->movieStream->close(rr->movieStream);
163		rr->movieStream = 0;
164		return false;
165	}
166
167	rr->isPlaying = true;
168	return true;
169}
170
171void GBARRStopPlaying(struct GBARRContext* rr) {
172	if (!GBARRIsPlaying(rr)) {
173		return;
174	}
175	rr->isPlaying = false;
176	if (rr->movieStream) {
177		rr->movieStream->close(rr->movieStream);
178		rr->movieStream = 0;
179	}
180}
181
182bool GBARRStartRecording(struct GBARRContext* rr) {
183	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
184		return false;
185	}
186
187	char buffer[14];
188	snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
189	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_TRUNC | O_CREAT | O_WRONLY);
190	if (!rr->movieStream) {
191		return false;
192	}
193	_emitMagic(rr, rr->movieStream);
194	if (!_emitTag(rr, rr->movieStream, TAG_BEGIN)) {
195		rr->movieStream->close(rr->movieStream);
196		rr->movieStream = 0;
197		return false;
198	}
199
200	if (!rr->maxStreamIdOffset) {
201		_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
202		rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
203		rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
204	}
205
206	rr->isRecording = true;
207	return true;
208}
209
210void GBARRStopRecording(struct GBARRContext* rr) {
211	if (!GBARRIsRecording(rr)) {
212		return;
213	}
214	rr->isRecording = false;
215	if (rr->movieStream) {
216		_emitTag(rr, rr->movieStream, TAG_END);
217		rr->movieStream->close(rr->movieStream);
218		rr->movieStream = 0;
219	}
220}
221
222bool GBARRIsPlaying(struct GBARRContext* rr) {
223	return rr && rr->isPlaying;
224}
225
226bool GBARRIsRecording(struct GBARRContext* rr) {
227	return rr && rr->isRecording;
228}
229
230void GBARRNextFrame(struct GBARRContext* rr) {
231	if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
232		return;
233	}
234
235	++rr->frames;
236	if (!rr->inputThisFrame) {
237		++rr->lagFrames;
238	}
239
240	if (GBARRIsRecording(rr)) {
241		if (!rr->inputThisFrame) {
242			_emitTag(rr, rr->movieStream, TAG_LAG);
243		}
244		_emitTag(rr, rr->movieStream, TAG_FRAME);
245
246		rr->inputThisFrame = false;
247	} else {
248		if (rr->peekedTag == TAG_INPUT) {
249			GBALog(0, GBA_LOG_WARN, "RR desync detected!");
250		}
251		if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
252			uint32_t endStreamId = rr->streamId;
253			GBARRStopPlaying(rr);
254			if (rr->autorecord) {
255				rr->isRecording = true;
256				GBARRLoadStream(rr, endStreamId);
257				GBARRIncrementStream(rr);
258			}
259		}
260	}
261}
262
263void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
264	if (!GBARRIsRecording(rr)) {
265		return;
266	}
267
268	if (keys != rr->currentInput) {
269		_emitTag(rr, rr->movieStream, TAG_INPUT);
270		rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
271		rr->currentInput = keys;
272	}
273	rr->inputThisFrame = true;
274}
275
276uint16_t GBARRQueryInput(struct GBARRContext* rr) {
277	if (!GBARRIsPlaying(rr)) {
278		return 0;
279	}
280
281	if (rr->peekedTag == TAG_INPUT) {
282		_readTag(rr, rr->movieStream);
283	}
284	return rr->currentInput;
285}
286
287bool GBARRSkipSegment(struct GBARRContext* rr) {
288	rr->nextTime = 0;
289	while (_readTag(rr, rr->movieStream) != TAG_EOF);
290	if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
291		return false;
292	}
293	return true;
294}
295
296bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
297	UNUSED(rr);
298	return vf->write(vf, BINARY_MAGIC, 4) == 4;
299}
300
301bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
302	UNUSED(rr);
303	char buffer[4];
304	if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
305		return false;
306	}
307	if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
308		return false;
309	}
310	return true;
311}
312
313enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
314	if (!rr || !vf) {
315		return TAG_EOF;
316	}
317
318	enum GBARRTag tag = rr->peekedTag;
319	switch (tag) {
320	case TAG_INPUT:
321		vf->read(vf, &rr->currentInput, sizeof(uint16_t));
322		break;
323	case TAG_PREVIOUSLY:
324		vf->read(vf, &rr->previously, sizeof(rr->previously));
325		break;
326	case TAG_NEXT_TIME:
327		vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
328		break;
329	case TAG_MAX_STREAM:
330		vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
331		break;
332	case TAG_FRAME_COUNT:
333		vf->read(vf, &rr->frames, sizeof(rr->frames));
334		break;
335	case TAG_LAG_COUNT:
336		vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
337		break;
338
339	case TAG_INIT_EX_NIHILO:
340		rr->initFrom = INIT_EX_NIHILO;
341		break;
342	case TAG_INIT_FROM_SAVEGAME:
343		rr->initFrom = INIT_FROM_SAVEGAME;
344		break;
345	case TAG_INIT_FROM_SAVESTATE:
346		rr->initFrom = INIT_FROM_SAVESTATE;
347	case TAG_INIT_FROM_BOTH:
348		rr->initFrom = INIT_FROM_BOTH;
349		break;
350
351	// To be spec'd
352	case TAG_RR_COUNT:
353	case TAG_AUTHOR:
354	case TAG_COMMENT:
355		break;
356
357	// Empty markers
358	case TAG_FRAME:
359	case TAG_LAG:
360	case TAG_BEGIN:
361	case TAG_END:
362	case TAG_INVALID:
363	case TAG_EOF:
364		break;
365	}
366
367	uint8_t tagBuffer;
368	if (vf->read(vf, &tagBuffer, 1) != 1) {
369		rr->peekedTag = TAG_EOF;
370	} else {
371		rr->peekedTag = tagBuffer;
372	}
373	return tag;
374}
375
376bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
377	enum GBARRTag readTag;
378	while ((readTag = _readTag(rr, vf)) != tag) {
379		if (vf == rr->movieStream && readTag == TAG_END) {
380			if (!GBARRSkipSegment(rr)) {
381				return false;
382			}
383			vf = rr->movieStream;
384		} else if (readTag == TAG_EOF) {
385			return false;
386		}
387	}
388	return true;
389}
390
391bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
392	UNUSED(rr);
393	return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
394}
395
396bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
397	if (!_verifyMagic(rr, vf)) {
398		return false;
399	}
400	while (_readTag(rr, vf) != TAG_EOF) {
401		switch (rr->peekedTag) {
402		case TAG_MAX_STREAM:
403			rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
404			break;
405		case TAG_INIT_EX_NIHILO:
406		case TAG_INIT_FROM_SAVEGAME:
407		case TAG_INIT_FROM_SAVESTATE:
408		case TAG_INIT_FROM_BOTH:
409			rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
410			break;
411		default:
412			break;
413		}
414	}
415	rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_SET);
416	return true;
417}