all repos — mgba @ 26711ae9e662a086f753bf6b830fe5a02b2d6928

mGBA Game Boy Advance Emulator

src/gba/serialize.c (view raw)

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