GBA: Add extra data section to the end of savestates

This commit is contained in:
Jeffrey Pfau 2015-12-28 03:26:17 -05:00
parent 70b9a1bfe0
commit 5c007289e4
14 changed files with 288 additions and 52 deletions

View File

@ -8,8 +8,10 @@
#include "util/common.h" #include "util/common.h"
#define LOAD_64 LOAD_64LE
#define LOAD_32 LOAD_32LE #define LOAD_32 LOAD_32LE
#define LOAD_16 LOAD_16LE #define LOAD_16 LOAD_16LE
#define STORE_64 STORE_64LE
#define STORE_32 STORE_32LE #define STORE_32 STORE_32LE
#define STORE_16 STORE_16LE #define STORE_16 STORE_16LE

View File

@ -311,14 +311,14 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
case RUNNER_SAVE_STATE: case RUNNER_SAVE_STATE:
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true); vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true);
if (vf) { if (vf) {
GBASaveStateNamed(runner->context.gba, vf, true); GBASaveStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
vf->close(vf); vf->close(vf);
} }
break; break;
case RUNNER_LOAD_STATE: case RUNNER_LOAD_STATE:
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false); vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false);
if (vf) { if (vf) {
GBALoadStateNamed(runner->context.gba, vf); GBALoadStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
vf->close(vf); vf->close(vf);
} }
break; break;

View File

@ -27,7 +27,7 @@ void GBARRInitRecord(struct GBA* gba) {
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
GBASaveStateNamed(gba, vf, false); GBASaveStateNamed(gba, vf, 0);
vf->close(vf); vf->close(vf);
} else { } else {
ARMReset(gba->cpu); ARMReset(gba->cpu);
@ -51,7 +51,7 @@ void GBARRInitPlay(struct GBA* gba) {
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY); struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
GBALoadStateNamed(gba, vf); GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT);
vf->close(vf); vf->close(vf);
} else { } else {
ARMReset(gba->cpu); ARMReset(gba->cpu);

View File

@ -123,6 +123,33 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
return true; return true;
} }
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
if (savedata->data) {
switch (savedata->type) {
case SAVEDATA_SRAM:
return in->read(in, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
case SAVEDATA_FLASH512:
return in->read(in, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
case SAVEDATA_FLASH1M:
return in->read(in, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
case SAVEDATA_EEPROM:
return in->read(in, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
case SAVEDATA_AUTODETECT:
case SAVEDATA_FORCE_NONE:
return true;
}
} else if (savedata->vf) {
off_t read = 0;
uint8_t buffer[2048];
do {
in->read(in, buffer, read);
read = savedata->vf->write(savedata->vf, buffer, sizeof(buffer));
} while (read == sizeof(buffer));
return read >= 0;
}
return true;
}
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) { void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
if (savedata->type != SAVEDATA_AUTODETECT) { if (savedata->type != SAVEDATA_AUTODETECT) {
struct VFile* vf = savedata->vf; struct VFile* vf = savedata->vf;
@ -441,7 +468,7 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
} }
} }
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) { void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
state->savedata.type = savedata->type; state->savedata.type = savedata->type;
state->savedata.command = savedata->command; state->savedata.command = savedata->command;
GBASerializedSavedataFlags flags = 0; GBASerializedSavedataFlags flags = 0;
@ -453,11 +480,9 @@ void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializ
STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress); STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
STORE_16(savedata->settling, 0, &state->savedata.settlingSector); STORE_16(savedata->settling, 0, &state->savedata.settlingSector);
STORE_16(savedata->dust, 0, &state->savedata.settlingDust); STORE_16(savedata->dust, 0, &state->savedata.settlingDust);
UNUSED(includeData); // TODO
} }
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData) { void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state) {
if (state->savedata.type == SAVEDATA_FORCE_NONE) { if (state->savedata.type == SAVEDATA_FORCE_NONE) {
return; return;
} }
@ -476,8 +501,6 @@ void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerial
if (savedata->type == SAVEDATA_FLASH1M) { if (savedata->type == SAVEDATA_FLASH1M) {
_flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags)); _flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags));
} }
UNUSED(includeData); // TODO
} }
void _flashSwitchBank(struct GBASavedata* savedata, int bank) { void _flashSwitchBank(struct GBASavedata* savedata, int bank) {

View File

@ -94,6 +94,7 @@ void GBASavedataDeinit(struct GBASavedata* savedata);
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataUnmask(struct GBASavedata* savedata); void GBASavedataUnmask(struct GBASavedata* savedata);
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out); bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in);
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming); void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming);
void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming); void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming);
@ -109,7 +110,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount); void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount);
struct GBASerializedState; struct GBASerializedState;
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData); void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state);
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData); void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state);
#endif #endif

View File

@ -28,6 +28,17 @@ struct GBAExtdata {
struct GBAExtdataItem data[EXTDATA_MAX]; struct GBAExtdataItem data[EXTDATA_MAX];
}; };
struct GBABundledState {
struct GBASerializedState* state;
struct GBAExtdata* extdata;
};
struct GBAExtdataHeader {
uint32_t tag;
int32_t size;
int64_t offset;
};
void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
STORE_32(GBA_SAVESTATE_MAGIC, 0, &state->versionMagic); STORE_32(GBA_SAVESTATE_MAGIC, 0, &state->versionMagic);
STORE_32(gba->biosChecksum, 0, &state->biosChecksum); STORE_32(gba->biosChecksum, 0, &state->biosChecksum);
@ -65,7 +76,7 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
GBAIOSerialize(gba, state); GBAIOSerialize(gba, state);
GBAVideoSerialize(&gba->video, state); GBAVideoSerialize(&gba->video, state);
GBAAudioSerialize(&gba->audio, state); GBAAudioSerialize(&gba->audio, state);
GBASavedataSerialize(&gba->memory.savedata, state, false); GBASavedataSerialize(&gba->memory.savedata, state);
state->associatedStreamId = 0; state->associatedStreamId = 0;
if (gba->rr) { if (gba->rr) {
@ -173,7 +184,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
GBAIODeserialize(gba, state); GBAIODeserialize(gba, state);
GBAVideoDeserialize(&gba->video, state); GBAVideoDeserialize(&gba->video, state);
GBAAudioDeserialize(&gba->audio, state); GBAAudioDeserialize(&gba->audio, state);
GBASavedataDeserialize(&gba->memory.savedata, state, false); GBASavedataDeserialize(&gba->memory.savedata, state);
if (gba->rr) { if (gba->rr) {
gba->rr->stateLoaded(gba->rr, state); gba->rr->stateLoaded(gba->rr, state);
@ -188,7 +199,7 @@ struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool writ
} }
#ifdef USE_PNG #ifdef USE_PNG
static bool _savePNGState(struct GBA* gba, struct VFile* vf) { static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
unsigned stride; unsigned stride;
const void* pixels = 0; const void* pixels = 0;
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
@ -219,19 +230,72 @@ static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
} }
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
PNGWriteCustomChunk(png, "gbAs", len, buffer); 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); PNGWriteClose(png, info);
free(buffer); free(buffer);
return true; return true;
} }
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
if (strcmp((const char*) chunk->name, "gbAs") != 0) { struct GBABundledState* bundle = png_get_user_chunk_ptr(png);
if (!bundle) {
return 0; return 0;
} }
struct GBASerializedState* state = png_get_user_chunk_ptr(png); if (!strcmp((const char*) chunk->name, "gbAs")) {
uLongf len = sizeof(*state); struct GBASerializedState* state = bundle->state;
uncompress((Bytef*) state, &len, chunk->data, chunk->size); if (!state) {
return 1; return 0;
}
uLongf len = sizeof(*state);
uncompress((Bytef*) state, &len, chunk->data, chunk->size);
return 1;
}
if (!strcmp((const char*) chunk->name, "gbAx")) {
struct GBAExtdata* extdata = bundle->extdata;
if (!extdata) {
return 0;
}
struct GBAExtdataItem 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) {
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;
GBAExtdataPut(extdata, tag, &item);
return 1;
}
return 0;
} }
static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) { static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) {
@ -249,8 +313,12 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd
} }
struct GBASerializedState* state = GBAAllocateState(); struct GBASerializedState* state = GBAAllocateState();
struct GBABundledState bundle = {
.state = state,
.extdata = extdata
};
PNGInstallChunkHandler(png, state, _loadPNGChunkHandler, "gbAs"); PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx");
bool success = PNGReadHeader(png, info); bool success = PNGReadHeader(png, info);
success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS); success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
success = success && PNGReadFooter(png, end); success = success && PNGReadFooter(png, end);
@ -272,13 +340,12 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd
} }
#endif #endif
#ifndef _3DS bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, int flags) {
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) {
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true); struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true);
if (!vf) { if (!vf) {
return false; return false;
} }
bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot); bool success = GBASaveStateNamed(threadContext->gba, vf, flags);
vf->close(vf); vf->close(vf);
if (success) { if (success) {
#if SAVESTATE_DEBUG #if SAVESTATE_DEBUG
@ -288,7 +355,7 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
memcpy(backup, threadContext->gba, sizeof(*backup)); memcpy(backup, threadContext->gba, sizeof(*backup));
memset(threadContext->gba->memory.io, 0, sizeof(threadContext->gba->memory.io)); memset(threadContext->gba->memory.io, 0, sizeof(threadContext->gba->memory.io));
memset(threadContext->gba->timers, 0, sizeof(threadContext->gba->timers)); memset(threadContext->gba->timers, 0, sizeof(threadContext->gba->timers));
GBALoadStateNamed(threadContext->gba, vf); GBALoadStateNamed(threadContext->gba, vf, flags);
if (memcmp(backup, threadContext->gba, sizeof(*backup))) { if (memcmp(backup, threadContext->gba, sizeof(*backup))) {
char suffix[16] = { '\0' }; char suffix[16] = { '\0' };
struct VFile* vf2; struct VFile* vf2;
@ -317,13 +384,13 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
return success; return success;
} }
bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) { bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot, int flags) {
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false); struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false);
if (!vf) { if (!vf) {
return false; return false;
} }
threadContext->rewindBufferSize = 0; threadContext->rewindBufferSize = 0;
bool success = GBALoadStateNamed(threadContext->gba, vf); bool success = GBALoadStateNamed(threadContext->gba, vf, flags);
vf->close(vf); vf->close(vf);
if (success) { if (success) {
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot); GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot);
@ -332,28 +399,52 @@ bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
} }
return success; return success;
} }
#endif
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) { bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
struct GBAExtdata extdata;
GBAExtdataInit(&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 GBAExtdataItem item = {
.size = svf->seek(svf, 0, SEEK_CUR),
.data = sram,
.clean = free
};
GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item);
} else {
free(sram);
}
svf->close(svf);
}
#ifdef USE_PNG #ifdef USE_PNG
if (!screenshot) { if (!(flags & SAVESTATE_SCREENSHOT)) {
#else #else
UNUSED(screenshot); UNUSED(flags);
#endif #endif
vf->truncate(vf, sizeof(struct GBASerializedState)); vf->truncate(vf, sizeof(struct GBASerializedState));
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE); struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
if (!state) { if (!state) {
GBAExtdataDeinit(&extdata);
return false; return false;
} }
GBASerialize(gba, state); GBASerialize(gba, state);
vf->unmap(vf, state, sizeof(struct GBASerializedState)); vf->unmap(vf, state, sizeof(struct GBASerializedState));
vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
GBAExtdataSerialize(&extdata, vf);
GBAExtdataDeinit(&extdata);
return true; return true;
#ifdef USE_PNG #ifdef USE_PNG
} }
else { else {
return _savePNGState(gba, vf); bool success = _savePNGState(gba, vf, &extdata);
GBAExtdataDeinit(&extdata);
return success;
} }
#endif #endif
GBAExtdataDeinit(&extdata);
return false; return false;
} }
@ -371,10 +462,13 @@ struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata*
GBADeallocateState(state); GBADeallocateState(state);
return 0; return 0;
} }
if (extdata) {
GBAExtdataDeserialize(extdata, vf);
}
return state; return state;
} }
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) { bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
struct GBAExtdata extdata; struct GBAExtdata extdata;
GBAExtdataInit(&extdata); GBAExtdataInit(&extdata);
struct GBASerializedState* state = GBAExtractState(vf, &extdata); struct GBASerializedState* state = GBAExtractState(vf, &extdata);
@ -384,10 +478,21 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
bool success = GBADeserialize(gba, state); bool success = GBADeserialize(gba, state);
GBADeallocateState(state); GBADeallocateState(state);
struct GBAExtdataItem screenshot; struct GBAExtdataItem item;
if (GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &screenshot)) { if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, screenshot.data); if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) {
GBASyncForceFrame(gba->sync); gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data);
GBASyncForceFrame(gba->sync);
} else {
GBALog(gba, GBA_LOG_WARN, "Savestate includes invalid screenshot");
}
}
if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) {
struct VFile* svf = VFileFromMemory(item.data, item.size);
if (svf) {
GBASavedataLoad(&gba->memory.savedata, svf);
svf->close(svf);
}
} }
GBAExtdataDeinit(&extdata); GBAExtdataDeinit(&extdata);
return success; return success;
@ -427,6 +532,89 @@ bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GB
return true; return true;
} }
bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) {
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
ssize_t size = 2;
size_t i = 0;
for (i = 1; i < EXTDATA_MAX; ++i) {
if (extdata->data[i].data) {
size += sizeof(uint64_t) * 2;
}
}
if (size == 2) {
return true;
}
struct GBAExtdataHeader* header = malloc(size);
position += size;
size_t j;
for (i = 1, j = 0; i < EXTDATA_MAX; ++i) {
if (extdata->data[i].data) {
STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]);
STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]);
STORE_64(position, offsetof(struct GBAExtdataHeader, offset), &header[j]);
position += extdata->data[i].size;
++j;
}
}
header[j].tag = 0;
header[j].size = 0;
header[j].offset = 0;
if (vf->write(vf, header, size) != size) {
free(header);
return false;
}
free(header);
for (i = 1; i < EXTDATA_MAX; ++i) {
if (extdata->data[i].data) {
if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) {
return false;
}
}
}
return true;
}
bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) {
while (true) {
struct GBAExtdataHeader buffer, header;
if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) {
return false;
}
LOAD_32(header.tag, 0, &buffer.tag);
LOAD_32(header.size, 0, &buffer.size);
LOAD_64(header.offset, 0, &buffer.offset);
if (header.tag == EXTDATA_NONE) {
break;
}
if (header.tag >= EXTDATA_MAX) {
continue;
}
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
return false;
}
struct GBAExtdataItem item = {
.data = malloc(header.size),
.size = header.size,
.clean = free
};
if (!item.data) {
continue;
}
if (vf->read(vf, item.data, header.size) != header.size) {
free(item.data);
continue;
}
GBAExtdataPut(extdata, header.tag, &item);
vf->seek(vf, position, SEEK_SET);
};
return true;
}
struct GBASerializedState* GBAAllocateState(void) { struct GBASerializedState* GBAAllocateState(void) {
return anonymousMemoryMap(sizeof(struct GBASerializedState)); return anonymousMemoryMap(sizeof(struct GBASerializedState));
} }

View File

@ -333,12 +333,16 @@ struct GBASerializedState {
enum GBAExtdataTag { enum GBAExtdataTag {
EXTDATA_NONE = 0, EXTDATA_NONE = 0,
EXTDATA_SCREENSHOT = 1, EXTDATA_SCREENSHOT = 1,
EXTDATA_SAVEDATA = 2,
EXTDATA_MAX EXTDATA_MAX
}; };
#define SAVESTATE_SCREENSHOT 1
#define SAVESTATE_SAVEDATA 2
struct GBAExtdata; struct GBAExtdata;
struct GBAExtdataItem { struct GBAExtdataItem {
uint64_t size; int32_t size;
void* data; void* data;
void (*clean)(void*); void (*clean)(void*);
}; };
@ -349,17 +353,19 @@ struct GBAThread;
void GBASerialize(struct GBA* gba, struct GBASerializedState* state); void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot); bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, int flags);
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot); bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot, int flags);
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write); struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write);
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot); bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags);
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf); bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags);
bool GBAExtdataInit(struct GBAExtdata*); bool GBAExtdataInit(struct GBAExtdata*);
void GBAExtdataDeinit(struct GBAExtdata*); void GBAExtdataDeinit(struct GBAExtdata*);
void GBAExtdataPut(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*); void GBAExtdataPut(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*);
bool GBAExtdataGet(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*); bool GBAExtdataGet(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*);
bool GBAExtdataSerialize(struct GBAExtdata* extpdata, struct VFile* vf);
bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf);
struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata); struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata);
struct GBASerializedState* GBAAllocateState(void); struct GBASerializedState* GBAAllocateState(void);

View File

@ -106,7 +106,7 @@ static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
GBALoadState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue); GBALoadState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, SAVESTATE_SCREENSHOT);
} }
static void _rewind(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { static void _rewind(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
@ -133,6 +133,6 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system;
GBASaveState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, true); GBASaveState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, SAVESTATE_SCREENSHOT);
} }
#endif #endif

View File

@ -108,7 +108,7 @@ GameController::GameController(QObject* parent)
context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4]; context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4];
controller->m_fpsTarget = context->fpsTarget; controller->m_fpsTarget = context->fpsTarget;
if (GBALoadState(context, context->stateDir, 0)) { if (GBALoadState(context, context->stateDir, 0, SAVESTATE_SCREENSHOT)) {
VFile* vf = GBAGetState(context->gba, context->stateDir, 0, true); VFile* vf = GBAGetState(context->gba, context->stateDir, 0, true);
if (vf) { if (vf) {
vf->truncate(vf, 0); vf->truncate(vf, 0);
@ -701,7 +701,7 @@ void GameController::loadState(int slot) {
controller->m_backupLoadState = new GBASerializedState; controller->m_backupLoadState = new GBASerializedState;
} }
GBASerialize(context->gba, controller->m_backupLoadState); GBASerialize(context->gba, controller->m_backupLoadState);
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) { if (GBALoadState(context, context->stateDir, controller->m_stateSlot, SAVESTATE_SCREENSHOT)) {
controller->frameAvailable(controller->m_drawContext); controller->frameAvailable(controller->m_drawContext);
controller->stateLoaded(context); controller->stateLoaded(context);
} }
@ -720,7 +720,7 @@ void GameController::saveState(int slot) {
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
vf->close(vf); vf->close(vf);
} }
GBASaveState(context, context->stateDir, controller->m_stateSlot, true); GBASaveState(context, context->stateDir, controller->m_stateSlot, SAVESTATE_SCREENSHOT | EXTDATA_SAVEDATA);
}); });
} }

View File

@ -423,7 +423,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer
case SDLK_F8: case SDLK_F8:
case SDLK_F9: case SDLK_F9:
GBAThreadInterrupt(context); GBAThreadInterrupt(context);
GBASaveState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1, true); GBASaveState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT);
GBAThreadContinue(context); GBAThreadContinue(context);
break; break;
default: default:
@ -441,7 +441,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer
case SDLK_F8: case SDLK_F8:
case SDLK_F9: case SDLK_F9:
GBAThreadInterrupt(context); GBAThreadInterrupt(context);
GBALoadState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1); GBALoadState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT);
GBAThreadContinue(context); GBAThreadContinue(context);
break; break;
default: default:

View File

@ -112,7 +112,7 @@ int main(int argc, char** argv) {
} }
if (savestate) { if (savestate) {
if (!savestateOverlay) { if (!savestateOverlay) {
GBALoadStateNamed(context.gba, savestate); GBALoadStateNamed(context.gba, savestate, 0);
} else { } else {
struct GBASerializedState* state = GBAAllocateState(); struct GBASerializedState* state = GBAAllocateState();
savestate->read(savestate, state, sizeof(*state)); savestate->read(savestate, state, sizeof(*state));

View File

@ -238,7 +238,7 @@ static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, i
} }
static void _loadSavestate(struct GBAThread* context) { static void _loadSavestate(struct GBAThread* context) {
GBALoadStateNamed(context->gba, _savestate); GBALoadStateNamed(context->gba, _savestate, 0);
_savestate->close(_savestate); _savestate->close(_savestate);
_savestate = 0; _savestate = 0;
} }

View File

@ -73,18 +73,25 @@ typedef intptr_t ssize_t;
void* _ptr = (ARR); \ void* _ptr = (ARR); \
__asm__("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ __asm__("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \
} }
#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3])
#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = __builtin_bswap64(SRC)
#elif defined __BIG_ENDIAN__ #elif defined __BIG_ENDIAN__
#if defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) #if defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3])
#define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2])
#define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(((uint16_t*) ARR)[(ADDR) >> 1]) #define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(((uint16_t*) ARR)[(ADDR) >> 1])
#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = __builtin_bswap64(SRC)
#define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = __builtin_bswap32(SRC) #define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = __builtin_bswap32(SRC)
#define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = __builtin_bswap16(SRC) #define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = __builtin_bswap16(SRC)
#else #else
#error Big endian build not supported on this platform. #error Big endian build not supported on this platform.
#endif #endif
#else #else
#define LOAD_64LE(DEST, ADDR, ARR) DEST = ((uint64_t*) ARR)[(ADDR) >> 3]
#define LOAD_32LE(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2] #define LOAD_32LE(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2]
#define LOAD_16LE(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1] #define LOAD_16LE(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1]
#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = SRC
#define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC #define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC
#define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC #define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC
#endif #endif

View File

@ -126,7 +126,16 @@ bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler
return false; return false;
} }
png_set_read_user_chunk_fn(png, context, handler); png_set_read_user_chunk_fn(png, context, handler);
png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_bytep) chunkName, 1); int len = strlen(chunkName);
int chunks = 0;
char* chunkList = strdup(chunkName);
int i;
for (i = 4; i <= len; i += 5) {
chunkList[i] = '\0';
++chunks;
}
png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_bytep) chunkList, chunks);
free(chunkList);
return true; return true;
} }