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