mirror of https://github.com/mgba-emu/mgba.git
GBA: Better savestate error reporting
This commit is contained in:
parent
83dfbe6123
commit
2bb16fd0a8
|
@ -61,67 +61,71 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||||
|
bool error = false;
|
||||||
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->biosChecksum != gba->biosChecksum) {
|
if (state->biosChecksum != gba->biosChecksum) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS");
|
GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS");
|
||||||
if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) {
|
if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) {
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
|
if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game");
|
||||||
return;
|
error = true;
|
||||||
} else if (!gba->memory.rom && state->id != 0) {
|
} else if (!gba->memory.rom && state->id != 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->romCrc32 != gba->romCrc32) {
|
if (state->romCrc32 != gba->romCrc32) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game");
|
||||||
}
|
}
|
||||||
if (state->cpu.cycles < 0) {
|
if (state->cpu.cycles < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->video.eventDiff < 0) {
|
if (state->video.eventDiff < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->video.nextHblank - state->video.eventDiff < 0) {
|
if (state->video.nextHblank - state->video.eventDiff < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) {
|
if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->audio.eventDiff < 0) {
|
if (state->audio.eventDiff < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) {
|
if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) {
|
if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->audio.ch3.nextEvent < 0) {
|
if (state->audio.ch3.nextEvent < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) {
|
if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative");
|
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative");
|
||||||
return;
|
error = true;
|
||||||
}
|
}
|
||||||
int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);
|
int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);
|
||||||
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
|
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
|
||||||
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
|
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
|
||||||
return;
|
error = true;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs));
|
memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs));
|
||||||
gba->cpu->cpsr = state->cpu.cpsr;
|
gba->cpu->cpsr = state->cpu.cpsr;
|
||||||
|
@ -166,6 +170,7 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||||
if (gba->rr) {
|
if (gba->rr) {
|
||||||
gba->rr->stateLoaded(gba->rr, state);
|
gba->rr->stateLoaded(gba->rr, state);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -218,8 +223,7 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||||
struct GBASerializedState state;
|
struct GBASerializedState state;
|
||||||
uLongf len = sizeof(state);
|
uLongf len = sizeof(state);
|
||||||
uncompress((Bytef*) &state, &len, chunk->data, chunk->size);
|
uncompress((Bytef*) &state, &len, chunk->data, chunk->size);
|
||||||
GBADeserialize(png_get_user_chunk_ptr(png), &state);
|
return GBADeserialize(png_get_user_chunk_ptr(png), &state);
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
||||||
|
@ -254,6 +258,8 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
if (success) {
|
if (success) {
|
||||||
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot);
|
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot);
|
||||||
|
} else {
|
||||||
|
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -268,6 +274,8 @@ bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
|
||||||
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);
|
||||||
|
} else {
|
||||||
|
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to load", slot);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -304,9 +312,9 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GBADeserialize(gba, state);
|
bool success = GBADeserialize(gba, state);
|
||||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GBASerializedState* GBAAllocateState(void) {
|
struct GBASerializedState* GBAAllocateState(void) {
|
||||||
|
|
|
@ -321,7 +321,7 @@ struct VDir;
|
||||||
struct GBAThread;
|
struct GBAThread;
|
||||||
|
|
||||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||||
void 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, bool screenshot);
|
||||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||||
|
|
|
@ -573,9 +573,10 @@ void GameController::loadState(int slot) {
|
||||||
}
|
}
|
||||||
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
|
||||||
GameController* controller = static_cast<GameController*>(context->userData);
|
GameController* controller = static_cast<GameController*>(context->userData);
|
||||||
GBALoadState(context, context->stateDir, controller->m_stateSlot);
|
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
|
||||||
controller->stateLoaded(context);
|
controller->stateLoaded(context);
|
||||||
controller->frameAvailable(controller->m_drawContext);
|
controller->frameAvailable(controller->m_drawContext);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue