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}