all repos — mgba @ 77787a957cb409e3dd730c50468f6e83981e8e8a

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