all repos — mgba @ 8615defda763c928eadb4c0919081a74b6bd28fc

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