all repos — mgba @ a09bb6d51ad83085f1c4e04a30a19a744a83c749

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 BINEXT ".dat"
  7#define METADATA_FILENAME "metadata" BINEXT
  8
  9static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
 10static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
 11static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
 12static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
 13
 14void GBARRContextCreate(struct GBA* gba) {
 15	if (gba->rr) {
 16		return;
 17	}
 18
 19	gba->rr = calloc(1, sizeof(*gba->rr));
 20}
 21
 22void GBARRContextDestroy(struct GBA* gba) {
 23	if (!gba->rr) {
 24		return;
 25	}
 26
 27	if (GBARRIsPlaying(gba->rr)) {
 28		GBARRStopPlaying(gba->rr);
 29	}
 30	if (GBARRIsRecording(gba->rr)) {
 31		GBARRStopRecording(gba->rr);
 32	}
 33	if (gba->rr->metadataFile) {
 34		gba->rr->metadataFile->close(gba->rr->metadataFile);
 35	}
 36
 37	free(gba->rr);
 38	gba->rr = 0;
 39}
 40
 41bool GBARRSetStream(struct GBARRContext* rr, struct VDir* stream) {
 42	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
 43		return false;
 44	}
 45
 46	if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
 47		return false;
 48	}
 49
 50	rr->streamDir = stream;
 51	rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
 52	if (!_parseMetadata(rr, rr->metadataFile)) {
 53		rr->metadataFile->close(rr->metadataFile);
 54		rr->streamDir = 0;
 55		rr->metadataFile = 0;
 56		return false;
 57	}
 58	rr->movieStream = 0;
 59	rr->streamId = 1;
 60	rr->maxStreamId = 1;
 61	return true;
 62}
 63
 64bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
 65	if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
 66		return false;
 67	}
 68	rr->movieStream = 0;
 69	rr->streamId = streamId;
 70	char buffer[14];
 71	snprintf(buffer, sizeof(buffer), "%u" BINEXT, streamId);
 72	if (GBARRIsRecording(rr)) {
 73		int flags = O_CREAT | O_WRONLY;
 74		if (streamId > rr->maxStreamId) {
 75			flags |= O_TRUNC;
 76		} else {
 77			flags |= O_APPEND;
 78		}
 79		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
 80	} else if (GBARRIsPlaying(rr)) {
 81		rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
 82		rr->peekedTag = TAG_INVALID;
 83		if (!rr->movieStream || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
 84			GBARRStopPlaying(rr);
 85		}
 86	}
 87	rr->frames = 0;
 88	rr->lagFrames = 0;
 89	return true;
 90}
 91
 92bool GBARRIncrementStream(struct GBARRContext* rr) {
 93	uint32_t newStreamId = rr->maxStreamId + 1;
 94	uint32_t oldStreamId = rr->streamId;
 95	if (GBARRIsRecording(rr) && rr->movieStream) {
 96		_emitTag(rr, rr->movieStream, TAG_END);
 97		_emitTag(rr, rr->movieStream, TAG_FRAME_COUNT);
 98		rr->movieStream->write(rr->movieStream, &rr->frames, sizeof(rr->frames));
 99		_emitTag(rr, rr->movieStream, TAG_LAG_COUNT);
100		rr->movieStream->write(rr->movieStream, &rr->lagFrames, sizeof(rr->lagFrames));
101		_emitTag(rr, rr->movieStream, TAG_NEXT_TIME);
102		rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId));
103	}
104	if (!GBARRLoadStream(rr, newStreamId)) {
105		return false;
106	}
107	rr->maxStreamId = newStreamId;
108	_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
109	rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
110	_emitTag(rr, rr->movieStream, TAG_BEGIN);
111
112	rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
113	rr->metadataFile->write(rr->movieStream, &rr->maxStreamId, sizeof(rr->maxStreamId));
114	return true;
115}
116
117bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
118	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
119		return false;
120	}
121
122	char buffer[14];
123	snprintf(buffer, sizeof(buffer), "%u" BINEXT, rr->streamId);
124	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
125	if (!rr->movieStream) {
126		return false;
127	}
128
129	rr->autorecord = autorecord;
130	rr->peekedTag = TAG_INVALID;
131	_readTag(rr, rr->movieStream); // Discard the buffer
132	enum GBARRTag tag = _readTag(rr, rr->movieStream);
133	if (tag != TAG_BEGIN) {
134		rr->movieStream->close(rr->movieStream);
135		rr->movieStream = 0;
136		return false;
137	}
138
139	rr->isPlaying = true;
140	return true;
141}
142
143void GBARRStopPlaying(struct GBARRContext* rr) {
144	if (!GBARRIsPlaying(rr)) {
145		return;
146	}
147	rr->isPlaying = false;
148	if (rr->movieStream) {
149		rr->movieStream->close(rr->movieStream);
150		rr->movieStream = 0;
151	}
152}
153
154bool GBARRStartRecording(struct GBARRContext* rr) {
155	if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
156		return false;
157	}
158
159	char buffer[14];
160	snprintf(buffer, sizeof(buffer), "%u" BINEXT, rr->streamId);
161	rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_TRUNC | O_CREAT | O_WRONLY);
162	if (!_emitTag(rr, rr->movieStream, TAG_BEGIN)) {
163		rr->movieStream->close(rr->movieStream);
164		rr->movieStream = 0;
165		return false;
166	}
167
168	if (!rr->maxStreamIdOffset) {
169		_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
170		rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
171		rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
172	}
173
174	rr->isRecording = true;
175	return true;
176}
177
178void GBARRStopRecording(struct GBARRContext* rr) {
179	if (!GBARRIsRecording(rr)) {
180		return;
181	}
182	rr->isRecording = false;
183	if (rr->movieStream) {
184		_emitTag(rr, rr->movieStream, TAG_END);
185		rr->movieStream->close(rr->movieStream);
186		rr->movieStream = 0;
187	}
188}
189
190bool GBARRIsPlaying(struct GBARRContext* rr) {
191	return rr && rr->isPlaying;
192}
193
194bool GBARRIsRecording(struct GBARRContext* rr) {
195	return rr && rr->isRecording;
196}
197
198void GBARRNextFrame(struct GBARRContext* rr) {
199	if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
200		return;
201	}
202
203	++rr->frames;
204	if (!rr->inputThisFrame) {
205		++rr->lagFrames;
206	}
207
208	if (GBARRIsRecording(rr)) {
209		if (!rr->inputThisFrame) {
210			_emitTag(rr, rr->movieStream, TAG_LAG);
211		}
212		_emitTag(rr, rr->movieStream, TAG_FRAME);
213
214		rr->inputThisFrame = false;
215	} else {
216		if (rr->peekedTag == TAG_INPUT) {
217			GBALog(0, GBA_LOG_WARN, "RR desync detected!");
218		}
219		if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
220			uint32_t endStreamId = rr->streamId;
221			GBARRStopPlaying(rr);
222			if (rr->autorecord) {
223				rr->isRecording = true;
224				GBARRLoadStream(rr, endStreamId);
225				GBARRIncrementStream(rr);
226			}
227		}
228	}
229}
230
231void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
232	if (!GBARRIsRecording(rr)) {
233		return;
234	}
235
236	if (keys != rr->currentInput) {
237		_emitTag(rr, rr->movieStream, TAG_INPUT);
238		rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
239		rr->currentInput = keys;
240	}
241	rr->inputThisFrame = true;
242}
243
244uint16_t GBARRQueryInput(struct GBARRContext* rr) {
245	if (!GBARRIsPlaying(rr)) {
246		return 0;
247	}
248
249	if (rr->peekedTag == TAG_INPUT) {
250		_readTag(rr, rr->movieStream);
251	}
252	return rr->currentInput;
253}
254
255bool GBARRSkipSegment(struct GBARRContext* rr) {
256	rr->nextTime = 0;
257	while (_readTag(rr, rr->movieStream) != TAG_EOF);
258	if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
259		return false;
260	}
261	return true;
262}
263
264enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
265	if (!rr || !vf) {
266		return TAG_EOF;
267	}
268
269	enum GBARRTag tag = rr->peekedTag;
270	switch (tag) {
271	case TAG_INPUT:
272		vf->read(vf, &rr->currentInput, sizeof(uint16_t));
273		break;
274	case TAG_PREVIOUSLY:
275		vf->read(vf, &rr->previously, sizeof(rr->previously));
276		break;
277	case TAG_NEXT_TIME:
278		vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
279		break;
280	case TAG_MAX_STREAM:
281		vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
282		break;
283	case TAG_FRAME_COUNT:
284		vf->read(vf, &rr->frames, sizeof(rr->frames));
285		break;
286	case TAG_LAG_COUNT:
287		vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
288		break;
289
290	// To be spec'd
291	case TAG_RR_COUNT:
292	case TAG_INIT_TYPE:
293	case TAG_AUTHOR:
294	case TAG_COMMENT:
295		break;
296
297	// Empty markers
298	case TAG_FRAME:
299	case TAG_LAG:
300	case TAG_BEGIN:
301	case TAG_END:
302	case TAG_INVALID:
303	case TAG_EOF:
304		break;
305	}
306
307	uint8_t tagBuffer;
308	if (vf->read(vf, &tagBuffer, 1) != 1) {
309		rr->peekedTag = TAG_EOF;
310	} else {
311		rr->peekedTag = tagBuffer;
312	}
313	return tag;
314}
315
316bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
317	enum GBARRTag readTag;
318	while ((readTag = _readTag(rr, vf)) != tag) {
319		if (vf == rr->movieStream && readTag == TAG_END) {
320			if (!GBARRSkipSegment(rr)) {
321				return false;
322			}
323			vf = rr->movieStream;
324		} else if (readTag == TAG_EOF) {
325			return false;
326		}
327	}
328	return true;
329}
330
331bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
332	UNUSED(rr);
333	return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
334}
335
336bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
337	while (_readTag(rr, vf) != TAG_EOF) {
338		if (rr->peekedTag == TAG_MAX_STREAM) {
339			rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
340		}
341	}
342	rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_SET);
343	return true;
344}