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