all repos — mgba @ 5a2c6d037dcb0d77d96780ced908c733411ee30d

mGBA Game Boy Advance Emulator

GB: First pass at savestates
Jeffrey Pfau jeffrey@endrift.com
Mon, 30 May 2016 15:03:20 -0700
commit

5a2c6d037dcb0d77d96780ced908c733411ee30d

parent

a3a380d9f551bbc5ad726ef07c11d20c0263d1e5

M src/gb/audio.csrc/gb/audio.c

@@ -8,6 +8,7 @@

#include "core/interface.h" #include "core/sync.h" #include "gb/gb.h" +#include "gb/serialize.h" #include "gb/io.h" #ifdef _3DS

@@ -859,3 +860,104 @@ } else {

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); +} +
M src/gb/audio.hsrc/gb/audio.h

@@ -235,4 +235,12 @@

int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles); 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
M src/gb/cli.csrc/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 void _frame(struct CLIDebugger*, struct CLIDebugVector*); +static void _load(struct CLIDebugger*, struct CLIDebugVector*); +static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = { { "frame", _frame, 0, "Frame advance" }, + { "load", _load, CLIDVParse, "Load a savestate" }, + { "save", _save, CLIDVParse, "Save a savestate" }, { 0, 0, 0, 0 } };

@@ -85,4 +89,35 @@ gbDebugger->frameAdvance = true;

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
M src/gb/core.csrc/gb/core.c

@@ -10,6 +10,7 @@ #include "gb/cheats.h"

#include "gb/cli.h" #include "gb/gb.h" #include "gb/renderers/software.h" +#include "gb/serialize.h" #include "lr35902/debugger/debugger.h" #include "util/memory.h" #include "util/patch.h"

@@ -213,20 +214,17 @@ LR35902Tick(cpu);

} 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(vf); - UNUSED(flags); - // TODO - return false; + return sizeof(struct GBSerializedState); +} + +static bool _GBCoreLoadState(struct mCore* core, const void* state) { + return GBDeserialize(core->board, state); } -static bool _GBCoreSaveState(struct mCore* core, struct VFile* vf, int flags) { - UNUSED(core); - UNUSED(vf); - UNUSED(flags); - // TODO - return false; +static bool _GBCoreSaveState(struct mCore* core, void* state) { + return GBSerialize(core->board, state); } static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {

@@ -439,6 +437,7 @@ core->reset = _GBCoreReset;

core->runFrame = _GBCoreRunFrame; core->runLoop = _GBCoreRunLoop; core->step = _GBCoreStep; + core->stateSize = _GBCoreStateSize; core->loadState = _GBCoreLoadState; core->saveState = _GBCoreSaveState; core->setKeys = _GBCoreSetKeys;
M src/gb/interface.hsrc/gb/interface.h

@@ -9,10 +9,10 @@

#include "util/common.h" enum GBModel { - GB_MODEL_DMG, - GB_MODEL_SGB, - GB_MODEL_CGB, - GB_MODEL_AGB + GB_MODEL_DMG = 0x00, + GB_MODEL_SGB = 0x40, + GB_MODEL_CGB = 0x80, + GB_MODEL_AGB = 0xC0 }; #endif
M src/gb/io.csrc/gb/io.c

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "io.h" #include "gb/gb.h" +#include "gb/serialize.h" mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O");

@@ -551,3 +552,19 @@ }

success: 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]); +}
M src/gb/io.hsrc/gb/io.h

@@ -114,4 +114,8 @@

void GBIOWrite(struct GB* gb, unsigned address, uint8_t value); 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
M src/gb/memory.csrc/gb/memory.c

@@ -8,6 +8,7 @@

#include "core/interface.h" #include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" #include "util/memory.h"

@@ -930,6 +931,64 @@ mbc7->state = GBMBC7_STATE_NULL;

} } } +} + +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) {
M src/gb/memory.hsrc/gb/memory.h

@@ -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); +struct GBSerializedState; +void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state); +void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state); + #endif
A src/gb/serialize.c

@@ -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; +}
A src/gb/serialize.h

@@ -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
M src/gb/timer.csrc/gb/timer.c

@@ -7,6 +7,7 @@ #include "timer.h"

#include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" void GBTimerReset(struct GBTimer* timer) { timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences

@@ -97,3 +98,19 @@ timer->p->cpu->nextEvent = timer->nextEvent;

} } } + +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); +}
M src/gb/video.csrc/gb/video.c

@@ -9,6 +9,7 @@ #include "core/sync.h"

#include "core/thread.h" #include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" #include "util/memory.h"

@@ -431,3 +432,55 @@ UNUSED(stride);

UNUSED(pixels); // 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); +}
M src/gb/video.hsrc/gb/video.h

@@ -138,4 +138,8 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value);

void GBVideoWritePalette(struct GBVideo* video, uint16_t address, 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
M src/gba/audio.csrc/gba/audio.c

@@ -332,48 +332,7 @@ }

} void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) { - uint32_t flags = 0; - 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); + GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags); CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA)); CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB));

@@ -386,44 +345,7 @@ STORE_32(audio->nextSample, 0, &state->audio.nextSample);

} void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { - uint32_t 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); + GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); CircleBufferClear(&audio->chA.fifo); CircleBufferClear(&audio->chB.fifo);
M src/gba/serialize.hsrc/gba/serialize.h

@@ -10,6 +10,7 @@ #include "util/common.h"

#include "core/core.h" #include "gba/gba.h" +#include "gb/serialize.h" extern const uint32_t GBA_SAVESTATE_MAGIC; extern const uint32_t GBA_SAVESTATE_VERSION;

@@ -204,24 +205,6 @@ * 0x21000 - 0x60FFF: WRAM

* 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_BIT(GBASerializedHWFlags1, ReadWrite, 0); DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);

@@ -261,36 +244,14 @@ int32_t bankedSPSRs[6];

} cpu; struct { - struct { - 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; + struct GBSerializedPSGState psg; uint8_t fifoA[32]; uint8_t fifoB[32]; int32_t nextEvent; int32_t eventDiff; int32_t nextSample; uint32_t fifoSize; - GBASerializedAudioFlags flags; + GBSerializedAudioFlags flags; } audio; struct {