mirror of https://github.com/mgba-emu/mgba.git
GBA: Add extra data section to the end of savestates
This commit is contained in:
parent
70b9a1bfe0
commit
5c007289e4
|
@ -8,8 +8,10 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#define LOAD_64 LOAD_64LE
|
||||
#define LOAD_32 LOAD_32LE
|
||||
#define LOAD_16 LOAD_16LE
|
||||
#define STORE_64 STORE_64LE
|
||||
#define STORE_32 STORE_32LE
|
||||
#define STORE_16 STORE_16LE
|
||||
|
||||
|
|
|
@ -311,14 +311,14 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
|
|||
case RUNNER_SAVE_STATE:
|
||||
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true);
|
||||
if (vf) {
|
||||
GBASaveStateNamed(runner->context.gba, vf, true);
|
||||
GBASaveStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
case RUNNER_LOAD_STATE:
|
||||
vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false);
|
||||
if (vf) {
|
||||
GBALoadStateNamed(runner->context.gba, vf);
|
||||
GBALoadStateNamed(runner->context.gba, vf, SAVESTATE_SCREENSHOT);
|
||||
vf->close(vf);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -27,7 +27,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, false);
|
||||
GBASaveStateNamed(gba, vf, 0);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
@ -51,7 +51,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);
|
||||
GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
|
|
|
@ -123,6 +123,33 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
|||
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) {
|
||||
if (savedata->type != SAVEDATA_AUTODETECT) {
|
||||
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.command = savedata->command;
|
||||
GBASerializedSavedataFlags flags = 0;
|
||||
|
@ -453,11 +480,9 @@ void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializ
|
|||
STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
|
||||
STORE_16(savedata->settling, 0, &state->savedata.settlingSector);
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -476,8 +501,6 @@ void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerial
|
|||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
_flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags));
|
||||
}
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
||||
|
|
|
@ -94,6 +94,7 @@ void GBASavedataDeinit(struct GBASavedata* savedata);
|
|||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
|
||||
void GBASavedataUnmask(struct GBASavedata* savedata);
|
||||
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 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);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -28,6 +28,17 @@ struct GBAExtdata {
|
|||
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) {
|
||||
STORE_32(GBA_SAVESTATE_MAGIC, 0, &state->versionMagic);
|
||||
STORE_32(gba->biosChecksum, 0, &state->biosChecksum);
|
||||
|
@ -65,7 +76,7 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state, false);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state);
|
||||
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
|
@ -173,7 +184,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBAIODeserialize(gba, state);
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state, false);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state);
|
||||
|
||||
if (gba->rr) {
|
||||
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
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) {
|
||||
unsigned stride;
|
||||
const void* pixels = 0;
|
||||
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
|
||||
|
@ -219,20 +230,73 @@ static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
|||
}
|
||||
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) {
|
||||
if (strcmp((const char*) chunk->name, "gbAs") != 0) {
|
||||
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;
|
||||
}
|
||||
struct GBASerializedState* state = png_get_user_chunk_ptr(png);
|
||||
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) {
|
||||
png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
|
||||
|
@ -249,8 +313,12 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd
|
|||
}
|
||||
|
||||
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);
|
||||
success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
|
||||
success = success && PNGReadFooter(png, end);
|
||||
|
@ -272,13 +340,12 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifndef _3DS
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) {
|
||||
bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, int flags) {
|
||||
struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);
|
||||
bool success = GBASaveStateNamed(threadContext->gba, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
#if SAVESTATE_DEBUG
|
||||
|
@ -288,7 +355,7 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
|
|||
memcpy(backup, threadContext->gba, sizeof(*backup));
|
||||
memset(threadContext->gba->memory.io, 0, sizeof(threadContext->gba->memory.io));
|
||||
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))) {
|
||||
char suffix[16] = { '\0' };
|
||||
struct VFile* vf2;
|
||||
|
@ -317,13 +384,13 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
|
|||
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);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
threadContext->rewindBufferSize = 0;
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf);
|
||||
bool success = GBALoadStateNamed(threadContext->gba, vf, flags);
|
||||
vf->close(vf);
|
||||
if (success) {
|
||||
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;
|
||||
}
|
||||
#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
|
||||
if (!screenshot) {
|
||||
if (!(flags & SAVESTATE_SCREENSHOT)) {
|
||||
#else
|
||||
UNUSED(screenshot);
|
||||
UNUSED(flags);
|
||||
#endif
|
||||
vf->truncate(vf, sizeof(struct GBASerializedState));
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
|
||||
if (!state) {
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET);
|
||||
GBAExtdataSerialize(&extdata, vf);
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return true;
|
||||
#ifdef USE_PNG
|
||||
}
|
||||
else {
|
||||
return _savePNGState(gba, vf);
|
||||
bool success = _savePNGState(gba, vf, &extdata);
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
GBAExtdataDeinit(&extdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -371,10 +462,13 @@ struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata*
|
|||
GBADeallocateState(state);
|
||||
return 0;
|
||||
}
|
||||
if (extdata) {
|
||||
GBAExtdataDeserialize(extdata, vf);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) {
|
||||
struct GBAExtdata extdata;
|
||||
GBAExtdataInit(&extdata);
|
||||
struct GBASerializedState* state = GBAExtractState(vf, &extdata);
|
||||
|
@ -384,10 +478,21 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
|||
bool success = GBADeserialize(gba, state);
|
||||
GBADeallocateState(state);
|
||||
|
||||
struct GBAExtdataItem screenshot;
|
||||
if (GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &screenshot)) {
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, screenshot.data);
|
||||
struct GBAExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&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);
|
||||
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);
|
||||
return success;
|
||||
|
@ -427,6 +532,89 @@ bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GB
|
|||
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) {
|
||||
return anonymousMemoryMap(sizeof(struct GBASerializedState));
|
||||
}
|
||||
|
|
|
@ -333,12 +333,16 @@ struct GBASerializedState {
|
|||
enum GBAExtdataTag {
|
||||
EXTDATA_NONE = 0,
|
||||
EXTDATA_SCREENSHOT = 1,
|
||||
EXTDATA_SAVEDATA = 2,
|
||||
EXTDATA_MAX
|
||||
};
|
||||
|
||||
#define SAVESTATE_SCREENSHOT 1
|
||||
#define SAVESTATE_SAVEDATA 2
|
||||
|
||||
struct GBAExtdata;
|
||||
struct GBAExtdataItem {
|
||||
uint64_t size;
|
||||
int32_t size;
|
||||
void* data;
|
||||
void (*clean)(void*);
|
||||
};
|
||||
|
@ -349,17 +353,19 @@ struct GBAThread;
|
|||
void GBASerialize(struct GBA* gba, 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 GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, int flags);
|
||||
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);
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf);
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags);
|
||||
|
||||
bool GBAExtdataInit(struct GBAExtdata*);
|
||||
void GBAExtdataDeinit(struct GBAExtdata*);
|
||||
void GBAExtdataPut(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* GBAAllocateState(void);
|
||||
|
|
|
@ -106,7 +106,7 @@ static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
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) {
|
||||
|
@ -133,6 +133,6 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
|||
|
||||
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
|
||||
|
|
|
@ -108,7 +108,7 @@ GameController::GameController(QObject* parent)
|
|||
context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4];
|
||||
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);
|
||||
if (vf) {
|
||||
vf->truncate(vf, 0);
|
||||
|
@ -701,7 +701,7 @@ void GameController::loadState(int slot) {
|
|||
controller->m_backupLoadState = new GBASerializedState;
|
||||
}
|
||||
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->stateLoaded(context);
|
||||
}
|
||||
|
@ -720,7 +720,7 @@ void GameController::saveState(int slot) {
|
|||
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
||||
vf->close(vf);
|
||||
}
|
||||
GBASaveState(context, context->stateDir, controller->m_stateSlot, true);
|
||||
GBASaveState(context, context->stateDir, controller->m_stateSlot, SAVESTATE_SCREENSHOT | EXTDATA_SAVEDATA);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -423,7 +423,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer
|
|||
case SDLK_F8:
|
||||
case SDLK_F9:
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
|
@ -441,7 +441,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer
|
|||
case SDLK_F8:
|
||||
case SDLK_F9:
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -112,7 +112,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
if (savestate) {
|
||||
if (!savestateOverlay) {
|
||||
GBALoadStateNamed(context.gba, savestate);
|
||||
GBALoadStateNamed(context.gba, savestate, 0);
|
||||
} else {
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
savestate->read(savestate, state, sizeof(*state));
|
||||
|
|
|
@ -238,7 +238,7 @@ static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, i
|
|||
}
|
||||
|
||||
static void _loadSavestate(struct GBAThread* context) {
|
||||
GBALoadStateNamed(context->gba, _savestate);
|
||||
GBALoadStateNamed(context->gba, _savestate, 0);
|
||||
_savestate->close(_savestate);
|
||||
_savestate = 0;
|
||||
}
|
||||
|
|
|
@ -73,18 +73,25 @@ typedef intptr_t ssize_t;
|
|||
void* _ptr = (ARR); \
|
||||
__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__
|
||||
#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_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_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = __builtin_bswap16(SRC)
|
||||
#else
|
||||
#error Big endian build not supported on this platform.
|
||||
#endif
|
||||
#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_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_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC
|
||||
#endif
|
||||
|
|
|
@ -126,7 +126,16 @@ bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler
|
|||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue