mc.cpp: It is now possible for DeSmuME to import its own battery save file format using the new method, BackupDevice::import_dsv().

This commit is contained in:
rogerman 2017-11-13 14:13:09 -08:00
parent 3b2a9ededc
commit 0907207222
3 changed files with 245 additions and 55 deletions

View File

@ -660,6 +660,7 @@
[panel setAllowsMultipleSelection:NO]; [panel setAllowsMultipleSelection:NO];
[panel setTitle:NSSTRING_TITLE_IMPORT_ROM_SAVE_PANEL]; [panel setTitle:NSSTRING_TITLE_IMPORT_ROM_SAVE_PANEL];
NSArray *fileTypes = [NSArray arrayWithObjects: NSArray *fileTypes = [NSArray arrayWithObjects:
@FILE_EXT_ROM_SAVE,
@FILE_EXT_ROM_SAVE_RAW, @FILE_EXT_ROM_SAVE_RAW,
@FILE_EXT_ACTION_REPLAY_SAVE, @FILE_EXT_ACTION_REPLAY_SAVE,
@FILE_EXT_ACTION_REPLAY_MAX_SAVE, @FILE_EXT_ACTION_REPLAY_MAX_SAVE,

View File

@ -231,16 +231,16 @@ BackupDevice::BackupDevice()
char buf[MAX_PATH] = {0}; char buf[MAX_PATH] = {0};
memset(buf, 0, MAX_PATH); memset(buf, 0, MAX_PATH);
path.getpathnoext(path.BATTERY, buf); path.getpathnoext(path.BATTERY, buf);
filename = std::string(buf) + ".dsv"; _fileName = std::string(buf) + ".dsv";
MCLOG("MC: %s\n", filename.c_str()); MCLOG("MC: %s\n", _fileName.c_str());
bool fexists = (access(filename.c_str(), 0) == 0)?true:false; bool fexists = (access(_fileName.c_str(), 0) == 0)?true:false;
if (fexists && CommonSettings.backupSave) if (fexists && CommonSettings.backupSave)
{ {
std::string tmp_fsav = std::string(buf) + ".dsv.bak"; std::string tmp_fsav = std::string(buf) + ".dsv.bak";
EMUFILE_FILE in = EMUFILE_FILE(filename, "rb"); EMUFILE_FILE in = EMUFILE_FILE(_fileName, "rb");
if (!in.fail()) if (!in.fail())
{ {
u32 sz = in.size(); u32 sz = in.size();
@ -278,7 +278,7 @@ BackupDevice::BackupDevice()
if (sz > 0) if (sz > 0)
{ {
EMUFILE_FILE fpOut = EMUFILE_FILE(filename, "wb"); EMUFILE_FILE fpOut = EMUFILE_FILE(_fileName, "wb");
if (!fpOut.fail()) if (!fpOut.fail())
{ {
u8 *buf = new u8[sz + 1]; u8 *buf = new u8[sz + 1];
@ -301,15 +301,15 @@ BackupDevice::BackupDevice()
u8 res = searchFileSaveType(sz); u8 res = searchFileSaveType(sz);
if (res != 0xFF) if (res != 0xFF)
{ {
info.type = (res + 1); _info.type = (res + 1);
addr_size = info.addr_size = save_types[info.type].addr_size; addr_size = _info.addr_size = save_types[_info.type].addr_size;
info.size = fsize = sz; _info.size = fsize = sz;
fpMC = &fpOut; //so ensure() works fpMC = &fpOut; //so ensure() works
ensure(sz, &fpOut); ensure(sz, &fpOut);
fsize = 0; fsize = 0;
} }
else else
info.type = 0; _info.type = 0;
fexists = true; fexists = true;
} }
else else
@ -323,7 +323,7 @@ BackupDevice::BackupDevice()
} }
} }
fpMC = new EMUFILE_FILE(filename, fexists?"rb+":"wb+"); fpMC = new EMUFILE_FILE(_fileName, fexists?"rb+":"wb+");
const bool fileCanReadWrite = (fpMC->get_fp() != NULL); const bool fileCanReadWrite = (fpMC->get_fp() != NULL);
if (!fileCanReadWrite) if (!fileCanReadWrite)
{ {
@ -339,10 +339,10 @@ BackupDevice::BackupDevice()
fpMC->truncate(0); fpMC->truncate(0);
if (readFooter() == 0) if (readFooter() == 0)
fsize -= (strlen(kDesmumeSaveCookie) + strlen(DESMUME_BACKUP_FOOTER_TXT) + 24); fsize -= BackupDevice::GetDSVFooterSize();
else else
{ {
memset(&info, 0, sizeof(info)); memset(&_info, 0, sizeof(_info));
fsize = 0; fsize = 0;
} }
@ -353,22 +353,22 @@ BackupDevice::BackupDevice()
{ {
if (advsc.isLoaded()) if (advsc.isLoaded())
{ {
info.type = advsc.getSaveType(); _info.type = advsc.getSaveType();
if (info.type != 0xFF && info.type != 0xFE) if (_info.type != 0xFF && _info.type != 0xFE)
{ {
info.type++; _info.type++;
u32 adv_size = save_types[info.type].size; u32 adv_size = save_types[_info.type].size;
if (info.size > adv_size) if (_info.size > adv_size)
{ {
info.size = adv_size; _info.size = adv_size;
fpMC->truncate(adv_size); fpMC->truncate(adv_size);
ensure(adv_size, fpMC); ensure(adv_size, fpMC);
} }
else else
if (info.size < adv_size) if (_info.size < adv_size)
{ {
left = adv_size - info.size; left = adv_size - _info.size;
info.size = adv_size; _info.size = adv_size;
ensure(adv_size); ensure(adv_size);
} }
@ -377,17 +377,17 @@ BackupDevice::BackupDevice()
} }
} }
addr_size = info.addr_size; addr_size = _info.addr_size;
info.padSize = fsize; _info.padSize = fsize;
//none of the other fields are used right now //none of the other fields are used right now
if (CommonSettings.autodetectBackupMethod != 1 && info.type == 0) if (CommonSettings.autodetectBackupMethod != 1 && _info.type == 0)
{ {
info.type = searchFileSaveType(info.size); _info.type = searchFileSaveType(_info.size);
if (info.type == 0xFF) info.type = 0; if (_info.type == 0xFF) _info.type = 0;
} }
u32 ss = (info.padSize * 8) / 1024; u32 ss = (_info.padSize * 8) / 1024;
bool _Mbit = false; bool _Mbit = false;
if (ss >= 1024) if (ss >= 1024)
@ -413,7 +413,7 @@ BackupDevice::~BackupDevice()
int BackupDevice::readFooter() int BackupDevice::readFooter()
{ {
// Check if the footer data exists. // Check if the footer data exists.
if (fpMC->size() < (strlen(kDesmumeSaveCookie) + strlen(DESMUME_BACKUP_FOOTER_TXT) + 24)) if (fpMC->size() < BackupDevice::GetDSVFooterSize())
{ {
return -1; return -1;
} }
@ -440,18 +440,18 @@ int BackupDevice::readFooter()
return -2; return -2;
fpMC->fseek(-24, SEEK_CUR); fpMC->fseek(-24, SEEK_CUR);
fpMC->read_32LE(info.size); fpMC->read_32LE(this->_info.size);
fpMC->read_32LE(info.padSize); fpMC->read_32LE(this->_info.padSize);
fpMC->read_32LE(info.type); fpMC->read_32LE(this->_info.type);
fpMC->read_32LE(info.addr_size); fpMC->read_32LE(this->_info.addr_size);
fpMC->read_32LE(info.mem_size); fpMC->read_32LE(this->_info.mem_size);
MCLOG("DeSmuME backup footer:\n"); MCLOG("DeSmuME backup footer:\n");
MCLOG("\t* size:\t\t%u\n", info.size); MCLOG("\t* size:\t\t%u\n", this->_info.size);
MCLOG("\t* padSize:\t%u\n", info.padSize); MCLOG("\t* padSize:\t%u\n", this->_info.padSize);
MCLOG("\t* type (%u):\t%s\n", info.type, save_types[info.type].descr); MCLOG("\t* type (%u):\t%s\n", this->_info.type, save_types[this->_info.type].descr);
MCLOG("\t* addr_size:\t%u\n", info.addr_size); MCLOG("\t* addr_size:\t%u\n", this->_info.addr_size);
MCLOG("\t* mem_size:\t%u\n", info.mem_size); MCLOG("\t* mem_size:\t%u\n", this->_info.mem_size);
return 0; return 0;
} }
@ -952,9 +952,9 @@ void BackupDevice::ensure(u32 addr, u8 val, EMUFILE *fpOut)
u32 padSize = pad_up_size(addr); u32 padSize = pad_up_size(addr);
u32 size = padSize - fsize; u32 size = padSize - fsize;
info.padSize = info.size = fsize = padSize; this->_info.padSize = this->_info.size = fsize = padSize;
int type = searchFileSaveType(fsize); int type = searchFileSaveType(fsize);
if (type != 0xFF) info.type = (type + 1); if (type != 0xFF) this->_info.type = (type + 1);
#ifndef _DONT_SAVE_BACKUP #ifndef _DONT_SAVE_BACKUP
if (size > 0) if (size > 0)
@ -971,9 +971,9 @@ void BackupDevice::ensure(u32 addr, u8 val, EMUFILE *fpOut)
//and now the actual footer //and now the actual footer
fp->write_32LE(addr); //the size of data that has actually been written fp->write_32LE(addr); //the size of data that has actually been written
fp->write_32LE(padSize); //the size we padded it to fp->write_32LE(padSize); //the size we padded it to
fp->write_32LE(info.type); //save memory type fp->write_32LE(this->_info.type); //save memory type
fp->write_32LE(addr_size); fp->write_32LE(addr_size);
fp->write_32LE(info.size); //save memory size fp->write_32LE(this->_info.size); //save memory size
fp->write_32LE((u32)0); //version number fp->write_32LE((u32)0); //version number
fp->fprintf("%s", kDesmumeSaveCookie); //this is what we'll use to recognize the desmume format save fp->fprintf("%s", kDesmumeSaveCookie); //this is what we'll use to recognize the desmume format save
@ -1066,22 +1066,43 @@ u32 BackupDevice::importDataSize(const char *filename)
bool BackupDevice::importData(const char *filename, u32 force_size) bool BackupDevice::importData(const char *filename, u32 force_size)
{ {
bool res = false; bool res = false;
if (strlen(filename) < 4) return res;
if (strlen(filename) < 4)
{
return res;
}
std::string ext = strright(filename,4); std::string ext = strright(filename,4);
bool isNative = strncasecmp(ext.c_str(), ".dsv", 4) == 0;
bool isDuc = strncasecmp(ext.c_str(), ".duc", 4) == 0; bool isDuc = strncasecmp(ext.c_str(), ".duc", 4) == 0;
bool isDss = strncasecmp(ext.c_str(), ".dss", 4) == 0; bool isDss = strncasecmp(ext.c_str(), ".dss", 4) == 0;
if(isDuc || isDss)
if (isNative)
{
res = import_dsv(filename);
}
else if (isDuc || isDss)
{
res = import_duc(filename, force_size); res = import_duc(filename, force_size);
}
else else
{
if (import_no_gba(filename, force_size)) if (import_no_gba(filename, force_size))
{
res = true; res = true;
}
else else
{
res = import_raw(filename, force_size); res = import_raw(filename, force_size);
}
}
if (res) if (res)
{
NDS_Reset(); NDS_Reset();
}
return res; return res;
} }
@ -1573,6 +1594,75 @@ bool BackupDevice::import_duc(const char* filename, u32 force_size)
} }
bool BackupDevice::import_dsv(const char *filename)
{
bool result = false;
FILE *theFile = fopen(filename, "rb");
if (theFile == NULL)
{
return result;
}
// Validate the DeSmuME footer.
BackupDeviceFileSaveFooter importFileFooter;
size_t importFileSize = 0;
const bool isFileValid = BackupDevice::GetDSVFileInfo(theFile, &importFileFooter, &importFileSize);
if (!isFileValid)
{
return result;
}
if ( (this->addr_size != 0) && (this->addr_size != 0xFFFFFFFF) && (this->addr_size != importFileFooter.info.addr_size))
{
printf("BackupDevice: WARNING! Importing an address bus size that differs from what this game is currently using. (Importing \'%u\'; Expected \'%u\'.\n", (unsigned int)importFileFooter.info.addr_size, (unsigned int)addr_size);
}
if ( (this->_info.padSize > 0) && (this->_info.padSize != importFileFooter.info.padSize) )
{
printf("BackupDevice: NOTE - Importing a backup data size that differs from what this game is currently using. (Importing \'%u\'; Expected \'%u\'.\n", (unsigned int)importFileFooter.info.padSize, (unsigned int)this->_info.padSize);
}
// Read the backup data into memory.
u8 *backupData = (u8 *)malloc(importFileFooter.info.padSize);
fseek(theFile, 0, SEEK_SET);
const size_t backupDataReadByteCount = fread(backupData, 1, importFileFooter.info.padSize, theFile);
fclose(theFile); // At this point, both backup data and footer should have been read, so we can close the import file now.
if (backupDataReadByteCount != importFileFooter.info.padSize)
{
free(backupData);
printf("BackupDevice: DSV import failed! Could not read the backup data.\n");
return result;
}
// Write out the entirety of the file data to the EMUFILE.
// Note: If EMUFILE is an EMUFILE_FILE, know that we are doing a straight overwrite here.
// TODO: For better safety, we should create a new file and then swap.
this->fpMC->fseek(0, SEEK_SET);
if (importFileFooter.info.padSize > 0)
{
this->fpMC->fwrite(backupData, importFileFooter.info.padSize);
}
this->addr_size = importFileFooter.info.addr_size;
this->fsize = importFileFooter.info.padSize;
this->ensure(importFileFooter.info.padSize, this->fpMC);
free(backupData);
// Truncate the file if necessary.
// * Also see TODO note above, since that applies to this step as well.
const size_t newFileSize = this->_info.padSize + BackupDevice::GetDSVFooterSize();
this->fpMC->truncate(newFileSize);
result = true;
return result;
}
bool BackupDevice::load_movie(EMUFILE &is) bool BackupDevice::load_movie(EMUFILE &is)
{ {
const s32 cookieLen = (s32)strlen(kDesmumeSaveCookie); const s32 cookieLen = (s32)strlen(kDesmumeSaveCookie);
@ -1588,10 +1678,7 @@ bool BackupDevice::load_movie(EMUFILE &is)
} }
is.fseek(-24, SEEK_CUR); is.fseek(-24, SEEK_CUR);
struct BackupDeviceFileInfo info;
{
u32 size,padSize,type,addr_size,mem_size;
} info;
is.read_32LE(info.size); is.read_32LE(info.size);
is.read_32LE(info.padSize); is.read_32LE(info.padSize);
@ -1614,3 +1701,85 @@ void BackupDevice::forceManualBackupType()
addr_size = addr_size_for_old_save_size(save_types[CommonSettings.manualBackupType].size); addr_size = addr_size_for_old_save_size(save_types[CommonSettings.manualBackupType].size);
state = RUNNING; state = RUNNING;
} }
size_t BackupDevice::GetDSVFooterSize()
{
return (strlen(DESMUME_BACKUP_FOOTER_TXT) + sizeof(BackupDeviceFileSaveFooter));
}
bool BackupDevice::GetDSVFileInfo(FILE *inFileDSV, BackupDeviceFileSaveFooter *outFooter, size_t *outFileSize)
{
bool result = false;
if (inFileDSV == NULL)
{
return result;
}
// Get the total file size.
fseek(inFileDSV, 0, SEEK_END);
const size_t inFileSize = (size_t)ftell(inFileDSV);
fseek(inFileDSV, 0, SEEK_SET);
// Check for the DeSmuME footer.
if (inFileSize < BackupDevice::GetDSVFooterSize())
{
printf("BackupDevice: File validation failed! The file appears to be corrupted.\n");
return result;
}
// Validate the DeSmuME footer.
BackupDeviceFileSaveFooter inFileFooter;
fseek(inFileDSV, -(s32)sizeof(inFileFooter), SEEK_END);
const size_t footerReadByteCount = fread(&inFileFooter, 1, sizeof(inFileFooter), inFileDSV);
if (footerReadByteCount != sizeof(inFileFooter))
{
printf("BackupDevice: File validation failed! Could not read the file footer.\n");
return result;
}
if (strncmp(inFileFooter.cookie, kDesmumeSaveCookie, sizeof(inFileFooter.cookie)) != 0)
{
char inCookieTerminatedString[sizeof(inFileFooter.cookie) + 1];
strncpy(inCookieTerminatedString, inFileFooter.cookie, sizeof(inFileFooter.cookie));
inCookieTerminatedString[sizeof(inFileFooter.cookie)] = '\0';
printf("BackupDevice: File validation failed! Incorrect cookie found. (Read \'%s\'; Expected \'%s\'.\n", inCookieTerminatedString, kDesmumeSaveCookie);
return result;
}
inFileFooter.version = LE_TO_LOCAL_32(inFileFooter.version);
if (inFileFooter.version != 0)
{
printf("BackupDevice: File validation failed! Incorrect version. (Read \'%u\'; Expected \'%u\'.\n", (unsigned int)inFileFooter.version, 0);
return result;
}
inFileFooter.info.padSize = LE_TO_LOCAL_32(inFileFooter.info.padSize);
const size_t backupDataSize = (inFileSize - BackupDevice::GetDSVFooterSize());
if (inFileFooter.info.padSize != backupDataSize)
{
printf("BackupDevice: File validation failed! Incorrect backup data size. (Read \'%u\'; Expected \'%u\'.\n", (unsigned int)inFileFooter.info.padSize, (unsigned int)backupDataSize);
return result;
}
if (outFooter != NULL)
{
inFileFooter.info.size = LE_TO_LOCAL_32(inFileFooter.info.size);
inFileFooter.info.type = LE_TO_LOCAL_32(inFileFooter.info.type);
inFileFooter.info.addr_size = LE_TO_LOCAL_32(inFileFooter.info.addr_size);
inFileFooter.info.mem_size = LE_TO_LOCAL_32(inFileFooter.info.mem_size);
*outFooter = inFileFooter;
}
if (outFileSize != NULL)
{
*outFileSize = inFileSize;
}
result = true;
return result;
}

View File

@ -50,6 +50,24 @@
class EMUFILE; class EMUFILE;
struct BackupDeviceFileInfo
{
u32 size;
u32 padSize;
u32 type;
u32 addr_size;
u32 mem_size;
};
typedef struct BackupDeviceFileInfo BackupDeviceFileInfo;
struct BackupDeviceFileSaveFooter
{
BackupDeviceFileInfo info;
u32 version;
char cookie[16];
};
typedef struct BackupDeviceFileSaveFooter BackupDeviceFileSaveFooter;
//This "backup device" represents a typical retail NDS save memory accessible via AUXSPI. //This "backup device" represents a typical retail NDS save memory accessible via AUXSPI.
//It is managed as a core emulator service for historical reasons which are bad, //It is managed as a core emulator service for historical reasons which are bad,
//and possible infrastructural simplification reasons which are good. //and possible infrastructural simplification reasons which are good.
@ -66,7 +84,7 @@ public:
void close_rom(); void close_rom();
void forceManualBackupType(); void forceManualBackupType();
void reset_hardware(); void reset_hardware();
std::string getFilename() { return filename; } std::string getFilename() { return this->_fileName; }
u8 readByte(u32 addr, const u8 init); u8 readByte(u32 addr, const u8 init);
u16 readWord(u32 addr, const u16 init); u16 readWord(u32 addr, const u16 init);
@ -124,28 +142,30 @@ public:
bool import_duc(const char* filename, u32 force_size = 0); bool import_duc(const char* filename, u32 force_size = 0);
bool import_no_gba(const char *fname, u32 force_size = 0); bool import_no_gba(const char *fname, u32 force_size = 0);
bool import_raw(const char* filename, u32 force_size = 0); bool import_raw(const char* filename, u32 force_size = 0);
bool import_dsv(const char *filename);
bool export_no_gba(const char* fname); bool export_no_gba(const char* fname);
bool export_raw(const char* filename); bool export_raw(const char* filename);
bool no_gba_unpack(u8 *&buf, u32 &size); bool no_gba_unpack(u8 *&buf, u32 &size);
bool load_movie(EMUFILE &is); bool load_movie(EMUFILE &is);
struct { BackupDeviceFileInfo _info;
u32 size,padSize,type,addr_size,mem_size;
} info;
bool isMovieMode; bool isMovieMode;
u32 importDataSize(const char *filename); u32 importDataSize(const char *filename);
bool importData(const char *filename, u32 force_size = 0); bool importData(const char *filename, u32 force_size = 0);
bool exportData(const char *filename); bool exportData(const char *filename);
static size_t GetDSVFooterSize();
static bool GetDSVFileInfo(FILE *inFileDSV, BackupDeviceFileSaveFooter *outFooter, size_t *outFileSize);
//the value contained in memory when shipped from factory (before user program ever writes to it). more details commented elsewhere. //the value contained in memory when shipped from factory (before user program ever writes to it). more details commented elsewhere.
u8 uninitializedValue; u8 uninitializedValue;
private: private:
EMUFILE *fpMC; EMUFILE *fpMC;
std::string filename; std::string _fileName;
u32 fsize; u32 fsize;
int readFooter(); int readFooter();
bool write(u8 val); bool write(u8 val);