mirror of https://github.com/mgba-emu/mgba.git
GBA Savedata: Add GSV importing
This commit is contained in:
parent
b5e94b0abb
commit
a1641f7fae
3
CHANGES
3
CHANGES
|
@ -10,6 +10,7 @@ Features:
|
|||
- Support for 64 kiB SRAM saves used in some bootlegs
|
||||
- Discord Rich Presence now supports time elapsed
|
||||
- Additional scaling shaders
|
||||
- Support for GameShark Advance SP (.gsv) save file importing
|
||||
Emulation fixes:
|
||||
- ARM7: Fix unsigned multiply timing
|
||||
- GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032)
|
||||
|
@ -25,7 +26,7 @@ Misc:
|
|||
- Qt: Rearrange menus some
|
||||
- Qt: Clean up cheats dialog
|
||||
- Qt: Only set default controller bindings if loading fails (fixes mgba.io/i/799)
|
||||
- Qt: Save converter now supports importing SharkPort saves
|
||||
- Qt: Save converter now supports importing GameShark Advance saves
|
||||
|
||||
0.9.3: (2021-12-17)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2021 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
|
||||
|
@ -18,6 +18,10 @@ void* GBASavedataSharkPortGetPayload(struct VFile* vf, size_t* size, uint8_t* he
|
|||
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum);
|
||||
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf);
|
||||
|
||||
int GBASavedataGSVPayloadSize(struct VFile* vf);
|
||||
void* GBASavedataGSVGetPayload(struct VFile* vf, size_t* size, uint8_t* ident, bool testChecksum);
|
||||
bool GBASavedataImportGSV(struct GBA* gba, struct VFile* vf, bool testChecksum);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2021 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
|
||||
|
@ -10,6 +10,50 @@
|
|||
#include <mgba-util/vfs.h>
|
||||
|
||||
static const char* const SHARKPORT_HEADER = "SharkPortSave";
|
||||
static const char* const GSV_HEADER = "ADVSAVEG";
|
||||
static const char* const GSV_FOOTER = "xV4\x12";
|
||||
static const int GSV_IDENT_OFFSET = 0xC;
|
||||
static const int GSV_PAYLOAD_OFFSET = 0x430;
|
||||
|
||||
static bool _importSavedata(struct GBA* gba, void* payload, size_t size) {
|
||||
bool success = false;
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_FLASH512:
|
||||
if (size > SIZE_CART_FLASH512) {
|
||||
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M);
|
||||
}
|
||||
// Fall through
|
||||
default:
|
||||
if (size > GBASavedataSize(&gba->memory.savedata)) {
|
||||
size = GBASavedataSize(&gba->memory.savedata);
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (size == SIZE_CART_EEPROM || size == SIZE_CART_EEPROM512) {
|
||||
size_t i;
|
||||
for (i = 0; i < size; i += 8) {
|
||||
uint32_t lo, hi;
|
||||
LOAD_32BE(lo, i, payload);
|
||||
LOAD_32BE(hi, i + 4, payload);
|
||||
STORE_32LE(hi, i, gba->memory.savedata.data);
|
||||
STORE_32LE(lo, i + 4, gba->memory.savedata.data);
|
||||
}
|
||||
} else {
|
||||
memcpy(gba->memory.savedata.data, payload, size);
|
||||
}
|
||||
if (gba->memory.savedata.vf) {
|
||||
gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size);
|
||||
}
|
||||
success = true;
|
||||
|
||||
cleanup:
|
||||
free(payload);
|
||||
return success;
|
||||
}
|
||||
|
||||
int GBASavedataSharkPortPayloadSize(struct VFile* vf) {
|
||||
union {
|
||||
|
@ -121,7 +165,6 @@ cleanup:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) {
|
||||
uint8_t buffer[0x1C];
|
||||
uint8_t header[0x1C];
|
||||
|
@ -132,7 +175,6 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
|
|||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom;
|
||||
memcpy(buffer, &cart->title, 16);
|
||||
buffer[0x10] = 0;
|
||||
|
@ -148,46 +190,11 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
|
|||
buffer[0x1A] = 0;
|
||||
buffer[0x1B] = 0;
|
||||
if (memcmp(buffer, header, testChecksum ? 0x1C : 0xF) != 0) {
|
||||
goto cleanup;
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_FLASH512:
|
||||
if (size > SIZE_CART_FLASH512) {
|
||||
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M);
|
||||
}
|
||||
// Fall through
|
||||
default:
|
||||
if (size > GBASavedataSize(&gba->memory.savedata)) {
|
||||
size = GBASavedataSize(&gba->memory.savedata);
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
case SAVEDATA_AUTODETECT:
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
|
||||
if (size == SIZE_CART_EEPROM || size == SIZE_CART_EEPROM512) {
|
||||
size_t i;
|
||||
for (i = 0; i < size; i += 8) {
|
||||
uint32_t lo, hi;
|
||||
LOAD_32BE(lo, i, payload);
|
||||
LOAD_32BE(hi, i + 4, payload);
|
||||
STORE_32LE(hi, i, gba->memory.savedata.data);
|
||||
STORE_32LE(lo, i + 4, gba->memory.savedata.data);
|
||||
}
|
||||
} else {
|
||||
memcpy(gba->memory.savedata.data, payload, size);
|
||||
}
|
||||
if (gba->memory.savedata.vf) {
|
||||
gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size);
|
||||
}
|
||||
success = true;
|
||||
|
||||
cleanup:
|
||||
free(payload);
|
||||
return success;
|
||||
return _importSavedata(gba, payload, size);
|
||||
}
|
||||
|
||||
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
|
||||
|
@ -269,7 +276,6 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
|
|||
checksum += buffer.c[i] << (checksum % 24);
|
||||
}
|
||||
|
||||
|
||||
if (gba->memory.savedata.type == SAVEDATA_EEPROM) {
|
||||
for (i = 0; i < size; ++i) {
|
||||
char byte = gba->memory.savedata.data[i ^ 7];
|
||||
|
@ -291,3 +297,93 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GBASavedataGSVPayloadSize(struct VFile* vf) {
|
||||
union {
|
||||
char c[8];
|
||||
int32_t i;
|
||||
} buffer;
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (vf->read(vf, &buffer.c, 8) < 8) {
|
||||
return 0;
|
||||
}
|
||||
if (memcmp(GSV_HEADER, buffer.c, 8) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip the checksum
|
||||
if (vf->read(vf, &buffer.i, 4) < 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct {
|
||||
char name[12];
|
||||
int padding;
|
||||
int type;
|
||||
int unk[3];
|
||||
char description[0x400];
|
||||
char footer[4];
|
||||
} header;
|
||||
|
||||
if (vf->read(vf, &header, sizeof(header)) < (ssize_t) sizeof(header)) {
|
||||
return 0;
|
||||
}
|
||||
if (memcmp(GSV_FOOTER, header.footer, 4) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int type;
|
||||
LOAD_32(type, 0, &header.type);
|
||||
switch (type) {
|
||||
case 2:
|
||||
return SIZE_CART_SRAM;
|
||||
case 3:
|
||||
return SIZE_CART_EEPROM512;
|
||||
case 4:
|
||||
return SIZE_CART_EEPROM;
|
||||
case 5:
|
||||
return SIZE_CART_FLASH512;
|
||||
case 6:
|
||||
return SIZE_CART_FLASH1M; // Unconfirmed
|
||||
default:
|
||||
return vf->size(vf) - GSV_PAYLOAD_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
void* GBASavedataGSVGetPayload(struct VFile* vf, size_t* osize, uint8_t* ident, bool testChecksum) {
|
||||
int32_t size = GBASavedataGSVPayloadSize(vf);
|
||||
if (!size || size > SIZE_CART_FLASH1M) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vf->seek(vf, GSV_IDENT_OFFSET, SEEK_SET);
|
||||
if (ident && vf->read(vf, ident, 12) != 12) {
|
||||
return NULL;
|
||||
}
|
||||
vf->seek(vf, GSV_PAYLOAD_OFFSET, SEEK_SET);
|
||||
|
||||
int8_t* payload = malloc(size);
|
||||
if (vf->read(vf, payload, size) != size) {
|
||||
free(payload);
|
||||
return NULL;
|
||||
}
|
||||
UNUSED(testChecksum); // The checksum format is currently unknown
|
||||
*osize = size;
|
||||
return payload;
|
||||
}
|
||||
|
||||
bool GBASavedataImportGSV(struct GBA* gba, struct VFile* vf, bool testChecksum) {
|
||||
size_t size;
|
||||
uint8_t ident[12];
|
||||
void* payload = GBASavedataGSVGetPayload(vf, &size, ident, testChecksum);
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom;
|
||||
if (memcmp(ident, cart->title, sizeof(ident)) != 0) {
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _importSavedata(gba, payload, size);
|
||||
}
|
||||
|
|
|
@ -852,6 +852,7 @@ void CoreController::importSharkport(const QString& path) {
|
|||
}
|
||||
Interrupter interrupter(this);
|
||||
GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
|
||||
GBASavedataImportGSV(static_cast<GBA*>(m_threadContext.core->board), vf, false);
|
||||
vf->close(vf);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ SaveConverter::SaveConverter(std::shared_ptr<CoreController> controller, QWidget
|
|||
|
||||
connect(m_ui.inputFile, &QLineEdit::textEdited, this, &SaveConverter::refreshInputTypes);
|
||||
connect(m_ui.inputBrowse, &QAbstractButton::clicked, this, [this]() {
|
||||
QStringList formats{"*.sav", "*.sgm", "*.sps", "*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9", "*.xps"};
|
||||
QStringList formats{"*.gsv", "*.sav", "*.sgm", "*.sps", "*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9", "*.xps"};
|
||||
QString filter = tr("Save games and save states (%1)").arg(formats.join(QChar(' ')));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save game or save state"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
|
@ -256,6 +256,7 @@ void SaveConverter::detectFromSize(std::shared_ptr<VFileDevice> vf) {
|
|||
|
||||
void SaveConverter::detectFromHeaders(std::shared_ptr<VFileDevice> vf) {
|
||||
const QByteArray sharkport("\xd\0\0\0SharkPortSave", 0x11);
|
||||
const QByteArray gsv("ADVSAVEG", 8);
|
||||
QByteArray buffer;
|
||||
|
||||
vf->seek(0);
|
||||
|
@ -276,6 +277,32 @@ void SaveConverter::detectFromHeaders(std::shared_ptr<VFileDevice> vf) {
|
|||
}
|
||||
free(data);
|
||||
}
|
||||
} else if (buffer.left(gsv.count()) == gsv) {
|
||||
size_t size;
|
||||
void* data = GBASavedataGSVGetPayload(*vf, &size, nullptr, false);
|
||||
if (data) {
|
||||
QByteArray bytes = QByteArray::fromRawData(static_cast<const char*>(data), size);
|
||||
bytes.data(); // Trigger a deep copy before we delete the backing
|
||||
switch (size) {
|
||||
case SIZE_CART_FLASH1M:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, std::make_shared<VFileDevice>(bytes), Endian::NONE, Container::GSV});
|
||||
break;
|
||||
case SIZE_CART_FLASH512:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, std::make_shared<VFileDevice>(bytes), Endian::NONE, Container::GSV});
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, std::make_shared<VFileDevice>(bytes), Endian::NONE, Container::GSV});
|
||||
break;
|
||||
case SIZE_CART_SRAM:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_SRAM)), Endian::NONE, Container::GSV});
|
||||
break;
|
||||
case SIZE_CART_EEPROM:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_EEPROM)), Endian::BIG, Container::GSV});
|
||||
break;
|
||||
case SIZE_CART_EEPROM512:
|
||||
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_EEPROM512)), Endian::BIG, Container::GSV});
|
||||
break;
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,6 +528,9 @@ SaveConverter::AnnotatedSave::operator QString() const {
|
|||
case Container::SHARKPORT:
|
||||
format = QCoreApplication::translate("SaveConverter", "%1 SharkPort %2 save game");
|
||||
break;
|
||||
case Container::GSV:
|
||||
format = QCoreApplication::translate("SaveConverter", "%1 GameShark Advance SP %2 save game");
|
||||
break;
|
||||
case Container::NONE:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ private:
|
|||
enum class Container {
|
||||
NONE = 0,
|
||||
SAVESTATE,
|
||||
SHARKPORT
|
||||
SHARKPORT,
|
||||
GSV
|
||||
};
|
||||
struct AnnotatedSave {
|
||||
AnnotatedSave();
|
||||
|
|
|
@ -492,7 +492,7 @@ void Window::loadCamImage() {
|
|||
}
|
||||
|
||||
void Window::importSharkport() {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)"));
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.gsv *.sps *.xps)"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->importSharkport(filename);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue