all repos — mgba @ 33098926577f52a74730de1708a427e275a9bc88

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 <mgba/core/serialize.h>
  7
  8#include <mgba/core/core.h>
  9#include <mgba/core/cheats.h>
 10#include <mgba/core/interface.h>
 11#include <mgba-util/memory.h>
 12#include <mgba-util/vfs.h>
 13
 14#ifdef USE_PNG
 15#include <mgba-util/png-io.h>
 16#include <png.h>
 17#include <zlib.h>
 18#endif
 19
 20mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate", "core.serialize");
 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
 34void mStateExtdataInit(struct mStateExtdata* extdata) {
 35	memset(extdata->data, 0, sizeof(extdata->data));
 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	memset(extdata->data, 0, sizeof(extdata->data));
 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#ifdef USE_PNG
152static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
153	size_t stride;
154	const void* pixels = 0;
155
156	core->getPixels(core, &pixels, &stride);
157	if (!pixels) {
158		return false;
159	}
160
161	size_t stateSize = core->stateSize(core);
162	void* state = anonymousMemoryMap(stateSize);
163	if (!state) {
164		return false;
165	}
166	core->saveState(core, state);
167
168	uLongf len = compressBound(stateSize);
169	void* buffer = malloc(len);
170	if (!buffer) {
171		mappedMemoryFree(state, stateSize);
172		return false;
173	}
174	compress(buffer, &len, (const Bytef*) state, stateSize);
175	mappedMemoryFree(state, stateSize);
176
177	unsigned width, height;
178	core->desiredVideoDimensions(core, &width, &height);
179	png_structp png = PNGWriteOpen(vf);
180	png_infop info = PNGWriteHeader(png, width, height);
181	if (!png || !info) {
182		PNGWriteClose(png, info);
183		free(buffer);
184		return false;
185	}
186	PNGWritePixels(png, width, height, stride, pixels);
187	PNGWriteCustomChunk(png, "gbAs", len, buffer);
188	if (extdata) {
189		uint32_t i;
190		for (i = 1; i < EXTDATA_MAX; ++i) {
191			if (!extdata->data[i].data) {
192				continue;
193			}
194			uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
195			uint32_t* data = malloc(len);
196			if (!data) {
197				continue;
198			}
199			STORE_32LE(i, 0, data);
200			STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
201			compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
202			PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
203			free(data);
204		}
205	}
206	PNGWriteClose(png, info);
207	free(buffer);
208	return true;
209}
210
211static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
212	struct mBundledState* bundle = png_get_user_chunk_ptr(png);
213	if (!bundle) {
214		return 0;
215	}
216	if (!strcmp((const char*) chunk->name, "gbAs")) {
217		void* state = bundle->state;
218		if (!state) {
219			return 1;
220		}
221		uLongf len = bundle->stateSize;
222		uncompress((Bytef*) state, &len, chunk->data, chunk->size);
223		return 1;
224	}
225	if (!strcmp((const char*) chunk->name, "gbAx")) {
226		struct mStateExtdata* extdata = bundle->extdata;
227		if (!extdata) {
228			return 0;
229		}
230		struct mStateExtdataItem item;
231		if (chunk->size < sizeof(uint32_t) * 2) {
232			return 0;
233		}
234		uint32_t tag;
235		LOAD_32LE(tag, 0, chunk->data);
236		LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
237		uLongf len = item.size;
238		if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
239			return 0;
240		}
241		item.data = malloc(item.size);
242		item.clean = free;
243		if (!item.data) {
244			return 0;
245		}
246		const uint8_t* data = chunk->data;
247		data += sizeof(uint32_t) * 2;
248		uncompress((Bytef*) item.data, &len, data, chunk->size);
249		item.size = len;
250		mStateExtdataPut(extdata, tag, &item);
251		return 1;
252	}
253	return 0;
254}
255
256static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
257	png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
258	png_infop info = png_create_info_struct(png);
259	png_infop end = png_create_info_struct(png);
260	if (!png || !info || !end) {
261		PNGReadClose(png, info, end);
262		return false;
263	}
264	unsigned width, height;
265	core->desiredVideoDimensions(core, &width, &height);
266	uint32_t* pixels = malloc(width * height * 4);
267	if (!pixels) {
268		PNGReadClose(png, info, end);
269		return false;
270	}
271
272	size_t stateSize = core->stateSize(core);
273	void* state = anonymousMemoryMap(stateSize);
274	struct mBundledState bundle = {
275		.stateSize = stateSize,
276		.state = state,
277		.extdata = extdata
278	};
279
280	PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
281	bool success = PNGReadHeader(png, info);
282	success = success && PNGReadPixels(png, info, pixels, width, height, width);
283	success = success && PNGReadFooter(png, end);
284	PNGReadClose(png, info, end);
285
286	if (!success) {
287		free(pixels);
288		mappedMemoryFree(state, stateSize);
289		return NULL;
290	} else if (extdata) {
291		struct mStateExtdataItem item = {
292			.size = width * height * 4,
293			.data = pixels,
294			.clean = free
295		};
296		mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
297	} else {
298		free(pixels);
299	}
300	return state;
301}
302
303static bool _loadPNGExtadata(struct VFile* vf, struct mStateExtdata* extdata) {
304	png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
305	png_infop info = png_create_info_struct(png);
306	png_infop end = png_create_info_struct(png);
307	if (!png || !info || !end) {
308		PNGReadClose(png, info, end);
309		return false;
310	}
311	struct mBundledState bundle = {
312		.stateSize = 0,
313		.state = NULL,
314		.extdata = extdata
315	};
316
317	PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
318	bool success = PNGReadHeader(png, info);
319	if (!success) {
320		PNGReadClose(png, info, end);
321		return false;
322	}
323
324	unsigned width = png_get_image_width(png, info);
325	unsigned height = png_get_image_height(png, info);
326	uint32_t* pixels = NULL;
327	pixels = malloc(width * height * 4);
328	if (!pixels) {
329		PNGReadClose(png, info, end);
330		return false;
331	}
332
333	success = PNGReadPixels(png, info, pixels, width, height, width);
334	success = success && PNGReadFooter(png, end);
335	PNGReadClose(png, info, end);
336
337	if (success) {
338		struct mStateExtdataItem item = {
339			.size = width * height * 4,
340			.data = pixels,
341			.clean = free
342		};
343		mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
344	} else {
345		free(pixels);
346		return false;
347	}
348	return true;
349}
350#endif
351
352bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
353	struct mStateExtdata extdata;
354	mStateExtdataInit(&extdata);
355	size_t stateSize = core->stateSize(core);
356
357	if (flags & SAVESTATE_METADATA) {
358		uint64_t* creationUsec = malloc(sizeof(*creationUsec));
359		if (creationUsec) {
360#ifndef _MSC_VER
361			struct timeval tv;
362			if (!gettimeofday(&tv, 0)) {
363				uint64_t usec = tv.tv_usec;
364				usec += tv.tv_sec * 1000000LL;
365				STORE_64LE(usec, 0, creationUsec);
366			}
367#else
368			struct timespec ts;
369			if (timespec_get(&ts, TIME_UTC)) {
370				uint64_t usec = ts.tv_nsec / 1000;
371				usec += ts.tv_sec * 1000000LL;
372				STORE_64LE(usec, 0, creationUsec);
373			}
374#endif
375			else {
376				free(creationUsec);
377				creationUsec = 0;
378			}
379		}
380
381		if (creationUsec) {
382			struct mStateExtdataItem item = {
383				.size = sizeof(*creationUsec),
384				.data = creationUsec,
385				.clean = free
386			};
387			mStateExtdataPut(&extdata, EXTDATA_META_TIME, &item);
388		}
389	}
390
391	if (flags & SAVESTATE_SAVEDATA) {
392		void* sram = NULL;
393		size_t size = core->savedataClone(core, &sram);
394		if (size) {
395			struct mStateExtdataItem item = {
396				.size = size,
397				.data = sram,
398				.clean = free
399			};
400			mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
401		}
402	}
403	struct VFile* cheatVf = 0;
404	struct mCheatDevice* device;
405	if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
406		cheatVf = VFileMemChunk(0, 0);
407		if (cheatVf) {
408			mCheatSaveFile(device, cheatVf);
409			struct mStateExtdataItem item = {
410				.size = cheatVf->size(cheatVf),
411				.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
412				.clean = 0
413			};
414			mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
415		}
416	}
417	if (flags & SAVESTATE_RTC) {
418		struct mStateExtdataItem item;
419		if (core->rtc.d.serialize) {
420			core->rtc.d.serialize(&core->rtc.d, &item);
421			mStateExtdataPut(&extdata, EXTDATA_RTC, &item);
422		}
423	}
424#ifdef USE_PNG
425	if (!(flags & SAVESTATE_SCREENSHOT)) {
426#else
427	UNUSED(flags);
428#endif
429		vf->truncate(vf, stateSize);
430		struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
431		if (!state) {
432			mStateExtdataDeinit(&extdata);
433			if (cheatVf) {
434				cheatVf->close(cheatVf);
435			}
436			return false;
437		}
438		core->saveState(core, state);
439		vf->unmap(vf, state, stateSize);
440		vf->seek(vf, stateSize, SEEK_SET);
441		mStateExtdataSerialize(&extdata, vf);
442		mStateExtdataDeinit(&extdata);
443		if (cheatVf) {
444			cheatVf->close(cheatVf);
445		}
446		return true;
447#ifdef USE_PNG
448	}
449	else {
450		bool success = _savePNGState(core, vf, &extdata);
451		mStateExtdataDeinit(&extdata);
452		return success;
453	}
454#endif
455	mStateExtdataDeinit(&extdata);
456	return false;
457}
458
459void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
460#ifdef USE_PNG
461	if (isPNG(vf)) {
462		return _loadPNGState(core, vf, extdata);
463	}
464#endif
465	ssize_t stateSize = core->stateSize(core);
466	void* state = anonymousMemoryMap(stateSize);
467	vf->seek(vf, 0, SEEK_SET);
468	if (vf->read(vf, state, stateSize) != stateSize) {
469		mappedMemoryFree(state, stateSize);
470		return 0;
471	}
472	if (extdata) {
473		mStateExtdataDeserialize(extdata, vf);
474	}
475	return state;
476}
477
478bool mCoreExtractExtdata(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
479#ifdef USE_PNG
480	if (isPNG(vf)) {
481		return _loadPNGExtadata(vf, extdata);
482	}
483#endif
484	if (!core) {
485		return false;
486	}
487	ssize_t stateSize = core->stateSize(core);
488	vf->seek(vf, stateSize, SEEK_SET);
489	return mStateExtdataDeserialize(extdata, vf);
490}
491
492bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
493	struct mStateExtdata extdata;
494	mStateExtdataInit(&extdata);
495	void* state = mCoreExtractState(core, vf, &extdata);
496	if (!state) {
497		return false;
498	}
499	bool success = core->loadState(core, state);
500	mappedMemoryFree(state, core->stateSize(core));
501
502	unsigned width, height;
503	core->desiredVideoDimensions(core, &width, &height);
504
505	struct mStateExtdataItem item;
506	if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
507		mLOG(SAVESTATE, INFO, "Loading screenshot");
508		if (item.size >= (int) (width * height) * 4) {
509			core->putPixels(core, item.data, width);
510		} else {
511			mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
512		}
513	}
514	if (mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
515		mLOG(SAVESTATE, INFO, "Loading savedata");
516		if (item.data) {
517			if (!core->savedataRestore(core, item.data, item.size, flags & SAVESTATE_SAVEDATA)) {
518				mLOG(SAVESTATE, WARN, "Failed to load savedata from savestate");
519			}
520		}
521	}
522	struct mCheatDevice* device;
523	if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
524		mLOG(SAVESTATE, INFO, "Loading cheats");
525		if (item.size) {
526			struct VFile* svf = VFileFromMemory(item.data, item.size);
527			if (svf) {
528				mCheatDeviceClear(device);
529				mCheatParseFile(device, svf);
530				svf->close(svf);
531			}
532		}
533	}
534	if (flags & SAVESTATE_RTC && mStateExtdataGet(&extdata, EXTDATA_RTC, &item)) {
535		mLOG(SAVESTATE, INFO, "Loading RTC");
536		if (core->rtc.d.deserialize) {
537			core->rtc.d.deserialize(&core->rtc.d, &item);
538		}
539	}
540	mStateExtdataDeinit(&extdata);
541	return success;
542}
543