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
209struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) {
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 return dir->openFile(dir, path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
215}
216
217void GBADeleteState(struct GBA* gba, struct VDir* dir, int slot) {
218 char basename[PATH_MAX];
219 separatePath(gba->activeFile, 0, basename, 0);
220 char path[PATH_MAX];
221 snprintf(path, sizeof(path), "%s.ss%i", basename, slot);
222 dir->deleteFile(dir, path);
223}
224
225#ifdef USE_PNG
226static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
227 unsigned stride;
228 const void* pixels = 0;
229 gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
230 if (!pixels) {
231 return false;
232 }
233
234 struct GBASerializedState* state = GBAAllocateState();
235 if (!state) {
236 return false;
237 }
238 GBASerialize(gba, state);
239 uLongf len = compressBound(sizeof(*state));
240 void* buffer = malloc(len);
241 if (!buffer) {
242 GBADeallocateState(state);
243 return false;
244 }
245 compress(buffer, &len, (const Bytef*) state, sizeof(*state));
246 GBADeallocateState(state);
247
248 png_structp png = PNGWriteOpen(vf);
249 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
250 if (!png || !info) {
251 PNGWriteClose(png, info);
252 free(buffer);
253 return false;
254 }
255 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
256 PNGWriteCustomChunk(png, "gbAs", len, buffer);
257 if (extdata) {
258 uint32_t i;
259 for (i = 1; i < EXTDATA_MAX; ++i) {
260 if (!extdata->data[i].data) {
261 continue;
262 }
263 uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
264 uint32_t* data = malloc(len);
265 if (!data) {
266 continue;
267 }
268 STORE_32(i, 0, data);
269 STORE_32(extdata->data[i].size, sizeof(uint32_t), data);
270 compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
271 PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
272 free(data);
273 }
274 }
275 PNGWriteClose(png, info);
276 free(buffer);
277 return true;
278}
279
280static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
281 struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
282 if (!bundle) {
283 return 0;
284 }
285 if (!strcmp((const char*) chunk->name, "gbAs")) {
286 struct GBASerializedState* state = bundle->state;
287 if (!state) {
288 return 0;
289 }
290 uLongf len = sizeof(*state);
291 uncompress((Bytef*) state, &len, chunk->data, chunk->size);
292 return 1;
293 }
294 if (!strcmp((const char*) chunk->name, "gbAx")) {
295 struct GBAExtdata* extdata = bundle->extdata;
296 if (!extdata) {
297 return 0;
298 }
299 struct GBAExtdataItem item;
300 if (chunk->size < sizeof(uint32_t) * 2) {
301 return 0;
302 }
303 uint32_t tag;
304 LOAD_32(tag, 0, chunk->data);
305 LOAD_32(item.size, sizeof(uint32_t), chunk->data);
306 uLongf len = item.size;
307 if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
308 return 0;
309 }
310 item.data = malloc(item.size);
311 item.clean = free;
312 if (!item.data) {
313 return 0;
314 }
315 const uint8_t* data = chunk->data;
316 data += sizeof(uint32_t) * 2;
317 uncompress((Bytef*) item.data, &len, data, chunk->size);
318 item.size = len;
319 GBAExtdataPut(extdata, tag, &item);
320 return 1;
321 }
322 return 0;
323}
324
325static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) {
326 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
327 png_infop info = png_create_info_struct(png);
328 png_infop end = png_create_info_struct(png);
329 if (!png || !info || !end) {
330 PNGReadClose(png, info, end);
331 return false;
332 }
333 uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
334 if (!pixels) {
335 PNGReadClose(png, info, end);
336 return false;
337 }
338
339 struct GBASerializedState* state = GBAAllocateState();
340 struct GBABundledState bundle = {
341 .state = state,
342 .extdata = extdata
343 };
344
345 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
346 bool success = PNGReadHeader(png, info);
347 success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
348 success = success && PNGReadFooter(png, end);
349 PNGReadClose(png, info, end);
350
351 if (success) {
352 struct GBAExtdataItem item = {
353 .size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
354 .data = pixels,
355 .clean = free
356 };
357 GBAExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
358 } else {
359 free(pixels);
360 GBADeallocateState(state);
361 return 0;
362 }
363 return state;
364}
365#endif
366
367bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
368 struct GBAExtdata extdata;
369 GBAExtdataInit(&extdata);
370 if (flags & SAVESTATE_SAVEDATA) {
371 // TODO: A better way to do this would be nice
372 void* sram = malloc(SIZE_CART_FLASH1M);
373 struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
374 if (GBASavedataClone(&gba->memory.savedata, svf)) {
375 struct GBAExtdataItem item = {
376 .size = svf->seek(svf, 0, SEEK_CUR),
377 .data = sram,
378 .clean = free
379 };
380 GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
381 } else {
382 free(sram);
383 }
384 svf->close(svf);
385 }
386 struct VFile* cheatVf = 0;
387 if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
388 struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
389 cheatVf = VFileMemChunk(0, 0);
390 if (cheatVf) {
391 GBACheatSaveFile(device, cheatVf);
392 struct GBAExtdataItem item = {
393 .size = cheatVf->size(cheatVf),
394 .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
395 .clean = 0
396 };
397 GBAExtdataPut(&extdata, EXTDATA_CHEATS, &item);
398 }
399 };
400#ifdef USE_PNG
401 if (!(flags & SAVESTATE_SCREENSHOT)) {
402#else
403 UNUSED(flags);
404#endif
405 vf->truncate(vf, sizeof(struct GBASerializedState));
406 struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
407 if (!state) {
408 GBAExtdataDeinit(&extdata);
409 if (cheatVf) {
410 cheatVf->close(cheatVf);
411 }
412 return false;
413 }
414 GBASerialize(gba, state);
415 vf->unmap(vf, state, sizeof(struct GBASerializedState));
416 vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
417 GBAExtdataSerialize(&extdata, vf);
418 GBAExtdataDeinit(&extdata);
419 if (cheatVf) {
420 cheatVf->close(cheatVf);
421 }
422 return true;
423#ifdef USE_PNG
424 }
425 else {
426 bool success = _savePNGState(gba, vf, &extdata);
427 GBAExtdataDeinit(&extdata);
428 return success;
429 }
430#endif
431 GBAExtdataDeinit(&extdata);
432 return false;
433}
434
435struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata) {
436#ifdef USE_PNG
437 if (isPNG(vf)) {
438 return _loadPNGState(vf, extdata);
439 }
440#endif
441 vf->seek(vf, 0, SEEK_SET);
442 if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
443 return false;
444 }
445 struct GBASerializedState* state = GBAAllocateState();
446 if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
447 GBADeallocateState(state);
448 return 0;
449 }
450 if (extdata) {
451 GBAExtdataDeserialize(extdata, vf);
452 }
453 return state;
454}
455
456bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
457 struct GBAExtdata extdata;
458 GBAExtdataInit(&extdata);
459 struct GBASerializedState* state = GBAExtractState(vf, &extdata);
460 if (!state) {
461 return false;
462 }
463 bool success = GBADeserialize(gba, state);
464 GBADeallocateState(state);
465
466 struct GBAExtdataItem item;
467 if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
468 if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
469 gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
470 mCoreSyncForceFrame(gba->sync);
471 } else {
472 mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot");
473 }
474 }
475 if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
476 struct VFile* svf = VFileFromMemory(item.data, item.size);
477 if (svf) {
478 GBASavedataLoad(&gba->memory.savedata, svf);
479 svf->close(svf);
480 }
481 }
482 if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] && GBAExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
483 if (item.size) {
484 struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
485 struct VFile* svf = VFileFromMemory(item.data, item.size);
486 if (svf) {
487 GBACheatDeviceClear(device);
488 GBACheatParseFile(device, svf);
489 svf->close(svf);
490 }
491 }
492 }
493 GBAExtdataDeinit(&extdata);
494 return success;
495}
496
497bool GBAExtdataInit(struct GBAExtdata* extdata) {
498 memset(extdata->data, 0, sizeof(extdata->data));
499 return true;
500}
501
502void GBAExtdataDeinit(struct GBAExtdata* extdata) {
503 size_t i;
504 for (i = 1; i < EXTDATA_MAX; ++i) {
505 if (extdata->data[i].data && extdata->data[i].clean) {
506 extdata->data[i].clean(extdata->data[i].data);
507 }
508 }
509}
510
511void GBAExtdataPut(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
512 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
513 return;
514 }
515
516 if (extdata->data[tag].data && extdata->data[tag].clean) {
517 extdata->data[tag].clean(extdata->data[tag].data);
518 }
519 extdata->data[tag] = *item;
520}
521
522bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) {
523 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
524 return false;
525 }
526
527 *item = extdata->data[tag];
528 return true;
529}
530
531bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) {
532 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
533 ssize_t size = 2;
534 size_t i = 0;
535 for (i = 1; i < EXTDATA_MAX; ++i) {
536 if (extdata->data[i].data) {
537 size += sizeof(uint64_t) * 2;
538 }
539 }
540 if (size == 2) {
541 return true;
542 }
543 struct GBAExtdataHeader* header = malloc(size);
544 position += size;
545
546 size_t j;
547 for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
548 if (extdata->data[i].data) {
549 STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]);
550 STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]);
551 STORE_64(position, offsetof(struct GBAExtdataHeader, offset), &header[j]);
552 position += extdata->data[i].size;
553 ++j;
554 }
555 }
556 header[j].tag = 0;
557 header[j].size = 0;
558 header[j].offset = 0;
559
560 if (vf->write(vf, header, size) != size) {
561 free(header);
562 return false;
563 }
564 free(header);
565
566 for (i = 1; i < EXTDATA_MAX; ++i) {
567 if (extdata->data[i].data) {
568 if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
569 return false;
570 }
571 }
572 }
573 return true;
574}
575
576bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) {
577 while (true) {
578 struct GBAExtdataHeader buffer, header;
579 if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
580 return false;
581 }
582 LOAD_32(header.tag, 0, &buffer.tag);
583 LOAD_32(header.size, 0, &buffer.size);
584 LOAD_64(header.offset, 0, &buffer.offset);
585
586 if (header.tag == EXTDATA_NONE) {
587 break;
588 }
589 if (header.tag >= EXTDATA_MAX) {
590 continue;
591 }
592 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
593 if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
594 return false;
595 }
596 struct GBAExtdataItem item = {
597 .data = malloc(header.size),
598 .size = header.size,
599 .clean = free
600 };
601 if (!item.data) {
602 continue;
603 }
604 if (vf->read(vf, item.data, header.size) != header.size) {
605 free(item.data);
606 continue;
607 }
608 GBAExtdataPut(extdata, header.tag, &item);
609 vf->seek(vf, position, SEEK_SET);
610 };
611 return true;
612}
613
614struct GBASerializedState* GBAAllocateState(void) {
615 return anonymousMemoryMap(sizeof(struct GBASerializedState));
616}
617
618void GBADeallocateState(struct GBASerializedState* state) {
619 mappedMemoryFree(state, sizeof(struct GBASerializedState));
620}
621
622// TODO: Put back rewind