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 "serialize.h"
7
8#include "core/core.h"
9#include "core/cheats.h"
10#include "core/sync.h"
11#include "util/memory.h"
12#include "util/vfs.h"
13
14#ifdef USE_PNG
15#include "util/png-io.h"
16#include <png.h>
17#include <zlib.h>
18#endif
19
20mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate");
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
34bool mStateExtdataInit(struct mStateExtdata* extdata) {
35 memset(extdata->data, 0, sizeof(extdata->data));
36 return true;
37}
38
39void mStateExtdataDeinit(struct mStateExtdata* extdata) {
40 size_t i;
41 for (i = 1; i < EXTDATA_MAX; ++i) {
42 if (extdata->data[i].data && extdata->data[i].clean) {
43 extdata->data[i].clean(extdata->data[i].data);
44 }
45 }
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
152
153
154
155
156
157#ifdef USE_PNG
158static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
159 size_t stride;
160 color_t* pixels = 0;
161
162 core->getVideoBuffer(core, &pixels, &stride);
163 if (!pixels) {
164 return false;
165 }
166
167 size_t stateSize = core->stateSize(core);
168 void* state = anonymousMemoryMap(stateSize);
169 if (!state) {
170 return false;
171 }
172 core->saveState(core, state);
173
174 uLongf len = compressBound(stateSize);
175 void* buffer = malloc(len);
176 if (!buffer) {
177 mappedMemoryFree(state, stateSize);
178 return false;
179 }
180 compress(buffer, &len, (const Bytef*) state, stateSize);
181 mappedMemoryFree(state, stateSize);
182
183 unsigned width, height;
184 core->desiredVideoDimensions(core, &width, &height);
185 png_structp png = PNGWriteOpen(vf);
186 png_infop info = PNGWriteHeader(png, width, height);
187 if (!png || !info) {
188 PNGWriteClose(png, info);
189 free(buffer);
190 return false;
191 }
192 PNGWritePixels(png, width, height, stride, pixels);
193 PNGWriteCustomChunk(png, "gbAs", len, buffer);
194 if (extdata) {
195 uint32_t i;
196 for (i = 1; i < EXTDATA_MAX; ++i) {
197 if (!extdata->data[i].data) {
198 continue;
199 }
200 uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
201 uint32_t* data = malloc(len);
202 if (!data) {
203 continue;
204 }
205 STORE_32LE(i, 0, data);
206 STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
207 compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
208 PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
209 free(data);
210 }
211 }
212 PNGWriteClose(png, info);
213 free(buffer);
214 return true;
215}
216
217static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
218 struct mBundledState* bundle = png_get_user_chunk_ptr(png);
219 if (!bundle) {
220 return 0;
221 }
222 if (!strcmp((const char*) chunk->name, "gbAs")) {
223 void* state = bundle->state;
224 if (!state) {
225 return 0;
226 }
227 uLongf len = bundle->stateSize;
228 uncompress((Bytef*) state, &len, chunk->data, chunk->size);
229 return 1;
230 }
231 if (!strcmp((const char*) chunk->name, "gbAx")) {
232 struct mStateExtdata* extdata = bundle->extdata;
233 if (!extdata) {
234 return 0;
235 }
236 struct mStateExtdataItem item;
237 if (chunk->size < sizeof(uint32_t) * 2) {
238 return 0;
239 }
240 uint32_t tag;
241 LOAD_32LE(tag, 0, chunk->data);
242 LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
243 uLongf len = item.size;
244 if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
245 return 0;
246 }
247 item.data = malloc(item.size);
248 item.clean = free;
249 if (!item.data) {
250 return 0;
251 }
252 const uint8_t* data = chunk->data;
253 data += sizeof(uint32_t) * 2;
254 uncompress((Bytef*) item.data, &len, data, chunk->size);
255 item.size = len;
256 mStateExtdataPut(extdata, tag, &item);
257 return 1;
258 }
259 return 0;
260}
261
262static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
263 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
264 png_infop info = png_create_info_struct(png);
265 png_infop end = png_create_info_struct(png);
266 if (!png || !info || !end) {
267 PNGReadClose(png, info, end);
268 return false;
269 }
270 unsigned width, height;
271 core->desiredVideoDimensions(core, &width, &height);
272 uint32_t* pixels = malloc(width * height * 4);
273 if (!pixels) {
274 PNGReadClose(png, info, end);
275 return false;
276 }
277
278 size_t stateSize = core->stateSize(core);
279 void* state = anonymousMemoryMap(stateSize);
280 struct mBundledState bundle = {
281 .stateSize = stateSize,
282 .state = state,
283 .extdata = extdata
284 };
285
286 PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
287 bool success = PNGReadHeader(png, info);
288 success = success && PNGReadPixels(png, info, pixels, width, height, width);
289 success = success && PNGReadFooter(png, end);
290 PNGReadClose(png, info, end);
291
292 if (success) {
293 struct mStateExtdataItem item = {
294 .size = width * height * 4,
295 .data = pixels,
296 .clean = free
297 };
298 mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
299 } else {
300 free(pixels);
301 mappedMemoryFree(state, stateSize);
302 return 0;
303 }
304 return state;
305}
306#endif
307
308bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
309 struct mStateExtdata extdata;
310 mStateExtdataInit(&extdata);
311 size_t stateSize = core->stateSize(core);
312 if (flags & SAVESTATE_SAVEDATA) {
313 /* // TODO: A better way to do this would be nice
314 void* sram = malloc(SIZE_CART_FLASH1M);
315 struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
316 if (GBASavedataClone(&gba->memory.savedata, svf)) {
317 struct mStateExtdataItem item = {
318 .size = svf->seek(svf, 0, SEEK_CUR),
319 .data = sram,
320 .clean = free
321 };
322 mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
323 } else {
324 free(sram);
325 }
326 svf->close(svf);*/
327 }
328 struct VFile* cheatVf = 0;
329 struct mCheatDevice* device;
330 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
331 cheatVf = VFileMemChunk(0, 0);
332 if (cheatVf) {
333 mCheatSaveFile(device, cheatVf);
334 struct mStateExtdataItem item = {
335 .size = cheatVf->size(cheatVf),
336 .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
337 .clean = 0
338 };
339 mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
340 }
341 }
342#ifdef USE_PNG
343 if (!(flags & SAVESTATE_SCREENSHOT)) {
344#else
345 UNUSED(flags);
346#endif
347 vf->truncate(vf, stateSize);
348 struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
349 if (!state) {
350 mStateExtdataDeinit(&extdata);
351 if (cheatVf) {
352 cheatVf->close(cheatVf);
353 }
354 return false;
355 }
356 core->saveState(core, state);
357 vf->unmap(vf, state, stateSize);
358 vf->seek(vf, stateSize, SEEK_SET);
359 mStateExtdataSerialize(&extdata, vf);
360 mStateExtdataDeinit(&extdata);
361 if (cheatVf) {
362 cheatVf->close(cheatVf);
363 }
364 return true;
365#ifdef USE_PNG
366 }
367 else {
368 bool success = _savePNGState(core, vf, &extdata);
369 mStateExtdataDeinit(&extdata);
370 return success;
371 }
372#endif
373 mStateExtdataDeinit(&extdata);
374 return false;
375}
376
377void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
378#ifdef USE_PNG
379 if (isPNG(vf)) {
380 return _loadPNGState(core, vf, extdata);
381 }
382#endif
383 ssize_t stateSize = core->stateSize(core);
384 vf->seek(vf, 0, SEEK_SET);
385 if (vf->size(vf) < stateSize) {
386 return false;
387 }
388 void* state = anonymousMemoryMap(stateSize);
389 if (vf->read(vf, state, stateSize) != stateSize) {
390 mappedMemoryFree(state, stateSize);
391 return 0;
392 }
393 if (extdata) {
394 mStateExtdataDeserialize(extdata, vf);
395 }
396 return state;
397}
398
399bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
400 struct mStateExtdata extdata;
401 mStateExtdataInit(&extdata);
402 void* state = mCoreExtractState(core, vf, &extdata);
403 if (!state) {
404 return false;
405 }
406 bool success = core->loadState(core, state);
407 mappedMemoryFree(state, core->stateSize(core));
408
409 unsigned width, height;
410 core->desiredVideoDimensions(core, &width, &height);
411
412 struct mStateExtdataItem item;
413 if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
414 if (item.size >= (int) (width * height) * 4) {
415 /*gba->video.renderer->putPixels(gba->video.renderer, width, item.data);
416 mCoreSyncForceFrame(core->sync);*/
417 } else {
418 mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
419 }
420 }
421 if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
422 /*struct VFile* svf = VFileFromMemory(item.data, item.size);
423 GBASavedataLoad(&gba->memory.savedata, svf);
424 if (svf) {
425 svf->close(svf);
426 }*/
427 }
428 struct mCheatDevice* device;
429 if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
430 if (item.size) {
431 struct VFile* svf = VFileFromMemory(item.data, item.size);
432 if (svf) {
433 mCheatDeviceClear(device);
434 mCheatParseFile(device, svf);
435 svf->close(svf);
436 }
437 }
438 }
439 mStateExtdataDeinit(&extdata);
440 return success;
441}
442