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