all repos — mgba @ a4e29886c9aab8a54a1232bac1bf4a4b7eb1b3db

mGBA Game Boy Advance Emulator

src/gba/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/sync.h"
  9#include "gba/audio.h"
 10#include "gba/cheats.h"
 11#include "gba/io.h"
 12#include "gba/rr/rr.h"
 13#include "gba/video.h"
 14
 15#include "util/memory.h"
 16#include "util/vfs.h"
 17
 18#include <fcntl.h>
 19#ifdef _MSC_VER
 20#include <time.h>
 21#else
 22#include <sys/time.h>
 23#endif
 24
 25#ifdef USE_PNG
 26#include "util/png-io.h"
 27#include <png.h>
 28#include <zlib.h>
 29#endif
 30
 31const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
 32const uint32_t GBA_SAVESTATE_VERSION = 0x00000001;
 33
 34mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate");
 35
 36struct GBABundledState {
 37	struct GBASerializedState* state;
 38	struct GBAExtdata* extdata;
 39};
 40
 41struct GBAExtdataHeader {
 42	uint32_t tag;
 43	int32_t size;
 44	int64_t offset;
 45};
 46
 47void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
 48	STORE_32(GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, 0, &state->versionMagic);
 49	STORE_32(gba->biosChecksum, 0, &state->biosChecksum);
 50	STORE_32(gba->romCrc32, 0, &state->romCrc32);
 51
 52	if (gba->memory.rom) {
 53		state->id = ((struct GBACartridge*) gba->memory.rom)->id;
 54		memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title));
 55	} else {
 56		state->id = 0;
 57		memset(state->title, 0, sizeof(state->title));
 58	}
 59
 60	int i;
 61	for (i = 0; i < 16; ++i) {
 62		STORE_32(gba->cpu->gprs[i], i * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
 63	}
 64	STORE_32(gba->cpu->cpsr.packed, 0, &state->cpu.cpsr.packed);
 65	STORE_32(gba->cpu->spsr.packed, 0, &state->cpu.spsr.packed);
 66	STORE_32(gba->cpu->cycles, 0, &state->cpu.cycles);
 67	STORE_32(gba->cpu->nextEvent, 0, &state->cpu.nextEvent);
 68	for (i = 0; i < 6; ++i) {
 69		int j;
 70		for (j = 0; j < 7; ++j) {
 71			STORE_32(gba->cpu->bankedRegisters[i][j], (i * 7 + j) * sizeof(gba->cpu->bankedRegisters[0][0]), state->cpu.bankedRegisters);
 72		}
 73		STORE_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
 74	}
 75
 76	state->biosPrefetch = gba->memory.biosPrefetch;
 77	STORE_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
 78	STORE_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
 79
 80	GBAMemorySerialize(&gba->memory, state);
 81	GBAIOSerialize(gba, state);
 82	GBAVideoSerialize(&gba->video, state);
 83	GBAAudioSerialize(&gba->audio, state);
 84	GBASavedataSerialize(&gba->memory.savedata, state);
 85
 86#ifndef _MSC_VER
 87	struct timeval tv;
 88	if (!gettimeofday(&tv, 0)) {
 89		uint64_t usec = tv.tv_usec;
 90		usec += tv.tv_sec * 1000000LL;
 91		STORE_64(usec, 0, &state->creationUsec);
 92	}
 93#else
 94	struct timespec ts;
 95	if (timespec_get(&ts, TIME_UTC)) {
 96		uint64_t usec = ts.tv_nsec / 1000;
 97		usec += ts.tv_sec * 1000000LL;
 98		STORE_64(usec, 0, &state->creationUsec);
 99	}
100#endif
101	else {
102		state->creationUsec = 0;
103	}
104	state->associatedStreamId = 0;
105	if (gba->rr) {
106		gba->rr->stateSaved(gba->rr, state);
107	}
108}
109
110bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
111	bool error = false;
112	int32_t check;
113	uint32_t ucheck;
114	LOAD_32(ucheck, 0, &state->versionMagic);
115	if (ucheck > GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) {
116		mLOG(GBA_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
117		error = true;
118	} else if (ucheck < GBA_SAVESTATE_MAGIC) {
119		mLOG(GBA_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
120		error = true;
121	} else {
122		mLOG(GBA_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
123	}
124	LOAD_32(ucheck, 0, &state->biosChecksum);
125	if (ucheck != gba->biosChecksum) {
126		mLOG(GBA_STATE, WARN, "Savestate created using a different version of the BIOS: expected %08X, got %08X", gba->biosChecksum, ucheck);
127		uint32_t pc;
128		LOAD_32(pc, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
129		if (pc < SIZE_BIOS && pc >= 0x20) {
130			error = true;
131		}
132	}
133	if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
134		mLOG(GBA_STATE, WARN, "Savestate is for a different game");
135		error = true;
136	} else if (!gba->memory.rom && state->id != 0) {
137		mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded");
138		error = true;
139	}
140	LOAD_32(ucheck, 0, &state->romCrc32);
141	if (ucheck != gba->romCrc32) {
142		mLOG(GBA_STATE, WARN, "Savestate is for a different version of the game");
143	}
144	LOAD_32(check, 0, &state->cpu.cycles);
145	if (check < 0) {
146		mLOG(GBA_STATE, WARN, "Savestate is corrupted: CPU cycles are negative");
147		error = true;
148	}
149	if (check >= (int32_t) GBA_ARM7TDMI_FREQUENCY) {
150		mLOG(GBA_STATE, WARN, "Savestate is corrupted: CPU cycles are too high");
151		error = true;
152	}
153	LOAD_32(check, 0, &state->video.eventDiff);
154	if (check < 0) {
155		mLOG(GBA_STATE, WARN, "Savestate is corrupted: video eventDiff is negative");
156		error = true;
157	}
158	LOAD_32(check, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
159	int region = (check >> BASE_OFFSET);
160	if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((check - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
161		mLOG(GBA_STATE, WARN, "Savestate created using a differently sized version of the ROM");
162		error = true;
163	}
164	if (error) {
165		return false;
166	}
167	size_t i;
168	for (i = 0; i < 16; ++i) {
169		LOAD_32(gba->cpu->gprs[i], i * sizeof(gba->cpu->gprs[0]), state->cpu.gprs);
170	}
171	LOAD_32(gba->cpu->cpsr.packed, 0, &state->cpu.cpsr.packed);
172	LOAD_32(gba->cpu->spsr.packed, 0, &state->cpu.spsr.packed);
173	LOAD_32(gba->cpu->cycles, 0, &state->cpu.cycles);
174	LOAD_32(gba->cpu->nextEvent, 0, &state->cpu.nextEvent);
175	for (i = 0; i < 6; ++i) {
176		int j;
177		for (j = 0; j < 7; ++j) {
178			LOAD_32(gba->cpu->bankedRegisters[i][j], (i * 7 + j) * sizeof(gba->cpu->bankedRegisters[0][0]), state->cpu.bankedRegisters);
179		}
180		LOAD_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
181	}
182	gba->cpu->privilegeMode = gba->cpu->cpsr.priv;
183	gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]);
184	if (state->biosPrefetch) {
185		LOAD_32(gba->memory.biosPrefetch, 0, &state->biosPrefetch);
186	}
187	if (gba->cpu->cpsr.t) {
188		gba->cpu->executionMode = MODE_THUMB;
189		if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
190			LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
191			LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
192			gba->cpu->prefetch[0] &= 0xFFFF;
193			gba->cpu->prefetch[1] &= 0xFFFF;
194		} else {
195			// Maintain backwards compat
196			LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
197			LOAD_16(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
198		}
199	} else {
200		gba->cpu->executionMode = MODE_ARM;
201		if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
202			LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
203			LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
204		} else {
205			// Maintain backwards compat
206			LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
207			LOAD_32(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
208		}
209	}
210
211	GBAMemoryDeserialize(&gba->memory, state);
212	GBAIODeserialize(gba, state);
213	GBAVideoDeserialize(&gba->video, state);
214	GBAAudioDeserialize(&gba->audio, state);
215	GBASavedataDeserialize(&gba->memory.savedata, state);
216
217	if (gba->rr) {
218		gba->rr->stateLoaded(gba->rr, state);
219	}
220	return true;
221}
222
223#ifdef USE_PNG
224static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
225	unsigned stride;
226	const void* pixels = 0;
227	gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
228	if (!pixels) {
229		return false;
230	}
231
232	struct GBASerializedState* state = GBAAllocateState();
233	if (!state) {
234		return false;
235	}
236	GBASerialize(gba, state);
237	uLongf len = compressBound(sizeof(*state));
238	void* buffer = malloc(len);
239	if (!buffer) {
240		GBADeallocateState(state);
241		return false;
242	}
243	compress(buffer, &len, (const Bytef*) state, sizeof(*state));
244	GBADeallocateState(state);
245
246	png_structp png = PNGWriteOpen(vf);
247	png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
248	if (!png || !info) {
249		PNGWriteClose(png, info);
250		free(buffer);
251		return false;
252	}
253	PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
254	PNGWriteCustomChunk(png, "gbAs", len, buffer);
255	if (extdata) {
256		uint32_t i;
257		for (i = 1; i < EXTDATA_MAX; ++i) {
258			if (!extdata->data[i].data) {
259				continue;
260			}
261			uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
262			uint32_t* data = malloc(len);
263			if (!data) {
264				continue;
265			}
266			STORE_32(i, 0, data);
267			STORE_32(extdata->data[i].size, sizeof(uint32_t), data);
268			compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
269			PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
270			free(data);
271		}
272	}
273	PNGWriteClose(png, info);
274	free(buffer);
275	return true;
276}
277
278static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
279	struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
280	if (!bundle) {
281		return 0;
282	}
283	if (!strcmp((const char*) chunk->name, "gbAs")) {
284		struct GBASerializedState* state = bundle->state;
285		if (!state) {
286			return 0;
287		}
288		uLongf len = sizeof(*state);
289		uncompress((Bytef*) state, &len, chunk->data, chunk->size);
290		return 1;
291	}
292	if (!strcmp((const char*) chunk->name, "gbAx")) {
293		struct GBAExtdata* extdata = bundle->extdata;
294		if (!extdata) {
295			return 0;
296		}
297		struct GBAExtdataItem item;
298		if (chunk->size < sizeof(uint32_t) * 2) {
299			return 0;
300		}
301		uint32_t tag;
302		LOAD_32(tag, 0, chunk->data);
303		LOAD_32(item.size, sizeof(uint32_t), chunk->data);
304		uLongf len = item.size;
305		if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
306			return 0;
307		}
308		item.data = malloc(item.size);
309		item.clean = free;
310		if (!item.data) {
311			return 0;
312		}
313		const uint8_t* data = chunk->data;
314		data += sizeof(uint32_t) * 2;
315		uncompress((Bytef*) item.data, &len, data, chunk->size);
316		item.size = len;
317		GBAExtdataPut(extdata, tag, &item);
318		return 1;
319	}
320	return 0;
321}
322
323static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) {
324	png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
325	png_infop info = png_create_info_struct(png);
326	png_infop end = png_create_info_struct(png);
327	if (!png || !info || !end) {
328		PNGReadClose(png, info, end);
329		return false;
330	}
331	uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
332	if (!pixels) {
333		PNGReadClose(png, info, end);
334		return false;
335	}
336
337	struct GBASerializedState* state = GBAAllocateState();
338	struct GBABundledState bundle = {
339		.state = state,
340		.extdata = extdata
341	};
342
343	PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
344	bool success = PNGReadHeader(png, info);
345	success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
346	success = success && PNGReadFooter(png, end);
347	PNGReadClose(png, info, end);
348
349	if (success) {
350		struct GBAExtdataItem item = {
351			.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
352			.data = pixels,
353			.clean = free
354		};
355		GBAExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
356	} else {
357		free(pixels);
358		GBADeallocateState(state);
359		return 0;
360	}
361	return state;
362}
363#endif
364
365bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
366	struct GBAExtdata extdata;
367	GBAExtdataInit(&extdata);
368	if (flags & SAVESTATE_SAVEDATA) {
369		// TODO: A better way to do this would be nice
370		void* sram = malloc(SIZE_CART_FLASH1M);
371		struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
372		if (GBASavedataClone(&gba->memory.savedata, svf)) {
373			struct GBAExtdataItem item = {
374				.size = svf->seek(svf, 0, SEEK_CUR),
375				.data = sram,
376				.clean = free
377			};
378			GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
379		} else {
380			free(sram);
381		}
382		svf->close(svf);
383	}
384	struct VFile* cheatVf = 0;
385	if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
386		struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
387		cheatVf = VFileMemChunk(0, 0);
388		if (cheatVf) {
389			GBACheatSaveFile(device, cheatVf);
390			struct GBAExtdataItem item = {
391				.size = cheatVf->size(cheatVf),
392				.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
393				.clean = 0
394			};
395			GBAExtdataPut(&extdata, EXTDATA_CHEATS, &item);
396		}
397	};
398#ifdef USE_PNG
399	if (!(flags & SAVESTATE_SCREENSHOT)) {
400#else
401	UNUSED(flags);
402#endif
403		vf->truncate(vf, sizeof(struct GBASerializedState));
404		struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
405		if (!state) {
406			GBAExtdataDeinit(&extdata);
407			if (cheatVf) {
408				cheatVf->close(cheatVf);
409			}
410			return false;
411		}
412		GBASerialize(gba, state);
413		vf->unmap(vf, state, sizeof(struct GBASerializedState));
414		vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
415		GBAExtdataSerialize(&extdata, vf);
416		GBAExtdataDeinit(&extdata);
417		if (cheatVf) {
418			cheatVf->close(cheatVf);
419		}
420		return true;
421#ifdef USE_PNG
422	}
423	else {
424		bool success = _savePNGState(gba, vf, &extdata);
425		GBAExtdataDeinit(&extdata);
426		return success;
427	}
428#endif
429	GBAExtdataDeinit(&extdata);
430	return false;
431}
432
433struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata) {
434#ifdef USE_PNG
435	if (isPNG(vf)) {
436		return _loadPNGState(vf, extdata);
437	}
438#endif
439	vf->seek(vf, 0, SEEK_SET);
440	if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
441		return false;
442	}
443	struct GBASerializedState* state = GBAAllocateState();
444	if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
445		GBADeallocateState(state);
446		return 0;
447	}
448	if (extdata) {
449		GBAExtdataDeserialize(extdata, vf);
450	}
451	return state;
452}
453
454bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
455	struct GBAExtdata extdata;
456	GBAExtdataInit(&extdata);
457	struct GBASerializedState* state = GBAExtractState(vf, &extdata);
458	if (!state) {
459		return false;
460	}
461	bool success = GBADeserialize(gba, state);
462	GBADeallocateState(state);
463
464	struct GBAExtdataItem item;
465	if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
466		if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
467			gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
468			mCoreSyncForceFrame(gba->sync);
469		} else {
470			mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot");
471		}
472	}
473	if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
474		struct VFile* svf = VFileFromMemory(item.data, item.size);
475		GBASavedataLoad(&gba->memory.savedata, svf);
476		if (svf) {
477			svf->close(svf);
478		}
479	}
480	if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] && GBAExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
481		if (item.size) {
482			struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
483			struct VFile* svf = VFileFromMemory(item.data, item.size);
484			if (svf) {
485				GBACheatDeviceClear(device);
486				GBACheatParseFile(device, svf);
487				svf->close(svf);
488			}
489		}
490	}
491	GBAExtdataDeinit(&extdata);
492	return success;
493}
494
495bool GBAExtdataInit(struct GBAExtdata* extdata) {
496	memset(extdata->data, 0, sizeof(extdata->data));
497	return true;
498}
499
500void GBAExtdataDeinit(struct GBAExtdata* extdata) {
501	size_t i;
502	for (i = 1; i < EXTDATA_MAX; ++i) {
503		if (extdata->data[i].data && extdata->data[i].clean) {
504			extdata->data[i].clean(extdata->data[i].data);
505		}
506	}
507}
508
509void GBAExtdataPut(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
510	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
511		return;
512	}
513
514	if (extdata->data[tag].data && extdata->data[tag].clean) {
515		extdata->data[tag].clean(extdata->data[tag].data);
516	}
517	extdata->data[tag] = *item;
518}
519
520bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
521	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
522		return false;
523	}
524
525	*item = extdata->data[tag];
526	return true;
527}
528
529bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) {
530	ssize_t position = vf->seek(vf, 0, SEEK_CUR);
531	ssize_t size = sizeof(struct GBAExtdataHeader);
532	size_t i = 0;
533	for (i = 1; i < EXTDATA_MAX; ++i) {
534		if (extdata->data[i].data) {
535			size += sizeof(struct GBAExtdataHeader);
536		}
537	}
538	if (size == sizeof(struct GBAExtdataHeader)) {
539		return true;
540	}
541	struct GBAExtdataHeader* header = malloc(size);
542	position += size;
543
544	size_t j;
545	for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
546		if (extdata->data[i].data) {
547			STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]);
548			STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]);
549			STORE_64(position, offsetof(struct GBAExtdataHeader, offset), &header[j]);
550			position += extdata->data[i].size;
551			++j;
552		}
553	}
554	header[j].tag = 0;
555	header[j].size = 0;
556	header[j].offset = 0;
557
558	if (vf->write(vf, header, size) != size) {
559		free(header);
560		return false;
561	}
562	free(header);
563
564	for (i = 1; i < EXTDATA_MAX; ++i) {
565		if (extdata->data[i].data) {
566			if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
567				return false;
568			}
569		}
570	}
571	return true;
572}
573
574bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) {
575	while (true) {
576		struct GBAExtdataHeader buffer, header;
577		if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
578			return false;
579		}
580		LOAD_32(header.tag, 0, &buffer.tag);
581		LOAD_32(header.size, 0, &buffer.size);
582		LOAD_64(header.offset, 0, &buffer.offset);
583
584		if (header.tag == EXTDATA_NONE) {
585			break;
586		}
587		if (header.tag >= EXTDATA_MAX) {
588			continue;
589		}
590		ssize_t position = vf->seek(vf, 0, SEEK_CUR);
591		if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
592			return false;
593		}
594		struct GBAExtdataItem item = {
595			.data = malloc(header.size),
596			.size = header.size,
597			.clean = free
598		};
599		if (!item.data) {
600			continue;
601		}
602		if (vf->read(vf, item.data, header.size) != header.size) {
603			free(item.data);
604			continue;
605		}
606		GBAExtdataPut(extdata, header.tag, &item);
607		vf->seek(vf, position, SEEK_SET);
608	};
609	return true;
610}
611
612struct GBASerializedState* GBAAllocateState(void) {
613	return anonymousMemoryMap(sizeof(struct GBASerializedState));
614}
615
616void GBADeallocateState(struct GBASerializedState* state) {
617	mappedMemoryFree(state, sizeof(struct GBASerializedState));
618}
619
620// TODO: Put back rewind