diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c new file mode 100644 index 000000000..815bb2a01 --- /dev/null +++ b/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; +} diff --git a/src/gba/sharkport.h b/src/gba/sharkport.h new file mode 100644 index 000000000..1c709b209 --- /dev/null +++ b/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 diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 2433ace39..9204bdbe9 100644 --- a/src/platform/qt/GameController.cpp +++ b/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" @@ -296,6 +297,20 @@ void GameController::loadPatch(const QString& 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() { if (!m_gameOpen) { return; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 2b3681d8a..d5dd2fd43 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -98,6 +98,7 @@ public slots: void setSkipBIOS(bool); void setUseBIOS(bool); void loadPatch(const QString& path); + void importSharkport(const QString& path); void openGame(); void closeGame(); void setPaused(bool paused); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 90116608c..1f6ad9904 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -255,6 +255,21 @@ void Window::openView(QWidget* widget) { 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); @@ -604,6 +619,12 @@ void Window::setupMenu(QMenuBar* menubar) { 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); connect(multiWindow, &QAction::triggered, [this]() { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 2cbaa960b..1e3937d63 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -65,6 +65,8 @@ public slots: void loadConfig(); void saveConfig(); + void importSharkport(); + void openKeymapWindow(); void openSettingsWindow(); void openShortcutWindow();