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