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