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