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