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