src/gba/rr/vbm.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "vbm.h"
7
8#include "gba/gba.h"
9#include "gba/serialize.h"
10#include "util/vfs.h"
11
12#ifdef USE_ZLIB
13#include <zlib.h>
14#endif
15
16static const char VBM_MAGIC[] = "VBM\x1A";
17
18static void GBAVBMContextDestroy(struct GBARRContext*);
19
20static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord);
21static void GBAVBMStopPlaying(struct GBARRContext*);
22static bool GBAVBMStartRecording(struct GBARRContext*);
23static void GBAVBMStopRecording(struct GBARRContext*);
24
25static bool GBAVBMIsPlaying(const struct GBARRContext*);
26static bool GBAVBMIsRecording(const struct GBARRContext*);
27
28static void GBAVBMNextFrame(struct GBARRContext*);
29static uint16_t GBAVBMQueryInput(struct GBARRContext*);
30static bool GBAVBMQueryReset(struct GBARRContext*);
31
32static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
33static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
34
35static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags);
36static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags);
37
38void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
39 memset(vbm, 0, sizeof(*vbm));
40
41 vbm->d.destroy = GBAVBMContextDestroy;
42
43 vbm->d.startPlaying = GBAVBMStartPlaying;
44 vbm->d.stopPlaying = GBAVBMStopPlaying;
45 vbm->d.startRecording = GBAVBMStartRecording;
46 vbm->d.stopRecording = GBAVBMStopRecording;
47
48 vbm->d.isPlaying = GBAVBMIsPlaying;
49 vbm->d.isRecording = GBAVBMIsRecording;
50
51 vbm->d.nextFrame = GBAVBMNextFrame;
52 vbm->d.logInput = 0;
53 vbm->d.queryInput = GBAVBMQueryInput;
54 vbm->d.queryReset = GBAVBMQueryReset;
55
56 vbm->d.stateSaved = GBAVBMStateSaved;
57 vbm->d.stateLoaded = GBAVBMStateLoaded;
58
59 vbm->d.openSavedata = GBAVBMOpenSavedata;
60 vbm->d.openSavestate = GBAVBMOpenSavestate;
61}
62
63bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) {
64 if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) {
65 return false;
66 }
67
68 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
69 vbm->isPlaying = true;
70 vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET);
71 return true;
72}
73
74void GBAVBMStopPlaying(struct GBARRContext* rr) {
75 if (!rr->isPlaying(rr)) {
76 return;
77 }
78
79 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
80 vbm->isPlaying = false;
81}
82
83bool GBAVBMStartRecording(struct GBARRContext* rr) {
84 UNUSED(rr);
85 return false;
86}
87
88void GBAVBMStopRecording(struct GBARRContext* rr) {
89 UNUSED(rr);
90}
91
92bool GBAVBMIsPlaying(const struct GBARRContext* rr) {
93 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
94 return vbm->isPlaying;
95}
96
97bool GBAVBMIsRecording(const struct GBARRContext* rr) {
98 UNUSED(rr);
99 return false;
100}
101
102void GBAVBMNextFrame(struct GBARRContext* rr) {
103 if (!rr->isPlaying(rr)) {
104 return;
105 }
106
107 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
108 vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR);
109}
110
111uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
112 if (!rr->isPlaying(rr)) {
113 return 0;
114 }
115
116 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
117 uint16_t input;
118 vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
119 vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
120 return input & 0x3FF;
121}
122
123bool GBAVBMQueryReset(struct GBARRContext* rr) {
124 if (!rr->isPlaying(rr)) {
125 return false;
126 }
127
128 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
129 uint16_t input;
130 vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
131 vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
132 return input & 0x800;
133}
134
135void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
136 UNUSED(rr);
137 UNUSED(state);
138}
139
140void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
141 UNUSED(rr);
142 UNUSED(state);
143}
144
145struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
146 UNUSED(flags);
147#ifndef USE_ZLIB
148 UNUSED(rr);
149 return 0;
150#else
151 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
152 off_t pos = vbm->vbmFile->seek(vbm->vbmFile, 0, SEEK_CUR);
153 uint32_t saveType, flashSize, sramOffset;
154 vbm->vbmFile->seek(vbm->vbmFile, 0x18, SEEK_SET);
155 vbm->vbmFile->read(vbm->vbmFile, &saveType, sizeof(saveType));
156 vbm->vbmFile->read(vbm->vbmFile, &flashSize, sizeof(flashSize));
157 vbm->vbmFile->seek(vbm->vbmFile, 0x38, SEEK_SET);
158 vbm->vbmFile->read(vbm->vbmFile, &sramOffset, sizeof(sramOffset));
159 if (!sramOffset) {
160 vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
161 return 0;
162 }
163 vbm->vbmFile->seek(vbm->vbmFile, sramOffset, SEEK_SET);
164 struct VFile* save = VFileMemChunk(0, 0);
165 size_t size;
166 switch (saveType) {
167 case 1:
168 size = SIZE_CART_SRAM;
169 break;
170 case 2:
171 size = flashSize;
172 if (size > SIZE_CART_FLASH1M) {
173 size = SIZE_CART_FLASH1M;
174 }
175 break;
176 case 3:
177 size = SIZE_CART_EEPROM;
178 break;
179 default:
180 size = SIZE_CART_FLASH1M;
181 break;
182 }
183 uLong zlen = vbm->inputOffset - sramOffset;
184 char buffer[8761];
185 char* zbuffer = malloc(zlen);
186 vbm->vbmFile->read(vbm->vbmFile, zbuffer, zlen);
187 z_stream zstr;
188 zstr.zalloc = Z_NULL;
189 zstr.zfree = Z_NULL;
190 zstr.opaque = Z_NULL;
191 zstr.avail_in = zlen;
192 zstr.next_in = (Bytef*) zbuffer;
193 zstr.avail_out = 0;
194 inflateInit2(&zstr, 31);
195 // Skip header, we know where the save file is
196 zstr.avail_out = sizeof(buffer);
197 zstr.next_out = (Bytef*) &buffer;
198 int err = inflate(&zstr, 0);
199 while (err != Z_STREAM_END && !zstr.avail_out) {
200 zstr.avail_out = sizeof(buffer);
201 zstr.next_out = (Bytef*) &buffer;
202 int err = inflate(&zstr, 0);
203 if (err < 0) {
204 break;
205 }
206 save->write(save, buffer, sizeof(buffer) - zstr.avail_out);
207 }
208 inflateEnd(&zstr);
209 vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
210 return save;
211#endif
212}
213
214struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
215 UNUSED(rr);
216 UNUSED(flags);
217 return 0;
218}
219
220void GBAVBMContextDestroy(struct GBARRContext* rr) {
221 struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
222 if (vbm->vbmFile) {
223 vbm->vbmFile->close(vbm->vbmFile);
224 }
225}
226
227bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
228 vf->seek(vf, 0, SEEK_SET);
229 char magic[4];
230 vf->read(vf, magic, sizeof(magic));
231 if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) {
232 return false;
233 }
234
235 uint32_t id;
236 vf->read(vf, &id, sizeof(id));
237 if (id != 1) {
238 return false;
239 }
240
241 vf->seek(vf, 4, SEEK_CUR);
242 vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames));
243 vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount));
244
245 uint8_t flags;
246 vf->read(vf, &flags, sizeof(flags));
247 if (flags & 2) {
248#ifdef USE_ZLIB
249 vbm->d.initFrom = INIT_FROM_SAVEGAME;
250#else
251 // zlib is needed to parse the savegame
252 return false;
253#endif
254 }
255 if (flags & 1) {
256 // Incompatible savestate format
257 return false;
258 }
259
260 vf->seek(vf, 1, SEEK_CUR);
261 vf->read(vf, &flags, sizeof(flags));
262 if ((flags & 0x7) != 1) {
263 // Non-GBA movie
264 return false;
265 }
266
267 // TODO: parse more flags
268
269 vf->seek(vf, 0x3C, SEEK_SET);
270 vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset));
271 vf->seek(vf, vbm->inputOffset, SEEK_SET);
272 vbm->vbmFile = vf;
273 return true;
274}