all repos — mgba @ f8fff828e3d9e0c27e0852eae6895db00d4ad83c

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