mirror of https://github.com/mgba-emu/mgba.git
GBA: Support for loading Gameshark snapshots
This commit is contained in:
parent
27a178fe3c
commit
3ff8467ba7
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]() {
|
||||
|
|
|
@ -65,6 +65,8 @@ public slots:
|
|||
void loadConfig();
|
||||
void saveConfig();
|
||||
|
||||
void importSharkport();
|
||||
|
||||
void openKeymapWindow();
|
||||
void openSettingsWindow();
|
||||
void openShortcutWindow();
|
||||
|
|
Loading…
Reference in New Issue