all repos — mgba @ 6d898542c765f4efc4a094c5ebd3f3465c36f417

mGBA Game Boy Advance Emulator

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}