mirror of https://github.com/mgba-emu/mgba.git
Qt: Save converter now supports importing SharkPort saves
This commit is contained in:
parent
d42a13c4b6
commit
283196ceb3
1
CHANGES
1
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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue