diff --git a/include/mgba/core/serialize.h b/include/mgba/core/serialize.h index afbf8de54..968f3f16f 100644 --- a/include/mgba/core/serialize.h +++ b/include/mgba/core/serialize.h @@ -36,7 +36,7 @@ struct mStateExtdata { struct mStateExtdataItem data[EXTDATA_MAX]; }; -bool mStateExtdataInit(struct mStateExtdata*); +void mStateExtdataInit(struct mStateExtdata*); void mStateExtdataDeinit(struct mStateExtdata*); void mStateExtdataPut(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*); bool mStateExtdataGet(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*); @@ -49,6 +49,7 @@ struct mCore; bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags); bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags); void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata); +bool mCoreExtractExtdata(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata); CXX_GUARD_END diff --git a/src/core/serialize.c b/src/core/serialize.c index ed99107dd..f965418e5 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -31,9 +31,8 @@ struct mStateExtdataHeader { int64_t offset; }; -bool mStateExtdataInit(struct mStateExtdata* extdata) { +void mStateExtdataInit(struct mStateExtdata* extdata) { memset(extdata->data, 0, sizeof(extdata->data)); - return true; } void mStateExtdataDeinit(struct mStateExtdata* extdata) { @@ -43,6 +42,7 @@ void mStateExtdataDeinit(struct mStateExtdata* extdata) { extdata->data[i].clean(extdata->data[i].data); } } + memset(extdata->data, 0, sizeof(extdata->data)); } void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) { @@ -216,7 +216,7 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { if (!strcmp((const char*) chunk->name, "gbAs")) { void* state = bundle->state; if (!state) { - return 0; + return 1; } uLongf len = bundle->stateSize; uncompress((Bytef*) state, &len, chunk->data, chunk->size); @@ -297,6 +297,54 @@ static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateEx } return state; } + +static bool _loadPNGExtadata(struct VFile* vf, struct mStateExtdata* extdata) { + 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; + } + struct mBundledState bundle = { + .stateSize = 0, + .state = NULL, + .extdata = extdata + }; + + PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx"); + bool success = PNGReadHeader(png, info); + if (!success) { + PNGReadClose(png, info, end); + return false; + } + + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + uint32_t* pixels = NULL; + pixels = malloc(width * height * 4); + if (!pixels) { + PNGReadClose(png, info, end); + return false; + } + + success = PNGReadPixels(png, info, pixels, width, height, width); + success = success && PNGReadFooter(png, end); + PNGReadClose(png, info, end); + + if (success) { + struct mStateExtdataItem item = { + .size = width * height * 4, + .data = pixels, + .clean = free + }; + mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item); + } else { + free(pixels); + return false; + } + return true; +} #endif bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { @@ -425,6 +473,20 @@ void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtda return state; } +bool mCoreExtractExtdata(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) { +#ifdef USE_PNG + if (isPNG(vf)) { + return _loadPNGExtadata(vf, extdata); + } +#endif + if (!core) { + return false; + } + ssize_t stateSize = core->stateSize(core); + vf->seek(vf, stateSize, SEEK_SET); + return mStateExtdataDeserialize(extdata, vf); +} + bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { struct mStateExtdata extdata; mStateExtdataInit(&extdata); diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index 74c38d1ea..fdd94ba80 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -7,12 +7,16 @@ #include "CoreController.h" #include "LogController.h" +#include "VFileDevice.h" #include #ifdef M_CORE_GBA #include #endif +#ifdef M_CORE_GB +#include +#endif #include #include @@ -27,6 +31,57 @@ void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) { m_multiplayer = multiplayer; } +QByteArray CoreManager::getExtdata(const QString& filename, mStateExtdataTag extdataType) { + VFileDevice vf(filename, QIODevice::ReadOnly); + + if (!vf.isOpen()) { + return {}; + } + + mStateExtdata extdata; + mStateExtdataInit(&extdata); + + QByteArray bytes; + auto extract = [&bytes, &extdata, &vf, extdataType](mCore* core) -> bool { + if (mCoreExtractExtdata(core, vf, &extdata)) { + mStateExtdataItem extitem; + if (!mStateExtdataGet(&extdata, extdataType, &extitem)) { + return false; + } + if (extitem.size) { + bytes = QByteArray::fromRawData(static_cast(extitem.data), extitem.size); + } + return true; + } + return false; + }; + + bool done = false; + struct mCore* core = nullptr; +#ifdef USE_PNG + done = extract(nullptr); +#endif +#ifdef M_CORE_GBA + if (!done) { + core = GBACoreCreate(); + core->init(core); + done = extract(core); + core->deinit(core); + } +#endif +#ifdef M_CORE_GB + if (!done) { + core = GBCoreCreate(); + core->init(core); + done = extract(core); + core->deinit(core); + } +#endif + + mStateExtdataDeinit(&extdata); + return bytes; +} + CoreController* CoreManager::loadGame(const QString& path) { QFileInfo info(path); if (!info.isReadable()) { diff --git a/src/platform/qt/CoreManager.h b/src/platform/qt/CoreManager.h index da7460f22..3a31288b9 100644 --- a/src/platform/qt/CoreManager.h +++ b/src/platform/qt/CoreManager.h @@ -9,6 +9,8 @@ #include #include +#include + struct mCoreConfig; struct VFile; @@ -25,6 +27,8 @@ public: void setMultiplayerController(MultiplayerController*); void setPreload(bool preload) { m_preload = preload; } + static QByteArray getExtdata(const QString& filename, mStateExtdataTag extdataType); + public slots: CoreController* loadGame(const QString& path); CoreController* loadGame(VFile* vf, const QString& path, const QString& base);