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}