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/serialize.h"
9#include "core/sync.h"
10#include "gba/audio.h"
11#include "gba/cheats.h"
12#include "gba/io.h"
13#include "gba/rr/rr.h"
14#include "gba/video.h"
15
16#include "util/memory.h"
17#include "util/vfs.h"
18
19#include <fcntl.h>
20#ifdef _MSC_VER
21#include <time.h>
22#else
23#include <sys/time.h>
24#endif
25
26#ifdef USE_PNG
27#include "util/png-io.h"
28#include <png.h>
29#include <zlib.h>
30#endif
31
32const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
33const uint32_t GBA_SAVESTATE_VERSION = 0x00000001;
34
35mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate");
36
37struct GBABundledState {
38 struct GBASerializedState* state;
39 struct mStateExtdata* extdata;
40};
41
42void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
43 STORE_32(GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, 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#ifndef _MSC_VER
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 }
88#else
89 struct timespec ts;
90 if (timespec_get(&ts, TIME_UTC)) {
91 uint64_t usec = ts.tv_nsec / 1000;
92 usec += ts.tv_sec * 1000000LL;
93 STORE_64(usec, 0, &state->creationUsec);
94 }
95#endif
96 else {
97 state->creationUsec = 0;
98 }
99 state->associatedStreamId = 0;
100 if (gba->rr) {
101 gba->rr->stateSaved(gba->rr, state);
102 }
103}
104
105bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
106 bool error = false;
107 int32_t check;
108 uint32_t ucheck;
109 LOAD_32(ucheck, 0, &state->versionMagic);
110 if (ucheck > GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) {
111 mLOG(GBA_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
112 error = true;
113 } else if (ucheck < GBA_SAVESTATE_MAGIC) {
114 mLOG(GBA_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
115 error = true;
116 } else if (ucheck < GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) {
117 mLOG(GBA_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck);
118 }
119 LOAD_32(ucheck, 0, &state->biosChecksum);
120 if (ucheck != gba->biosChecksum) {
121 mLOG(GBA_STATE, WARN, "Savestate created using a different version of the BIOS: expected %08X, got %08X", gba->biosChecksum, ucheck);
122 uint32_t pc;
123 LOAD_32(pc, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
124 if (pc < SIZE_BIOS && pc >= 0x20) {
125 error = true;
126 }
127 }
128 if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
129 mLOG(GBA_STATE, WARN, "Savestate is for a different game");
130 error = true;
131 } else if (!gba->memory.rom && state->id != 0) {
132 mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded");
133 error = true;
134 }
135 LOAD_32(ucheck, 0, &state->romCrc32);
136 if (ucheck != gba->romCrc32) {
137 mLOG(GBA_STATE, WARN, "Savestate is for a different version of the game");
138 }
139 LOAD_32(check, 0, &state->cpu.cycles);
140 if (check < 0) {
141 mLOG(GBA_STATE, WARN, "Savestate is corrupted: CPU cycles are negative");
142 error = true;
143 }
144 if (check >= (int32_t) GBA_ARM7TDMI_FREQUENCY) {
145 mLOG(GBA_STATE, WARN, "Savestate is corrupted: CPU cycles are too high");
146 error = true;
147 }
148 LOAD_32(check, 0, &state->video.eventDiff);
149 if (check < 0) {
150 mLOG(GBA_STATE, WARN, "Savestate is corrupted: video eventDiff is negative");
151 error = true;
152 }
153 LOAD_32(check, ARM_PC * sizeof(state->cpu.gprs[0]), state->cpu.gprs);
154 int region = (check >> BASE_OFFSET);
155 if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((check - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
156 mLOG(GBA_STATE, WARN, "Savestate created using a differently sized version of the ROM");
157 error = true;
158 }
159 if (error) {
160 return false;
161 }
162 size_t i;
163 for (i = 0; i < 16; ++i) {
164 LOAD_32(gba->cpu->gprs[i], i * sizeof(gba->cpu->gprs[0]), state->cpu.gprs);
165 }
166 LOAD_32(gba->cpu->cpsr.packed, 0, &state->cpu.cpsr.packed);
167 LOAD_32(gba->cpu->spsr.packed, 0, &state->cpu.spsr.packed);
168 LOAD_32(gba->cpu->cycles, 0, &state->cpu.cycles);
169 LOAD_32(gba->cpu->nextEvent, 0, &state->cpu.nextEvent);
170 for (i = 0; i < 6; ++i) {
171 int j;
172 for (j = 0; j < 7; ++j) {
173 LOAD_32(gba->cpu->bankedRegisters[i][j], (i * 7 + j) * sizeof(gba->cpu->bankedRegisters[0][0]), state->cpu.bankedRegisters);
174 }
175 LOAD_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
176 }
177 gba->cpu->privilegeMode = gba->cpu->cpsr.priv;
178 gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]);
179 if (state->biosPrefetch) {
180 LOAD_32(gba->memory.biosPrefetch, 0, &state->biosPrefetch);
181 }
182 if (gba->cpu->cpsr.t) {
183 gba->cpu->executionMode = MODE_THUMB;
184 if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
185 LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
186 LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
187 gba->cpu->prefetch[0] &= 0xFFFF;
188 gba->cpu->prefetch[1] &= 0xFFFF;
189 } else {
190 // Maintain backwards compat
191 LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
192 LOAD_16(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
193 }
194 } else {
195 gba->cpu->executionMode = MODE_ARM;
196 if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) {
197 LOAD_32(gba->cpu->prefetch[0], 0, state->cpuPrefetch);
198 LOAD_32(gba->cpu->prefetch[1], 4, state->cpuPrefetch);
199 } else {
200 // Maintain backwards compat
201 LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
202 LOAD_32(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion);
203 }
204 }
205
206 GBAMemoryDeserialize(&gba->memory, state);
207 GBAIODeserialize(gba, state);
208 GBAVideoDeserialize(&gba->video, state);
209 GBAAudioDeserialize(&gba->audio, state);
210 GBASavedataDeserialize(&gba->memory.savedata, state);
211
212 if (gba->rr) {
213 gba->rr->stateLoaded(gba->rr, state);
214 }
215 return true;
216}
217
218#ifdef USE_PNG
219static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct mStateExtdata* extdata) {
220 unsigned stride;
221 const void* pixels = 0;
222 gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
223 if (!pixels) {
224 return false;
225 }
226
227 struct GBASerializedState* state = GBAAllocateState();
228 if (!state) {
229 return false;
230 }
231 GBASerialize(gba, state);
232 uLongf len = compressBound(sizeof(*state));
233 void* buffer = malloc(len);
234 if (!buffer) {
235 GBADeallocateState(state);
236 return false;
237 }
238 compress(buffer, &len, (const Bytef*) state, sizeof(*state));
239 GBADeallocateState(state);
240
241 png_structp png = PNGWriteOpen(vf);
242 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
243 if (!png || !info) {
244 PNGWriteClose(png, info);
245 free(buffer);
246 return false;
247 }
248 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
249 PNGWriteCustomChunk(png, "gbAs", len, buffer);
250 if (extdata) {
251 uint32_t i;
252 for (i = 1; i < EXTDATA_MAX; ++i) {
253 if (!extdata->data[i].data) {
254 continue;
255 }
256 uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
257 uint32_t* data = malloc(len);
258 if (!data) {
259 continue;
260 }
261 STORE_32(i, 0, data);
262 STORE_32(extdata->data[i].size, sizeof(uint32_t), data);
263 compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
264 PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
265 free(data);
266 }
267 }
268 PNGWriteClose(png, info);
269 free(buffer);
270 return true;
271}
272
273static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
274 struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
275 if (!bundle) {
276 return 0;
277 }
278 if (!strcmp((const char*) chunk->name, "gbAs")) {
279 struct GBASerializedState* state = bundle->state;
280 if (!state) {
281 return 0;
282 }
283 uLongf len = sizeof(*state);
284 uncompress((Bytef*) state, &len, chunk->data, chunk->size);
285 return 1;
286 }
287 if (!strcmp((const char*) chunk->name, "gbAx")) {
288 struct mStateExtdata* extdata = bundle->extdata;
289 if (!extdata) {
290 return 0;
291 }
292 struct mStateExtdataItem item;
293 if (chunk->size < sizeof(uint32_t) * 2) {
294 return 0;
295 }
296 uint32_t tag;
297 LOAD_32(tag, 0, chunk->data);
298 LOAD_32(item.size, sizeof(uint32_t), chunk->data);
299 uLongf len = item.size;
300 if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
301 return 0;
302 }
303 item.data = malloc(item.size);
304 item.clean = free;
305 if (!item.data) {
306 return 0;
307 }
308 const uint8_t* data = chunk->data;
309 data += sizeof(uint32_t) * 2;
310 uncompress((Bytef*) item.data, &len, data, chunk->size);
311 item.size = len;
312 mStateExtdataPut(extdata, tag, &item);
313 return 1;
314 }
315 return 0;
316}
317
318static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct mStateExtdata* extdata) {
319 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
320 png_infop info = png_create_info_struct(png);
321 png_infop end = png_create_info_struct(png);
322 if (!png || !info || !end) {
323 PNGReadClose(png, info, end);
324 return false;
325 }
326 uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
327 if (!pixels) {
328 PNGReadClose(png, info, end);
329 return false;
330 }
331
332 struct GBASerializedState* state = GBAAllocateState();
333 struct GBABundledState bundle = {
334 .state = state,
335 .extdata = extdata
336 };
337
338 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
339 bool success = PNGReadHeader(png, info);
340 success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
341 success = success && PNGReadFooter(png, end);
342 PNGReadClose(png, info, end);
343
344 if (success) {
345 struct mStateExtdataItem item = {
346 .size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
347 .data = pixels,
348 .clean = free
349 };
350 mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
351 } else {
352 free(pixels);
353 GBADeallocateState(state);
354 return 0;
355 }
356 return state;
357}
358#endif
359
360bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
361 struct mStateExtdata extdata;
362 mStateExtdataInit(&extdata);
363 if (flags & SAVESTATE_SAVEDATA) {
364 // TODO: A better way to do this would be nice
365 void* sram = malloc(SIZE_CART_FLASH1M);
366 struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
367 if (GBASavedataClone(&gba->memory.savedata, svf)) {
368 struct mStateExtdataItem item = {
369 .size = svf->seek(svf, 0, SEEK_CUR),
370 .data = sram,
371 .clean = free
372 };
373 mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
374 } else {
375 free(sram);
376 }
377 svf->close(svf);
378 }
379 struct VFile* cheatVf = 0;
380 if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
381 struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
382 cheatVf = VFileMemChunk(0, 0);
383 if (cheatVf) {
384 mCheatSaveFile(device, cheatVf);
385 struct mStateExtdataItem item = {
386 .size = cheatVf->size(cheatVf),
387 .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
388 .clean = 0
389 };
390 mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
391 }
392 };
393#ifdef USE_PNG
394 if (!(flags & SAVESTATE_SCREENSHOT)) {
395#else
396 UNUSED(flags);
397#endif
398 vf->truncate(vf, sizeof(struct GBASerializedState));
399 struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
400 if (!state) {
401 mStateExtdataDeinit(&extdata);
402 if (cheatVf) {
403 cheatVf->close(cheatVf);
404 }
405 return false;
406 }
407 GBASerialize(gba, state);
408 vf->unmap(vf, state, sizeof(struct GBASerializedState));
409 vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
410 mStateExtdataSerialize(&extdata, vf);
411 mStateExtdataDeinit(&extdata);
412 if (cheatVf) {
413 cheatVf->close(cheatVf);
414 }
415 return true;
416#ifdef USE_PNG
417 }
418 else {
419 bool success = _savePNGState(gba, vf, &extdata);
420 mStateExtdataDeinit(&extdata);
421 return success;
422 }
423#endif
424 mStateExtdataDeinit(&extdata);
425 return false;
426}
427
428struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata) {
429#ifdef USE_PNG
430 if (isPNG(vf)) {
431 return _loadPNGState(vf, extdata);
432 }
433#endif
434 vf->seek(vf, 0, SEEK_SET);
435 if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
436 return false;
437 }
438 struct GBASerializedState* state = GBAAllocateState();
439 if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
440 GBADeallocateState(state);
441 return 0;
442 }
443 if (extdata) {
444 mStateExtdataDeserialize(extdata, vf);
445 }
446 return state;
447}
448
449bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
450 struct mStateExtdata extdata;
451 mStateExtdataInit(&extdata);
452 struct GBASerializedState* state = GBAExtractState(vf, &extdata);
453 if (!state) {
454 return false;
455 }
456 bool success = GBADeserialize(gba, state);
457 GBADeallocateState(state);
458
459 struct mStateExtdataItem item;
460 if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
461 if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
462 gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
463 mCoreSyncForceFrame(gba->sync);
464 } else {
465 mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot");
466 }
467 }
468 if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
469 struct VFile* svf = VFileFromMemory(item.data, item.size);
470 GBASavedataLoad(&gba->memory.savedata, svf);
471 if (svf) {
472 svf->close(svf);
473 }
474 }
475 if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE] && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
476 if (item.size) {
477 struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
478 struct VFile* svf = VFileFromMemory(item.data, item.size);
479 if (svf) {
480 mCheatDeviceClear(device);
481 mCheatParseFile(device, svf);
482 svf->close(svf);
483 }
484 }
485 }
486 mStateExtdataDeinit(&extdata);
487 return success;
488}
489
490struct GBASerializedState* GBAAllocateState(void) {
491 return anonymousMemoryMap(sizeof(struct GBASerializedState));
492}
493
494void GBADeallocateState(struct GBASerializedState* state) {
495 mappedMemoryFree(state, sizeof(struct GBASerializedState));
496}
497
498// TODO: Put back rewind