diff --git a/src/gba/gba-serialize.c b/src/gba/gba-serialize.c index ecf6b69a1..a07848c53 100644 --- a/src/gba/gba-serialize.c +++ b/src/gba/gba-serialize.c @@ -10,6 +10,7 @@ #include "util/vfs.h" #include +#include #include const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000; @@ -110,6 +111,33 @@ static bool _savePNGState(struct GBA* gba, struct VFile* vf) { return state && png && info && buffer; } +static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { + if (strcmp((const char*) chunk->name, "gbAs") != 0) { + return 0; + } + struct GBASerializedState state; + uLongf len = sizeof(state); + uncompress((Bytef*) &state, &len, chunk->data, chunk->size); + GBADeserialize(png_get_user_chunk_ptr(png), &state); + return 1; +} + +static bool _loadPNGState(struct GBA* gba, struct VFile* vf) { + png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + PNGReadClose(png, info, end); + return false; + } + PNGInstallChunkHandler(png, gba, _loadPNGChunkHandler, "gbAs"); + PNGReadHeader(png, info); + PNGIgnorePixels(png, info); + PNGReadFooter(png, end); + PNGReadClose(png, info, end); + return true; +} + bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot) { struct VFile* vf = _getStateVf(gba, dir, slot, true); if (!vf) { @@ -133,9 +161,13 @@ bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) { if (!vf) { return false; } - struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ); - GBADeserialize(gba, state); - vf->unmap(vf, state, sizeof(struct GBASerializedState)); + if (!isPNG(vf)) { + struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ); + GBADeserialize(gba, state); + vf->unmap(vf, state, sizeof(struct GBASerializedState)); + } else { + _loadPNGState(gba, vf); + } vf->close(vf); return true; } diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 12689b65c..21f89ed32 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -163,7 +163,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents case SDLK_F9: case SDLK_F10: GBAThreadInterrupt(context); - GBASaveState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1, false); + GBASaveState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1, true); GBAThreadContinue(context); break; default: diff --git a/src/util/png-io.c b/src/util/png-io.c index dd9439433..fdc6239f0 100644 --- a/src/util/png-io.c +++ b/src/util/png-io.c @@ -10,6 +10,14 @@ static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) { } } +static void _pngRead(png_structp png, png_bytep buffer, png_size_t size) { + struct VFile* vf = png_get_io_ptr(png); + size_t read = vf->read(vf, buffer, size); + if (read != size) { + png_error(png, "Could not read PNG"); + } +} + png_structp PNGWriteOpen(struct VFile* source) { png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png) { @@ -71,6 +79,70 @@ bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* d } void PNGWriteClose(png_structp png, png_infop info) { - png_write_end(png, info); + if (!setjmp(png_jmpbuf(png))) { + png_write_end(png, info); + } png_destroy_write_struct(&png, &info); } + +bool isPNG(struct VFile* source) { + png_byte header[PNG_HEADER_BYTES]; + source->read(source, header, PNG_HEADER_BYTES); + return !png_sig_cmp(header, 0, PNG_HEADER_BYTES); +} + +png_structp PNGReadOpen(struct VFile* source, unsigned offset) { + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png) { + return 0; + } + if (setjmp(png_jmpbuf(png))) { + png_destroy_read_struct(&png, 0, 0); + return 0; + } + png_set_read_fn(png, source, _pngRead); + png_set_sig_bytes(png, offset); + return png; +} + +bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_set_read_user_chunk_fn(png, context, handler); + png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_const_bytep) chunkName, 1); + return true; +} + +bool PNGReadHeader(png_structp png, png_infop info) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_read_info(png, info); + return true; +} + +bool PNGIgnorePixels(png_structp png, png_infop info) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + + unsigned height = png_get_image_height(png, info); + unsigned i; + for (i = 0; i < height; ++i) { + png_read_row(png, 0, 0); + } + return true; +} + +bool PNGReadFooter(png_structp png, png_infop end) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_read_end(png, end); + return true; +} + +void PNGReadClose(png_structp png, png_infop info, png_infop end) { + png_destroy_read_struct(&png, &info, &end); +} diff --git a/src/util/png-io.h b/src/util/png-io.h index 2d3f0ebbc..05bff7bc4 100644 --- a/src/util/png-io.h +++ b/src/util/png-io.h @@ -7,10 +7,24 @@ struct VFile; +enum { + PNG_HEADER_BYTES = 8 +}; + png_structp PNGWriteOpen(struct VFile* source); png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height); bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, void* pixels); bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data); void PNGWriteClose(png_structp png, png_infop info); +typedef int (*ChunkHandler)(png_structp, png_unknown_chunkp); + +bool isPNG(struct VFile* source); +png_structp PNGReadOpen(struct VFile* source, unsigned offset); +bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName); +bool PNGReadHeader(png_structp png, png_infop info); +bool PNGIgnorePixels(png_structp png, png_infop info); +bool PNGReadFooter(png_structp png, png_infop end); +void PNGReadClose(png_structp png, png_infop info, png_infop end); + #endif