new save file system. the manual save type selection no longer does anything. If you think you still need it, we need to study the case and find out why.

This commit is contained in:
zeromus 2009-05-18 04:25:19 +00:00
parent 89df112dd8
commit ca540cb6b6
9 changed files with 526 additions and 45 deletions

37
desmume/sav.txt Normal file
View File

@ -0,0 +1,37 @@
01234567890123456789012345678901234567890123456789012345678901234567890123456789
The desmume save file format is merely a raw save file with a FOOTER.
This was chosen in order to maximize compatibility with other emulators,
which tend load the savefile as-is and let the game read out of whatever range
it is expecting. To assist with this, before writing the save file, desmume
will pad the raw save file out to the next highest known length. Note that this
may sometimes be incorrect if the savefile hasnt been written through to the end
during initialization. This could cause other emulators to fail to recognize the
save file.
The footer format can be identified by locating the 16Byte ascii string
"|-DESMUME SAVE-|"
at the end of the file. This corresponds with the following save structure:
struct Footer {
u32 actually_written_size;
u32 padded_size;
u32 save_type; //(not currently used)
u32 address_size; //address bus size
u32 save_size; //size parameter of the save type (not currently used)
u32 version_number; //should be 0
char cookie[16];
};
note that padded_size should be where you see the end of the raw save data
and the beginning of desmume-specific information, including some junk before
the actual footer.
actually_written_size is the highest address (plus one) written to by the game.
The new desmume savefile system tries to make as few decisions as possible,
which is the reason for the behavior of actually_written_size and the disuse
of save_type and save_size. If few decisions are made, then few mistakes can
be made. That is the idea, anyway. We'll make decisions later if we need to.
save_type and save_size are reserved in case we do.

View File

@ -361,6 +361,9 @@ static FORCEINLINE u32 MMU_LCDmap(u32 addr, bool& unmapped)
//shared wram mapping for arm7
if(PROCNUM==ARMCPU_ARM7)
{
//necessary? not sure
//addr &= 0x3FFFF;
//addr += 0x06000000;
u32 ofs = addr & 0x1FFFF;
u32 bank = (addr >> 17)&1;
if(vram_arm7_map[bank] == VRAM_PAGE_UNMAPPED)
@ -918,9 +921,9 @@ void MMU_Init(void) {
MMU.fw.fp = NULL;
// Init Backup Memory device, this should really be done when the rom is loaded
mc_init(&MMU.bupmem, MC_TYPE_AUTODETECT);
mc_alloc(&MMU.bupmem, 1);
MMU.bupmem.fp = NULL;
//mc_init(&MMU.bupmem, MC_TYPE_AUTODETECT);
//mc_alloc(&MMU.bupmem, 1);
//MMU.bupmem.fp = NULL;
rtcInit();
addonsInit();
if(Mic_Init() == FALSE)
@ -934,9 +937,9 @@ void MMU_DeInit(void) {
if (MMU.fw.fp)
fclose(MMU.fw.fp);
mc_free(&MMU.fw);
if (MMU.bupmem.fp)
fclose(MMU.bupmem.fp);
mc_free(&MMU.bupmem);
//if (MMU.bupmem.fp)
// fclose(MMU.bupmem.fp);
//mc_free(&MMU.bupmem);
addonsClose();
Mic_DeInit();
}
@ -1781,23 +1784,26 @@ void FASTCALL _MMU_ARM9_write16(u32 adr, u16 val)
u16 oldval = T1ReadWord(MMU.MMU_MEM[ARMCPU_ARM7][0x40], 0x204);
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM9][0x40], 0x204, val);
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][0x40], 0x204, (val & 0xFF80) | (oldval & 0x7F));
return;
}
return;
case REG_AUXSPICNT:
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM9][(REG_AUXSPICNT >> 20) & 0xff], REG_AUXSPICNT & 0xfff, val);
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPICNT >> 20) & 0xff], REG_AUXSPICNT & 0xfff, val);
AUX_SPI_CNT = val;
if (val == 0)
mc_reset_com(&MMU.bupmem); /* reset backup memory device communication */
//mc_reset_com(&MMU.bupmem); // reset backup memory device communication
MMU.backupDevice.reset_command();
return;
case REG_AUXSPIDATA:
if(val!=0)
AUX_SPI_CMD = val & 0xFF;
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM9][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, bm_transfer(&MMU.bupmem, val));
//T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, bm_transfer(&MMU.bupmem, val));
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, MMU.backupDevice.data_command(val));
return;
case REG_DISPA_BG0CNT :
//GPULOG("MAIN BG0 SETPROP 16B %08X\r\n", val);
GPU_setBGProp(MainScreen.gpu, 0, val);
@ -3079,14 +3085,16 @@ void FASTCALL _MMU_ARM7_write16(u32 adr, u16 val)
AUX_SPI_CNT = val;
if (val == 0)
mc_reset_com(&MMU.bupmem); // reset backup memory device communication
//mc_reset_com(&MMU.bupmem); // reset backup memory device communication
MMU.backupDevice.reset_command();
return;
case REG_AUXSPIDATA:
if(val!=0)
AUX_SPI_CMD = val & 0xFF;
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, bm_transfer(&MMU.bupmem, val));
//T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, bm_transfer(&MMU.bupmem, val));
T1WriteWord(MMU.MMU_MEM[ARMCPU_ARM7][(REG_AUXSPIDATA >> 20) & 0xff], REG_AUXSPIDATA & 0xfff, MMU.backupDevice.data_command(val));
return;
case REG_SPICNT :
@ -4019,10 +4027,10 @@ void FASTCALL MMU_DumpMemBlock(u8 proc, u32 address, u32 size, u8 *buffer)
}
void mmu_select_savetype(int type, int *bmemtype, u32 *bmemsize) {
if (type<0 || type > 6) return;
*bmemtype=save_types[type][0];
*bmemsize=save_types[type][1];
mc_realloc(&MMU.bupmem, *bmemtype, *bmemsize);
//if (type<0 || type > 6) return;
//*bmemtype=save_types[type][0];
//*bmemsize=save_types[type][1];
//mc_realloc(&MMU.bupmem, *bmemtype, *bmemsize);
}
////////////////////////////////////////////////////////////

View File

@ -118,6 +118,7 @@ struct MMU_struct {
memory_chip_t fw;
memory_chip_t bupmem;
BackupDevice backupDevice;
nds_dscard dscard[2];
u32 CheckTimers;

View File

@ -832,8 +832,9 @@ int NDS_LoadROM( const char *filename, int bmtype, u32 bmsize,
strcpy(buf, pathFilenameToROMwithoutExt);
strcat(buf, ".sav"); // DeSmuME memory card :)
mc_realloc(&MMU.bupmem, bmtype, bmsize);
mc_load_file(&MMU.bupmem, buf);
//mc_realloc(&MMU.bupmem, bmtype, bmsize);
//mc_load_file(&MMU.bupmem, buf);
MMU.backupDevice.load_rom(buf);
memset(buf, 0, MAX_PATH);
strcpy(buf, pathFilenameToROMwithoutExt);
@ -903,6 +904,7 @@ void NDS_Reset(BOOL resetBySavestate)
currFrameCounter=0;
MMU_clearMem();
MMU.backupDevice.reset();
//ARM7 BIOS IRQ HANDLER
if(CommonSettings.UseExtBIOS == true)

View File

@ -24,6 +24,7 @@
#include "types.h"
#include "mc.h"
#include "movie.h"
#include "readwrite.h"
#define FW_CMD_READ 0x3
#define FW_CMD_WRITEDISABLE 0x4
@ -50,6 +51,16 @@
#define CARDFLASH_DEEP_POWDOWN 0xB9 /* Not used*/
#define CARDFLASH_WAKEUP 0xAB /* Not used*/
//this should probably be 0xFF but we're using 0x00 until we find out otherwise
//(no$ appears definitely to initialized to 0xFF)
static const u8 kUninitializedSaveDataValue = 0x00;
static const char* kDesmumeSaveCookie = "|-DESMUME SAVE-|";
static const u32 saveSizes[] = {512,8*1024,64*1024,256*1024,512*1024,32*1024};
static const u32 saveSizes_count = ARRAY_SIZE(saveSizes);
void mc_init(memory_chip_t *mc, int type)
{
mc->com = 0;
@ -510,3 +521,342 @@ u8 bm_transfer(memory_chip_t *mc, u8 data)
return data;
}
bool BackupDevice::save_state(std::ostream* os)
{
int version = 0;
write32le(version,os);
// write32le(0,os); //reserved for type if i need it later
// write32le(0,os); //reserved for size if i need it later
write32le(write_enable,os);
write32le(com,os);
write32le(addr_size,os);
write32le(addr_counter,os);
write32le((u32)state,os);
writebuffer(data,os);
writebuffer(data_autodetect,os);
return true;
}
bool BackupDevice::load_state(std::istream* is)
{
int version;
if(read32le(&version,is)!=1) return false;
if(version==0) {
u32 size, type;
// read32le(&size,is);
// read32le(&type,is);
read32le(&write_enable,is);
read32le(&com,is);
read32le(&addr_size,is);
read32le(&addr_counter,is);
read32le((u32*)&state,is);
readbuffer(data,is);
readbuffer(data_autodetect,is);
}
return true;
}
//due to unfortunate shortcomings in the emulator architecture,
//at reset-time, we won't have a filename to the .sav file.
//so the only difference between load_rom (init) and reset is that
//one of them saves the filename
void BackupDevice::load_rom(const char* filename)
{
this->filename = filename;
reset();
}
void BackupDevice::reset()
{
state = DETECTING;
data.resize(0);
data_autodetect.resize(0);
loadfile();
flushPending = false;
}
void BackupDevice::close_rom() {}
void BackupDevice::reset_command()
{
//for a performance hack, save files are only flushed after each reset command
//(hopefully, after each page)
if(flushPending)
{
flush();
flushPending = false;
}
if(state == DETECTING && data_autodetect.size()>0)
{
//we can now safely detect the save address size
u32 autodetect_size = data_autodetect.size();
addr_size = autodetect_size - 1;
if(autodetect_size==6) addr_size = 2; //castlevania dawn of sorrow 64kbit eeprom (EEPROM2 in the old system)
if(addr_size>4)
{
LOG("Unexpected backup memory address size: %d\n",addr_size);
}
state = RUNNING;
data_autodetect.resize(0);
}
com = 0;
}
u8 BackupDevice::data_command(u8 val)
{
if(com == BM_CMD_READLOW || com == BM_CMD_WRITELOW)
{
//handle data or address
if(state == DETECTING)
{
if(com == BM_CMD_WRITELOW)
{
LOG("Unexpected backup device initialization sequence using writes!\n");
}
//just buffer the data until we're no longer detecting
data_autodetect.push_back(val);
val = 0;
}
else
{
if(addr_counter<addr_size)
{
//continue building address
addr <<= 8;
addr |= val;
addr_counter++;
}
else
{
//address is complete
ensure(addr);
if(com == BM_CMD_READLOW)
{
val = data[addr];
//printf("read: %08X\n",addr);
}
else
{
data[addr] = val;
flushPending = true;
//printf("writ: %08X\n",addr);
}
addr++;
}
}
}
else if(com == BM_CMD_READSTATUS)
{
//handle request to read status
//LOG("Backup Memory Read Status: %02X\n", mc->write_enable << 1);
return (write_enable << 1);
}
else
{
//there is no current command. receive one
switch(val)
{
case 0: break; //??
case BM_CMD_WRITEDISABLE:
write_enable = FALSE;
break;
case BM_CMD_READSTATUS:
com = BM_CMD_READSTATUS;
break;
case BM_CMD_WRITEENABLE:
write_enable = TRUE;
break;
case BM_CMD_WRITELOW:
case BM_CMD_READLOW:
com = val;
addr_counter = 0;
addr = 0;
break;
case BM_CMD_WRITEHIGH:
case BM_CMD_READHIGH:
if(val == BM_CMD_WRITEHIGH) val = BM_CMD_WRITELOW;
if(val == BM_CMD_READHIGH) val = BM_CMD_READLOW;
com = val;
addr_counter = 0;
addr = 0;
if(addr_size==1) {
//"write command that's only available on ST M95040-W that I know of"
//this makes sense, since this device would only have a 256 bytes address space with writelow
//and writehigh would allow access to the upper 256 bytes
//but it was detected in pokemon diamond also during the main save process
addr = 0x1;
}
break;
default:
LOG("Unhandled Backup Memory command: %02X\n", val);
break;
}
}
return val;
}
//guarantees that the data buffer has room enough for a byte at the specified address
void BackupDevice::ensure(u32 addr)
{
u32 size = data.size();
if(size<addr+1)
{
data.resize(addr+1);
}
for(u32 i=size;i<=addr;i++)
data[i] = kUninitializedSaveDataValue;
}
void BackupDevice::loadfile()
{
FILE* inf = fopen(filename.c_str(),"rb");
if(inf)
{
//scan for desmume save footer
const u32 cookieLen = strlen(kDesmumeSaveCookie);
char *sigbuf = new char[cookieLen];
fseek(inf, -cookieLen, SEEK_END);
fread(sigbuf,1,cookieLen,inf);
int cmp = memcmp(sigbuf,kDesmumeSaveCookie,cookieLen);
delete[] sigbuf;
if(cmp)
{
//raw save file
fseek(inf, 0, SEEK_END);
int size = ftell(inf);
fseek(inf, 0, SEEK_SET);
data.resize(size);
fread(&data[0],1,size,inf);
fclose(inf);
return;
}
//desmume format
fseek(inf, -cookieLen, SEEK_END);
fseek(inf, -4, SEEK_CUR);
u32 version = 0xFFFFFFFF;
read32le(&version,inf);
if(version!=0) {
LOG("Unknown save file format\n");
return;
}
fseek(inf, -24, SEEK_CUR);
struct {
u32 size,padSize,type,addr_size,mem_size;
} info;
read32le(&info.size,inf);
read32le(&info.padSize,inf);
read32le(&info.type,inf);
read32le(&info.addr_size,inf);
read32le(&info.mem_size,inf);
//establish the save data
data.resize(info.size);
fseek(inf, 0, SEEK_SET);
fread(&data[0],1,info.size,inf); //read all the raw data we have
state = RUNNING;
addr_size = info.addr_size;
//none of the other fields are used right now
fclose(inf);
}
else
{
LOG("Missing save file %s\n",filename.c_str());
}
}
u32 BackupDevice::addr_size_for_old_save_size(int bupmem_size)
{
switch(bupmem_size) {
case MC_SIZE_4KBITS: return 2; //1? hi command?
case MC_SIZE_64KBITS:
case MC_SIZE_256KBITS:
case MC_SIZE_512KBITS:
return 2;
case MC_SIZE_1MBITS:
case MC_SIZE_2MBITS:
case MC_SIZE_4MBITS:
case MC_SIZE_8MBITS:
case MC_SIZE_16MBITS:
case MC_SIZE_64MBITS:
return 3;
default:
return 0xFFFFFFFF;
}
}
u32 BackupDevice::addr_size_for_old_save_type(int bupmem_type)
{
switch(bupmem_type)
{
case MC_TYPE_EEPROM1:
return 1;
case MC_TYPE_EEPROM2:
case MC_TYPE_FRAM:
return 2;
case MC_TYPE_FLASH:
return 3;
default:
return 0xFFFFFFFF;
}
}
void BackupDevice::load_old_state(u32 addr_size, u8* data, u32 datasize)
{
state = RUNNING;
this->addr_size = addr_size;
this->data.resize(datasize);
memcpy(&this->data[0],data,datasize);
}
void BackupDevice::flush()
{
FILE* outf = fopen(filename.c_str(),"wb");
if(outf)
{
fwrite(&data[0],1,data.size(),outf);
//write the footer. we use a footer so that we can maximize the chance of the
//save file being recognized as a raw save file by other emulators etc.
//first, pad up to the next largest known save size.
u32 size = data.size();
int ctr=0;
while(ctr<saveSizes_count && size > saveSizes[ctr]) ctr++;
u32 padSize = saveSizes[ctr];
for(u32 i=size;i<padSize;i++)
fputc(kUninitializedSaveDataValue,outf);
//this is just for humans to read
fprintf(outf,"|<--Snip above here to create a raw sav by excluding this DeSmuME savedata footer:");
//and now the actual footer
write32le(size,outf); //the size of data that has actually been written
write32le(padSize,outf); //the size we padded it to
write32le(0,outf); //save memory type
write32le(addr_size,outf);
write32le(0,outf); //save memory size
write32le(0,outf); //version number
fprintf(outf,kDesmumeSaveCookie); //this is what we'll use to recognize the desmume format save
fclose(outf);
}
else
{
LOG("Unable to open savefile %s\n",filename.c_str());
}
}

View File

@ -22,6 +22,8 @@
#define __FW_H__
#include <stdio.h>
#include <vector>
#include <string>
#include "types.h"
#define MC_TYPE_AUTODETECT 0x0
@ -60,6 +62,52 @@ typedef struct
int autodetectsize;
} memory_chip_t;
//the new backup system by zeromus
class BackupDevice
{
public:
void load_rom(const char* filename);
void reset();
void close_rom();
bool save_state(std::ostream* os);
bool load_state(std::istream* is);
//commands from mmu
void reset_command();
u8 data_command(u8);
//this info was saved before the last reset (used for savestate compatibility)
struct SavedInfo
{
u32 addr_size;
} savedInfo;
void load_old_state(u32 addr_size, u8* data, u32 datasize);
static u32 addr_size_for_old_save_size(int bupmem_size);
static u32 addr_size_for_old_save_type(int bupmem_type);
private:
BOOL write_enable; //is write enabled?
u32 com; //persistent command actually handled
u32 addr_size, addr_counter;
u32 addr;
std::string filename;
std::vector<u8> data;
std::vector<u8> data_autodetect;
enum : u32 {
DETECTING = 0, RUNNING = 1
} state;
void loadfile();
void ensure(u32 addr);
void flush();
bool flushPending;
};
#define NDS_FW_SIZE_V1 (256 * 1024) /* size of fw memory on nds v1 */
#define NDS_FW_SIZE_V2 (512 * 1024) /* size of fw memory on nds v2 */

View File

@ -155,3 +155,19 @@ int read32le(u32 *Bufo, std::istream *is)
return 1;
}
int readbuffer(std::vector<u8> &vec, std::istream* is)
{
u32 size;
if(read32le(&size,is) != 1) return 0;
vec.resize(size);
if(size>0) is->read((char*)&vec[0],size);
return 1;
}
int writebuffer(std::vector<u8>& vec, std::ostream* os)
{
u32 size = vec.size();
write32le(size,os);
if(size>0) os->write((char*)&vec[0],size);
return 1;
}

View File

@ -4,6 +4,7 @@
#include "types.h"
#include <iostream>
#include <cstdio>
#include <vector>
//well. just for the sake of consistency
int write8le(u8 b, FILE *fp);
@ -21,4 +22,7 @@ int read16le(u16 *Bufo, std::istream *is);
inline int read16le(s16 *Bufo, std::istream* is) { return read16le((u16*)Bufo,is); }
int read8le(u8 *Bufo, std::istream *is);
int readbuffer(std::vector<u8> &vec, std::istream* is);
int writebuffer(std::vector<u8>& vec, std::ostream* os);
#endif

View File

@ -255,11 +255,10 @@ SFORMAT SF_MOVIE[]={
static void mmu_savestate(std::ostream* os)
{
//version
write32le(1,os);
write32le(MMU.bupmem.type,os);
write32le(MMU.bupmem.size,os);
os->write((char*)MMU.bupmem.data,MMU.bupmem.size);
write32le(2,os);
//newer savefile system:
MMU.backupDevice.save_state(os);
}
static bool mmu_loadstate(std::istream* is, int size)
@ -268,29 +267,45 @@ static bool mmu_loadstate(std::istream* is, int size)
int version;
if(read32le(&version,is) != 1) return false;
u32 bupmem_size;
if(version == 0)
if(version == 0 || version == 1)
{
//version 0 was buggy and didnt save the type.
//it would silently fail if there was a size mismatch
SAV_silent_fail_flag = true;
if(read32le(&bupmem_size,is) != 1) return false;
//if(bupmem_size != MMU.bupmem.size) return false; //mismatch between current initialized and saved size
mc_realloc(&MMU.bupmem,MC_TYPE_AUTODETECT,bupmem_size);
}
else
{
//version 1 reinitializes the save system with the type that was saved
int bupmem_type;
if(read32le(&bupmem_type,is) != 1) return false;
if(read32le(&bupmem_size,is) != 1) return false;
mc_realloc(&MMU.bupmem,bupmem_type,bupmem_size);
}
u32 bupmem_size;
u32 addr_size;
is->read((char*)MMU.bupmem.data,bupmem_size);
if(is->fail()) return false;
if(version == 0)
{
//version 0 was buggy and didnt save the type.
//it would silently fail if there was a size mismatch
SAV_silent_fail_flag = true;
if(read32le(&bupmem_size,is) != 1) return false;
//if(bupmem_size != MMU.bupmem.size) return false; //mismatch between current initialized and saved size
addr_size = BackupDevice::addr_size_for_old_save_size(bupmem_size);
}
else if(version == 1)
{
//version 1 reinitializes the save system with the type that was saved
int bupmem_type;
if(read32le(&bupmem_type,is) != 1) return false;
if(read32le(&bupmem_size,is) != 1) return false;
addr_size = BackupDevice::addr_size_for_old_save_type(bupmem_type);
if(addr_size == 0xFFFFFFFF)
addr_size = BackupDevice::addr_size_for_old_save_size(bupmem_size);
}
if(addr_size == 0xFFFFFFFF)
return false;
u8* temp = new u8[bupmem_size];
is->read((char*)temp,bupmem_size);
MMU.backupDevice.load_old_state(addr_size,temp,bupmem_size);
delete[] temp;
if(is->fail()) return false;
}
else if(version == 2)
{
//newer savefile system:
MMU.backupDevice.load_state(is);
}
return true;
}