Qt: Save converter now supports importing SharkPort saves

This commit is contained in:
Vicki Pfau 2021-11-25 18:02:54 -08:00
parent d42a13c4b6
commit 283196ceb3
5 changed files with 148 additions and 69 deletions

View File

@ -40,6 +40,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
- SDL: Use SDL_JoystickRumble where available
- Wii: Add adjustable gyroscope settings (closes mgba.io/i/2245)

View File

@ -13,6 +13,8 @@ CXX_GUARD_START
struct GBA;
struct VFile;
int GBASavedataSharkPortPayloadSize(struct VFile* vf);
void* GBASavedataSharkPortGetPayload(struct VFile* vf, size_t* size, uint8_t* header, bool testChecksum);
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum);
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf);

View File

@ -11,118 +11,155 @@
static const char* const SHARKPORT_HEADER = "SharkPortSave";
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) {
int GBASavedataSharkPortPayloadSize(struct VFile* vf) {
union {
char c[0x1C];
int32_t i;
} buffer;
vf->seek(vf, 0, SEEK_SET);
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
int32_t size;
LOAD_32(size, 0, &buffer.i);
if (size != (int32_t) strlen(SHARKPORT_HEADER)) {
return false;
return 0;
}
if (vf->read(vf, buffer.c, size) < size) {
return false;
return 0;
}
if (memcmp(SHARKPORT_HEADER, buffer.c, size) != 0) {
return false;
return 0;
}
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
LOAD_32(size, 0, &buffer.i);
if (size != 0x000F0000) {
// What is this value?
return false;
return 0;
}
// Skip first three fields
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
LOAD_32(size, 0, &buffer.i);
if (vf->seek(vf, size, SEEK_CUR) < 0) {
return false;
return 0;
}
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
LOAD_32(size, 0, &buffer.i);
if (vf->seek(vf, size, SEEK_CUR) < 0) {
return false;
return 0;
}
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
LOAD_32(size, 0, &buffer.i);
if (vf->seek(vf, size, SEEK_CUR) < 0) {
return false;
return 0;
}
// Read payload
if (vf->read(vf, &buffer.i, 4) < 4) {
return false;
return 0;
}
LOAD_32(size, 0, &buffer.i);
return size;
}
void* GBASavedataSharkPortGetPayload(struct VFile* vf, size_t* osize, uint8_t* oheader, bool testChecksum) {
int32_t size = GBASavedataSharkPortPayloadSize(vf);
if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) {
return false;
return NULL;
}
size -= 0x1C;
uint8_t header[0x1C];
int8_t* payload = malloc(size);
if (vf->read(vf, header, sizeof(header)) < (int) sizeof(header)) {
goto cleanup;
}
char* payload = malloc(size);
if (vf->read(vf, payload, size) < size) {
goto cleanup;
}
struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom;
memcpy(buffer.c, &cart->title, 16);
buffer.c[0x10] = 0;
buffer.c[0x11] = 0;
buffer.c[0x12] = cart->checksum;
buffer.c[0x13] = cart->maker;
buffer.c[0x14] = 1;
buffer.c[0x15] = 0;
buffer.c[0x16] = 0;
buffer.c[0x17] = 0;
buffer.c[0x18] = 0;
buffer.c[0x19] = 0;
buffer.c[0x1A] = 0;
buffer.c[0x1B] = 0;
if (memcmp(buffer.c, payload, testChecksum ? 0x1C : 0xF) != 0) {
goto cleanup;
}
uint32_t buffer;
uint32_t checksum;
if (vf->read(vf, &buffer.i, 4) < 4) {
if (vf->read(vf, &buffer, 4) < 4) {
goto cleanup;
}
LOAD_32(checksum, 0, &buffer.i);
LOAD_32(checksum, 0, &buffer);
if (testChecksum) {
uint32_t calcChecksum = 0;
int i;
for (i = 0; i < (int) sizeof(header); ++i) {
calcChecksum += ((int32_t) header[i]) << (calcChecksum % 24);
}
for (i = 0; i < size; ++i) {
calcChecksum += ((int32_t) payload[i]) << (calcChecksum % 24);
}
if (calcChecksum != checksum) {
goto cleanup;
return NULL;
}
}
*osize = size;
if (oheader) {
memcpy(oheader, header, sizeof(header));
}
return payload;
cleanup:
free(payload);
return NULL;
}
bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) {
uint8_t buffer[0x1C];
uint8_t header[0x1C];
size_t size;
void* payload = GBASavedataSharkPortGetPayload(vf, &size, header, testChecksum);
if (!payload) {
return false;
}
bool success = false;
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, header, testChecksum ? 0x1C : 0xF) != 0) {
goto cleanup;
}
uint32_t copySize = size - 0x1C;
switch (gba->memory.savedata.type) {
case SAVEDATA_FLASH512:
if (copySize > SIZE_CART_FLASH512) {
if (size > SIZE_CART_FLASH512) {
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M);
}
// Fall through
default:
if (copySize > GBASavedataSize(&gba->memory.savedata)) {
copySize = GBASavedataSize(&gba->memory.savedata);
if (size > GBASavedataSize(&gba->memory.savedata)) {
size = GBASavedataSize(&gba->memory.savedata);
}
break;
case SAVEDATA_FORCE_NONE:
@ -130,28 +167,27 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
goto cleanup;
}
if (gba->memory.savedata.type == SAVEDATA_EEPROM) {
if (size == SIZE_CART_EEPROM || size == SIZE_CART_EEPROM512) {
size_t i;
for (i = 0; i < copySize; i += 8) {
for (i = 0; i < size; i += 8) {
uint32_t lo, hi;
LOAD_32BE(lo, i + 0x1C, payload);
LOAD_32BE(hi, i + 0x20, payload);
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[0x1C], copySize);
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);
}
free(payload);
return true;
success = true;
cleanup:
free(payload);
return false;
return success;
}
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {

View File

@ -15,6 +15,7 @@
#ifdef M_CORE_GBA
#include <mgba/gba/core.h>
#include <mgba/internal/gba/serialize.h>
#include <mgba/internal/gba/sharkport.h>
#endif
#ifdef M_CORE_GB
#include <mgba/gb/core.h>
@ -34,8 +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]() {
// TODO: Add gameshark saves here too
QStringList formats{"*.sav", "*.sgm", "*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9"};
QStringList formats{"*.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()) {
@ -101,6 +101,7 @@ void SaveConverter::refreshInputTypes() {
detectFromSavestate(*vf);
detectFromSize(vf);
detectFromHeaders(vf);
for (const auto& save : m_validSaves) {
m_ui.inputType->addItem(save);
@ -166,7 +167,7 @@ void SaveConverter::detectFromSavestate(VFile* vf) {
}
QByteArray state = getState(vf, platform);
AnnotatedSave save{platform, std::make_shared<VFileDevice>(extSavedata)};
AnnotatedSave save{platform, std::make_shared<VFileDevice>(extSavedata), Endian::NONE, Container::SAVESTATE};
switch (platform) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
@ -253,6 +254,31 @@ void SaveConverter::detectFromSize(std::shared_ptr<VFileDevice> vf) {
#endif
}
void SaveConverter::detectFromHeaders(std::shared_ptr<VFileDevice> vf) {
const QByteArray sharkport("\xd\0\0\0SharkPortSave", 0x11);
QByteArray buffer;
vf->seek(0);
buffer = vf->read(sharkport.size());
if (buffer == sharkport) {
size_t size;
void* data = GBASavedataSharkPortGetPayload(*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
if (size == SIZE_CART_FLASH1M) {
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, std::make_shared<VFileDevice>(bytes), Endian::NONE, Container::SHARKPORT});
} else {
m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_SRAM)), Endian::NONE, Container::SHARKPORT});
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_FLASH512)), Endian::NONE, Container::SHARKPORT});
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_EEPROM)), Endian::BIG, Container::SHARKPORT});
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, std::make_shared<VFileDevice>(bytes.left(SIZE_CART_EEPROM512)), Endian::BIG, Container::SHARKPORT});
}
free(data);
}
}
}
mPlatform SaveConverter::getStatePlatform(VFile* vf) {
uint32_t magic;
void* state = nullptr;
@ -326,7 +352,7 @@ QByteArray SaveConverter::getExtdata(VFile* vf, mPlatform platform, mStateExtdat
}
SaveConverter::AnnotatedSave::AnnotatedSave()
: savestate(false)
: container(Container::NONE)
, platform(mPLATFORM_NONE)
, size(0)
, backing()
@ -334,8 +360,8 @@ SaveConverter::AnnotatedSave::AnnotatedSave()
{
}
SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr<VFileDevice> vf, Endian endianness)
: savestate(true)
SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr<VFileDevice> vf, Endian endianness, Container container)
: container(container)
, platform(platform)
, size(vf->size())
, backing(vf)
@ -344,8 +370,8 @@ SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr<
}
#ifdef M_CORE_GBA
SaveConverter::AnnotatedSave::AnnotatedSave(SavedataType type, std::shared_ptr<VFileDevice> vf, Endian endianness)
: savestate(false)
SaveConverter::AnnotatedSave::AnnotatedSave(SavedataType type, std::shared_ptr<VFileDevice> vf, Endian endianness, Container container)
: container(container)
, platform(mPLATFORM_GBA)
, size(vf->size())
, backing(vf)
@ -356,8 +382,8 @@ SaveConverter::AnnotatedSave::AnnotatedSave(SavedataType type, std::shared_ptr<V
#endif
#ifdef M_CORE_GB
SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std::shared_ptr<VFileDevice> vf, Endian endianness)
: savestate(false)
SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std::shared_ptr<VFileDevice> vf, Endian endianness, Container container)
: container(container)
, platform(mPLATFORM_GB)
, size(vf->size())
, backing(vf)
@ -468,14 +494,21 @@ SaveConverter::AnnotatedSave::operator QString() const {
if (!endianStr.isEmpty()) {
saveType = QCoreApplication::translate("SaveConverter", "%1 (%2)").arg(saveType).arg(endianStr);
}
if (savestate) {
switch (container) {
case Container::SAVESTATE:
format = QCoreApplication::translate("SaveConverter", "%1 save state with embedded %2 save game");
break;
case Container::SHARKPORT:
format = QCoreApplication::translate("SaveConverter", "%1 SharkPort %2 save game");
break;
case Container::NONE:
break;
}
return format.arg(nicePlatformFormat(platform)).arg(saveType);
}
bool SaveConverter::AnnotatedSave::operator==(const AnnotatedSave& other) const {
if (other.savestate != savestate || other.platform != platform || other.size != size || other.endianness != endianness) {
if (other.container != container || other.platform != platform || other.size != size || other.endianness != endianness) {
return false;
}
switch (platform) {
@ -503,13 +536,12 @@ QList<SaveConverter::AnnotatedSave> SaveConverter::AnnotatedSave::possibleConver
QList<AnnotatedSave> possible;
AnnotatedSave same = asRaw();
same.backing.reset();
same.savestate = false;
same.container = Container::NONE;
if (savestate) {
if (container != Container::NONE) {
possible.append(same);
}
AnnotatedSave endianSwapped = same;
switch (endianness) {
case Endian::LITTLE:
@ -583,6 +615,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate
switch (gba.type) {
case SAVEDATA_EEPROM:
case SAVEDATA_EEPROM512:
if (endianness == target.endianness) {
break;
}
converted.resize(target.size);
buffer = backing->readAll();
for (int i = 0; i < size; i += 8) {

View File

@ -56,14 +56,19 @@ private:
GBMemoryBankControllerType type;
};
#endif
enum class Container {
NONE = 0,
SAVESTATE,
SHARKPORT
};
struct AnnotatedSave {
AnnotatedSave();
AnnotatedSave(mPlatform, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
AnnotatedSave(mPlatform, std::shared_ptr<VFileDevice>, Endian = Endian::NONE, Container = Container::NONE);
#ifdef M_CORE_GBA
AnnotatedSave(SavedataType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
AnnotatedSave(SavedataType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE, Container = Container::NONE);
#endif
#ifdef M_CORE_GB
AnnotatedSave(GBMemoryBankControllerType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE);
AnnotatedSave(GBMemoryBankControllerType, std::shared_ptr<VFileDevice>, Endian = Endian::NONE, Container = Container::NONE);
#endif
AnnotatedSave asRaw() const;
@ -73,7 +78,7 @@ private:
QList<AnnotatedSave> possibleConversions() const;
QByteArray convertTo(const AnnotatedSave&) const;
bool savestate;
Container container;
mPlatform platform;
ssize_t size;
std::shared_ptr<VFileDevice> backing;