all repos — mgba @ d25ba2ec59b940940950aa1330fe563bced278bd

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