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