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