src/gba/gba-rr.c (view raw)
1#include "gba-rr.h"
2
3#include "gba.h"
4#include "util/vfs.h"
5
6#define BINARY_EXT ".dat"
7#define BINARY_MAGIC "GBAb"
8#define METADATA_FILENAME "metadata" BINARY_EXT
9
10static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
11static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
12static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
13static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
14static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
15static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
16
17void GBARRContextCreate(struct GBA* gba) {
18 if (gba->rr) {
19 return;
20 }
21
22 gba->rr = calloc(1, sizeof(*gba->rr));
23}
24
25void GBARRContextDestroy(struct GBA* gba) {
26 if (!gba->rr) {
27 return;
28 }
29
30 if (GBARRIsPlaying(gba->rr)) {
31 GBARRStopPlaying(gba->rr);
32 }
33 if (GBARRIsRecording(gba->rr)) {
34 GBARRStopRecording(gba->rr);
35 }
36 if (gba->rr->metadataFile) {
37 gba->rr->metadataFile->close(gba->rr->metadataFile);
38 }
39
40 free(gba->rr);
41 gba->rr = 0;
42}
43
44void GBARRAlterSavedata(struct GBA* gba) {
45 if (!gba || !gba->rr) {
46 return;
47 }
48
49 if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
50 // TOOD
51 } else {
52 GBASavedataMask(&gba->memory.savedata, 0);
53 }
54}
55
56bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
57 if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
58 return false;
59 }
60
61 if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
62 return false;
63 }
64
65 rr->streamDir = stream;
66 rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
67 if (!_parseMetadata(rr, rr->metadataFile)) {
68 rr->metadataFile->close(rr->metadataFile);
69 rr->metadataFile = 0;
70 rr->maxStreamId = 1;
71 }
72 rr->streamId = 1;
73 rr->movieStream = 0;
74 return true;
75}
76
77bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
78 if (!rr) {
79 return false;
80 }
81
82 if (rr->metadataFile) {
83 rr->metadataFile->truncate(rr->metadataFile, 0);
84 } else {
85 rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
86 }
87 _emitMagic(rr, rr->metadataFile);
88
89 rr->initFrom = initFrom;
90 rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
91 _emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
92
93 rr->streamId = 1;
94 rr->maxStreamId = 1;
95 rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
96 _emitTag(rr, rr->metadataFile, 1);
97 return true;
98}
99
100bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
101 if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
102 return false;
103 }
104 rr->movieStream = 0;
105 rr->streamId = streamId;
106 char buffer[14];
107 snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
108 if (GBARRIsRecording(rr)) {
109 int flags = O_CREAT | O_WRONLY;
110 if (streamId > rr->maxStreamId) {
111 flags |= O_TRUNC;
112 } else {
113 flags |= O_APPEND;
114 }
115 rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
116 } else if (GBARRIsPlaying(rr)) {
117 rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
118 rr->peekedTag = TAG_INVALID;
119 if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
120 GBARRStopPlaying(rr);
121 }
122 }
123 rr->frames = 0;
124 rr->lagFrames = 0;
125 return true;
126}
127
128bool GBARRIncrementStream(struct GBARRContext* rr) {
129 uint32_t newStreamId = rr->maxStreamId + 1;
130 uint32_t oldStreamId = rr->streamId;
131 if (GBARRIsRecording(rr) && rr->movieStream) {
132 _emitTag(rr, rr->movieStream, TAG_END);
133 _emitTag(rr, rr->movieStream, TAG_FRAME_COUNT);
134 rr->movieStream->write(rr->movieStream, &rr->frames, sizeof(rr->frames));
135 _emitTag(rr, rr->movieStream, TAG_LAG_COUNT);
136 rr->movieStream->write(rr->movieStream, &rr->lagFrames, sizeof(rr->lagFrames));
137 _emitTag(rr, rr->movieStream, TAG_NEXT_TIME);
138 rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId));
139 }
140 if (!GBARRLoadStream(rr, newStreamId)) {
141 return false;
142 }
143 _emitMagic(rr, rr->movieStream);
144 rr->maxStreamId = newStreamId;
145 _emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
146 rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
147 _emitTag(rr, rr->movieStream, TAG_BEGIN);
148
149 rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
150 rr->metadataFile->write(rr->movieStream, &rr->maxStreamId, sizeof(rr->maxStreamId));
151 return true;
152}
153
154bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
155 if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
156 return false;
157 }
158
159 char buffer[14];
160 snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
161 rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
162 if (!rr->movieStream) {
163 return false;
164 }
165
166 rr->autorecord = autorecord;
167 if (!_verifyMagic(rr, rr->movieStream)) {
168 return false;
169 }
170 rr->peekedTag = TAG_INVALID;
171 _readTag(rr, rr->movieStream); // Discard the buffer
172 enum GBARRTag tag = _readTag(rr, rr->movieStream);
173 if (tag != TAG_BEGIN) {
174 rr->movieStream->close(rr->movieStream);
175 rr->movieStream = 0;
176 return false;
177 }
178
179 rr->isPlaying = true;
180 return true;
181}
182
183void GBARRStopPlaying(struct GBARRContext* rr) {
184 if (!GBARRIsPlaying(rr)) {
185 return;
186 }
187 rr->isPlaying = false;
188 if (rr->movieStream) {
189 rr->movieStream->close(rr->movieStream);
190 rr->movieStream = 0;
191 }
192}
193
194bool GBARRStartRecording(struct GBARRContext* rr) {
195 if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
196 return false;
197 }
198
199 char buffer[14];
200 snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, rr->streamId);
201 rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_TRUNC | O_CREAT | O_WRONLY);
202 if (!rr->movieStream) {
203 return false;
204 }
205 _emitMagic(rr, rr->movieStream);
206 if (!_emitTag(rr, rr->movieStream, TAG_BEGIN)) {
207 rr->movieStream->close(rr->movieStream);
208 rr->movieStream = 0;
209 return false;
210 }
211
212 if (!rr->maxStreamIdOffset) {
213 _emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
214 rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
215 rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
216 }
217
218 rr->isRecording = true;
219 return true;
220}
221
222void GBARRStopRecording(struct GBARRContext* rr) {
223 if (!GBARRIsRecording(rr)) {
224 return;
225 }
226 rr->isRecording = false;
227 if (rr->movieStream) {
228 _emitTag(rr, rr->movieStream, TAG_END);
229 rr->movieStream->close(rr->movieStream);
230 rr->movieStream = 0;
231 }
232}
233
234bool GBARRIsPlaying(struct GBARRContext* rr) {
235 return rr && rr->isPlaying;
236}
237
238bool GBARRIsRecording(struct GBARRContext* rr) {
239 return rr && rr->isRecording;
240}
241
242void GBARRNextFrame(struct GBARRContext* rr) {
243 if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
244 return;
245 }
246
247 ++rr->frames;
248 if (!rr->inputThisFrame) {
249 ++rr->lagFrames;
250 }
251
252 if (GBARRIsRecording(rr)) {
253 if (!rr->inputThisFrame) {
254 _emitTag(rr, rr->movieStream, TAG_LAG);
255 }
256 _emitTag(rr, rr->movieStream, TAG_FRAME);
257
258 rr->inputThisFrame = false;
259 } else {
260 if (rr->peekedTag == TAG_INPUT) {
261 GBALog(0, GBA_LOG_WARN, "RR desync detected!");
262 }
263 if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
264 uint32_t endStreamId = rr->streamId;
265 GBARRStopPlaying(rr);
266 if (rr->autorecord) {
267 rr->isRecording = true;
268 GBARRLoadStream(rr, endStreamId);
269 GBARRIncrementStream(rr);
270 }
271 }
272 }
273}
274
275void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
276 if (!GBARRIsRecording(rr)) {
277 return;
278 }
279
280 if (keys != rr->currentInput) {
281 _emitTag(rr, rr->movieStream, TAG_INPUT);
282 rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
283 rr->currentInput = keys;
284 }
285 rr->inputThisFrame = true;
286}
287
288uint16_t GBARRQueryInput(struct GBARRContext* rr) {
289 if (!GBARRIsPlaying(rr)) {
290 return 0;
291 }
292
293 if (rr->peekedTag == TAG_INPUT) {
294 _readTag(rr, rr->movieStream);
295 }
296 return rr->currentInput;
297}
298
299bool GBARRSkipSegment(struct GBARRContext* rr) {
300 rr->nextTime = 0;
301 while (_readTag(rr, rr->movieStream) != TAG_EOF);
302 if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
303 return false;
304 }
305 return true;
306}
307
308bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
309 UNUSED(rr);
310 return vf->write(vf, BINARY_MAGIC, 4) == 4;
311}
312
313bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
314 UNUSED(rr);
315 char buffer[4];
316 if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
317 return false;
318 }
319 if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
320 return false;
321 }
322 return true;
323}
324
325enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
326 if (!rr || !vf) {
327 return TAG_EOF;
328 }
329
330 enum GBARRTag tag = rr->peekedTag;
331 switch (tag) {
332 case TAG_INPUT:
333 vf->read(vf, &rr->currentInput, sizeof(uint16_t));
334 break;
335 case TAG_PREVIOUSLY:
336 vf->read(vf, &rr->previously, sizeof(rr->previously));
337 break;
338 case TAG_NEXT_TIME:
339 vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
340 break;
341 case TAG_MAX_STREAM:
342 vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
343 break;
344 case TAG_FRAME_COUNT:
345 vf->read(vf, &rr->frames, sizeof(rr->frames));
346 break;
347 case TAG_LAG_COUNT:
348 vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
349 break;
350
351 case TAG_INIT_EX_NIHILO:
352 rr->initFrom = INIT_EX_NIHILO;
353 break;
354 case TAG_INIT_FROM_SAVEGAME:
355 rr->initFrom = INIT_FROM_SAVEGAME;
356 break;
357 case TAG_INIT_FROM_SAVESTATE:
358 rr->initFrom = INIT_FROM_SAVESTATE;
359 case TAG_INIT_FROM_BOTH:
360 rr->initFrom = INIT_FROM_BOTH;
361 break;
362
363 // To be spec'd
364 case TAG_RR_COUNT:
365 case TAG_AUTHOR:
366 case TAG_COMMENT:
367 break;
368
369 // Empty markers
370 case TAG_FRAME:
371 case TAG_LAG:
372 case TAG_BEGIN:
373 case TAG_END:
374 case TAG_INVALID:
375 case TAG_EOF:
376 break;
377 }
378
379 uint8_t tagBuffer;
380 if (vf->read(vf, &tagBuffer, 1) != 1) {
381 rr->peekedTag = TAG_EOF;
382 } else {
383 rr->peekedTag = tagBuffer;
384 }
385 return tag;
386}
387
388bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
389 enum GBARRTag readTag;
390 while ((readTag = _readTag(rr, vf)) != tag) {
391 if (vf == rr->movieStream && readTag == TAG_END) {
392 if (!GBARRSkipSegment(rr)) {
393 return false;
394 }
395 vf = rr->movieStream;
396 } else if (readTag == TAG_EOF) {
397 return false;
398 }
399 }
400 return true;
401}
402
403bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
404 UNUSED(rr);
405 return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
406}
407
408bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
409 if (!_verifyMagic(rr, vf)) {
410 return false;
411 }
412 while (_readTag(rr, vf) != TAG_EOF) {
413 switch (rr->peekedTag) {
414 case TAG_MAX_STREAM:
415 rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
416 break;
417 case TAG_INIT_EX_NIHILO:
418 case TAG_INIT_FROM_SAVEGAME:
419 case TAG_INIT_FROM_SAVESTATE:
420 case TAG_INIT_FROM_BOTH:
421 rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
422 break;
423 default:
424 break;
425 }
426 }
427 rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_SET);
428 return true;
429}