all repos — mgba @ c3f69e7b693ae568e33d83cee264040c2db3d516

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