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 GBASyncForceFrame(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}