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-util/memory.h>
11#include <mgba-util/vfs.h>
12
13#ifdef USE_PNG
14#include <mgba-util/png-io.h>
15#include <png.h>
16#include <zlib.h>
17#endif
18
19mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate");
20
21struct mBundledState {
22 size_t stateSize;
23 void* state;
24 struct mStateExtdata* extdata;
25};
26
27struct mStateExtdataHeader {
28 uint32_t tag;
29 int32_t size;
30 int64_t offset;
31};
32
33bool mStateExtdataInit(struct mStateExtdata* extdata) {
34 memset(extdata->data, 0, sizeof(extdata->data));
35 return true;
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}
46
47void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
48 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
49 return;
50 }
51
52 if (extdata->data[tag].data && extdata->data[tag].clean) {
53 extdata->data[tag].clean(extdata->data[tag].data);
54 }
55 extdata->data[tag] = *item;
56}
57
58bool mStateExtdataGet(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) {
59 if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
60 return false;
61 }
62
63 *item = extdata->data[tag];
64 return true;
65}
66
67bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf) {
68 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
69 ssize_t size = sizeof(struct mStateExtdataHeader);
70 size_t i = 0;
71 for (i = 1; i < EXTDATA_MAX; ++i) {
72 if (extdata->data[i].data) {
73 size += sizeof(struct mStateExtdataHeader);
74 }
75 }
76 if (size == sizeof(struct mStateExtdataHeader)) {
77 return true;
78 }
79 struct mStateExtdataHeader* header = malloc(size);
80 position += size;
81
82 size_t j;
83 for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
84 if (extdata->data[i].data) {
85 STORE_32LE(i, offsetof(struct mStateExtdataHeader, tag), &header[j]);
86 STORE_32LE(extdata->data[i].size, offsetof(struct mStateExtdataHeader, size), &header[j]);
87 STORE_64LE(position, offsetof(struct mStateExtdataHeader, offset), &header[j]);
88 position += extdata->data[i].size;
89 ++j;
90 }
91 }
92 header[j].tag = 0;
93 header[j].size = 0;
94 header[j].offset = 0;
95
96 if (vf->write(vf, header, size) != size) {
97 free(header);
98 return false;
99 }
100 free(header);
101
102 for (i = 1; i < EXTDATA_MAX; ++i) {
103 if (extdata->data[i].data) {
104 if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
105 return false;
106 }
107 }
108 }
109 return true;
110}
111
112bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
113 while (true) {
114 struct mStateExtdataHeader buffer, header;
115 if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
116 return false;
117 }
118 LOAD_32LE(header.tag, 0, &buffer.tag);
119 LOAD_32LE(header.size, 0, &buffer.size);
120 LOAD_64LE(header.offset, 0, &buffer.offset);
121
122 if (header.tag == EXTDATA_NONE) {
123 break;
124 }
125 if (header.tag >= EXTDATA_MAX) {
126 continue;
127 }
128 ssize_t position = vf->seek(vf, 0, SEEK_CUR);
129 if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
130 return false;
131 }
132 struct mStateExtdataItem item = {
133 .data = malloc(header.size),
134 .size = header.size,
135 .clean = free
136 };
137 if (!item.data) {
138 continue;
139 }
140 if (vf->read(vf, item.data, header.size) != header.size) {
141 free(item.data);
142 continue;
143 }
144 mStateExtdataPut(extdata, header.tag, &item);
145 vf->seek(vf, position, SEEK_SET);
146 };
147 return true;
148}
149
150#ifdef USE_PNG
151static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
152 size_t stride;
153 const void* pixels = 0;
154
155 core->getPixels(core, &pixels, &stride);
156 if (!pixels) {
157 return false;
158 }
159
160 size_t stateSize = core->stateSize(core);
161 void* state = anonymousMemoryMap(stateSize);
162 if (!state) {
163 return false;
164 }
165 core->saveState(core, state);
166
167 uLongf len = compressBound(stateSize);
168 void* buffer = malloc(len);
169 if (!buffer) {
170 mappedMemoryFree(state, stateSize);
171 return false;
172 }
173 compress(buffer, &len, (const Bytef*) state, stateSize);
174 mappedMemoryFree(state, stateSize);
175
176 unsigned width, height;
177 core->desiredVideoDimensions(core, &width, &height);
178 png_structp png = PNGWriteOpen(vf);
179 png_infop info = PNGWriteHeader(png, width, height);
180 if (!png || !info) {
181 PNGWriteClose(png, info);
182 free(buffer);
183 return false;
184 }
185 PNGWritePixels(png, width, height, stride, pixels);
186 PNGWriteCustomChunk(png, "gbAs", len, buffer);
187 if (extdata) {
188 uint32_t i;
189 for (i = 1; i < EXTDATA_MAX; ++i) {
190 if (!extdata->data[i].data) {
191 continue;
192 }
193 uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
194 uint32_t* data = malloc(len);
195 if (!data) {
196 continue;
197 }
198 STORE_32LE(i, 0, data);
199 STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
200 compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
201 PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
202 free(data);
203 }
204 }
205 PNGWriteClose(png, info);
206 free(buffer);
207 return true;
208}
209
210static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
211 struct mBundledState* bundle = png_get_user_chunk_ptr(png);
212 if (!bundle) {
213 return 0;
214 }
215 if (!strcmp((const char*) chunk->name, "gbAs")) {
216 void* state = bundle->state;
217 if (!state) {
218 return 0;
219 }
220 uLongf len = bundle->stateSize;
221 uncompress((Bytef*) state, &len, chunk->data, chunk->size);
222 return 1;
223 }
224 if (!strcmp((const char*) chunk->name, "gbAx")) {
225 struct mStateExtdata* extdata = bundle->extdata;
226 if (!extdata) {
227 return 0;
228 }
229 struct mStateExtdataItem item;
230 if (chunk->size < sizeof(uint32_t) * 2) {
231 return 0;
232 }
233 uint32_t tag;
234 LOAD_32LE(tag, 0, chunk->data);
235 LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
236 uLongf len = item.size;
237 if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
238 return 0;
239 }
240 item.data = malloc(item.size);
241 item.clean = free;
242 if (!item.data) {
243 return 0;
244 }
245 const uint8_t* data = chunk->data;
246 data += sizeof(uint32_t) * 2;
247 uncompress((Bytef*) item.data, &len, data, chunk->size);
248 item.size = len;
249 mStateExtdataPut(extdata, tag, &item);
250 return 1;
251 }
252 return 0;
253}
254
255static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
256 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
257 png_infop info = png_create_info_struct(png);
258 png_infop end = png_create_info_struct(png);
259 if (!png || !info || !end) {
260 PNGReadClose(png, info, end);
261 return false;
262 }
263 unsigned width, height;
264 core->desiredVideoDimensions(core, &width, &height);
265 uint32_t* pixels = malloc(width * height * 4);
266 if (!pixels) {
267 PNGReadClose(png, info, end);
268 return false;
269 }
270
271 size_t stateSize = core->stateSize(core);
272 void* state = anonymousMemoryMap(stateSize);
273 struct mBundledState bundle = {
274 .stateSize = stateSize,
275 .state = state,
276 .extdata = extdata
277 };
278
279 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
280 bool success = PNGReadHeader(png, info);
281 success = success && PNGReadPixels(png, info, pixels, width, height, width);
282 success = success && PNGReadFooter(png, end);
283 PNGReadClose(png, info, end);
284
285 if (success) {
286 struct mStateExtdataItem item = {
287 .size = width * height * 4,
288 .data = pixels,
289 .clean = free
290 };
291 mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
292 } else {
293 free(pixels);
294 mappedMemoryFree(state, stateSize);
295 return 0;
296 }
297 return state;
298}
299#endif
300
301bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
302 struct mStateExtdata extdata;
303 mStateExtdataInit(&extdata);
304 size_t stateSize = core->stateSize(core);
305 if (flags & SAVESTATE_SAVEDATA) {
306 void* sram = NULL;
307 size_t size = core->savedataClone(core, &sram);
308 if (size) {
309 struct mStateExtdataItem item = {
310 .size = size,
311 .data = sram,
312 .clean = free
313 };
314 mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
315 }
316 }
317 struct VFile* cheatVf = 0;
318 struct mCheatDevice* device;
319 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
320 cheatVf = VFileMemChunk(0, 0);
321 if (cheatVf) {
322 mCheatSaveFile(device, cheatVf);
323 struct mStateExtdataItem item = {
324 .size = cheatVf->size(cheatVf),
325 .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
326 .clean = 0
327 };
328 mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
329 }
330 }
331#ifdef USE_PNG
332 if (!(flags & SAVESTATE_SCREENSHOT)) {
333#else
334 UNUSED(flags);
335#endif
336 vf->truncate(vf, stateSize);
337 struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
338 if (!state) {
339 mStateExtdataDeinit(&extdata);
340 if (cheatVf) {
341 cheatVf->close(cheatVf);
342 }
343 return false;
344 }
345 core->saveState(core, state);
346 vf->unmap(vf, state, stateSize);
347 vf->seek(vf, stateSize, SEEK_SET);
348 mStateExtdataSerialize(&extdata, vf);
349 mStateExtdataDeinit(&extdata);
350 if (cheatVf) {
351 cheatVf->close(cheatVf);
352 }
353 return true;
354#ifdef USE_PNG
355 }
356 else {
357 bool success = _savePNGState(core, vf, &extdata);
358 mStateExtdataDeinit(&extdata);
359 return success;
360 }
361#endif
362 mStateExtdataDeinit(&extdata);
363 return false;
364}
365
366void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
367#ifdef USE_PNG
368 if (isPNG(vf)) {
369 return _loadPNGState(core, vf, extdata);
370 }
371#endif
372 ssize_t stateSize = core->stateSize(core);
373 vf->seek(vf, 0, SEEK_SET);
374 if (vf->size(vf) < stateSize) {
375 return false;
376 }
377 void* state = anonymousMemoryMap(stateSize);
378 if (vf->read(vf, state, stateSize) != stateSize) {
379 mappedMemoryFree(state, stateSize);
380 return 0;
381 }
382 if (extdata) {
383 mStateExtdataDeserialize(extdata, vf);
384 }
385 return state;
386}
387
388bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
389 struct mStateExtdata extdata;
390 mStateExtdataInit(&extdata);
391 void* state = mCoreExtractState(core, vf, &extdata);
392 if (!state) {
393 return false;
394 }
395 bool success = core->loadState(core, state);
396 mappedMemoryFree(state, core->stateSize(core));
397
398 unsigned width, height;
399 core->desiredVideoDimensions(core, &width, &height);
400
401 struct mStateExtdataItem item;
402 if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
403 mLOG(SAVESTATE, INFO, "Loading screenshot");
404 if (item.size >= (int) (width * height) * 4) {
405 core->putPixels(core, item.data, width);
406 } else {
407 mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
408 }
409 }
410 if (mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
411 mLOG(SAVESTATE, INFO, "Loading savedata");
412 if (item.data) {
413 core->savedataRestore(core, item.data, item.size, flags & SAVESTATE_SAVEDATA);
414 }
415 }
416 struct mCheatDevice* device;
417 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
418 mLOG(SAVESTATE, INFO, "Loading cheats");
419 if (item.size) {
420 struct VFile* svf = VFileFromMemory(item.data, item.size);
421 if (svf) {
422 mCheatDeviceClear(device);
423 mCheatParseFile(device, svf);
424 svf->close(svf);
425 }
426 }
427 }
428 mStateExtdataDeinit(&extdata);
429 return success;
430}
431