diff --git a/desmume/sav.txt b/desmume/sav.txt new file mode 100644 index 000000000..76482a0f2 --- /dev/null +++ b/desmume/sav.txt @@ -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. + diff --git a/desmume/src/MMU.cpp b/desmume/src/MMU.cpp index 27aa609ce..2e0f84129 100644 --- a/desmume/src/MMU.cpp +++ b/desmume/src/MMU.cpp @@ -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); } //////////////////////////////////////////////////////////// diff --git a/desmume/src/MMU.h b/desmume/src/MMU.h index 627fa0754..98db5c2d1 100644 --- a/desmume/src/MMU.h +++ b/desmume/src/MMU.h @@ -118,6 +118,7 @@ struct MMU_struct { memory_chip_t fw; memory_chip_t bupmem; + BackupDevice backupDevice; nds_dscard dscard[2]; u32 CheckTimers; diff --git a/desmume/src/NDSSystem.cpp b/desmume/src/NDSSystem.cpp index 32e7c0449..e063f4bee 100644 --- a/desmume/src/NDSSystem.cpp +++ b/desmume/src/NDSSystem.cpp @@ -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) diff --git a/desmume/src/mc.cpp b/desmume/src/mc.cpp index abccf08b9..565ae050c 100644 --- a/desmume/src/mc.cpp +++ b/desmume/src/mc.cpp @@ -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_counterwrite_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(sizeaddr_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[ctr]) ctr++; + u32 padSize = saveSizes[ctr]; + + for(u32 i=size;i +#include +#include #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 data; + std::vector 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 */ diff --git a/desmume/src/readwrite.cpp b/desmume/src/readwrite.cpp index 43c6f9325..5099648ba 100644 --- a/desmume/src/readwrite.cpp +++ b/desmume/src/readwrite.cpp @@ -155,3 +155,19 @@ int read32le(u32 *Bufo, std::istream *is) return 1; } +int readbuffer(std::vector &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& vec, std::ostream* os) +{ + u32 size = vec.size(); + write32le(size,os); + if(size>0) os->write((char*)&vec[0],size); + return 1; +} \ No newline at end of file diff --git a/desmume/src/readwrite.h b/desmume/src/readwrite.h index 99fc65a52..7b4713fe3 100644 --- a/desmume/src/readwrite.h +++ b/desmume/src/readwrite.h @@ -4,6 +4,7 @@ #include "types.h" #include #include +#include //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 &vec, std::istream* is); +int writebuffer(std::vector& vec, std::ostream* os); + #endif diff --git a/desmume/src/saves.cpp b/desmume/src/saves.cpp index 32d1a0ab5..05fe8540a 100644 --- a/desmume/src/saves.cpp +++ b/desmume/src/saves.cpp @@ -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; }