mirror of https://github.com/mgba-emu/mgba.git
Core: Refactor savestates
This commit is contained in:
parent
cf8868d5cb
commit
a3a380d9f5
|
@ -108,7 +108,7 @@ bool mCoreSaveState(struct mCore* core, int slot, int flags) {
|
|||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = core->saveState(core, vf, flags);
|
||||
bool success = mCoreSaveStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
mLOG(STATUS, INFO, "State %i saved", slot);
|
||||
|
@ -124,7 +124,7 @@ bool mCoreLoadState(struct mCore* core, int slot, int flags) {
|
|||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = core->loadState(core, vf, flags);
|
||||
bool success = mCoreLoadStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
mLOG(STATUS, INFO, "State %i loaded", slot);
|
||||
|
|
|
@ -31,6 +31,7 @@ enum mPlatform {
|
|||
struct mRTCSource;
|
||||
struct mCoreConfig;
|
||||
struct mCoreSync;
|
||||
struct mStateExtdata;
|
||||
struct mCore {
|
||||
void* cpu;
|
||||
void* board;
|
||||
|
@ -78,8 +79,9 @@ struct mCore {
|
|||
void (*runLoop)(struct mCore*);
|
||||
void (*step)(struct mCore*);
|
||||
|
||||
bool (*loadState)(struct mCore*, struct VFile*, int flags);
|
||||
bool (*saveState)(struct mCore*, struct VFile*, int flags);
|
||||
size_t (*stateSize)(struct mCore*);
|
||||
bool (*loadState)(struct mCore*, const void* state);
|
||||
bool (*saveState)(struct mCore*, void* state);
|
||||
|
||||
void (*setKeys)(struct mCore*, uint32_t keys);
|
||||
void (*addKeys)(struct mCore*, uint32_t keys);
|
||||
|
|
|
@ -5,8 +5,26 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "serialize.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/cheats.h"
|
||||
#include "core/sync.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include "util/png-io.h"
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate");
|
||||
|
||||
struct mBundledState {
|
||||
size_t stateSize;
|
||||
void* state;
|
||||
struct mStateExtdata* extdata;
|
||||
};
|
||||
|
||||
struct mStateExtdataHeader {
|
||||
uint32_t tag;
|
||||
int32_t size;
|
||||
|
@ -129,3 +147,296 @@ bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
|
|||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef USE_PNG
|
||||
static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
size_t stride;
|
||||
color_t* pixels = 0;
|
||||
|
||||
core->getVideoBuffer(core, &pixels, &stride);
|
||||
if (!pixels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t stateSize = core->stateSize(core);
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
core->saveState(core, state);
|
||||
|
||||
uLongf len = compressBound(stateSize);
|
||||
void* buffer = malloc(len);
|
||||
if (!buffer) {
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return false;
|
||||
}
|
||||
compress(buffer, &len, (const Bytef*) state, stateSize);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, width, height);
|
||||
if (!png || !info) {
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
PNGWritePixels(png, width, height, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
if (extdata) {
|
||||
uint32_t i;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (!extdata->data[i].data) {
|
||||
continue;
|
||||
}
|
||||
uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
|
||||
uint32_t* data = malloc(len);
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
STORE_32LE(i, 0, data);
|
||||
STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data);
|
||||
compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
|
||||
PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||
struct mBundledState* bundle = png_get_user_chunk_ptr(png);
|
||||
if (!bundle) {
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAs")) {
|
||||
void* state = bundle->state;
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
uLongf len = bundle->stateSize;
|
||||
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAx")) {
|
||||
struct mStateExtdata* extdata = bundle->extdata;
|
||||
if (!extdata) {
|
||||
return 0;
|
||||
}
|
||||
struct mStateExtdataItem item;
|
||||
if (chunk->size < sizeof(uint32_t) * 2) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t tag;
|
||||
LOAD_32LE(tag, 0, chunk->data);
|
||||
LOAD_32LE(item.size, sizeof(uint32_t), chunk->data);
|
||||
uLongf len = item.size;
|
||||
if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return 0;
|
||||
}
|
||||
item.data = malloc(item.size);
|
||||
item.clean = free;
|
||||
if (!item.data) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t* data = chunk->data;
|
||||
data += sizeof(uint32_t) * 2;
|
||||
uncompress((Bytef*) item.data, &len, data, chunk->size);
|
||||
item.size = len;
|
||||
mStateExtdataPut(extdata, tag, &item);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* _loadPNGState(struct mCore* core, 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;
|
||||
}
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
uint32_t* pixels = malloc(width * height * 4);
|
||||
if (!pixels) {
|
||||
PNGReadClose(png, info, end);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t stateSize = core->stateSize(core);
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
struct mBundledState bundle = {
|
||||
.stateSize = stateSize,
|
||||
.state = state,
|
||||
.extdata = extdata
|
||||
};
|
||||
|
||||
PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
|
||||
bool success = PNGReadHeader(png, info);
|
||||
success = 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);
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
size_t stateSize = core->stateSize(core);
|
||||
if (flags & SAVESTATE_SAVEDATA) {
|
||||
/* // TODO: A better way to do this would be nice
|
||||
void* sram = malloc(SIZE_CART_FLASH1M);
|
||||
struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
|
||||
if (GBASavedataClone(&gba->memory.savedata, svf)) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = svf->seek(svf, 0, SEEK_CUR),
|
||||
.data = sram,
|
||||
.clean = free
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
|
||||
} else {
|
||||
free(sram);
|
||||
}
|
||||
svf->close(svf);*/
|
||||
}
|
||||
struct VFile* cheatVf = 0;
|
||||
struct mCheatDevice* device;
|
||||
if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) {
|
||||
cheatVf = VFileMemChunk(0, 0);
|
||||
if (cheatVf) {
|
||||
mCheatSaveFile(device, cheatVf);
|
||||
struct mStateExtdataItem item = {
|
||||
.size = cheatVf->size(cheatVf),
|
||||
.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
|
||||
.clean = 0
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
|
||||
}
|
||||
}
|
||||
#ifdef USE_PNG
|
||||
if (!(flags & SAVESTATE_SCREENSHOT)) {
|
||||
#else
|
||||
UNUSED(flags);
|
||||
#endif
|
||||
vf->truncate(vf, stateSize);
|
||||
struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE);
|
||||
if (!state) {
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
core->saveState(core, state);
|
||||
vf->unmap(vf, state, stateSize);
|
||||
vf->seek(vf, stateSize, SEEK_SET);
|
||||
mStateExtdataSerialize(&extdata, vf);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return true;
|
||||
#ifdef USE_PNG
|
||||
}
|
||||
else {
|
||||
bool success = _savePNGState(core, vf, &extdata);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
#ifdef USE_PNG
|
||||
if (isPNG(vf)) {
|
||||
return _loadPNGState(core, vf, extdata);
|
||||
}
|
||||
#endif
|
||||
ssize_t stateSize = core->stateSize(core);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (vf->size(vf) < stateSize) {
|
||||
return false;
|
||||
}
|
||||
void* state = anonymousMemoryMap(stateSize);
|
||||
if (vf->read(vf, state, stateSize) != stateSize) {
|
||||
mappedMemoryFree(state, stateSize);
|
||||
return 0;
|
||||
}
|
||||
if (extdata) {
|
||||
mStateExtdataDeserialize(extdata, vf);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
void* state = mCoreExtractState(core, vf, &extdata);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
bool success = core->loadState(core, state);
|
||||
mappedMemoryFree(state, core->stateSize(core));
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
|
||||
struct mStateExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
|
||||
if (item.size >= (int) (width * height) * 4) {
|
||||
/*gba->video.renderer->putPixels(gba->video.renderer, width, item.data);
|
||||
mCoreSyncForceFrame(core->sync);*/
|
||||
} else {
|
||||
mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot");
|
||||
}
|
||||
}
|
||||
if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
|
||||
/*struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
GBASavedataLoad(&gba->memory.savedata, svf);
|
||||
if (svf) {
|
||||
svf->close(svf);
|
||||
}*/
|
||||
}
|
||||
struct mCheatDevice* device;
|
||||
if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
|
||||
if (item.size) {
|
||||
struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
if (svf) {
|
||||
mCheatDeviceClear(device);
|
||||
mCheatParseFile(device, svf);
|
||||
svf->close(svf);
|
||||
}
|
||||
}
|
||||
}
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,4 +39,9 @@ struct VFile;
|
|||
bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf);
|
||||
bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf);
|
||||
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -244,12 +244,18 @@ static void _GBACoreStep(struct mCore* core) {
|
|||
ARMRun(core->cpu);
|
||||
}
|
||||
|
||||
static bool _GBACoreLoadState(struct mCore* core, struct VFile* vf, int flags) {
|
||||
return GBALoadStateNamed(core->board, vf, flags);
|
||||
static size_t _GBACoreStateSize(struct mCore* core) {
|
||||
UNUSED(core);
|
||||
return sizeof(struct GBASerializedState);
|
||||
}
|
||||
|
||||
static bool _GBACoreSaveState(struct mCore* core, struct VFile* vf, int flags) {
|
||||
return GBASaveStateNamed(core->board, vf, flags);
|
||||
static bool _GBACoreLoadState(struct mCore* core, const void* state) {
|
||||
return GBADeserialize(core->board, state);
|
||||
}
|
||||
|
||||
static bool _GBACoreSaveState(struct mCore* core, void* state) {
|
||||
GBASerialize(core->board, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _GBACoreSetKeys(struct mCore* core, uint32_t keys) {
|
||||
|
@ -452,6 +458,7 @@ struct mCore* GBACoreCreate(void) {
|
|||
core->runFrame = _GBACoreRunFrame;
|
||||
core->runLoop = _GBACoreRunLoop;
|
||||
core->step = _GBACoreStep;
|
||||
core->stateSize = _GBACoreStateSize;
|
||||
core->loadState = _GBACoreLoadState;
|
||||
core->saveState = _GBACoreSaveState;
|
||||
core->setKeys = _GBACoreSetKeys;
|
||||
|
|
|
@ -30,7 +30,7 @@ void GBARRInitRecord(struct GBA* gba) {
|
|||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA);
|
||||
//GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
@ -54,7 +54,7 @@ void GBARRInitPlay(struct GBA* gba) {
|
|||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA);
|
||||
//GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "serialize.h"
|
||||
|
||||
#include "core/serialize.h"
|
||||
#include "core/sync.h"
|
||||
#include "gba/audio.h"
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/io.h"
|
||||
|
@ -23,12 +22,6 @@
|
|||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include "util/png-io.h"
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
|
||||
const uint32_t GBA_SAVESTATE_VERSION = 0x00000001;
|
||||
|
||||
|
@ -215,278 +208,6 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_PNG
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
unsigned stride;
|
||||
const void* pixels = 0;
|
||||
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
|
||||
if (!pixels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
uLongf len = compressBound(sizeof(*state));
|
||||
void* buffer = malloc(len);
|
||||
if (!buffer) {
|
||||
GBADeallocateState(state);
|
||||
return false;
|
||||
}
|
||||
compress(buffer, &len, (const Bytef*) state, sizeof(*state));
|
||||
GBADeallocateState(state);
|
||||
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
if (!png || !info) {
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
if (extdata) {
|
||||
uint32_t i;
|
||||
for (i = 1; i < EXTDATA_MAX; ++i) {
|
||||
if (!extdata->data[i].data) {
|
||||
continue;
|
||||
}
|
||||
uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2;
|
||||
uint32_t* data = malloc(len);
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
STORE_32(i, 0, data);
|
||||
STORE_32(extdata->data[i].size, sizeof(uint32_t), data);
|
||||
compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size);
|
||||
PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||
struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
|
||||
if (!bundle) {
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAs")) {
|
||||
struct GBASerializedState* state = bundle->state;
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
uLongf len = sizeof(*state);
|
||||
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp((const char*) chunk->name, "gbAx")) {
|
||||
struct mStateExtdata* extdata = bundle->extdata;
|
||||
if (!extdata) {
|
||||
return 0;
|
||||
}
|
||||
struct mStateExtdataItem item;
|
||||
if (chunk->size < sizeof(uint32_t) * 2) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t tag;
|
||||
LOAD_32(tag, 0, chunk->data);
|
||||
LOAD_32(item.size, sizeof(uint32_t), chunk->data);
|
||||
uLongf len = item.size;
|
||||
if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) {
|
||||
return 0;
|
||||
}
|
||||
item.data = malloc(item.size);
|
||||
item.clean = free;
|
||||
if (!item.data) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t* data = chunk->data;
|
||||
data += sizeof(uint32_t) * 2;
|
||||
uncompress((Bytef*) item.data, &len, data, chunk->size);
|
||||
item.size = len;
|
||||
mStateExtdataPut(extdata, tag, &item);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct GBASerializedState* _loadPNGState(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;
|
||||
}
|
||||
uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
if (!pixels) {
|
||||
PNGReadClose(png, info, end);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
struct GBABundledState bundle = {
|
||||
.state = state,
|
||||
.extdata = extdata
|
||||
};
|
||||
|
||||
PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
|
||||
bool success = PNGReadHeader(png, info);
|
||||
success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
|
||||
success = success && PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
|
||||
if (success) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4,
|
||||
.data = pixels,
|
||||
.clean = free
|
||||
};
|
||||
mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item);
|
||||
} else {
|
||||
free(pixels);
|
||||
GBADeallocateState(state);
|
||||
return 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
if (flags & SAVESTATE_SAVEDATA) {
|
||||
// TODO: A better way to do this would be nice
|
||||
void* sram = malloc(SIZE_CART_FLASH1M);
|
||||
struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M);
|
||||
if (GBASavedataClone(&gba->memory.savedata, svf)) {
|
||||
struct mStateExtdataItem item = {
|
||||
.size = svf->seek(svf, 0, SEEK_CUR),
|
||||
.data = sram,
|
||||
.clean = free
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
|
||||
} else {
|
||||
free(sram);
|
||||
}
|
||||
svf->close(svf);
|
||||
}
|
||||
struct VFile* cheatVf = 0;
|
||||
if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
|
||||
cheatVf = VFileMemChunk(0, 0);
|
||||
if (cheatVf) {
|
||||
mCheatSaveFile(device, cheatVf);
|
||||
struct mStateExtdataItem item = {
|
||||
.size = cheatVf->size(cheatVf),
|
||||
.data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ),
|
||||
.clean = 0
|
||||
};
|
||||
mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);
|
||||
}
|
||||
};
|
||||
#ifdef USE_PNG
|
||||
if (!(flags & SAVESTATE_SCREENSHOT)) {
|
||||
#else
|
||||
UNUSED(flags);
|
||||
#endif
|
||||
vf->truncate(vf, sizeof(struct GBASerializedState));
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
|
||||
if (!state) {
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
|
||||
mStateExtdataSerialize(&extdata, vf);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
if (cheatVf) {
|
||||
cheatVf->close(cheatVf);
|
||||
}
|
||||
return true;
|
||||
#ifdef USE_PNG
|
||||
}
|
||||
else {
|
||||
bool success = _savePNGState(gba, vf, &extdata);
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata) {
|
||||
#ifdef USE_PNG
|
||||
if (isPNG(vf)) {
|
||||
return _loadPNGState(vf, extdata);
|
||||
}
|
||||
#endif
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) {
|
||||
return false;
|
||||
}
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) {
|
||||
GBADeallocateState(state);
|
||||
return 0;
|
||||
}
|
||||
if (extdata) {
|
||||
mStateExtdataDeserialize(extdata, vf);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
|
||||
struct mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
struct GBASerializedState* state = GBAExtractState(vf, &extdata);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBADeserialize(gba, state);
|
||||
GBADeallocateState(state);
|
||||
|
||||
struct mStateExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
|
||||
if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
|
||||
mCoreSyncForceFrame(gba->sync);
|
||||
} else {
|
||||
mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot");
|
||||
}
|
||||
}
|
||||
if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
|
||||
struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
GBASavedataLoad(&gba->memory.savedata, svf);
|
||||
if (svf) {
|
||||
svf->close(svf);
|
||||
}
|
||||
}
|
||||
if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE] && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) {
|
||||
if (item.size) {
|
||||
struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
|
||||
struct VFile* svf = VFileFromMemory(item.data, item.size);
|
||||
if (svf) {
|
||||
mCheatDeviceClear(device);
|
||||
mCheatParseFile(device, svf);
|
||||
svf->close(svf);
|
||||
}
|
||||
}
|
||||
}
|
||||
mStateExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
|
||||
struct GBASerializedState* GBAAllocateState(void) {
|
||||
return anonymousMemoryMap(sizeof(struct GBASerializedState));
|
||||
}
|
||||
|
|
|
@ -365,11 +365,6 @@ struct VDir;
|
|||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
|
||||
struct mStateExtdata;
|
||||
struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata);
|
||||
struct GBASerializedState* GBAAllocateState(void);
|
||||
void GBADeallocateState(struct GBASerializedState* state);
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@
|
|||
- (NSData *)serializeStateWithError:(NSError **)outError
|
||||
{
|
||||
struct VFile* vf = VFileMemChunk(nil, 0);
|
||||
if (!core->saveState(core, vf, SAVESTATE_SAVEDATA)) {
|
||||
if (!mCoreSaveStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
|
||||
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
|
||||
vf->close(vf);
|
||||
return nil;
|
||||
|
@ -212,7 +212,7 @@
|
|||
- (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError
|
||||
{
|
||||
struct VFile* vf = VFileFromConstMemory(state.bytes, state.length);
|
||||
if (!core->loadState(core, vf, SAVESTATE_SAVEDATA)) {
|
||||
if (!mCoreLoadStateNamed(core, vf, SAVESTATE_SAVEDATA)) {
|
||||
*outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil];
|
||||
vf->close(vf);
|
||||
return NO;
|
||||
|
@ -224,14 +224,14 @@
|
|||
- (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
||||
{
|
||||
struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR);
|
||||
block(core->saveState(core, vf, 0), nil);
|
||||
block(mCoreSaveStateNamed(core, vf, 0), nil);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
- (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block
|
||||
{
|
||||
struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY);
|
||||
block(core->loadState(core, vf, 0), nil);
|
||||
block(mCoreLoadStateNamed(core, vf, 0), nil);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
|
|
|
@ -788,7 +788,7 @@ void GameController::loadState(int slot) {
|
|||
if (!controller->m_backupLoadState) {
|
||||
controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
|
||||
}
|
||||
context->core->saveState(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||
mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||
if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
|
||||
controller->frameAvailable(controller->m_drawContext);
|
||||
controller->stateLoaded(context);
|
||||
|
@ -824,7 +824,7 @@ void GameController::loadBackupState() {
|
|||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET);
|
||||
if (context->core->loadState(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
|
||||
if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
|
||||
mLOG(STATUS, INFO, "Undid state load");
|
||||
controller->frameAvailable(controller->m_drawContext);
|
||||
controller->stateLoaded(context);
|
||||
|
|
|
@ -19,6 +19,7 @@ extern "C" {
|
|||
#ifdef M_CORE_GBA
|
||||
#include "gba/serialize.h"
|
||||
#endif
|
||||
#include "util/memory.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -180,7 +181,7 @@ void LoadSaveState::loadState(int slot) {
|
|||
|
||||
mStateExtdata extdata;
|
||||
mStateExtdataInit(&extdata);
|
||||
GBASerializedState* state = GBAExtractState(vf, &extdata);
|
||||
void* state = mCoreExtractState(thread->core, vf, &extdata);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (!state) {
|
||||
m_slots[slot - 1]->setText(tr("Corrupted"));
|
||||
|
@ -188,7 +189,7 @@ void LoadSaveState::loadState(int slot) {
|
|||
return;
|
||||
}
|
||||
|
||||
QDateTime creation(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL));
|
||||
QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO
|
||||
QImage stateImage;
|
||||
|
||||
mStateExtdataItem item;
|
||||
|
@ -209,7 +210,7 @@ void LoadSaveState::loadState(int slot) {
|
|||
m_slots[slot - 1]->setText(QString());
|
||||
}
|
||||
vf->close(vf);
|
||||
GBADeallocateState(state);
|
||||
mappedMemoryFree(state, thread->core->stateSize(thread->core));
|
||||
}
|
||||
|
||||
void LoadSaveState::triggerState(int slot) {
|
||||
|
|
|
@ -102,7 +102,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
if (savestate) {
|
||||
if (!savestateOverlay) {
|
||||
core->loadState(core, savestate, 0);
|
||||
mCoreLoadStateNamed(core, savestate, 0);
|
||||
} else {
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
savestate->read(savestate, state, sizeof(*state));
|
||||
|
|
|
@ -162,7 +162,7 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc
|
|||
|
||||
core->reset(core);
|
||||
if (_savestate) {
|
||||
core->loadState(core, _savestate, 0);
|
||||
mCoreLoadStateNamed(core, _savestate, 0);
|
||||
}
|
||||
|
||||
core->getGameCode(core, gameCode);
|
||||
|
|
Loading…
Reference in New Issue