From 283196ceb3f392c7ced7e554300e7f6093a2e4d7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Nov 2021 18:02:54 -0800 Subject: [PATCH] Qt: Save converter now supports importing SharkPort saves --- CHANGES | 1 + include/mgba/internal/gba/sharkport.h | 2 + src/gba/sharkport.c | 136 ++++++++++++++++---------- src/platform/qt/SaveConverter.cpp | 65 +++++++++--- src/platform/qt/SaveConverter.h | 13 ++- 5 files changed, 148 insertions(+), 69 deletions(-) diff --git a/CHANGES b/CHANGES index 40be45656..c42b72859 100644 --- a/CHANGES +++ b/CHANGES @@ -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) diff --git a/include/mgba/internal/gba/sharkport.h b/include/mgba/internal/gba/sharkport.h index 361d4eb44..7c689d7b7 100644 --- a/include/mgba/internal/gba/sharkport.h +++ b/include/mgba/internal/gba/sharkport.h @@ -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); diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index de8ccd4d4..3de45e1c5 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -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) { diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 3ccc952d6..050da9be9 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -15,6 +15,7 @@ #ifdef M_CORE_GBA #include #include +#include #endif #ifdef M_CORE_GB #include @@ -34,8 +35,7 @@ SaveConverter::SaveConverter(std::shared_ptr 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(extSavedata)}; + AnnotatedSave save{platform, std::make_shared(extSavedata), Endian::NONE, Container::SAVESTATE}; switch (platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: @@ -253,6 +254,31 @@ void SaveConverter::detectFromSize(std::shared_ptr vf) { #endif } +void SaveConverter::detectFromHeaders(std::shared_ptr 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(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(bytes), Endian::NONE, Container::SHARKPORT}); + } else { + m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, std::make_shared(bytes.left(SIZE_CART_SRAM)), Endian::NONE, Container::SHARKPORT}); + m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, std::make_shared(bytes.left(SIZE_CART_FLASH512)), Endian::NONE, Container::SHARKPORT}); + m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, std::make_shared(bytes.left(SIZE_CART_EEPROM)), Endian::BIG, Container::SHARKPORT}); + m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, std::make_shared(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 vf, Endian endianness) - : savestate(true) +SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr 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 vf, Endian endianness) - : savestate(false) +SaveConverter::AnnotatedSave::AnnotatedSave(SavedataType type, std::shared_ptr 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 vf, Endian endianness) - : savestate(false) +SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std::shared_ptr 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::possibleConver QList 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) { diff --git a/src/platform/qt/SaveConverter.h b/src/platform/qt/SaveConverter.h index a9da27952..5a94c4804 100644 --- a/src/platform/qt/SaveConverter.h +++ b/src/platform/qt/SaveConverter.h @@ -56,14 +56,19 @@ private: GBMemoryBankControllerType type; }; #endif + enum class Container { + NONE = 0, + SAVESTATE, + SHARKPORT + }; struct AnnotatedSave { AnnotatedSave(); - AnnotatedSave(mPlatform, std::shared_ptr, Endian = Endian::NONE); + AnnotatedSave(mPlatform, std::shared_ptr, Endian = Endian::NONE, Container = Container::NONE); #ifdef M_CORE_GBA - AnnotatedSave(SavedataType, std::shared_ptr, Endian = Endian::NONE); + AnnotatedSave(SavedataType, std::shared_ptr, Endian = Endian::NONE, Container = Container::NONE); #endif #ifdef M_CORE_GB - AnnotatedSave(GBMemoryBankControllerType, std::shared_ptr, Endian = Endian::NONE); + AnnotatedSave(GBMemoryBankControllerType, std::shared_ptr, Endian = Endian::NONE, Container = Container::NONE); #endif AnnotatedSave asRaw() const; @@ -73,7 +78,7 @@ private: QList possibleConversions() const; QByteArray convertTo(const AnnotatedSave&) const; - bool savestate; + Container container; mPlatform platform; ssize_t size; std::shared_ptr backing;