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