GB: First pass at savestates

This commit is contained in:
Jeffrey Pfau 2016-05-30 15:03:20 -07:00
parent a3a380d9f5
commit 5a2c6d037d
16 changed files with 759 additions and 137 deletions

View File

@ -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 @@ void _scheduleEvent(struct GBAudio* audio) {
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);
}

View File

@ -235,4 +235,12 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
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

View File

@ -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 @@ static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
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

View File

@ -10,6 +10,7 @@
#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 @@ static void _GBCoreStep(struct mCore* core) {
} 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 _GBCoreSaveState(struct mCore* core, struct VFile* vf, int flags) {
UNUSED(core);
UNUSED(vf);
UNUSED(flags);
// TODO
return false;
static bool _GBCoreLoadState(struct mCore* core, const void* state) {
return GBDeserialize(core->board, state);
}
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 @@ struct mCore* GBCoreCreate(void) {
core->runFrame = _GBCoreRunFrame;
core->runLoop = _GBCoreRunLoop;
core->step = _GBCoreStep;
core->stateSize = _GBCoreStateSize;
core->loadState = _GBCoreLoadState;
core->saveState = _GBCoreSaveState;
core->setKeys = _GBCoreSetKeys;

View File

@ -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

View File

@ -6,6 +6,7 @@
#include "io.h"
#include "gb/gb.h"
#include "gb/serialize.h"
mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O");
@ -551,3 +552,19 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
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]);
}

View File

@ -114,4 +114,8 @@ void GBIOReset(struct GB* gb);
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

View File

@ -8,6 +8,7 @@
#include "core/interface.h"
#include "gb/gb.h"
#include "gb/io.h"
#include "gb/serialize.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) {
if (gb->memory.rom != gb->pristineRom) {
return;

View File

@ -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

163
src/gb/serialize.c Normal file
View File

@ -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;
}

274
src/gb/serialize.h Normal file
View File

@ -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

View File

@ -7,6 +7,7 @@
#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 @@ 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);
}

View File

@ -9,6 +9,7 @@
#include "core/thread.h"
#include "gb/gb.h"
#include "gb/io.h"
#include "gb/serialize.h"
#include "util/memory.h"
@ -431,3 +432,55 @@ static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsi
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);
}

View File

@ -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

View File

@ -332,48 +332,7 @@ static void _sample(struct GBAAudio* audio) {
}
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 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
}
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);

View File

@ -10,6 +10,7 @@
#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 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* 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 @@ struct GBASerializedState {
} 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 {