GBA: Support for loading Gameshark snapshots
Jeffrey Pfau jeffrey@endrift.com
Wed, 15 Apr 2015 03:58:58 -0700
6 files changed,
201 insertions(+),
0 deletions(-)
A
src/gba/sharkport.c
@@ -0,0 +1,145 @@
+/* Copyright (c) 2013-2015 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 "sharkport.h" + +#include "gba/gba.h" +#include "util/vfs.h" + +static const char* const SHARKPORT_HEADER = "SharkPortSave"; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { + char buffer[0x1C]; + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + uint32_t size; + LOAD_32(size, 0, buffer); + if (size != strlen(SHARKPORT_HEADER)) { + return false; + } + if (vf->read(vf, buffer, size) < size) { + return false; + } + if (memcmp(SHARKPORT_HEADER, buffer, size) != 0) { + return false; + } + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (size != 0x000F0000) { + // What is this value? + return false; + } + + // Skip first three fields + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + // Read payload + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) { + return false; + } + char* payload = malloc(size); + if (vf->read(vf, payload, size) < size) { + goto cleanup; + } + + struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; + memcpy(buffer, cart->title, 16); + buffer[0x10] = 0; + buffer[0x11] = 0; + buffer[0x12] = cart->checksum; + buffer[0x13] = cart->maker; + buffer[0x14] = 1; + buffer[0x15] = 0; + buffer[0x16] = 0; + buffer[0x17] = 0; + buffer[0x18] = 0; + buffer[0x19] = 0; + buffer[0x1A] = 0; + buffer[0x1B] = 0; + if (memcmp(buffer, payload, 0x1C) != 0) { + goto cleanup; + } + + uint32_t checksum; + if (vf->read(vf, buffer, 4) < 4) { + goto cleanup; + } + LOAD_32(checksum, 0, buffer); + + uint32_t calcChecksum = 0; + uint32_t i; + for (i = 0; i < size; ++i) { + calcChecksum += payload[i] << (calcChecksum % 24); + } + + if (calcChecksum != checksum) { + goto cleanup; + } + + uint32_t copySize = size - 0x1C; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + if (copySize > SIZE_CART_SRAM) { + copySize = SIZE_CART_SRAM; + } + break; + case SAVEDATA_FLASH512: + if (copySize > SIZE_CART_FLASH512) { + GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); + } + // Fall through + case SAVEDATA_FLASH1M: + if (copySize > SIZE_CART_FLASH1M) { + copySize = SIZE_CART_FLASH1M; + } + break; + case SAVEDATA_EEPROM: + if (copySize > SIZE_CART_EEPROM) { + copySize = SAVEDATA_EEPROM; + } + break; + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: + goto cleanup; + } + + memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + + free(payload); + return true; + +cleanup: + free(payload); + return false; +}
A
src/gba/sharkport.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2013-2015 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 GBA_SHARKPORT_H +#define GBA_SHARKPORT_H + +#include "util/common.h" + +struct GBA; +struct VFile; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf); +bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf); + +#endif
M
src/platform/qt/GameController.cpp
→
src/platform/qt/GameController.cpp
@@ -18,6 +18,7 @@ extern "C" {
#include "gba/audio.h" #include "gba/gba.h" #include "gba/serialize.h" +#include "gba/sharkport.h" #include "gba/renderers/video-software.h" #include "gba/supervisor/config.h" #include "util/vfs.h"@@ -294,6 +295,20 @@ openGame();
} else { m_patch = path; } +} + +void GameController::importSharkport(const QString& path) { + if (!m_gameOpen) { + return; + } + VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY); + if (!vf) { + return; + } + threadInterrupt(); + GBASavedataImportSharkPort(m_threadContext.gba, vf); + threadContinue(); + vf->close(vf); } void GameController::closeGame() {
M
src/platform/qt/GameController.h
→
src/platform/qt/GameController.h
@@ -98,6 +98,7 @@ void loadBIOS(const QString& path);
void setSkipBIOS(bool); void setUseBIOS(bool); void loadPatch(const QString& path); + void importSharkport(const QString& path); void openGame(); void closeGame(); void setPaused(bool paused);
M
src/platform/qt/Window.cpp
→
src/platform/qt/Window.cpp
@@ -255,6 +255,21 @@ widget->setAttribute(Qt::WA_DeleteOnClose);
widget->show(); } +void Window::importSharkport() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } + QString filename = QFileDialog::getOpenFileName(this, tr("Select save"), m_config->getQtOption("lastDirectory").toString(), tr("GameShark saves (*.sps *.xps)")); + if (doPause) { + m_controller->setPaused(false); + } + if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + m_controller->importSharkport(filename); + } +} + void Window::openKeymapWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); openView(keyEditor);@@ -603,6 +618,12 @@ connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); });
m_gameActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } + + fileMenu->addSeparator(); + QAction* loadSharkport = new QAction(tr("Import GameShark Save"), fileMenu); + connect(loadSharkport, SIGNAL(triggered()), this, SLOT(importSharkport())); + m_gameActions.append(loadSharkport); + addControlledAction(fileMenu, loadSharkport, "loadSharkport"); fileMenu->addSeparator(); QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu);
M
src/platform/qt/Window.h
→
src/platform/qt/Window.h
@@ -65,6 +65,8 @@ void toggleFullScreen();
void loadConfig(); void saveConfig(); + void importSharkport(); + void openKeymapWindow(); void openSettingsWindow(); void openShortcutWindow();