src/core/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 <mgba/core/serialize.h>
7
8#include <mgba/core/core.h>
9#include <mgba/core/cheats.h>
10#include <mgba/core/interface.h>
11#include <mgba-util/memory.h>
12#include <mgba-util/vfs.h>
13
14#ifdef USE_PNG
15#include <mgba-util/png-io.h>
16#include <png.h>
17#include <zlib.h>
18#endif
19
20mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate", "core.serialize");
21
22struct mBundledState {
23 size_t stateSize;
24 void* state;
25 struct mStateExtdata* extdata;
26};
27
28struct mStateExtdataHeader {
29 uint32_t tag;
30 int32_t size;
31 int64_t offset;
32};
33
34void mStateExtdataInit(struct mStateExtdata* extdata) {
35 memset(extdata->data, 0, sizeof(extdata->data));
36}
37
38void mStateExtdataDeinit(struct mStateExtdata* extdata) {
39 size_t i;
40 for (i = 1; i < EXTDATA_MAX; ++i) {
41 if (extdata->data[i].data && extdata->data[i].clean) {
42 extdata->data[i].clean(extdata->data[i].data);
43 }
44 }
45 memset(extdata->data, 0, sizeof(extdata->data));
46}
47
48void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
49 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
50 return;
51 }
52
53 if (extdata->data[tag].data && extdata->data[tag].clean) {
54 extdata->data[tag].clean(extdata->data[tag].data);
55 }
56 extdata->data[tag] = *item;
57}
58
59bool mStateExtdataGet(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
60 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
61 return false;
62 }
63
64 *item = extdata->data[tag];
65 return true;
66}
67
68bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf) {
69 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
70 ssize_t size = sizeof(struct mStateExtdataHeader);
71 size_t i = 0;
72 for (i = 1; i < EXTDATA_MAX; ++i) {
73 if (extdata->data[i].data) {
74 size += sizeof(struct mStateExtdataHeader);
75 }
76 }
77 if (size == sizeof(struct mStateExtdataHeader)) {
78 return true;
79 }
80 struct mStateExtdataHeader* header = malloc(size);
81 position += size;
82
83 size_t j;
84 for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
85 if (extdata->data[i].data) {
86 STORE_32LE(i, offsetof(struct mStateExtdataHeader, tag), &header[j]);
87 STORE_32LE(extdata->data[i].size, offsetof(struct mStateExtdataHeader, size), &header[j]);
88 STORE_64LE(position, offsetof(struct mStateExtdataHeader, offset), &header[j]);
89 position += extdata->data[i].size;
90 ++j;
91 }
92 }
93 header[j].tag = 0;
94 header[j].size = 0;
95 header[j].offset = 0;
96
97 if (vf->write(vf, header, size) != size) {
98 free(header);
99 return false;
100 }
101 free(header);
102
103 for (i = 1; i < EXTDATA_MAX; ++i) {
104 if (extdata->data[i].data) {
105 if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
106 return false;
107 }
108 }
109 }
110 return true;
111}
112
113bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
114 while (true) {
115 struct mStateExtdataHeader buffer, header;
116 if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
117 return false;
118 }
119 LOAD_32LE(header.tag, 0, &buffer.tag);
120 LOAD_32LE(header.size, 0, &buffer.size);
121 LOAD_64LE(header.offset, 0, &buffer.offset);
122
123 if (header.tag == EXTDATA_NONE) {
124 break;
125 }
126 if (header.tag >= EXTDATA_MAX) {
127 continue;
128 }
129 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
130 if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
131 return false;
132 }
133 struct mStateExtdataItem item = {
134 .data = malloc(header.size),
135 .size = header.size,
136 .clean = free
137 };
138 if (!item.data) {
139 continue;
140 }
141 if (vf->read(vf, item.data, header.size) != header.size) {
142 free(item.data);
143 continue;
144 }
145 mStateExtdataPut(extdata, header.tag, &item);
146 vf->seek(vf, position, SEEK_SET);
147 };
148 return true;
149}
150
151#ifdef USE_PNG
152static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
153 size_t stride;
154 const void* pixels = 0;
155
156 core->getPixels(core, &pixels, &stride);
157 if (!pixels) {
158 return false;
159 }
160
161 size_t stateSize = core->stateSize(core);
162 void* state = anonymousMemoryMap(stateSize);
163 if (!state) {
164 return false;
165 }
166 core->saveState(core, state);
167
168 uLongf len = compressBound(stateSize);
169 void* buffer = malloc(len);
170 if (!buffer) {
171 mappedMemoryFree(state, stateSize);
172 return false;
173 }
174 compress(buffer, &len, (const Bytef*) state, stateSize);
175 mappedMemoryFree(state, stateSize);
176
177 unsigned width, height;
178 core->desiredVideoDimensions(core, &width, &height);
179 png_structp png = PNGWriteOpen(vf);
180 png_infop info = PNGWriteHeader(png, width, height);
181 if (!png || !info) {
182 PNGWriteClose(png, info);
183 free(buffer);
184 return false;
185 }
186 PNGWritePixels(png, width, height, stride, pixels);
187 PNGWriteCustomChunk(png, "gbAs", len, buffer);
188 if (extdata) {
189 uint32_t i;
190 for (i = 1; i < EXTDATA_MAX; ++i) {
191 if (!extdata->data[i].data) {
192 continue;
193 }
194 uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
195 uint32_t* data = malloc(len);
196 if (!data) {
197 continue;
198 }
199 STORE_32LE(i, 0, data);
200 STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
201 compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
202 PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
203 free(data);
204 }
205 }
206 PNGWriteClose(png, info);
207 free(buffer);
208 return true;
209}
210
211static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
212 struct mBundledState* bundle = png_get_user_chunk_ptr(png);
213 if (!bundle) {
214 return 0;
215 }
216 if (!strcmp((const char*) chunk->name, "gbAs")) {
217 void* state = bundle->state;
218 if (!state) {
219 return 1;
220 }
221 uLongf len = bundle->stateSize;
222 uncompress((Bytef*) state, &len, chunk->data, chunk->size);
223 return 1;
224 }
225 if (!strcmp((const char*) chunk->name, "gbAx")) {
226 struct mStateExtdata* extdata = bundle->extdata;
227 if (!extdata) {
228 return 0;
229 }
230 struct mStateExtdataItem item;
231 if (chunk->size < sizeof(uint32_t) * 2) {
232 return 0;
233 }
234 uint32_t tag;
235 LOAD_32LE(tag, 0, chunk->data);
236 LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
237 uLongf len = item.size;
238 if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
239 return 0;
240 }
241 item.data = malloc(item.size);
242 item.clean = free;
243 if (!item.data) {
244 return 0;
245 }
246 const uint8_t* data = chunk->data;
247 data += sizeof(uint32_t) * 2;
248 uncompress((Bytef*) item.data, &len, data, chunk->size);
249 item.size = len;
250 mStateExtdataPut(extdata, tag, &item);
251 return 1;
252 }
253 return 0;
254}
255
256static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
257 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
258 png_infop info = png_create_info_struct(png);
259 png_infop end = png_create_info_struct(png);
260 if (!png || !info || !end) {
261 PNGReadClose(png, info, end);
262 return false;
263 }
264 unsigned width, height;
265 core->desiredVideoDimensions(core, &width, &height);
266 uint32_t* pixels = malloc(width * height * 4);
267 if (!pixels) {
268 PNGReadClose(png, info, end);
269 return false;
270 }
271
272 size_t stateSize = core->stateSize(core);
273 void* state = anonymousMemoryMap(stateSize);
274 struct mBundledState bundle = {
275 .stateSize = stateSize,
276 .state = state,
277 .extdata = extdata
278 };
279
280 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
281 bool success = PNGReadHeader(png, info);
282 success = success && PNGReadPixels(png, info, pixels, width, height, width);
283 success = success && PNGReadFooter(png, end);
284 PNGReadClose(png, info, end);
285
286 if (success) {
287 struct mStateExtdataItem item = {
288 .size = width * height * 4,
289 .data = pixels,
290 .clean = free
291 };
292 mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
293 } else {
294 free(pixels);
295 mappedMemoryFree(state, stateSize);
296 return 0;
297 }
298 return state;
299}
300
301static bool _loadPNGExtadata(struct VFile* vf, struct mStateExtdata* extdata) {
302 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
303 png_infop info = png_create_info_struct(png);
304 png_infop end = png_create_info_struct(png);
305 if (!png || !info || !end) {
306 PNGReadClose(png, info, end);
307 return false;
308 }
309 struct mBundledState bundle = {
310 .stateSize = 0,
311 .state = NULL,
312 .extdata = extdata
313 };
314
315 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
316 bool success = PNGReadHeader(png, info);
317 if (!success) {
318 PNGReadClose(png, info, end);
319 return false;
320 }
321
322 unsigned width = png_get_image_width(png, info);
323 unsigned height = png_get_image_height(png, info);
324 uint32_t* pixels = NULL;
325 pixels = malloc(width * height * 4);
326 if (!pixels) {
327 PNGReadClose(png, info, end);
328 return false;
329 }
330
331 success = PNGReadPixels(png, info, pixels, width, height, width);
332 success = success && PNGReadFooter(png, end);
333 PNGReadClose(png, info, end);
334
335 if (success) {
336 struct mStateExtdataItem item = {
337 .size = width * height * 4,
338 .data = pixels,
339 .clean = free
340 };
341 mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
342 } else {
343 free(pixels);
344 return false;
345 }
346 return true;
347}
348#endif
349
350bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
351 struct mStateExtdata extdata;
352 mStateExtdataInit(&extdata);
353 size_t stateSize = core->stateSize(core);
354
355 if (flags & SAVESTATE_METADATA) {
356 uint64_t* creationUsec = malloc(sizeof(*creationUsec));
357 if (creationUsec) {
358#ifndef _MSC_VER
359 struct timeval tv;
360 if (!gettimeofday(&tv, 0)) {
361 uint64_t usec = tv.tv_usec;
362 usec += tv.tv_sec * 1000000LL;
363 STORE_64LE(usec, 0, creationUsec);
364 }
365#else
366 struct timespec ts;
367 if (timespec_get(&ts, TIME_UTC)) {
368 uint64_t usec = ts.tv_nsec / 1000;
369 usec += ts.tv_sec * 1000000LL;
370 STORE_64LE(usec, 0, creationUsec);
371 }
372#endif
373 else {
374 free(creationUsec);
375 creationUsec = 0;
376 }
377 }
378
379 if (creationUsec) {
380 struct mStateExtdataItem item = {
381 .size = sizeof(*creationUsec),
382 .data = creationUsec,
383 .clean = free
384 };
385 mStateExtdataPut(&extdata, EXTDATA_META_TIME, &item);
386 }
387 }
388
389 if (flags & SAVESTATE_SAVEDATA) {
390 void* sram = NULL;
391 size_t size = core->savedataClone(core, &sram);
392 if (size) {
393 struct mStateExtdataItem item = {
394 .size = size,
395 .data = sram,
396 .clean = free
397 };
398 mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
399 }
400 }
401 struct VFile* cheatVf = 0;
402 struct mCheatDevice* device;
403 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
404 cheatVf = VFileMemChunk(0, 0);
405 if (cheatVf) {
406 mCheatSaveFile(device, cheatVf);
407 struct mStateExtdataItem item = {
408 .size = cheatVf->size(cheatVf),
409 .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
410 .clean = 0
411 };
412 mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
413 }
414 }
415 if (flags & SAVESTATE_RTC) {
416 struct mStateExtdataItem item;
417 if (core->rtc.d.serialize) {
418 core->rtc.d.serialize(&core->rtc.d, &item);
419 mStateExtdataPut(&extdata, EXTDATA_RTC, &item);
420 }
421 }
422#ifdef USE_PNG
423 if (!(flags & SAVESTATE_SCREENSHOT)) {
424#else
425 UNUSED(flags);
426#endif
427 vf->truncate(vf, stateSize);
428 struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
429 if (!state) {
430 mStateExtdataDeinit(&extdata);
431 if (cheatVf) {
432 cheatVf->close(cheatVf);
433 }
434 return false;
435 }
436 core->saveState(core, state);
437 vf->unmap(vf, state, stateSize);
438 vf->seek(vf, stateSize, SEEK_SET);
439 mStateExtdataSerialize(&extdata, vf);
440 mStateExtdataDeinit(&extdata);
441 if (cheatVf) {
442 cheatVf->close(cheatVf);
443 }
444 return true;
445#ifdef USE_PNG
446 }
447 else {
448 bool success = _savePNGState(core, vf, &extdata);
449 mStateExtdataDeinit(&extdata);
450 return success;
451 }
452#endif
453 mStateExtdataDeinit(&extdata);
454 return false;
455}
456
457void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
458#ifdef USE_PNG
459 if (isPNG(vf)) {
460 return _loadPNGState(core, vf, extdata);
461 }
462#endif
463 ssize_t stateSize = core->stateSize(core);
464 void* state = anonymousMemoryMap(stateSize);
465 vf->seek(vf, 0, SEEK_SET);
466 if (vf->read(vf, state, stateSize) != stateSize) {
467 mappedMemoryFree(state, stateSize);
468 return 0;
469 }
470 if (extdata) {
471 mStateExtdataDeserialize(extdata, vf);
472 }
473 return state;
474}
475
476bool mCoreExtractExtdata(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
477#ifdef USE_PNG
478 if (isPNG(vf)) {
479 return _loadPNGExtadata(vf, extdata);
480 }
481#endif
482 if (!core) {
483 return false;
484 }
485 ssize_t stateSize = core->stateSize(core);
486 vf->seek(vf, stateSize, SEEK_SET);
487 return mStateExtdataDeserialize(extdata, vf);
488}
489
490bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
491 struct mStateExtdata extdata;
492 mStateExtdataInit(&extdata);
493 void* state = mCoreExtractState(core, vf, &extdata);
494 if (!state) {
495 return false;
496 }
497 bool success = core->loadState(core, state);
498 mappedMemoryFree(state, core->stateSize(core));
499
500 unsigned width, height;
501 core->desiredVideoDimensions(core, &width, &height);
502
503 struct mStateExtdataItem item;
504 if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
505 mLOG(SAVESTATE, INFO, "Loading screenshot");
506 if (item.size >= (int) (width * height) * 4) {
507 core->putPixels(core, item.data, width);
508 } else {
509 mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
510 }
511 }
512 if (mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
513 mLOG(SAVESTATE, INFO, "Loading savedata");
514 if (item.data) {
515 if (!core->savedataRestore(core, item.data, item.size, flags & SAVESTATE_SAVEDATA)) {
516 mLOG(SAVESTATE, WARN, "Failed to load savedata from savestate");
517 }
518 }
519 }
520 struct mCheatDevice* device;
521 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
522 mLOG(SAVESTATE, INFO, "Loading cheats");
523 if (item.size) {
524 struct VFile* svf = VFileFromMemory(item.data, item.size);
525 if (svf) {
526 mCheatDeviceClear(device);
527 mCheatParseFile(device, svf);
528 svf->close(svf);
529 }
530 }
531 }
532 if (flags & SAVESTATE_RTC && mStateExtdataGet(&extdata, EXTDATA_RTC, &item)) {
533 mLOG(SAVESTATE, INFO, "Loading RTC");
534 if (core->rtc.d.deserialize) {
535 core->rtc.d.deserialize(&core->rtc.d, &item);
536 }
537 }
538 mStateExtdataDeinit(&extdata);
539 return success;
540}
541