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