all repos — mgba @ 86a2c45848582025e2a15e0fb67a6632ace2f482

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
209#ifdef USE_PNG
210static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
211	unsigned stride;
212	const void* pixels = 0;
213	gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
214	if (!pixels) {
215		return false;
216	}
217
218	struct GBASerializedState* state = GBAAllocateState();
219	if (!state) {
220		return false;
221	}
222	GBASerialize(gba, state);
223	uLongf len = compressBound(sizeof(*state));
224	void* buffer = malloc(len);
225	if (!buffer) {
226		GBADeallocateState(state);
227		return false;
228	}
229	compress(buffer, &len, (const Bytef*) state, sizeof(*state));
230	GBADeallocateState(state);
231
232	png_structp png = PNGWriteOpen(vf);
233	png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
234	if (!png || !info) {
235		PNGWriteClose(png, info);
236		free(buffer);
237		return false;
238	}
239	PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
240	PNGWriteCustomChunk(png, "gbAs", len, buffer);
241	if (extdata) {
242		uint32_t i;
243		for (i = 1; i < EXTDATA_MAX; ++i) {
244			if (!extdata->data[i].data) {
245				continue;
246			}
247			uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
248			uint32_t* data = malloc(len);
249			if (!data) {
250				continue;
251			}
252			STORE_32(i, 0, data);
253			STORE_32(extdata->data[i].size, sizeof(uint32_t), data);
254			compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
255			PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
256			free(data);
257		}
258	}
259	PNGWriteClose(png, info);
260	free(buffer);
261	return true;
262}
263
264static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
265	struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
266	if (!bundle) {
267		return 0;
268	}
269	if (!strcmp((const char*) chunk->name, "gbAs")) {
270		struct GBASerializedState* state = bundle->state;
271		if (!state) {
272			return 0;
273		}
274		uLongf len = sizeof(*state);
275		uncompress((Bytef*) state, &len, chunk->data, chunk->size);
276		return 1;
277	}
278	if (!strcmp((const char*) chunk->name, "gbAx")) {
279		struct GBAExtdata* extdata = bundle->extdata;
280		if (!extdata) {
281			return 0;
282		}
283		struct GBAExtdataItem item;
284		if (chunk->size < sizeof(uint32_t) * 2) {
285			return 0;
286		}
287		uint32_t tag;
288		LOAD_32(tag, 0, chunk->data);
289		LOAD_32(item.size, sizeof(uint32_t), chunk->data);
290		uLongf len = item.size;
291		if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
292			return 0;
293		}
294		item.data = malloc(item.size);
295		item.clean = free;
296		if (!item.data) {
297			return 0;
298		}
299		const uint8_t* data = chunk->data;
300		data += sizeof(uint32_t) * 2;
301		uncompress((Bytef*) item.data, &len, data, chunk->size);
302		item.size = len;
303		GBAExtdataPut(extdata, tag, &item);
304		return 1;
305	}
306	return 0;
307}
308
309static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) {
310	png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
311	png_infop info = png_create_info_struct(png);
312	png_infop end = png_create_info_struct(png);
313	if (!png || !info || !end) {
314		PNGReadClose(png, info, end);
315		return false;
316	}
317	uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
318	if (!pixels) {
319		PNGReadClose(png, info, end);
320		return false;
321	}
322
323	struct GBASerializedState* state = GBAAllocateState();
324	struct GBABundledState bundle = {
325		.state = state,
326		.extdata = extdata
327	};
328
329	PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
330	bool success = PNGReadHeader(png, info);
331	success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
332	success = success && PNGReadFooter(png, end);
333	PNGReadClose(png, info, end);
334
335	if (success) {
336		struct GBAExtdataItem item = {
337			.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
338			.data = pixels,
339			.clean = free
340		};
341		GBAExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
342	} else {
343		free(pixels);
344		GBADeallocateState(state);
345		return 0;
346	}
347	return state;
348}
349#endif
350
351bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
352	struct GBAExtdata extdata;
353	GBAExtdataInit(&extdata);
354	if (flags & SAVESTATE_SAVEDATA) {
355		// TODO: A better way to do this would be nice
356		void* sram = malloc(SIZE_CART_FLASH1M);
357		struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
358		if (GBASavedataClone(&gba->memory.savedata, svf)) {
359			struct GBAExtdataItem item = {
360				.size = svf->seek(svf, 0, SEEK_CUR),
361				.data = sram,
362				.clean = free
363			};
364			GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
365		} else {
366			free(sram);
367		}
368		svf->close(svf);
369	}
370	struct VFile* cheatVf = 0;
371	if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
372		struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
373		cheatVf = VFileMemChunk(0, 0);
374		if (cheatVf) {
375			GBACheatSaveFile(device, cheatVf);
376			struct GBAExtdataItem item = {
377				.size = cheatVf->size(cheatVf),
378				.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
379				.clean = 0
380			};
381			GBAExtdataPut(&extdata, EXTDATA_CHEATS, &item);
382		}
383	};
384#ifdef USE_PNG
385	if (!(flags & SAVESTATE_SCREENSHOT)) {
386#else
387	UNUSED(flags);
388#endif
389		vf->truncate(vf, sizeof(struct GBASerializedState));
390		struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
391		if (!state) {
392			GBAExtdataDeinit(&extdata);
393			if (cheatVf) {
394				cheatVf->close(cheatVf);
395			}
396			return false;
397		}
398		GBASerialize(gba, state);
399		vf->unmap(vf, state, sizeof(struct GBASerializedState));
400		vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
401		GBAExtdataSerialize(&extdata, vf);
402		GBAExtdataDeinit(&extdata);
403		if (cheatVf) {
404			cheatVf->close(cheatVf);
405		}
406		return true;
407#ifdef USE_PNG
408	}
409	else {
410		bool success = _savePNGState(gba, vf, &extdata);
411		GBAExtdataDeinit(&extdata);
412		return success;
413	}
414#endif
415	GBAExtdataDeinit(&extdata);
416	return false;
417}
418
419struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata) {
420#ifdef USE_PNG
421	if (isPNG(vf)) {
422		return _loadPNGState(vf, extdata);
423	}
424#endif
425	vf->seek(vf, 0, SEEK_SET);
426	if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
427		return false;
428	}
429	struct GBASerializedState* state = GBAAllocateState();
430	if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
431		GBADeallocateState(state);
432		return 0;
433	}
434	if (extdata) {
435		GBAExtdataDeserialize(extdata, vf);
436	}
437	return state;
438}
439
440bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
441	struct GBAExtdata extdata;
442	GBAExtdataInit(&extdata);
443	struct GBASerializedState* state = GBAExtractState(vf, &extdata);
444	if (!state) {
445		return false;
446	}
447	bool success = GBADeserialize(gba, state);
448	GBADeallocateState(state);
449
450	struct GBAExtdataItem item;
451	if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
452		if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
453			gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
454			mCoreSyncForceFrame(gba->sync);
455		} else {
456			mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot");
457		}
458	}
459	if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
460		struct VFile* svf = VFileFromMemory(item.data, item.size);
461		if (svf) {
462			GBASavedataLoad(&gba->memory.savedata, svf);
463			svf->close(svf);
464		}
465	}
466	if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] && GBAExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
467		if (item.size) {
468			struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
469			struct VFile* svf = VFileFromMemory(item.data, item.size);
470			if (svf) {
471				GBACheatDeviceClear(device);
472				GBACheatParseFile(device, svf);
473				svf->close(svf);
474			}
475		}
476	}
477	GBAExtdataDeinit(&extdata);
478	return success;
479}
480
481bool GBAExtdataInit(struct GBAExtdata* extdata) {
482	memset(extdata->data, 0, sizeof(extdata->data));
483	return true;
484}
485
486void GBAExtdataDeinit(struct GBAExtdata* extdata) {
487	size_t i;
488	for (i = 1; i < EXTDATA_MAX; ++i) {
489		if (extdata->data[i].data && extdata->data[i].clean) {
490			extdata->data[i].clean(extdata->data[i].data);
491		}
492	}
493}
494
495void GBAExtdataPut(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
496	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
497		return;
498	}
499
500	if (extdata->data[tag].data && extdata->data[tag].clean) {
501		extdata->data[tag].clean(extdata->data[tag].data);
502	}
503	extdata->data[tag] = *item;
504}
505
506bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
507	if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
508		return false;
509	}
510
511	*item = extdata->data[tag];
512	return true;
513}
514
515bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) {
516	ssize_t position = vf->seek(vf, 0, SEEK_CUR);
517	ssize_t size = sizeof(struct GBAExtdataHeader);
518	size_t i = 0;
519	for (i = 1; i < EXTDATA_MAX; ++i) {
520		if (extdata->data[i].data) {
521			size += sizeof(struct GBAExtdataHeader);
522		}
523	}
524	if (size == sizeof(struct GBAExtdataHeader)) {
525		return true;
526	}
527	struct GBAExtdataHeader* header = malloc(size);
528	position += size;
529
530	size_t j;
531	for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
532		if (extdata->data[i].data) {
533			STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]);
534			STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]);
535			STORE_64(position, offsetof(struct GBAExtdataHeader, offset), &header[j]);
536			position += extdata->data[i].size;
537			++j;
538		}
539	}
540	header[j].tag = 0;
541	header[j].size = 0;
542	header[j].offset = 0;
543
544	if (vf->write(vf, header, size) != size) {
545		free(header);
546		return false;
547	}
548	free(header);
549
550	for (i = 1; i < EXTDATA_MAX; ++i) {
551		if (extdata->data[i].data) {
552			if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
553				return false;
554			}
555		}
556	}
557	return true;
558}
559
560bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) {
561	while (true) {
562		struct GBAExtdataHeader buffer, header;
563		if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
564			return false;
565		}
566		LOAD_32(header.tag, 0, &buffer.tag);
567		LOAD_32(header.size, 0, &buffer.size);
568		LOAD_64(header.offset, 0, &buffer.offset);
569
570		if (header.tag == EXTDATA_NONE) {
571			break;
572		}
573		if (header.tag >= EXTDATA_MAX) {
574			continue;
575		}
576		ssize_t position = vf->seek(vf, 0, SEEK_CUR);
577		if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
578			return false;
579		}
580		struct GBAExtdataItem item = {
581			.data = malloc(header.size),
582			.size = header.size,
583			.clean = free
584		};
585		if (!item.data) {
586			continue;
587		}
588		if (vf->read(vf, item.data, header.size) != header.size) {
589			free(item.data);
590			continue;
591		}
592		GBAExtdataPut(extdata, header.tag, &item);
593		vf->seek(vf, position, SEEK_SET);
594	};
595	return true;
596}
597
598struct GBASerializedState* GBAAllocateState(void) {
599	return anonymousMemoryMap(sizeof(struct GBASerializedState));
600}
601
602void GBADeallocateState(struct GBASerializedState* state) {
603	mappedMemoryFree(state, sizeof(struct GBASerializedState));
604}
605
606// TODO: Put back rewind