diff --git a/CHANGES b/CHANGES index 33313d59d..f74d7ce0c 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Features: - Ability to cap fast forward speed - Finer control over FPS target - Holdable shortcut for rewinding one frame at a time + - Ability to boot directly into the BIOS Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit diff --git a/src/gba/gba.c b/src/gba/gba.c index 35eb8573e..d1f0538a5 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -633,10 +633,18 @@ bool GBAIsBIOS(struct VFile* vf) { } void GBAGetGameCode(struct GBA* gba, char* out) { + if (!gba->memory.rom) { + out[0] = '\0'; + return; + } memcpy(out, &((struct GBACartridge*) gba->memory.rom)->id, 4); } void GBAGetGameTitle(struct GBA* gba, char* out) { + if (!gba->memory.rom) { + strncpy(out, "(BIOS)", 12); + return; + } memcpy(out, &((struct GBACartridge*) gba->memory.rom)->title, 12); } diff --git a/src/gba/memory.c b/src/gba/memory.c index d15894105..63f0a54ed 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -47,6 +47,7 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.wram = 0; gba->memory.iwram = 0; gba->memory.rom = 0; + gba->memory.romSize = 0; gba->memory.hw.p = gba; int i; diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 7c6ce214e..ac62bbe3b 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -29,8 +29,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { state->biosChecksum = gba->biosChecksum; state->romCrc32 = gba->romCrc32; - state->id = ((struct GBACartridge*) gba->memory.rom)->id; - memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + if (gba->memory.rom) { + state->id = ((struct GBACartridge*) gba->memory.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + } else { + state->id = 0; + memset(state->title, 0, sizeof(state->title)); + } memcpy(state->cpu.gprs, gba->cpu->gprs, sizeof(state->cpu.gprs)); state->cpu.cpsr = gba->cpu->cpsr; @@ -67,9 +72,12 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { return; } } - if (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title))) { + if (gba->memory.rom && state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title))) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game"); return; + } else if (!gba->memory.rom && state->id != 0) { + GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded"); + return; } if (state->romCrc32 != gba->romCrc32) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game"); diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 6367d0cd0..74a631655 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -167,15 +167,15 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { GBAOverrideApply(&gba, &threadContext->override); } - if (threadContext->bios && GBAIsBIOS(threadContext->bios)) { - GBALoadBIOS(&gba, threadContext->bios); - } - if (threadContext->patch && loadPatch(threadContext->patch, &patch)) { GBAApplyPatch(&gba, &patch); } } + if (threadContext->bios && GBAIsBIOS(threadContext->bios)) { + GBALoadBIOS(&gba, threadContext->bios); + } + if (threadContext->movie) { struct VDir* movieDir = VDirOpen(threadContext->movie); #ifdef USE_LIBZIP @@ -400,7 +400,9 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->fpsTarget = _defaultFPSTarget; } - if (threadContext->rom && !GBAIsROM(threadContext->rom)) { + bool bootBios = threadContext->bootBios && threadContext->bios; + + if (threadContext->rom && (!GBAIsROM(threadContext->rom) || bootBios)) { threadContext->rom->close(threadContext->rom); threadContext->rom = 0; } @@ -427,7 +429,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) { } - if (!threadContext->rom) { + if (!threadContext->rom && !bootBios) { threadContext->state = THREAD_SHUTDOWN; return false; } diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index 4f4bc55b7..f004b2c08 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -72,6 +72,7 @@ struct GBAThread { struct GBAAVStream* stream; struct Configuration* overrides; enum GBAIdleLoopOptimization idleOptimization; + bool bootBios; bool hasOverride; struct GBACartridgeOverride override; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index fed36345c..6df9a2bb2 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -247,7 +247,18 @@ void GameController::loadGame(const QString& path, bool dirmode) { openGame(); } -void GameController::openGame() { +void GameController::bootBIOS() { + closeGame(); + m_fname = QString(); + m_dirmode = false; + openGame(true); +} + +void GameController::openGame(bool biosOnly) { + if (biosOnly && (!m_useBios || m_bios.isNull())) { + return; + } + m_gameOpen = true; m_pauseAfterFrame = false; @@ -261,25 +272,30 @@ void GameController::openGame() { } m_threadContext.gameDir = 0; - m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); - if (m_dirmode) { - m_threadContext.gameDir = VDirOpen(m_threadContext.fname); - m_threadContext.stateDir = m_threadContext.gameDir; + m_threadContext.bootBios = biosOnly; + if (biosOnly) { + m_threadContext.fname = nullptr; } else { - m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); + m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); + if (m_dirmode) { + m_threadContext.gameDir = VDirOpen(m_threadContext.fname); + m_threadContext.stateDir = m_threadContext.gameDir; + } else { + m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); #if USE_LIBZIP - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); - } + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); + } #endif #if USE_LZMA - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); - } + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); + } #endif + } } - if (!m_bios.isNull() &&m_useBios) { + if (!m_bios.isNull() && m_useBios) { m_threadContext.bios = VFileDevice::open(m_bios, O_RDONLY); } else { m_threadContext.bios = nullptr; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 6848c6fd2..798c83ec8 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -102,7 +102,7 @@ public slots: void loadPatch(const QString& path); void importSharkport(const QString& path); void exportSharkport(const QString& path); - void openGame(); + void bootBIOS(); void closeGame(); void setPaused(bool paused); void reset(); @@ -148,6 +148,7 @@ public slots: void disableLogLevel(int); private slots: + void openGame(bool bios = false); void crashGame(const QString& crashMessage); void pollEvents(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index efe2a9b48..10d30aa32 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -482,7 +482,9 @@ void Window::gameStarted(GBAThread* context) { foreach (QAction* action, m_gameActions) { action->setDisabled(false); } - appendMRU(context->fname); + if (context->fname) { + appendMRU(context->fname); + } updateTitle(); attachWidget(m_display); @@ -609,6 +611,7 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS"); m_mruMenu = fileMenu->addMenu(tr("Recent"));