mirror of https://github.com/mgba-emu/mgba.git
GB: First pass at savestates
This commit is contained in:
parent
a3a380d9f5
commit
5a2c6d037d
102
src/gb/audio.c
102
src/gb/audio.c
|
@ -8,6 +8,7 @@
|
||||||
#include "core/interface.h"
|
#include "core/interface.h"
|
||||||
#include "core/sync.h"
|
#include "core/sync.h"
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
#include "gb/io.h"
|
#include "gb/io.h"
|
||||||
|
|
||||||
#ifdef _3DS
|
#ifdef _3DS
|
||||||
|
@ -859,3 +860,104 @@ void _scheduleEvent(struct GBAudio* audio) {
|
||||||
audio->nextEvent = 0;
|
audio->nextEvent = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) {
|
||||||
|
uint32_t flags = 0;
|
||||||
|
uint32_t ch1Flags = 0;
|
||||||
|
uint32_t ch2Flags = 0;
|
||||||
|
uint32_t ch4Flags = 0;
|
||||||
|
|
||||||
|
flags = GBSerializedAudioFlagsSetFrame(flags, audio->frame);
|
||||||
|
|
||||||
|
flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweepEnable);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweepOccurred);
|
||||||
|
ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length);
|
||||||
|
ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep);
|
||||||
|
ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.realFrequency);
|
||||||
|
STORE_32LE(ch1Flags, 0, &state->ch1.envelope);
|
||||||
|
STORE_32LE(audio->nextFrame, 0, &state->ch1.nextFrame);
|
||||||
|
STORE_32LE(audio->nextCh1, 0, &state->ch1.nextEvent);
|
||||||
|
|
||||||
|
flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi);
|
||||||
|
ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length);
|
||||||
|
ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep);
|
||||||
|
STORE_32LE(ch2Flags, 0, &state->ch2.envelope);
|
||||||
|
STORE_32LE(audio->nextCh2, 0, &state->ch2.nextEvent);
|
||||||
|
|
||||||
|
memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks));
|
||||||
|
STORE_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||||
|
STORE_32LE(audio->nextCh3, 0, &state->ch3.nextEvent);
|
||||||
|
|
||||||
|
flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume);
|
||||||
|
flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead);
|
||||||
|
STORE_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr);
|
||||||
|
ch4Flags = GBSerializedAudioEnvelopeSetLength(ch4Flags, audio->ch4.length);
|
||||||
|
ch4Flags = GBSerializedAudioEnvelopeSetNextStep(ch4Flags, audio->ch4.envelope.nextStep);
|
||||||
|
STORE_32LE(ch4Flags, 0, &state->ch4.envelope);
|
||||||
|
STORE_32LE(audio->nextCh4, 0, &state->ch4.nextEvent);
|
||||||
|
|
||||||
|
STORE_32LE(flags, 0, flagsOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn) {
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t ch1Flags = 0;
|
||||||
|
uint32_t ch2Flags = 0;
|
||||||
|
uint32_t ch4Flags = 0;
|
||||||
|
|
||||||
|
LOAD_32LE(flags, 0, flagsIn);
|
||||||
|
LOAD_32LE(ch1Flags, 0, &state->ch1.envelope);
|
||||||
|
audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags);
|
||||||
|
audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags);
|
||||||
|
audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags);
|
||||||
|
audio->ch1.sweepEnable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags);
|
||||||
|
audio->ch1.sweepOccurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags);
|
||||||
|
audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags);
|
||||||
|
audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags);
|
||||||
|
audio->ch1.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags);
|
||||||
|
LOAD_32LE(audio->nextFrame, 0, &state->ch1.nextFrame);
|
||||||
|
LOAD_32LE(audio->nextCh1, 0, &state->ch1.nextEvent);
|
||||||
|
|
||||||
|
LOAD_32LE(ch2Flags, 0, &state->ch1.envelope);
|
||||||
|
audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags);
|
||||||
|
audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags);
|
||||||
|
audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags);
|
||||||
|
audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags);
|
||||||
|
audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags);
|
||||||
|
LOAD_32LE(audio->nextCh2, 0, &state->ch2.nextEvent);
|
||||||
|
|
||||||
|
// TODO: Big endian?
|
||||||
|
memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32));
|
||||||
|
LOAD_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||||
|
LOAD_32LE(audio->nextCh3, 0, &state->ch3.nextEvent);
|
||||||
|
|
||||||
|
LOAD_32LE(ch4Flags, 0, &state->ch1.envelope);
|
||||||
|
audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags);
|
||||||
|
audio->ch4.envelope.dead = GBSerializedAudioFlagsGetCh4Dead(flags);
|
||||||
|
audio->ch4.length = GBSerializedAudioEnvelopeGetLength(ch4Flags);
|
||||||
|
audio->ch4.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch4Flags);
|
||||||
|
LOAD_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr);
|
||||||
|
LOAD_32LE(audio->nextCh4, 0, &state->ch4.nextEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {
|
||||||
|
GBAudioPSGSerialize(audio, &state->audio.psg, &state->audio.flags);
|
||||||
|
|
||||||
|
STORE_32LE(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||||
|
STORE_32LE(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||||
|
STORE_32LE(audio->nextSample, 0, &state->audio.nextSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state) {
|
||||||
|
GBAudioPSGDeserialize(audio, &state->audio.psg, &state->audio.flags);
|
||||||
|
|
||||||
|
LOAD_32LE(audio->nextEvent, 0, &state->audio.nextEvent);
|
||||||
|
LOAD_32LE(audio->eventDiff, 0, &state->audio.eventDiff);
|
||||||
|
LOAD_32LE(audio->nextSample, 0, &state->audio.nextSample);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -235,4 +235,12 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
|
||||||
int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles);
|
int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles);
|
||||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);
|
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);
|
||||||
|
|
||||||
|
struct GBSerializedPSGState;
|
||||||
|
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut);
|
||||||
|
void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn);
|
||||||
|
|
||||||
|
struct GBSerializedState;
|
||||||
|
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state);
|
||||||
|
void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
35
src/gb/cli.c
35
src/gb/cli.c
|
@ -19,9 +19,13 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*);
|
||||||
static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
|
||||||
|
|
||||||
static void _frame(struct CLIDebugger*, struct CLIDebugVector*);
|
static void _frame(struct CLIDebugger*, struct CLIDebugVector*);
|
||||||
|
static void _load(struct CLIDebugger*, struct CLIDebugVector*);
|
||||||
|
static void _save(struct CLIDebugger*, struct CLIDebugVector*);
|
||||||
|
|
||||||
struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = {
|
struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = {
|
||||||
{ "frame", _frame, 0, "Frame advance" },
|
{ "frame", _frame, 0, "Frame advance" },
|
||||||
|
{ "load", _load, CLIDVParse, "Load a savestate" },
|
||||||
|
{ "save", _save, CLIDVParse, "Save a savestate" },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,4 +89,35 @@ static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||||
gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1;
|
gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||||
|
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||||
|
printf("%s\n", ERROR_MISSING_ARGS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int state = dv->intValue;
|
||||||
|
if (state < 1 || state > 9) {
|
||||||
|
printf("State %u out of range", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system;
|
||||||
|
|
||||||
|
mCoreLoadState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
||||||
|
if (!dv || dv->type != CLIDV_INT_TYPE) {
|
||||||
|
printf("%s\n", ERROR_MISSING_ARGS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int state = dv->intValue;
|
||||||
|
if (state < 1 || state > 9) {
|
||||||
|
printf("State %u out of range", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system;
|
||||||
|
|
||||||
|
mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "gb/cli.h"
|
#include "gb/cli.h"
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
#include "gb/renderers/software.h"
|
#include "gb/renderers/software.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
#include "lr35902/debugger/debugger.h"
|
#include "lr35902/debugger/debugger.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/patch.h"
|
#include "util/patch.h"
|
||||||
|
@ -213,20 +214,17 @@ static void _GBCoreStep(struct mCore* core) {
|
||||||
} while (cpu->executionState != LR35902_CORE_FETCH);
|
} while (cpu->executionState != LR35902_CORE_FETCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _GBCoreLoadState(struct mCore* core, struct VFile* vf, int flags) {
|
static size_t _GBCoreStateSize(struct mCore* core) {
|
||||||
UNUSED(core);
|
UNUSED(core);
|
||||||
UNUSED(vf);
|
return sizeof(struct GBSerializedState);
|
||||||
UNUSED(flags);
|
|
||||||
// TODO
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _GBCoreSaveState(struct mCore* core, struct VFile* vf, int flags) {
|
static bool _GBCoreLoadState(struct mCore* core, const void* state) {
|
||||||
UNUSED(core);
|
return GBDeserialize(core->board, state);
|
||||||
UNUSED(vf);
|
}
|
||||||
UNUSED(flags);
|
|
||||||
// TODO
|
static bool _GBCoreSaveState(struct mCore* core, void* state) {
|
||||||
return false;
|
return GBSerialize(core->board, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
|
static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
|
||||||
|
@ -439,6 +437,7 @@ struct mCore* GBCoreCreate(void) {
|
||||||
core->runFrame = _GBCoreRunFrame;
|
core->runFrame = _GBCoreRunFrame;
|
||||||
core->runLoop = _GBCoreRunLoop;
|
core->runLoop = _GBCoreRunLoop;
|
||||||
core->step = _GBCoreStep;
|
core->step = _GBCoreStep;
|
||||||
|
core->stateSize = _GBCoreStateSize;
|
||||||
core->loadState = _GBCoreLoadState;
|
core->loadState = _GBCoreLoadState;
|
||||||
core->saveState = _GBCoreSaveState;
|
core->saveState = _GBCoreSaveState;
|
||||||
core->setKeys = _GBCoreSetKeys;
|
core->setKeys = _GBCoreSetKeys;
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
#include "util/common.h"
|
#include "util/common.h"
|
||||||
|
|
||||||
enum GBModel {
|
enum GBModel {
|
||||||
GB_MODEL_DMG,
|
GB_MODEL_DMG = 0x00,
|
||||||
GB_MODEL_SGB,
|
GB_MODEL_SGB = 0x40,
|
||||||
GB_MODEL_CGB,
|
GB_MODEL_CGB = 0x80,
|
||||||
GB_MODEL_AGB
|
GB_MODEL_AGB = 0xC0
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
17
src/gb/io.c
17
src/gb/io.c
|
@ -6,6 +6,7 @@
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
|
|
||||||
mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O");
|
mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O");
|
||||||
|
|
||||||
|
@ -551,3 +552,19 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
|
||||||
success:
|
success:
|
||||||
return gb->memory.io[address] | _registerMask[address];
|
return gb->memory.io[address] | _registerMask[address];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GBSerializedState;
|
||||||
|
void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state) {
|
||||||
|
memcpy(state->io, gb->memory.io, GB_SIZE_IO);
|
||||||
|
state->ie = gb->memory.ie;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
|
memcpy(gb->memory.io, state->io, GB_SIZE_IO);
|
||||||
|
gb->memory.ie = state->ie;
|
||||||
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_LCDC, state->io[REG_LCDC]);
|
||||||
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCY, state->io[REG_SCY]);
|
||||||
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]);
|
||||||
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WY, state->io[REG_WY]);
|
||||||
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WX, state->io[REG_WX]);
|
||||||
|
}
|
||||||
|
|
|
@ -114,4 +114,8 @@ void GBIOReset(struct GB* gb);
|
||||||
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value);
|
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value);
|
||||||
uint8_t GBIORead(struct GB* gb, unsigned address);
|
uint8_t GBIORead(struct GB* gb, unsigned address);
|
||||||
|
|
||||||
|
struct GBSerializedState;
|
||||||
|
void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state);
|
||||||
|
void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "core/interface.h"
|
#include "core/interface.h"
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
#include "gb/io.h"
|
#include "gb/io.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
@ -932,6 +933,64 @@ void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state) {
|
||||||
|
memcpy(state->wram, memory->wram, GB_SIZE_WORKING_RAM);
|
||||||
|
memcpy(state->hram, memory->hram, GB_SIZE_HRAM);
|
||||||
|
STORE_16LE(memory->currentBank, 0, &state->memory.currentBank);
|
||||||
|
state->memory.wramCurrentBank = memory->wramCurrentBank;
|
||||||
|
state->memory.sramCurrentBank = memory->sramCurrentBank;
|
||||||
|
|
||||||
|
STORE_32LE(memory->dmaNext, 0, &state->memory.dmaNext);
|
||||||
|
STORE_16LE(memory->dmaSource, 0, &state->memory.dmaSource);
|
||||||
|
STORE_16LE(memory->dmaDest, 0, &state->memory.dmaDest);
|
||||||
|
|
||||||
|
STORE_32LE(memory->hdmaNext, 0, &state->memory.hdmaNext);
|
||||||
|
STORE_16LE(memory->hdmaSource, 0, &state->memory.hdmaSource);
|
||||||
|
STORE_16LE(memory->hdmaDest, 0, &state->memory.hdmaDest);
|
||||||
|
|
||||||
|
STORE_16LE(memory->hdmaRemaining, 0, &state->memory.hdmaRemaining);
|
||||||
|
state->memory.dmaRemaining = memory->dmaRemaining;
|
||||||
|
memcpy(state->memory.rtcRegs, memory->rtcRegs, sizeof(state->memory.rtcRegs));
|
||||||
|
|
||||||
|
state->memory.sramAccess = memory->sramAccess;
|
||||||
|
state->memory.rtcAccess = memory->rtcAccess;
|
||||||
|
state->memory.rtcLatched = memory->rtcLatched;
|
||||||
|
state->memory.ime = memory->ime;
|
||||||
|
state->memory.isHdma = memory->isHdma;
|
||||||
|
state->memory.activeRtcReg = memory->activeRtcReg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state) {
|
||||||
|
memcpy(memory->wram, state->wram, GB_SIZE_WORKING_RAM);
|
||||||
|
memcpy(memory->hram, state->hram, GB_SIZE_HRAM);
|
||||||
|
LOAD_16LE(memory->currentBank, 0, &state->memory.currentBank);
|
||||||
|
memory->wramCurrentBank = state->memory.wramCurrentBank;
|
||||||
|
memory->sramCurrentBank = state->memory.sramCurrentBank;
|
||||||
|
|
||||||
|
_switchBank(memory, memory->currentBank);
|
||||||
|
GBMemorySwitchWramBank(memory, memory->wramCurrentBank);
|
||||||
|
_switchSramBank(memory, memory->sramCurrentBank);
|
||||||
|
|
||||||
|
LOAD_32LE(memory->dmaNext, 0, &state->memory.dmaNext);
|
||||||
|
LOAD_16LE(memory->dmaSource, 0, &state->memory.dmaSource);
|
||||||
|
LOAD_16LE(memory->dmaDest, 0, &state->memory.dmaDest);
|
||||||
|
|
||||||
|
LOAD_32LE(memory->hdmaNext, 0, &state->memory.hdmaNext);
|
||||||
|
LOAD_16LE(memory->hdmaSource, 0, &state->memory.hdmaSource);
|
||||||
|
LOAD_16LE(memory->hdmaDest, 0, &state->memory.hdmaDest);
|
||||||
|
|
||||||
|
LOAD_16LE(memory->hdmaRemaining, 0, &state->memory.hdmaRemaining);
|
||||||
|
memory->dmaRemaining = state->memory.dmaRemaining;
|
||||||
|
memcpy(memory->rtcRegs, state->memory.rtcRegs, sizeof(state->memory.rtcRegs));
|
||||||
|
|
||||||
|
memory->sramAccess = state->memory.sramAccess;
|
||||||
|
memory->rtcAccess = state->memory.rtcAccess;
|
||||||
|
memory->rtcLatched = state->memory.rtcLatched;
|
||||||
|
memory->ime = state->memory.ime;
|
||||||
|
memory->isHdma = state->memory.isHdma;
|
||||||
|
memory->activeRtcReg = state->memory.activeRtcReg;
|
||||||
|
}
|
||||||
|
|
||||||
void _pristineCow(struct GB* gb) {
|
void _pristineCow(struct GB* gb) {
|
||||||
if (gb->memory.rom != gb->pristineRom) {
|
if (gb->memory.rom != gb->pristineRom) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -174,4 +174,8 @@ void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value);
|
||||||
|
|
||||||
void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old);
|
void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old);
|
||||||
|
|
||||||
|
struct GBSerializedState;
|
||||||
|
void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state);
|
||||||
|
void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#include "serialize.h"
|
||||||
|
|
||||||
|
mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate");
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <time.h>
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const uint32_t GB_SAVESTATE_MAGIC = 0x00400000;
|
||||||
|
const uint32_t GB_SAVESTATE_VERSION = 0x00000000;
|
||||||
|
|
||||||
|
void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
|
||||||
|
STORE_32LE(GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, 0, &state->versionMagic);
|
||||||
|
STORE_32LE(gb->romCrc32, 0, &state->romCrc32);
|
||||||
|
|
||||||
|
if (gb->memory.rom) {
|
||||||
|
memcpy(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title));
|
||||||
|
} else {
|
||||||
|
memset(state->title, 0, sizeof(state->title));
|
||||||
|
}
|
||||||
|
|
||||||
|
state->model = gb->model;
|
||||||
|
|
||||||
|
state->cpu.a = gb->cpu->a;
|
||||||
|
state->cpu.f = gb->cpu->f.packed;
|
||||||
|
state->cpu.b = gb->cpu->b;
|
||||||
|
state->cpu.c = gb->cpu->c;
|
||||||
|
state->cpu.d = gb->cpu->d;
|
||||||
|
state->cpu.e = gb->cpu->e;
|
||||||
|
state->cpu.h = gb->cpu->h;
|
||||||
|
state->cpu.l = gb->cpu->l;
|
||||||
|
STORE_16LE(gb->cpu->sp, 0, &state->cpu.sp);
|
||||||
|
STORE_16LE(gb->cpu->pc, 0, &state->cpu.pc);
|
||||||
|
|
||||||
|
STORE_32LE(gb->cpu->cycles, 0, &state->cpu.cycles);
|
||||||
|
STORE_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||||
|
|
||||||
|
STORE_16LE(gb->cpu->index, 0, &state->cpu.index);
|
||||||
|
state->cpu.bus = gb->cpu->bus;
|
||||||
|
state->cpu.executionState = gb->cpu->executionState;
|
||||||
|
STORE_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector);
|
||||||
|
|
||||||
|
state->cpu.condition = gb->cpu->condition;
|
||||||
|
state->cpu.irqPending = gb->cpu->irqPending;
|
||||||
|
|
||||||
|
state->cpu.doubleSpeed = gb->doubleSpeed;
|
||||||
|
|
||||||
|
GBMemorySerialize(&gb->memory, state);
|
||||||
|
GBIOSerialize(gb, state);
|
||||||
|
GBVideoSerialize(&gb->video, state);
|
||||||
|
GBTimerSerialize(&gb->timer, state);
|
||||||
|
GBAudioSerialize(&gb->audio, state);
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
struct timeval tv;
|
||||||
|
if (!gettimeofday(&tv, 0)) {
|
||||||
|
uint64_t usec = tv.tv_usec;
|
||||||
|
usec += tv.tv_sec * 1000000LL;
|
||||||
|
STORE_64LE(usec, 0, &state->creationUsec);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
struct timespec ts;
|
||||||
|
if (timespec_get(&ts, TIME_UTC)) {
|
||||||
|
uint64_t usec = ts.tv_nsec / 1000;
|
||||||
|
usec += ts.tv_sec * 1000000LL;
|
||||||
|
STORE_64LE(usec, 0, &state->creationUsec);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
|
state->creationUsec = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
|
bool error = false;
|
||||||
|
int32_t check;
|
||||||
|
uint32_t ucheck;
|
||||||
|
LOAD_32LE(ucheck, 0, &state->versionMagic);
|
||||||
|
if (ucheck > GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||||
|
mLOG(GB_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||||
|
error = true;
|
||||||
|
} else if (ucheck < GB_SAVESTATE_MAGIC) {
|
||||||
|
mLOG(GB_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||||
|
error = true;
|
||||||
|
} else if (ucheck < GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) {
|
||||||
|
mLOG(GB_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gb->memory.rom && memcmp(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title))) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is for a different game");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
LOAD_32LE(ucheck, 0, &state->romCrc32);
|
||||||
|
if (ucheck != gb->romCrc32) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is for a different version of the game");
|
||||||
|
}
|
||||||
|
LOAD_32LE(check, 0, &state->cpu.cycles);
|
||||||
|
if (check < 0) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are negative");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
if (check >= (int32_t) DMG_LR35902_FREQUENCY) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are too high");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
LOAD_32LE(check, 0, &state->video.eventDiff);
|
||||||
|
if (check < 0) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: video eventDiff is negative");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gb->cpu->a = state->cpu.a;
|
||||||
|
gb->cpu->f.packed = state->cpu.f;
|
||||||
|
gb->cpu->b = state->cpu.b;
|
||||||
|
gb->cpu->c = state->cpu.c;
|
||||||
|
gb->cpu->d = state->cpu.d;
|
||||||
|
gb->cpu->e = state->cpu.e;
|
||||||
|
gb->cpu->h = state->cpu.h;
|
||||||
|
gb->cpu->l = state->cpu.l;
|
||||||
|
LOAD_16LE(gb->cpu->sp, 0, &state->cpu.sp);
|
||||||
|
LOAD_16LE(gb->cpu->pc, 0, &state->cpu.pc);
|
||||||
|
|
||||||
|
LOAD_16LE(gb->cpu->index, 0, &state->cpu.index);
|
||||||
|
gb->cpu->bus = state->cpu.bus;
|
||||||
|
gb->cpu->executionState = state->cpu.executionState;
|
||||||
|
LOAD_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector);
|
||||||
|
|
||||||
|
gb->cpu->condition = state->cpu.condition;
|
||||||
|
gb->cpu->irqPending = state->cpu.irqPending;
|
||||||
|
|
||||||
|
gb->doubleSpeed = state->cpu.doubleSpeed;
|
||||||
|
|
||||||
|
LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles);
|
||||||
|
LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent);
|
||||||
|
|
||||||
|
gb->model = state->model;
|
||||||
|
|
||||||
|
if (gb->model < GB_MODEL_CGB) {
|
||||||
|
gb->audio.style = GB_AUDIO_DMG;
|
||||||
|
} else {
|
||||||
|
gb->audio.style = GB_AUDIO_CGB;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBMemoryDeserialize(&gb->memory, state);
|
||||||
|
GBIODeserialize(gb, state);
|
||||||
|
GBVideoDeserialize(&gb->video, state);
|
||||||
|
GBTimerDeserialize(&gb->timer, state);
|
||||||
|
GBAudioDeserialize(&gb->audio, state);
|
||||||
|
|
||||||
|
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef GB_SERIALIZE_H
|
||||||
|
#define GB_SERIALIZE_H
|
||||||
|
|
||||||
|
#include "util/common.h"
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "gb/gb.h"
|
||||||
|
|
||||||
|
extern const uint32_t GB_SAVESTATE_MAGIC;
|
||||||
|
extern const uint32_t GB_SAVESTATE_VERSION;
|
||||||
|
|
||||||
|
mLOG_DECLARE_CATEGORY(GB_STATE);
|
||||||
|
|
||||||
|
/* Savestate format:
|
||||||
|
* 0x00000 - 0x00003: Version Magic (0x01000001)
|
||||||
|
* 0x00004 - 0x00007: ROM CRC32
|
||||||
|
* 0x00008: Game Boy model
|
||||||
|
* 0x00009 - 0x0000F: Reserved (leave zero)
|
||||||
|
* 0x00010 - 0x0001F: Game title/code (e.g. PM_CRYSTALBYTE)
|
||||||
|
* 0x00020 - 0x00047: CPU state:
|
||||||
|
* | 0x00020: A register
|
||||||
|
* | 0x00021: F register
|
||||||
|
* | 0x00022: B register
|
||||||
|
* | 0x00023: C register
|
||||||
|
* | 0x00024: D register
|
||||||
|
* | 0x00025: E register
|
||||||
|
* | 0x00026: H register
|
||||||
|
* | 0x00027: L register
|
||||||
|
* | 0x00028 - 0z00029: SP register
|
||||||
|
* | 0x0002A - 0z0002B: PC register
|
||||||
|
* | 0x0002C - 0x0002F: Cycles since last event
|
||||||
|
* | 0x00030 - 0x00033: Cycles until next event
|
||||||
|
* | 0x00034 - 0x00035: Reserved (current instruction)
|
||||||
|
* | 0x00036 - 0x00037: Index address
|
||||||
|
* | 0x00038: Bus value
|
||||||
|
* | 0x00039: Execution state
|
||||||
|
* | 0x0003A - 0x0003B: IRQ vector
|
||||||
|
* | 0x0003C - 0x0003F: EI pending cycles
|
||||||
|
* | 0x00040 - 0x00043: Reserved (DI pending cycles)
|
||||||
|
* | 0x00044 - 0x00048: Flags
|
||||||
|
* 0x00048 - 0x0005B: Audio channel 1/framer state
|
||||||
|
* | 0x00048 - 0x0004B: Envelepe timing
|
||||||
|
* | bits 0 - 6: Remaining length
|
||||||
|
* | bits 7 - 9: Next step
|
||||||
|
* | bits 10 - 20: Shadow frequency register
|
||||||
|
* | bits 21 - 31: Reserved
|
||||||
|
* | 0x0004C - 0x0004F: Next frame
|
||||||
|
* | 0x00050 - 0x00057: Reserved
|
||||||
|
* | 0x00058 - 0x0005B: Next event
|
||||||
|
* 0x0005C - 0x0006B: Audio channel 2 state
|
||||||
|
* | 0x0005C - 0x0005F: Envelepe timing
|
||||||
|
* | bits 0 - 2: Remaining length
|
||||||
|
* | bits 3 - 5: Next step
|
||||||
|
* | bits 6 - 31: Reserved
|
||||||
|
* | 0x00060 - 0x00067: Reserved
|
||||||
|
* | 0x00068 - 0x0006B: Next event
|
||||||
|
* 0x0006C - 0x00093: Audio channel 3 state
|
||||||
|
* | 0x0006C - 0x0008B: Wave banks
|
||||||
|
* | 0x0008C - 0x0008D: Remaining length
|
||||||
|
* | 0x0008E - 0x0008F: Reserved
|
||||||
|
* | 0x00090 - 0x00093: Next event
|
||||||
|
* 0x00094 - 0x000A3: Audio channel 4 state
|
||||||
|
* | 0x00094 - 0x00097: Linear feedback shift register state
|
||||||
|
* | 0x00098 - 0x0009B: Envelepe timing
|
||||||
|
* | bits 0 - 2: Remaining length
|
||||||
|
* | bits 3 - 5: Next step
|
||||||
|
* | bits 6 - 31: Reserved
|
||||||
|
* | 0x00098 - 0x0009F: Reserved
|
||||||
|
* | 0x000A0 - 0x000A3: Next event
|
||||||
|
* 0x000A4 - 0x000B7: Audio miscellaneous state
|
||||||
|
* | TODO: Fix this, they're in big-endian order, but field is little-endian
|
||||||
|
* | 0x000A4: Channel 1 envelope state
|
||||||
|
* | bits 0 - 3: Current volume
|
||||||
|
* | bits 4 - 5: Is dead?
|
||||||
|
* | bit 6: Is high?
|
||||||
|
* | 0x000A5: Channel 2 envelope state
|
||||||
|
* | bits 0 - 3: Current volume
|
||||||
|
* | bits 4 - 5: Is dead?
|
||||||
|
* | bit 6: Is high?
|
||||||
|
* | bits 7: Reserved
|
||||||
|
* | 0x000A6: Channel 4 envelope state
|
||||||
|
* | bits 0 - 3: Current volume
|
||||||
|
* | bits 4 - 5: Is dead?
|
||||||
|
* | bit 6: Is high?
|
||||||
|
* | bits 7: Reserved
|
||||||
|
* | 0x000A7: Miscellaneous audio flags
|
||||||
|
* | bits 0 - 3: Current frame
|
||||||
|
* | bit 4: Is channel 1 sweep enabled?
|
||||||
|
* | bit 5: Has channel 1 sweep occurred?
|
||||||
|
* | bits 6 - 7: Reserved
|
||||||
|
* | 0x000A8 - 0x000AB: Next event
|
||||||
|
* | 0x000AC - 0x000AF: Event diff
|
||||||
|
* | 0x000B0 - 0x000B3: Next sample
|
||||||
|
*/
|
||||||
|
|
||||||
|
DECL_BITFIELD(GBSerializedAudioFlags, uint32_t);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch1Volume, 0, 4);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch1Dead, 4, 2);
|
||||||
|
DECL_BIT(GBSerializedAudioFlags, Ch1Hi, 6);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch2Volume, 8, 4);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch2Dead, 12, 2);
|
||||||
|
DECL_BIT(GBSerializedAudioFlags, Ch2Hi, 14);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch4Volume, 16, 4);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Ch4Dead, 20, 2);
|
||||||
|
DECL_BITS(GBSerializedAudioFlags, Frame, 22, 3);
|
||||||
|
DECL_BIT(GBSerializedAudioFlags, Ch1SweepEnabled, 25);
|
||||||
|
DECL_BIT(GBSerializedAudioFlags, Ch1SweepOccurred, 26);
|
||||||
|
|
||||||
|
DECL_BITFIELD(GBSerializedAudioEnvelope, uint32_t);
|
||||||
|
DECL_BITS(GBSerializedAudioEnvelope, Length, 0, 7);
|
||||||
|
DECL_BITS(GBSerializedAudioEnvelope, NextStep, 7, 3);
|
||||||
|
DECL_BITS(GBSerializedAudioEnvelope, Frequency, 10, 11);
|
||||||
|
|
||||||
|
struct GBSerializedPSGState {
|
||||||
|
struct {
|
||||||
|
GBSerializedAudioEnvelope envelope;
|
||||||
|
int32_t nextFrame;
|
||||||
|
int32_t reserved[2];
|
||||||
|
int32_t nextEvent;
|
||||||
|
} ch1;
|
||||||
|
struct {
|
||||||
|
GBSerializedAudioEnvelope envelope;
|
||||||
|
int32_t reserved[2];
|
||||||
|
int32_t nextEvent;
|
||||||
|
} ch2;
|
||||||
|
struct {
|
||||||
|
uint32_t wavebanks[8];
|
||||||
|
int16_t length;
|
||||||
|
int16_t reserved;
|
||||||
|
int32_t nextEvent;
|
||||||
|
} ch3;
|
||||||
|
struct {
|
||||||
|
int32_t lfsr;
|
||||||
|
GBSerializedAudioEnvelope envelope;
|
||||||
|
int32_t reserved;
|
||||||
|
int32_t nextEvent;
|
||||||
|
} ch4;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct GBSerializedState {
|
||||||
|
uint32_t versionMagic;
|
||||||
|
uint32_t romCrc32;
|
||||||
|
uint8_t model;
|
||||||
|
uint8_t reservedHeader[7];
|
||||||
|
|
||||||
|
char title[16];
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint8_t a;
|
||||||
|
uint8_t f;
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t c;
|
||||||
|
uint8_t d;
|
||||||
|
uint8_t e;
|
||||||
|
uint8_t h;
|
||||||
|
uint8_t l;
|
||||||
|
uint16_t sp;
|
||||||
|
uint16_t pc;
|
||||||
|
|
||||||
|
int32_t cycles;
|
||||||
|
int32_t nextEvent;
|
||||||
|
|
||||||
|
uint16_t reservedInstruction;
|
||||||
|
uint16_t index;
|
||||||
|
uint8_t bus;
|
||||||
|
uint8_t executionState;
|
||||||
|
|
||||||
|
uint16_t irqVector;
|
||||||
|
|
||||||
|
int32_t eiPending;
|
||||||
|
int32_t reservedDiPending;
|
||||||
|
bool condition : 1;
|
||||||
|
bool irqPending : 1;
|
||||||
|
bool doubleSpeed : 1;
|
||||||
|
} cpu;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct GBSerializedPSGState psg;
|
||||||
|
GBSerializedAudioFlags flags;
|
||||||
|
int32_t nextEvent;
|
||||||
|
int32_t eventDiff;
|
||||||
|
int32_t nextSample;
|
||||||
|
} audio;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int16_t x;
|
||||||
|
int16_t ly;
|
||||||
|
int32_t nextEvent;
|
||||||
|
int32_t eventDiff;
|
||||||
|
int32_t nextMode;
|
||||||
|
int32_t dotCounter;
|
||||||
|
int32_t frameCounter;
|
||||||
|
|
||||||
|
uint8_t vramCurrentBank;
|
||||||
|
|
||||||
|
bool bcpIncrement : 1;
|
||||||
|
bool ocpIncrement : 1;
|
||||||
|
uint16_t bcpIndex;
|
||||||
|
uint16_t ocpIndex;
|
||||||
|
|
||||||
|
uint16_t palette[64];
|
||||||
|
} video;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int32_t nextEvent;
|
||||||
|
int32_t eventDiff;
|
||||||
|
|
||||||
|
int32_t nextDiv;
|
||||||
|
int32_t nextTima;
|
||||||
|
int32_t timaPeriod;
|
||||||
|
} timer;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint16_t currentBank;
|
||||||
|
uint8_t wramCurrentBank;
|
||||||
|
uint8_t sramCurrentBank;
|
||||||
|
|
||||||
|
int32_t dmaNext;
|
||||||
|
uint16_t dmaSource;
|
||||||
|
uint16_t dmaDest;
|
||||||
|
|
||||||
|
int32_t hdmaNext;
|
||||||
|
uint16_t hdmaSource;
|
||||||
|
uint16_t hdmaDest;
|
||||||
|
|
||||||
|
uint16_t hdmaRemaining;
|
||||||
|
uint8_t dmaRemaining;
|
||||||
|
uint8_t rtcRegs[5];
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t mode;
|
||||||
|
} mbc1;
|
||||||
|
struct {
|
||||||
|
int8_t machineState;
|
||||||
|
GBMBC7Field field;
|
||||||
|
int8_t address;
|
||||||
|
uint8_t srBits;
|
||||||
|
uint32_t sr;
|
||||||
|
uint8_t command : 2;
|
||||||
|
bool writable : 1;
|
||||||
|
} mbc7;
|
||||||
|
struct {
|
||||||
|
uint8_t reserved[16];
|
||||||
|
} padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool sramAccess : 1;
|
||||||
|
bool rtcAccess : 1;
|
||||||
|
bool rtcLatched : 1;
|
||||||
|
bool ime : 1;
|
||||||
|
bool isHdma : 1;
|
||||||
|
uint8_t activeRtcReg : 5;
|
||||||
|
} memory;
|
||||||
|
|
||||||
|
uint8_t io[GB_SIZE_IO];
|
||||||
|
uint8_t hram[GB_SIZE_HRAM];
|
||||||
|
uint8_t ie;
|
||||||
|
|
||||||
|
uint64_t creationUsec;
|
||||||
|
|
||||||
|
uint8_t oam[GB_SIZE_OAM];
|
||||||
|
uint8_t vram[GB_SIZE_VRAM];
|
||||||
|
uint8_t wram[GB_SIZE_WORKING_RAM];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#endif
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
#include "gb/io.h"
|
#include "gb/io.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
|
|
||||||
void GBTimerReset(struct GBTimer* timer) {
|
void GBTimerReset(struct GBTimer* timer) {
|
||||||
timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
|
timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
|
||||||
|
@ -97,3 +98,19 @@ void GBTimerUpdateTIMA(struct GBTimer* timer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) {
|
||||||
|
STORE_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
||||||
|
STORE_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
||||||
|
STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
|
||||||
|
STORE_32LE(timer->nextTima, 0, &state->timer.nextTima);
|
||||||
|
STORE_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) {
|
||||||
|
LOAD_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
||||||
|
LOAD_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
||||||
|
LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
|
||||||
|
LOAD_32LE(timer->nextTima, 0, &state->timer.nextTima);
|
||||||
|
LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "core/thread.h"
|
#include "core/thread.h"
|
||||||
#include "gb/gb.h"
|
#include "gb/gb.h"
|
||||||
#include "gb/io.h"
|
#include "gb/io.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
@ -431,3 +432,55 @@ static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsi
|
||||||
UNUSED(pixels);
|
UNUSED(pixels);
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state) {
|
||||||
|
STORE_16LE(video->x, 0, &state->video.x);
|
||||||
|
STORE_16LE(video->ly, 0, &state->video.ly);
|
||||||
|
STORE_32LE(video->nextEvent, 0, &state->video.nextEvent);
|
||||||
|
STORE_32LE(video->eventDiff, 0, &state->video.eventDiff);
|
||||||
|
STORE_32LE(video->nextMode, 0, &state->video.nextMode);
|
||||||
|
STORE_32LE(video->dotCounter, 0, &state->video.dotCounter);
|
||||||
|
STORE_32LE(video->frameCounter, 0, &state->video.frameCounter);
|
||||||
|
state->video.vramCurrentBank = video->vramCurrentBank;
|
||||||
|
|
||||||
|
state->video.bcpIncrement = video->bcpIncrement;
|
||||||
|
state->video.ocpIncrement = video->ocpIncrement;
|
||||||
|
STORE_16LE(video->bcpIndex, 0, &state->video.bcpIndex);
|
||||||
|
STORE_16LE(video->ocpIndex, 0, &state->video.ocpIndex);
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < 64; ++i) {
|
||||||
|
STORE_16LE(video->palette[i], i * 2, state->video.palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(state->vram, video->vram, GB_SIZE_VRAM);
|
||||||
|
memcpy(state->oam, &video->oam.raw, GB_SIZE_OAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state) {
|
||||||
|
LOAD_16LE(video->x, 0, &state->video.x);
|
||||||
|
LOAD_16LE(video->ly, 0, &state->video.ly);
|
||||||
|
LOAD_32LE(video->nextEvent, 0, &state->video.nextEvent);
|
||||||
|
LOAD_32LE(video->eventDiff, 0, &state->video.eventDiff);
|
||||||
|
LOAD_32LE(video->nextMode, 0, &state->video.nextMode);
|
||||||
|
LOAD_32LE(video->dotCounter, 0, &state->video.dotCounter);
|
||||||
|
LOAD_32LE(video->frameCounter, 0, &state->video.frameCounter);
|
||||||
|
video->vramCurrentBank = state->video.vramCurrentBank;
|
||||||
|
|
||||||
|
video->bcpIncrement = state->video.bcpIncrement;
|
||||||
|
video->ocpIncrement = state->video.ocpIncrement;
|
||||||
|
LOAD_16LE(video->bcpIndex, 0, &state->video.bcpIndex);
|
||||||
|
LOAD_16LE(video->ocpIndex, 0, &state->video.ocpIndex);
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < 64; ++i) {
|
||||||
|
LOAD_16LE(video->palette[i], i * 2, state->video.palette);
|
||||||
|
video->renderer->writePalette(video->renderer, i, video->palette[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(video->vram, state->vram, GB_SIZE_VRAM);
|
||||||
|
memcpy(&video->oam.raw, state->oam, GB_SIZE_OAM);
|
||||||
|
|
||||||
|
_cleanOAM(video, video->ly);
|
||||||
|
GBVideoSwitchBank(video, video->vramCurrentBank);
|
||||||
|
}
|
||||||
|
|
|
@ -138,4 +138,8 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value);
|
||||||
void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value);
|
void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value);
|
||||||
void GBVideoSwitchBank(struct GBVideo* video, uint8_t value);
|
void GBVideoSwitchBank(struct GBVideo* video, uint8_t value);
|
||||||
|
|
||||||
|
struct GBSerializedState;
|
||||||
|
void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state);
|
||||||
|
void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -332,48 +332,7 @@ static void _sample(struct GBAAudio* audio) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||||
uint32_t flags = 0;
|
GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags);
|
||||||
uint32_t ch1Flags = 0;
|
|
||||||
uint32_t ch2Flags = 0;
|
|
||||||
uint32_t ch4Flags = 0;
|
|
||||||
|
|
||||||
flags = GBASerializedAudioFlagsSetFrame(flags, audio->psg.frame);
|
|
||||||
|
|
||||||
flags = GBASerializedAudioFlagsSetCh1Volume(flags, audio->psg.ch1.envelope.currentVolume);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh1Dead(flags, audio->psg.ch1.envelope.dead);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh1Hi(flags, audio->psg.ch1.control.hi);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh1SweepEnabled(flags, audio->psg.ch1.sweepEnable);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh1SweepOccurred(flags, audio->psg.ch1.sweepOccurred);
|
|
||||||
ch1Flags = GBASerializedAudioEnvelopeSetLength(ch1Flags, audio->psg.ch1.control.length);
|
|
||||||
ch1Flags = GBASerializedAudioEnvelopeSetNextStep(ch1Flags, audio->psg.ch1.envelope.nextStep);
|
|
||||||
ch1Flags = GBASerializedAudioEnvelopeSetFrequency(ch1Flags, audio->psg.ch1.realFrequency);
|
|
||||||
STORE_32(ch1Flags, 0, &state->audio.ch1.envelope);
|
|
||||||
STORE_32(audio->psg.nextFrame, 0, &state->audio.ch1.nextFrame);
|
|
||||||
STORE_32(audio->psg.nextCh1, 0, &state->audio.ch1.nextEvent);
|
|
||||||
|
|
||||||
flags = GBASerializedAudioFlagsSetCh2Volume(flags, audio->psg.ch2.envelope.currentVolume);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh2Dead(flags, audio->psg.ch2.envelope.dead);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh2Hi(flags, audio->psg.ch2.control.hi);
|
|
||||||
ch2Flags = GBASerializedAudioEnvelopeSetLength(ch2Flags, audio->psg.ch2.control.length);
|
|
||||||
ch2Flags = GBASerializedAudioEnvelopeSetNextStep(ch2Flags, audio->psg.ch2.envelope.nextStep);
|
|
||||||
STORE_32(ch2Flags, 0, &state->audio.ch2.envelope);
|
|
||||||
STORE_32(audio->psg.nextCh2, 0, &state->audio.ch2.nextEvent);
|
|
||||||
|
|
||||||
memcpy(state->audio.ch3.wavebanks, audio->psg.ch3.wavedata32, sizeof(state->audio.ch3.wavebanks));
|
|
||||||
STORE_16(audio->psg.ch3.length, 0, &state->audio.ch3.length);
|
|
||||||
STORE_32(audio->psg.nextCh3, 0, &state->audio.ch3.nextEvent);
|
|
||||||
|
|
||||||
flags = GBASerializedAudioFlagsSetCh4Volume(flags, audio->psg.ch4.envelope.currentVolume);
|
|
||||||
flags = GBASerializedAudioFlagsSetCh4Dead(flags, audio->psg.ch4.envelope.dead);
|
|
||||||
state->audio.flags = GBASerializedAudioFlagsSetCh4Volume(flags, audio->psg.ch4.envelope.currentVolume);
|
|
||||||
state->audio.flags = GBASerializedAudioFlagsSetCh4Dead(flags, audio->psg.ch4.envelope.dead);
|
|
||||||
STORE_32(audio->psg.ch4.lfsr, 0, &state->audio.ch4.lfsr);
|
|
||||||
ch4Flags = GBASerializedAudioEnvelopeSetLength(ch4Flags, audio->psg.ch4.length);
|
|
||||||
ch4Flags = GBASerializedAudioEnvelopeSetNextStep(ch4Flags, audio->psg.ch4.envelope.nextStep);
|
|
||||||
STORE_32(ch4Flags, 0, &state->audio.ch4.envelope);
|
|
||||||
STORE_32(audio->psg.nextCh4, 0, &state->audio.ch4.nextEvent);
|
|
||||||
|
|
||||||
STORE_32(flags, 0, &state->audio.flags);
|
|
||||||
|
|
||||||
CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA));
|
CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA));
|
||||||
CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB));
|
CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB));
|
||||||
|
@ -386,44 +345,7 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
|
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
|
||||||
uint32_t flags;
|
GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags);
|
||||||
uint32_t ch1Flags = 0;
|
|
||||||
uint32_t ch2Flags = 0;
|
|
||||||
uint32_t ch4Flags = 0;
|
|
||||||
|
|
||||||
LOAD_32(flags, 0, &state->audio.flags);
|
|
||||||
LOAD_32(ch1Flags, 0, &state->audio.ch1.envelope);
|
|
||||||
audio->psg.ch1.envelope.currentVolume = GBASerializedAudioFlagsGetCh1Volume(flags);
|
|
||||||
audio->psg.ch1.envelope.dead = GBASerializedAudioFlagsGetCh1Dead(flags);
|
|
||||||
audio->psg.ch1.control.hi = GBASerializedAudioFlagsGetCh1Hi(flags);
|
|
||||||
audio->psg.ch1.sweepEnable = GBASerializedAudioFlagsGetCh1SweepEnabled(flags);
|
|
||||||
audio->psg.ch1.sweepOccurred = GBASerializedAudioFlagsGetCh1SweepOccurred(flags);
|
|
||||||
audio->psg.ch1.control.length = GBASerializedAudioEnvelopeGetLength(ch1Flags);
|
|
||||||
audio->psg.ch1.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch1Flags);
|
|
||||||
audio->psg.ch1.realFrequency = GBASerializedAudioEnvelopeGetFrequency(ch1Flags);
|
|
||||||
LOAD_32(audio->psg.nextFrame, 0, &state->audio.ch1.nextFrame);
|
|
||||||
LOAD_32(audio->psg.nextCh1, 0, &state->audio.ch1.nextEvent);
|
|
||||||
|
|
||||||
LOAD_32(ch2Flags, 0, &state->audio.ch1.envelope);
|
|
||||||
audio->psg.ch2.envelope.currentVolume = GBASerializedAudioFlagsGetCh2Volume(flags);
|
|
||||||
audio->psg.ch2.envelope.dead = GBASerializedAudioFlagsGetCh2Dead(flags);
|
|
||||||
audio->psg.ch2.control.hi = GBASerializedAudioFlagsGetCh2Hi(flags);
|
|
||||||
audio->psg.ch2.control.length = GBASerializedAudioEnvelopeGetLength(ch2Flags);
|
|
||||||
audio->psg.ch2.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch2Flags);
|
|
||||||
LOAD_32(audio->psg.nextCh2, 0, &state->audio.ch2.nextEvent);
|
|
||||||
|
|
||||||
// TODO: Big endian?
|
|
||||||
memcpy(audio->psg.ch3.wavedata32, state->audio.ch3.wavebanks, sizeof(audio->psg.ch3.wavedata32));
|
|
||||||
LOAD_16(audio->psg.ch3.length, 0, &state->audio.ch3.length);
|
|
||||||
LOAD_32(audio->psg.nextCh3, 0, &state->audio.ch3.nextEvent);
|
|
||||||
|
|
||||||
LOAD_32(ch4Flags, 0, &state->audio.ch1.envelope);
|
|
||||||
audio->psg.ch4.envelope.currentVolume = GBASerializedAudioFlagsGetCh4Volume(flags);
|
|
||||||
audio->psg.ch4.envelope.dead = GBASerializedAudioFlagsGetCh4Dead(flags);
|
|
||||||
audio->psg.ch4.length = GBASerializedAudioEnvelopeGetLength(ch4Flags);
|
|
||||||
audio->psg.ch4.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch4Flags);
|
|
||||||
LOAD_32(audio->psg.ch4.lfsr, 0, &state->audio.ch4.lfsr);
|
|
||||||
LOAD_32(audio->psg.nextCh4, 0, &state->audio.ch4.nextEvent);
|
|
||||||
|
|
||||||
CircleBufferClear(&audio->chA.fifo);
|
CircleBufferClear(&audio->chA.fifo);
|
||||||
CircleBufferClear(&audio->chB.fifo);
|
CircleBufferClear(&audio->chB.fifo);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "gba/gba.h"
|
#include "gba/gba.h"
|
||||||
|
#include "gb/serialize.h"
|
||||||
|
|
||||||
extern const uint32_t GBA_SAVESTATE_MAGIC;
|
extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||||
extern const uint32_t GBA_SAVESTATE_VERSION;
|
extern const uint32_t GBA_SAVESTATE_VERSION;
|
||||||
|
@ -204,24 +205,6 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
||||||
* Total size: 0x61000 (397,312) bytes
|
* Total size: 0x61000 (397,312) bytes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DECL_BITFIELD(GBASerializedAudioFlags, uint32_t);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch1Volume, 0, 4);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch1Dead, 4, 2);
|
|
||||||
DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 6);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch2Volume, 8, 4);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch2Dead, 12, 2);
|
|
||||||
DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 14);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch4Volume, 16, 4);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Ch4Dead, 20, 2);
|
|
||||||
DECL_BITS(GBASerializedAudioFlags, Frame, 22, 3);
|
|
||||||
DECL_BIT(GBASerializedAudioFlags, Ch1SweepEnabled, 25);
|
|
||||||
DECL_BIT(GBASerializedAudioFlags, Ch1SweepOccurred, 26);
|
|
||||||
|
|
||||||
DECL_BITFIELD(GBASerializedAudioEnvelope, uint32_t);
|
|
||||||
DECL_BITS(GBASerializedAudioEnvelope, Length, 0, 7);
|
|
||||||
DECL_BITS(GBASerializedAudioEnvelope, NextStep, 7, 3);
|
|
||||||
DECL_BITS(GBASerializedAudioEnvelope, Frequency, 10, 11);
|
|
||||||
|
|
||||||
DECL_BITFIELD(GBASerializedHWFlags1, uint16_t);
|
DECL_BITFIELD(GBASerializedHWFlags1, uint16_t);
|
||||||
DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0);
|
DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0);
|
||||||
DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);
|
DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);
|
||||||
|
@ -261,36 +244,14 @@ struct GBASerializedState {
|
||||||
} cpu;
|
} cpu;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
struct {
|
struct GBSerializedPSGState psg;
|
||||||
GBASerializedAudioEnvelope envelope;
|
|
||||||
int32_t nextFrame;
|
|
||||||
int32_t reserved[2];
|
|
||||||
int32_t nextEvent;
|
|
||||||
} ch1;
|
|
||||||
struct {
|
|
||||||
GBASerializedAudioEnvelope envelope;
|
|
||||||
int32_t reserved[2];
|
|
||||||
int32_t nextEvent;
|
|
||||||
} ch2;
|
|
||||||
struct {
|
|
||||||
uint32_t wavebanks[8];
|
|
||||||
int16_t length;
|
|
||||||
int16_t reserved;
|
|
||||||
int32_t nextEvent;
|
|
||||||
} ch3;
|
|
||||||
struct {
|
|
||||||
int32_t lfsr;
|
|
||||||
GBASerializedAudioEnvelope envelope;
|
|
||||||
int32_t reserved;
|
|
||||||
int32_t nextEvent;
|
|
||||||
} ch4;
|
|
||||||
uint8_t fifoA[32];
|
uint8_t fifoA[32];
|
||||||
uint8_t fifoB[32];
|
uint8_t fifoB[32];
|
||||||
int32_t nextEvent;
|
int32_t nextEvent;
|
||||||
int32_t eventDiff;
|
int32_t eventDiff;
|
||||||
int32_t nextSample;
|
int32_t nextSample;
|
||||||
uint32_t fifoSize;
|
uint32_t fifoSize;
|
||||||
GBASerializedAudioFlags flags;
|
GBSerializedAudioFlags flags;
|
||||||
} audio;
|
} audio;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
Loading…
Reference in New Issue