all repos — mgba @ 6d898542c765f4efc4a094c5ebd3f3465c36f417

mGBA Game Boy Advance Emulator

src/core/serialize.c (view raw)

  1/* Copyright (c) 2013-2016 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 "serialize.h"
  7
  8#include "core/core.h"
  9#include "core/cheats.h"
 10#include "util/memory.h"
 11#include "util/vfs.h"
 12
 13#ifdef USE_PNG
 14#include "util/png-io.h"
 15#include <png.h>
 16#include <zlib.h>
 17#endif
 18
 19mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate");
 20
 21struct mBundledState {
 22	size_t stateSize;
 23	void* state;
 24	struct mStateExtdata* extdata;
 25};
 26
 27struct mStateExtdataHeader {
 28	uint32_t tag;
 29	int32_t size;
 30	int64_t offset;
 31};
 32
 33bool mStateExtdataInit(struct mStateExtdata* extdata) {
 34	memset(extdata->data, 0, sizeof(extdata->data));
 35	return true;
 36}
 37
 38void mStateExtdataDeinit(struct mStateExtdata* extdata) {
 39	size_t i;
 40	for (i = 1; i < EXTDATA_MAX; ++i) {
 41		if (extdata->data[i].data && extdata->data[i].clean) {
 42			extdata->data[i].clean(extdata->data[i].data);
 43		}
 44	}
 45}
 46
 47void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
 48	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
 49		return;
 50	}
 51
 52	if (extdata->data[tag].data && extdata->data[tag].clean) {
 53		extdata->data[tag].clean(extdata->data[tag].data);
 54	}
 55	extdata->data[tag] = *item;
 56}
 57
 58bool mStateExtdataGet(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
 59	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
 60		return false;
 61	}
 62
 63	*item = extdata->data[tag];
 64	return true;
 65}
 66
 67bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf) {
 68	ssize_t position = vf->seek(vf, 0, SEEK_CUR);
 69	ssize_t size = sizeof(struct mStateExtdataHeader);
 70	size_t i = 0;
 71	for (i = 1; i < EXTDATA_MAX; ++i) {
 72		if (extdata->data[i].data) {
 73			size += sizeof(struct mStateExtdataHeader);
 74		}
 75	}
 76	if (size == sizeof(struct mStateExtdataHeader)) {
 77		return true;
 78	}
 79	struct mStateExtdataHeader* header = malloc(size);
 80	position += size;
 81
 82	size_t j;
 83	for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
 84		if (extdata->data[i].data) {
 85			STORE_32LE(i, offsetof(struct mStateExtdataHeader, tag), &header[j]);
 86			STORE_32LE(extdata->data[i].size, offsetof(struct mStateExtdataHeader, size), &header[j]);
 87			STORE_64LE(position, offsetof(struct mStateExtdataHeader, offset), &header[j]);
 88			position += extdata->data[i].size;
 89			++j;
 90		}
 91	}
 92	header[j].tag = 0;
 93	header[j].size = 0;
 94	header[j].offset = 0;
 95
 96	if (vf->write(vf, header, size) != size) {
 97		free(header);
 98		return false;
 99	}
100	free(header);
101
102	for (i = 1; i < EXTDATA_MAX; ++i) {
103		if (extdata->data[i].data) {
104			if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
105				return false;
106			}
107		}
108	}
109	return true;
110}
111
112bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
113	while (true) {
114		struct mStateExtdataHeader buffer, header;
115		if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
116			return false;
117		}
118		LOAD_32LE(header.tag, 0, &buffer.tag);
119		LOAD_32LE(header.size, 0, &buffer.size);
120		LOAD_64LE(header.offset, 0, &buffer.offset);
121
122		if (header.tag == EXTDATA_NONE) {
123			break;
124		}
125		if (header.tag >= EXTDATA_MAX) {
126			continue;
127		}
128		ssize_t position = vf->seek(vf, 0, SEEK_CUR);
129		if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
130			return false;
131		}
132		struct mStateExtdataItem item = {
133			.data = malloc(header.size),
134			.size = header.size,
135			.clean = free
136		};
137		if (!item.data) {
138			continue;
139		}
140		if (vf->read(vf, item.data, header.size) != header.size) {
141			free(item.data);
142			continue;
143		}
144		mStateExtdataPut(extdata, header.tag, &item);
145		vf->seek(vf, position, SEEK_SET);
146	};
147	return true;
148}
149
150#ifdef USE_PNG
151static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
152	size_t stride;
153	const void* pixels = 0;
154
155	core->getPixels(core, &pixels, &stride);
156	if (!pixels) {
157		return false;
158	}
159
160	size_t stateSize = core->stateSize(core);
161	void* state = anonymousMemoryMap(stateSize);
162	if (!state) {
163		return false;
164	}
165	core->saveState(core, state);
166
167	uLongf len = compressBound(stateSize);
168	void* buffer = malloc(len);
169	if (!buffer) {
170		mappedMemoryFree(state, stateSize);
171		return false;
172	}
173	compress(buffer, &len, (const Bytef*) state, stateSize);
174	mappedMemoryFree(state, stateSize);
175
176	unsigned width, height;
177	core->desiredVideoDimensions(core, &width, &height);
178	png_structp png = PNGWriteOpen(vf);
179	png_infop info = PNGWriteHeader(png, width, height);
180	if (!png || !info) {
181		PNGWriteClose(png, info);
182		free(buffer);
183		return false;
184	}
185	PNGWritePixels(png, width, height, stride, pixels);
186	PNGWriteCustomChunk(png, "gbAs", len, buffer);
187	if (extdata) {
188		uint32_t i;
189		for (i = 1; i < EXTDATA_MAX; ++i) {
190			if (!extdata->data[i].data) {
191				continue;
192			}
193			uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
194			uint32_t* data = malloc(len);
195			if (!data) {
196				continue;
197			}
198			STORE_32LE(i, 0, data);
199			STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
200			compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
201			PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
202			free(data);
203		}
204	}
205	PNGWriteClose(png, info);
206	free(buffer);
207	return true;
208}
209
210static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
211	struct mBundledState* bundle = png_get_user_chunk_ptr(png);
212	if (!bundle) {
213		return 0;
214	}
215	if (!strcmp((const char*) chunk->name, "gbAs")) {
216		void* state = bundle->state;
217		if (!state) {
218			return 0;
219		}
220		uLongf len = bundle->stateSize;
221		uncompress((Bytef*) state, &len, chunk->data, chunk->size);
222		return 1;
223	}
224	if (!strcmp((const char*) chunk->name, "gbAx")) {
225		struct mStateExtdata* extdata = bundle->extdata;
226		if (!extdata) {
227			return 0;
228		}
229		struct mStateExtdataItem item;
230		if (chunk->size < sizeof(uint32_t) * 2) {
231			return 0;
232		}
233		uint32_t tag;
234		LOAD_32LE(tag, 0, chunk->data);
235		LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
236		uLongf len = item.size;
237		if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
238			return 0;
239		}
240		item.data = malloc(item.size);
241		item.clean = free;
242		if (!item.data) {
243			return 0;
244		}
245		const uint8_t* data = chunk->data;
246		data += sizeof(uint32_t) * 2;
247		uncompress((Bytef*) item.data, &len, data, chunk->size);
248		item.size = len;
249		mStateExtdataPut(extdata, tag, &item);
250		return 1;
251	}
252	return 0;
253}
254
255static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
256	png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
257	png_infop info = png_create_info_struct(png);
258	png_infop end = png_create_info_struct(png);
259	if (!png || !info || !end) {
260		PNGReadClose(png, info, end);
261		return false;
262	}
263	unsigned width, height;
264	core->desiredVideoDimensions(core, &width, &height);
265	uint32_t* pixels = malloc(width * height * 4);
266	if (!pixels) {
267		PNGReadClose(png, info, end);
268		return false;
269	}
270
271	size_t stateSize = core->stateSize(core);
272	void* state = anonymousMemoryMap(stateSize);
273	struct mBundledState bundle = {
274		.stateSize = stateSize,
275		.state = state,
276		.extdata = extdata
277	};
278
279	PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
280	bool success = PNGReadHeader(png, info);
281	success = success && PNGReadPixels(png, info, pixels, width, height, width);
282	success = success && PNGReadFooter(png, end);
283	PNGReadClose(png, info, end);
284
285	if (success) {
286		struct mStateExtdataItem item = {
287			.size = width * height * 4,
288			.data = pixels,
289			.clean = free
290		};
291		mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
292	} else {
293		free(pixels);
294		mappedMemoryFree(state, stateSize);
295		return 0;
296	}
297	return state;
298}
299#endif
300
301bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
302	struct mStateExtdata extdata;
303	mStateExtdataInit(&extdata);
304	size_t stateSize = core->stateSize(core);
305	if (flags & SAVESTATE_SAVEDATA) {
306		void* sram = NULL;
307		size_t size = core->savedataClone(core, &sram);
308		if (size) {
309			struct mStateExtdataItem item = {
310				.size = size,
311				.data = sram,
312				.clean = free
313			};
314			mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
315		}
316	}
317	struct VFile* cheatVf = 0;
318	struct mCheatDevice* device;
319	if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
320		cheatVf = VFileMemChunk(0, 0);
321		if (cheatVf) {
322			mCheatSaveFile(device, cheatVf);
323			struct mStateExtdataItem item = {
324				.size = cheatVf->size(cheatVf),
325				.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
326				.clean = 0
327			};
328			mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
329		}
330	}
331#ifdef USE_PNG
332	if (!(flags & SAVESTATE_SCREENSHOT)) {
333#else
334	UNUSED(flags);
335#endif
336		vf->truncate(vf, stateSize);
337		struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
338		if (!state) {
339			mStateExtdataDeinit(&extdata);
340			if (cheatVf) {
341				cheatVf->close(cheatVf);
342			}
343			return false;
344		}
345		core->saveState(core, state);
346		vf->unmap(vf, state, stateSize);
347		vf->seek(vf, stateSize, SEEK_SET);
348		mStateExtdataSerialize(&extdata, vf);
349		mStateExtdataDeinit(&extdata);
350		if (cheatVf) {
351			cheatVf->close(cheatVf);
352		}
353		return true;
354#ifdef USE_PNG
355	}
356	else {
357		bool success = _savePNGState(core, vf, &extdata);
358		mStateExtdataDeinit(&extdata);
359		return success;
360	}
361#endif
362	mStateExtdataDeinit(&extdata);
363	return false;
364}
365
366void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
367#ifdef USE_PNG
368	if (isPNG(vf)) {
369		return _loadPNGState(core, vf, extdata);
370	}
371#endif
372	ssize_t stateSize = core->stateSize(core);
373	vf->seek(vf, 0, SEEK_SET);
374	if (vf->size(vf) < stateSize) {
375		return false;
376	}
377	void* state = anonymousMemoryMap(stateSize);
378	if (vf->read(vf, state, stateSize) != stateSize) {
379		mappedMemoryFree(state, stateSize);
380		return 0;
381	}
382	if (extdata) {
383		mStateExtdataDeserialize(extdata, vf);
384	}
385	return state;
386}
387
388bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
389	struct mStateExtdata extdata;
390	mStateExtdataInit(&extdata);
391	void* state = mCoreExtractState(core, vf, &extdata);
392	if (!state) {
393		return false;
394	}
395	bool success = core->loadState(core, state);
396	mappedMemoryFree(state, core->stateSize(core));
397
398	unsigned width, height;
399	core->desiredVideoDimensions(core, &width, &height);
400
401	struct mStateExtdataItem item;
402	if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
403		mLOG(SAVESTATE, INFO, "Loading screenshot");
404		if (item.size >= (int) (width * height) * 4) {
405			core->putPixels(core, item.data, width);
406		} else {
407			mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
408		}
409	}
410	if (mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
411		mLOG(SAVESTATE, INFO, "Loading savedata");
412		if (item.data) {
413			core->savedataRestore(core, item.data, item.size, flags & SAVESTATE_SAVEDATA);
414		}
415	}
416	struct mCheatDevice* device;
417	if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
418		mLOG(SAVESTATE, INFO, "Loading cheats");
419		if (item.size) {
420			struct VFile* svf = VFileFromMemory(item.data, item.size);
421			if (svf) {
422				mCheatDeviceClear(device);
423				mCheatParseFile(device, svf);
424				svf->close(svf);
425			}
426		}
427	}
428	mStateExtdataDeinit(&extdata);
429	return success;
430}
431