diff --git a/src/drivers/win/archive.cpp b/src/drivers/win/archive.cpp index 904ae385..27c28a38 100644 --- a/src/drivers/win/archive.cpp +++ b/src/drivers/win/archive.cpp @@ -15,6 +15,9 @@ #include "driver.h" #include "main.h" +//todo - we need a way to get a non-readable filepointer, just for probing the archive +//it would be nonreadable because we wouldnt actually decompress the contents + static FCEUARCHIVEFILEINFO *currFileSelectorContext; DEFINE_GUID(CLSID_CFormat_07,0x23170F69,0x40C1,0x278A,0x10,0x00,0x00,0x01,0x10,0x07,0x00,0x00); @@ -290,9 +293,9 @@ static BOOL CALLBACK ArchiveFileSelectorCallback(HWND hwndDlg, UINT uMsg, WPARAM case WM_INITDIALOG: { HWND hwndListbox = GetDlgItem(hwndDlg,IDC_LIST1); - for(uint32 i=0;iitems.size();i++) + for(uint32 i=0;isize();i++) { - std::string& name = currFileSelectorContext->items[i].name; + std::string& name = (*currFileSelectorContext)[i].name; SendMessage(hwndListbox,LB_ADDSTRING,0,(LPARAM)name.c_str()); } } @@ -380,7 +383,22 @@ void initArchiveSystem() ::VariantClear( reinterpret_cast(&prop) ); } +} +static std::string wstringFromPROPVARIANT(BSTR bstr, bool& success) { + std::wstring tempfname = bstr; + int buflen = tempfname.size()*2; + char* buf = new char[buflen]; + int ret = WideCharToMultiByte(CP_ACP,0,tempfname.c_str(),tempfname.size(),buf,buflen,0,0); + if(ret == 0) { + delete[] buf; + success = false; + } + buf[ret] = 0; + std::string strret = buf; + delete[] buf; + success = true; + return strret; } ArchiveScanRecord FCEUD_ScanArchive(std::string fname) @@ -435,7 +453,7 @@ ArchiveScanRecord FCEUD_ScanArchive(std::string fname) //scan the filename of each item for(uint32 i=0;iGetProperty( i, kpidPath, &prop )) || prop.vt != VT_BSTR || prop.bstrVal == NULL) goto bomb; - std::wstring tempfname = prop.bstrVal; - int buflen = tempfname.size()*2; - char* buf = new char[buflen]; - int ret = WideCharToMultiByte(CP_ACP,0,tempfname.c_str(),tempfname.size(),buf,buflen,0,0); - if(ret == 0) { - delete[] buf; - ::VariantClear( reinterpret_cast(&prop) ); - continue; - } - buf[ret] = 0; - item.name = buf; - - delete[] buf; - + bool ok; + item.name = wstringFromPROPVARIANT(prop.bstrVal,ok); ::VariantClear( reinterpret_cast(&prop) ); - asr.files.items.push_back(item); + + if(!ok) + continue; + + asr.files.push_back(item); } object->Release(); @@ -508,94 +518,55 @@ static FCEUFILE* FCEUD_OpenArchive(ArchiveScanRecord& asr, std::string& fname, s InFileStream ifs(fname); if (SUCCEEDED(object->Open(&ifs,0,0))) { - uint32 numFiles; - if (SUCCEEDED(object->GetNumberOfItems(&numFiles))) + uint32 numfiles = asr.numFilesInArchive; + currFileSelectorContext = &asr.files; + + //try to load the file directly if we're in autopilot + int ret = LB_ERR; + if(innerFilename || innerIndex != -1) { - FCEUARCHIVEFILEINFO fileSelectorContext; - currFileSelectorContext = &fileSelectorContext; + for(uint32 i=0;isize();i++) + if(i == (uint32)innerIndex || (innerFilename && (*currFileSelectorContext)[i].name == *innerFilename)) + { + ret = i; + break; + } + } + else if(asr.files.size()==1) + //or automatically choose the first file if there was only one file in the archive + ret = 0; + else + //otherwise use the UI + ret = DialogBoxParam(fceu_hInstance, "ARCHIVECHOOSERDIALOG", hAppWnd, ArchiveFileSelectorCallback, (LPARAM)0); - for(uint32 i=0;ibuf(), item.size); + const uint32 indices[1] = {item.index}; + HRESULT hr = object->Extract(indices,1,0,&outStream); + if (SUCCEEDED(hr)) { - FCEUARCHIVEFILEINFO::Item item; - item.index = i; - - PROPVARIANT prop; - prop.vt = VT_EMPTY; - - if (FAILED(object->GetProperty( i, kpidSize, &prop )) || prop.vt != VT_UI8 || !prop.uhVal.LowPart || prop.uhVal.HighPart) - { - goto bomb; - } - - item.size = prop.uhVal.LowPart; - - if (FAILED(object->GetProperty( i, kpidPath, &prop )) || prop.vt != VT_BSTR || prop.bstrVal == NULL) - { - //mbg 7/10/08 - this was attempting to handle gz files, but it fails later in the extraction - if(!unnamedFileFound) - { - unnamedFileFound = true; - item.name = ""; - } - else goto bomb; - } - else - { - - } - - ::VariantClear( reinterpret_cast(&prop) ); - fileSelectorContext.items.push_back(item); - } - - //try to load the file directly if we're in autopilot - int ret = LB_ERR; - if(innerFilename || innerIndex != -1) - { - for(uint32 i=0;iarchiveFilename = fname; + fp->filename = item.name; + fp->fullFilename = fp->archiveFilename + "|" + fp->filename; + fp->archiveIndex = ret; + fp->mode = FCEUFILE::READ; + fp->size = item.size; + fp->stream = ms; + fp->archiveCount = (int)asr.numFilesInArchive; + } else - //otherwise use the UI - ret = DialogBoxParam(fceu_hInstance, "ARCHIVECHOOSERDIALOG", hAppWnd, ArchiveFileSelectorCallback, (LPARAM)0); - - if(ret != LB_ERR) { - FCEUARCHIVEFILEINFO::Item& item = fileSelectorContext.items[ret]; - memorystream* ms = new memorystream(item.size); - OutStream outStream( item.index, ms->buf(), item.size); - const uint32 indices[1] = {item.index}; - HRESULT hr = object->Extract(indices,1,0,&outStream); - if (SUCCEEDED(hr)) - { - //if we extracted the file correctly - fp = new FCEUFILE(); - fp->archiveFilename = fname; - fp->filename = fileSelectorContext.items[ret].name; - fp->fullFilename = fp->archiveFilename + "|" + fp->filename; - fp->archiveIndex = ret; - fp->mode = FCEUFILE::READ; - fp->size = fileSelectorContext.items[ret].size; - fp->stream = ms; - fp->archiveCount = (int)numFiles; - } - else - { - delete ms; - } + delete ms; + } - } //if returned a file from the fileselector - - } //if we scanned the 7z correctly + } //if returned a file from the fileselector + } //if we opened the 7z correctly - bomb: object->Release(); } diff --git a/src/drivers/win/config.cpp b/src/drivers/win/config.cpp index 0cb6f56e..537dbac3 100644 --- a/src/drivers/win/config.cpp +++ b/src/drivers/win/config.cpp @@ -32,6 +32,7 @@ #include "window.h" #include "video.h" #include "memwatch.h" +#include "fceu.h" extern CFGSTRUCT NetplayConfig[]; extern CFGSTRUCT InputConfig[]; @@ -91,6 +92,7 @@ static CFGSTRUCT fceuconfig[] = { NAC("vgamode",vmod), NAC("sound",soundo), NAC("sicon",status_icon), + AC(newppu), NACS("odroms",directory_names[0]), NACS("odnonvol",directory_names[1]), diff --git a/src/drivers/win/replay.cpp b/src/drivers/win/replay.cpp index 968e2f00..545e1f2d 100644 --- a/src/drivers/win/replay.cpp +++ b/src/drivers/win/replay.cpp @@ -6,6 +6,8 @@ #include "archive.h" #include "utils/xstring.h" +static const char* fm2ext[] = {"fm2",0}; + // Used when deciding to automatically make the stop movie checkbox checked static bool stopframeWasEditedByUser = false; @@ -399,21 +401,6 @@ void HandleScan(HWND hwndDlg, FCEUFILE* file, int& i) SendDlgItemMessage(hwndDlg, IDC_COMBO_FILENAME, CB_INSERTSTRING, i++, (LPARAM)relative); } -//TODO - dont we already have another function that can do this -std::string getExtension(const char* input) { - char buf[1024]; - strcpy(buf,input); - char* dot=strrchr(buf,'.'); - if(!dot) - return ""; - char ext [512]; - strcpy(ext, dot+1); - int k, extlen=strlen(ext); - for(k=0;k1) { - for(int i=0;ifullFilename.c_str()); + delete fp; LONG lOtherIndex = SendDlgItemMessage(hwndDlg, IDC_COMBO_FILENAME, CB_FINDSTRING, (WPARAM)-1, (LPARAM)relative); if(lOtherIndex != CB_ERR) diff --git a/src/fceu.cpp b/src/fceu.cpp index 693ca4f4..b2973676 100644 --- a/src/fceu.cpp +++ b/src/fceu.cpp @@ -339,7 +339,8 @@ FCEUGI *FCEUI_LoadGameVirtual(const char *name, int OverwriteVidMode) FCEU_printf("Loading %s...\n\n",name); - fp=FCEU_fopen(name,0,"rb",0); + const char* romextensions[] = {"nes","fds",0}; + fp=FCEU_fopen(name,0,"rb",0,-1,romextensions); if(!fp) { return 0; @@ -378,6 +379,9 @@ FCEUGI *FCEUI_LoadGameVirtual(const char *name, int OverwriteVidMode) GameInfo->cspecial=SIS_NONE; //try to load each different format + bool FCEUXLoad(const char *name, FCEUFILE *fp); + /*if(FCEUXLoad(name,fp)) + goto endlseq;*/ if(iNESLoad(name,fp,OverwriteVidMode)) goto endlseq; if(NSFLoad(fp)) @@ -558,7 +562,7 @@ void FCEUI_Emulate(uint8 **pXBuf, int32 **SoundBuf, int32 *SoundBufSize, int ski FCEU_UpdateInput(); lagFlag = 1; if(geniestage!=1) FCEU_ApplyPeriodicCheats(); - r=FCEUPPU_Loop(skip); + r = FCEUPPU_Loop(skip); ssize=FlushEmulateSound(); @@ -934,3 +938,122 @@ bool FCEU_IsValidUI(EFCEUI ui) } return true; } + +//--------------------- +//experimental new mapper and ppu system follows + +class FCEUXCart { +public: + int mirroring; + int chrPages, prgPages; + uint32 chrSize, prgSize; + char* CHR, *PRG; + + FCEUXCart() + : CHR(0) + , PRG(0) + {} + + ~FCEUXCart() { + if(CHR) delete[] CHR; + if(PRG) delete[] PRG; + } + + virtual void Power() { + } + +protected: + //void SetReadHandler(int32 start, int32 end, readfunc func) { +}; + +FCEUXCart* cart = 0; + +//uint8 Read_ByteFromRom(uint32 A) { +// if(A>=cart->prgSize) return 0xFF; +// return cart->PRG[A]; +//} +// +//uint8 Read_Unmapped(uint32 A) { +// return 0xFF; +//} + + + +class NROM : FCEUXCart { +public: + virtual void Power() { + SetReadHandler(0x8000,0xFFFF,CartBR); + setprg16(0x8000,0); + setprg16(0xC000,~0); + setchr8(0); + + vnapage[0] = NTARAM; + vnapage[2] = NTARAM; + vnapage[1] = NTARAM+0x400; + vnapage[3] = NTARAM+0x400; + PPUNTARAM=0xF; + } +}; + +void FCEUXGameInterface(GI command) { + switch(command) { + case GI_POWER: + cart->Power(); + } +} + + + +bool FCEUXLoad(const char *name, FCEUFILE *fp) +{ + //read ines header + iNES_HEADER head; + if(FCEU_fread(&head,1,16,fp)!=16) + return false; + + //validate header + if(memcmp(&head,"NES\x1a",4)) + return 0; + + int mapper = (head.ROM_type>>4); + mapper |= (head.ROM_type2&0xF0); + + //choose what kind of cart to use. + cart = (FCEUXCart*)new NROM(); + + //fceu ines loading code uses 256 here when the romsize is 0. + cart->prgPages = head.ROM_size; + if(cart->prgPages == 0) { + printf("FCEUX: received zero prgpages\n"); + cart->prgPages = 256; + } + + cart->chrPages = head.VROM_size; + + cart->mirroring = (head.ROM_type&1); + if(head.ROM_type&8) cart->mirroring=2; + + //skip trainer + bool hasTrainer = (head.ROM_type&4)!=0; + if(hasTrainer) { + FCEU_fseek(fp,512,SEEK_CUR); + } + + //load data + cart->prgSize = cart->prgPages*16*1024; + cart->chrSize = cart->chrPages*8*1024; + cart->PRG = new char[cart->prgSize]; + cart->CHR = new char[cart->chrSize]; + FCEU_fread(cart->PRG,1,cart->prgSize,fp); + FCEU_fread(cart->CHR,1,cart->chrSize,fp); + + //setup the emulator + GameInterface=FCEUXGameInterface; + ResetCartMapping(); + SetupCartPRGMapping(0,(uint8*)cart->PRG,cart->prgSize,0); + SetupCartCHRMapping(0,(uint8*)cart->CHR,cart->chrSize,0); + + return true; +} + + diff --git a/src/fceu.h b/src/fceu.h index fdf4e3d2..286b7009 100644 --- a/src/fceu.h +++ b/src/fceu.h @@ -2,6 +2,7 @@ #define _FCEUH extern int fceuindbg; +extern int newppu; void ResetGameLoaded(void); #define DECLFR(x) uint8 x (uint32 A) diff --git a/src/file.cpp b/src/file.cpp index da050d1e..f7711553 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -252,7 +252,7 @@ zpfail: return 0; } -FCEUFILE * FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext, int index) +FCEUFILE * FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext, int index, const char** extensions) { FILE *ipsfile=0; FCEUFILE *fceufp=0; @@ -275,9 +275,9 @@ FCEUFILE * FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext if(read) { ArchiveScanRecord asr = FCEUD_ScanArchive(fileToOpen); - if(asr.numFiles == 0) + asr.files.FilterByExtension(extensions); + if(!asr.isArchive()) { - trygzip: //if the archive contained no files, try to open it the old fashioned way std::fstream* fp = FCEUD_UTF8_fstream(fileToOpen,mode); if(!fp) @@ -334,7 +334,7 @@ FCEUFILE * FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext } - + //open a plain old file fceufp = new FCEUFILE(); fceufp->filename = fileToOpen; fceufp->logicalPath = fileToOpen; @@ -357,7 +357,7 @@ FCEUFILE * FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext else fceufp = FCEUD_OpenArchive(asr, archive, &fname); - if(!fceufp) goto trygzip; + if(!fceufp) return 0; FileBaseInfo fbi = DetermineFileBase(fileToOpen); fceufp->logicalPath = fbi.filebasedirectory + fceufp->filename; @@ -726,3 +726,21 @@ bool FCEU_isFileInArchive(const char *path) return isarchive; } + + +void FCEUARCHIVEFILEINFO::FilterByExtension(const char** ext) +{ + if(!ext) return; + int count = size(); + for(int i=count-1;i>=0;i--) { + std::string fext = getExtension((*this)[i].name.c_str()); + const char** currext = ext; + while(*currext) { + if(fext == *currext) + goto ok; + currext++; + } + this->erase(begin()+i); + ok: ; + } +} \ No newline at end of file diff --git a/src/file.h b/src/file.h index 1ba98894..44c8e907 100644 --- a/src/file.h +++ b/src/file.h @@ -71,14 +71,14 @@ struct FCEUFILE { } }; -struct FCEUARCHIVEFILEINFO -{ - struct Item - { - std::string name; - uint32 size, index; - }; - std::vector items; +struct FCEUARCHIVEFILEINFO_ITEM { + std::string name; + uint32 size, index; +}; + +class FCEUARCHIVEFILEINFO : public std::vector { +public: + void FilterByExtension(const char** ext); }; struct FileBaseInfo { @@ -97,15 +97,18 @@ struct ArchiveScanRecord { ArchiveScanRecord() : type(-1) - , numFiles(0) + , numFilesInArchive(0) {} ArchiveScanRecord(int _type, int _numFiles) { type = _type; - numFiles = _numFiles; + numFilesInArchive = _numFiles; } int type; - int numFiles; + + //be careful: this is the number of files in the archive. + //the size of the files variable might be different. + int numFilesInArchive; FCEUARCHIVEFILEINFO files; @@ -113,7 +116,7 @@ struct ArchiveScanRecord }; -FCEUFILE *FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext, int index=-1); +FCEUFILE *FCEU_fopen(const char *path, const char *ipsfn, char *mode, char *ext, int index=-1, const char** extensions = 0); bool FCEU_isFileInArchive(const char *path); int FCEU_fclose(FCEUFILE*); uint64 FCEU_fread(void *ptr, size_t size, size_t nmemb, FCEUFILE*); diff --git a/src/ines.cpp b/src/ines.cpp index 2a859873..24e2227c 100644 --- a/src/ines.cpp +++ b/src/ines.cpp @@ -528,25 +528,11 @@ int iNESLoad(const char *name, FCEUFILE *fp, int OverwriteVidMode) if(memcmp(&head,"NES\x1a",4)) return 0; + head.cleanup(); + memset(&iNESCart,0,sizeof(iNESCart)); - if(!memcmp((char *)(&head)+0x7,"DiskDude",8)) - { - memset((char *)(&head)+0x7,0,0x9); - } - if(!memcmp((char *)(&head)+0x7,"demiforce",9)) - { - memset((char *)(&head)+0x7,0,0x9); - } - - if(!memcmp((char *)(&head)+0xA,"Ni03",4)) - { - if(!memcmp((char *)(&head)+0x7,"Dis",3)) - memset((char *)(&head)+0x7,0,0x9); - else - memset((char *)(&head)+0xA,0,0x6); - } // int ROM_size=0; if(!head.ROM_size) diff --git a/src/ines.h b/src/ines.h index 73226941..fcbbb414 100644 --- a/src/ines.h +++ b/src/ines.h @@ -80,6 +80,27 @@ struct iNES_HEADER { uint8 ROM_type; uint8 ROM_type2; uint8 reserve[8]; + + void cleanup() + { + if(!memcmp((char *)(this)+0x7,"DiskDude",8)) + { + memset((char *)(this)+0x7,0,0x9); + } + + if(!memcmp((char *)(this)+0x7,"demiforce",9)) + { + memset((char *)(this)+0x7,0,0x9); + } + + if(!memcmp((char *)(this)+0xA,"Ni03",4)) + { + if(!memcmp((char *)(this)+0x7,"Dis",3)) + memset((char *)(this)+0x7,0,0x9); + else + memset((char *)(this)+0xA,0,0x6); + } + } }; void VRAM_BANK1(uint32 A, uint8 V); diff --git a/src/lua-engine.cpp b/src/lua-engine.cpp index 3f789dc7..0d77ae62 100644 --- a/src/lua-engine.cpp +++ b/src/lua-engine.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef __linux #include @@ -40,6 +41,37 @@ extern "C" #define FALSE 0 #endif +struct LuaSaveState { + std::string filename; + memorystream *data; + bool anonymous, persisted; + LuaSaveState() + : data(0) + , anonymous(false) + , persisted(false) + {} + ~LuaSaveState() { + if(data) delete data; + } + void persist() { + persisted = true; + FILE* outf = fopen(filename.c_str(),"wb"); + fwrite(data->buf(),1,data->size(),outf); + fclose(outf); + } + void ensureLoad() { + if(data) return; + persisted = true; + FILE* inf = fopen(filename.c_str(),"rb"); + fseek(inf,0,SEEK_END); + int len = ftell(inf); + fseek(inf,0,SEEK_SET); + data = new memorystream(len); + fread(data->buf(),1,len,inf); + fclose(inf); + } +}; + static lua_State *L; // Are we running any code right now? @@ -425,42 +457,49 @@ static int joypad_set(lua_State *L) { - -// Helper function to convert a savestate object to the filename it represents. -static char *savestateobj2filename(lua_State *L, int offset) { - - // First we get the metatable of the indicated object - int result = lua_getmetatable(L, offset); - - if (!result) - luaL_error(L, "object not a savestate object"); - - // Also check that the type entry is set - lua_getfield(L, -1, "__metatable"); - if (strcmp(lua_tostring(L,-1), "FCEU Savestate") != 0) - luaL_error(L, "object not a savestate object"); - lua_pop(L,1); - - // Now, get the field we want - lua_getfield(L, -1, "filename"); - - // Return it - return (char *) lua_tostring(L, -1); -} - +// +//// Helper function to convert a savestate object to the filename it represents. +//static char *savestateobj2filename(lua_State *L, int offset) { +// +// // First we get the metatable of the indicated object +// int result = lua_getmetatable(L, offset); +// +// if (!result) +// luaL_error(L, "object not a savestate object"); +// +// // Also check that the type entry is set +// lua_getfield(L, -1, "__metatable"); +// if (strcmp(lua_tostring(L,-1), "FCEU Savestate") != 0) +// luaL_error(L, "object not a savestate object"); +// lua_pop(L,1); +// +// // Now, get the field we want +// lua_getfield(L, -1, "filename"); +// +// // Return it +// return (char *) lua_tostring(L, -1); +//} +// // Helper function for garbage collection. static int savestate_gc(lua_State *L) { - // The object we're collecting is on top of the stack - lua_getmetatable(L,1); - - // Get the filename - const char *filename; - lua_getfield(L, -1, "filename"); - filename = lua_tostring(L,-1); - // Delete the file - remove(filename); + LuaSaveState *ss = (LuaSaveState *)lua_touserdata(L, 1); + if(ss->persisted && ss->anonymous) + remove(ss->filename.c_str()); + ss->~LuaSaveState(); + + //// The object we're collecting is on top of the stack + //lua_getmetatable(L,1); + // + //// Get the filename + //const char *filename; + //lua_getfield(L, -1, "filename"); + //filename = lua_tostring(L,-1); + + //// Delete the file + //remove(filename); + // // We exit, and the garbage collector takes care of the rest. return 0; @@ -480,13 +519,14 @@ static int savestate_create(lua_State *L) { } } - std::string filename; + //lets use lua to allocate the memory, since it is effectively a memory pool. + LuaSaveState *ss = new(lua_newuserdata(L,sizeof(LuaSaveState))) LuaSaveState(); if (which > 0) { // Find an appropriate filename. This is OS specific, unfortunately. // So I turned the filename selection code into my bitch. :) // Numbers are 0 through 9 though. - filename = FCEU_MakeFName(FCEUMKF_STATE, which - 1, 0); + ss->filename = FCEU_MakeFName(FCEUMKF_STATE, which - 1, 0); } else { //char tempbuf[100] = "snluaXXXXXX"; @@ -494,36 +534,34 @@ static int savestate_create(lua_State *L) { //doesnt work -^ //mbg 8/13/08 - this needs to be this way. we'll make a better system later: - filename = tempnam(NULL, "snlua"); + ss->filename = tempnam(NULL, "snlua"); + ss->anonymous = true; } - // Our "object". We don't care about the type, we just need the memory and GC services. - lua_newuserdata(L,1); - + // The metatable we use, protected from Lua and contains garbage collection info and stuff. lua_newtable(L); - // First, we must protect it + //// First, we must protect it lua_pushstring(L, "FCEU Savestate"); lua_setfield(L, -2, "__metatable"); - - - // Now we need to save the file itself. - lua_pushstring(L, filename.c_str()); - lua_setfield(L, -2, "filename"); + // + // + //// Now we need to save the file itself. + //lua_pushstring(L, filename.c_str()); + //lua_setfield(L, -2, "filename"); // If it's an anonymous savestate, we must delete the file from disk should it be gargage collected - if (which < 0) { + //if (which < 0) { lua_pushcfunction(L, savestate_gc); lua_setfield(L, -2, "__gc"); - } + //} // Set the metatable lua_setmetatable(L, -2); // Awesome. Return the object return 1; - } @@ -532,16 +570,27 @@ static int savestate_create(lua_State *L) { // Saves a state to the given object. static int savestate_save(lua_State *L) { - char *filename = savestateobj2filename(L,1); + //char *filename = savestateobj2filename(L,1); + + LuaSaveState *ss = (LuaSaveState *)lua_touserdata(L, 1); + if(ss->data) delete ss->data; + ss->data = new memorystream(); // printf("saving %s\n", filename); // Save states are very expensive. They take time. numTries--; - FCEUI_SaveState(filename); + FCEUSS_SaveMS(ss->data,Z_NO_COMPRESSION); + ss->data->sync(); return 0; +} +static int savestate_persist(lua_State *L) { + + LuaSaveState *ss = (LuaSaveState *)lua_touserdata(L, 1); + ss->persist(); + return 0; } // savestate.load(object state) @@ -549,12 +598,13 @@ static int savestate_save(lua_State *L) { // Loads the given state static int savestate_load(lua_State *L) { - char *filename = savestateobj2filename(L,1); + //char *filename = savestateobj2filename(L,1); + + LuaSaveState *ss = (LuaSaveState *)lua_touserdata(L, 1); numTries--; -// printf("loading %s\n", filename); - FCEUI_LoadState(filename); + FCEUSS_LoadFP(ss->data,SSLOADPARAM_NOBACKUP); return 0; } @@ -1408,6 +1458,7 @@ static const struct luaL_reg joypadlib[] = { static const struct luaL_reg savestatelib[] = { {"create", savestate_create}, {"save", savestate_save}, + {"persist", savestate_persist}, {"load", savestate_load}, {NULL,NULL} diff --git a/src/ppu.cpp b/src/ppu.cpp index 484c65fa..795aa523 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -48,6 +48,7 @@ #define SpriteON (PPU[1]&0x10) //Show Sprite #define ScreenON (PPU[1]&0x08) //Show screen +#define PPUON (PPU[1]&0x18) //PPU should operate #define PPU_status (PPU[2]) @@ -63,6 +64,146 @@ static uint32 ppulut1[256]; static uint32 ppulut2[256]; static uint32 ppulut3[128]; + +template +struct BITREVLUT { + + T* lut; + BITREVLUT() { + int bits = BITS; + int n = 1<>1; + int j = 2; + + lut[0] = 0; + lut[1] = a; + + while(--bits) { + m <<= 1; + a >>= 1; + for(int i=0;i bitrevlut; + +//uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt +struct PPUREGS { + uint32 fv;//3 + uint32 v;//1 + uint32 h;//1 + uint32 vt;//5 + uint32 ht;//5 + uint32 fh;//3 + uint32 s;//1 + uint32 par;//8 + uint32 ar;//2 + + uint32 _fv, _v, _h, _vt, _ht; + + PPUREGS() + : fv(0), v(0), h(0), vt(0), ht(0), fh(0), s(0), par(0), ar(0) + , _fv(0), _v(0), _h(0), _vt(0), _ht(0) + {} + + void install_latches() { + fv = _fv; + v = _v; + h = _h; + vt = _vt; + ht = _ht; + } + + void install_h_latches() { + if(ht!=_ht || h != _h) { + int zzz=9; + } + ht = _ht; + h = _h; + } + + void clear_latches() { + _fv = _v = _h = _vt = _ht = 0; + fh = 0; + } + + void increment_hsc() { + //The first one, the horizontal scroll counter, consists of 6 bits, and is + //made up by daisy-chaining the HT counter to the H counter. The HT counter is + //then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles). + ht++; + h += (ht>>5); + ht &= 31; + h &= 1; + } + + void increment_vs() { + fv++; + vt += (fv>>3); + v += (vt==30)?1:0; + fv &= 7; + if(vt==30) vt=0; + v &= 1; + } + + uint32 get_ntread() { + return 0x2000 | (v<<0xB) | (h<<0xA) | (vt<<5) | ht; + } + + uint32 get_2007access() { + return ((fv&3)<<0xC) | (v<<0xB) | (h<<0xA) | (vt<<5) | ht; + } + + //The PPU has an internal 4-position, 2-bit shifter, which it uses for + //obtaining the 2-bit palette select data during an attribute table byte + //fetch. To represent how this data is shifted in the diagram, letters a..c + //are used in the diagram to represent the right-shift position amount to + //apply to the data read from the attribute data (a is always 0). This is why + //you only see bits 0 and 1 used off the read attribute data in the diagram. + uint32 get_atread() { + return 0x2000 | (v<<0xB) | (h<<0xA) | 0x3C0 | ((vt&0x1C)<<1) | ((ht&0x1C)>>2); + } + + //address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs). + uint32 get_ptread() { + return (s<<0xC) | (par<<0x4) | fv; + } + + void increment2007(bool by32) { + + //If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the + //scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that + //the carry out of each counter controls the next counter's clock rate. The + //result is that all 5 counters function as a single 15-bit one. Any access to + //2007 clocks the HT counter here. + // + //If the VRAM address increment bit is set (inc. amt. = 32), the only + //difference is that the HT counter is no longer being clocked, and the VT + //counter is now being clocked by access to 2007. + if(by32) { + vt++; + } else { + ht++; + vt+=(ht>>5)&1; + } + h+=(vt>>5); + v+=(h>>1); + fv+=(v>>1); + ht &= 31; + vt &= 31; + h &= 1; + v &= 1; + fv &= 7; + } +} ppur; + static void makeppulut(void) { int x; @@ -193,8 +334,56 @@ int FCEUPPU_GetAttr(int ntnum, int xt, int yt) { return (vnapage[ntnum][attraddr] & (3<> temp; } +//new ppu----- +void FFCEUX_PPUWrite_Default(uint32 A, uint8 V) { + uint32 tmp = A; + if(tmp>=0x3F00) + { + // hmmm.... + if(!(tmp&0xf)) + PALRAM[0x00]=PALRAM[0x04]=PALRAM[0x08]=PALRAM[0x0C]=V&0x3F; + else if(tmp&3) PALRAM[(tmp&0x1f)]=V&0x3f; + } + else if(tmp<0x2000) + { + if(PPUCHRRAM&(1<<(tmp>>10))) + VPage[tmp>>10][tmp]=V; + } + else + { + if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) + vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; + } +} + +uint8 FFCEUX_PPURead_Default(uint32 A) { + uint32 tmp = A; + if(tmp<0x2000) + { + return VPage[tmp>>10][tmp]; + } + else + { + return vnapage[(tmp>>10)&0x3][tmp&0x3FF]; + } +} + + +uint8 (*FFCEUX_PPURead)(uint32 A) = FFCEUX_PPURead_Default; +void (*FFCEUX_PPUWrite)(uint32 A, uint8 V) = FFCEUX_PPUWrite_Default; +//whether to use the new ppu +int newppu=0; +//--------------- + static DECLFR(A2002) { + if(newppu) + { + //once we thought we clear latches here, but that caused midframe glitches. + //i think we should only reset the state machine for 2005/2006 + //ppur.clear_latches(); + } + uint8 ret; FCEUPPU_LineUpdate(); @@ -209,6 +398,7 @@ static DECLFR(A2002) PPU_status&=0x7F; PPUGenLatch=ret; } + return ret; } @@ -248,34 +438,45 @@ static DECLFR(A2007) uint8 ret; uint32 tmp=RefreshAddr&0x3FFF; - FCEUPPU_LineUpdate(); + if(newppu) { + //mbg + ret = VRAMBuffer; + RefreshAddr = ppur.get_2007access(); + VRAMBuffer = FFCEUX_PPURead(RefreshAddr); + ppur.increment2007(INC32!=0); + RefreshAddr = ppur.get_2007access(); + return ret; + } else { + FCEUPPU_LineUpdate(); - ret=VRAMBuffer; + ret=VRAMBuffer; -#ifdef FCEUDEF_DEBUGGER - if(!fceuindbg) -#endif - { - if(PPU_hook) PPU_hook(tmp); - PPUGenLatch=VRAMBuffer; - if(tmp<0x2000) + #ifdef FCEUDEF_DEBUGGER + if(!fceuindbg) + #endif { - VRAMBuffer=VPage[tmp>>10][tmp]; + if(PPU_hook) PPU_hook(tmp); + PPUGenLatch=VRAMBuffer; + if(tmp<0x2000) + { + VRAMBuffer=VPage[tmp>>10][tmp]; + } + else + { + VRAMBuffer=vnapage[(tmp>>10)&0x3][tmp&0x3FF]; + } } - else - { - VRAMBuffer=vnapage[(tmp>>10)&0x3][tmp&0x3FF]; + #ifdef FCEUDEF_DEBUGGER + if(!fceuindbg) + #endif + { + if(INC32) RefreshAddr+=32; + else RefreshAddr++; + if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } + + return ret; } -#ifdef FCEUDEF_DEBUGGER - if(!fceuindbg) -#endif - { - if(INC32) RefreshAddr+=32; - else RefreshAddr++; - if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); - } - return ret; } static DECLFW(B2000) @@ -292,6 +493,10 @@ static DECLFW(B2000) PPU[0]=V; TempAddr&=0xF3FF; TempAddr|=(V&3)<<10; + + ppur._h = V&1; + ppur._v = (V>>1)&1; + ppur.s = (V>>4)&1; } static DECLFW(B2001) @@ -347,12 +552,16 @@ static DECLFW(B2005) tmp&=0xFFE0; tmp|=V>>3; XOffset=V&7; + ppur._ht = V>>3; + ppur.fh = V&7; } else { tmp&=0x8C1F; tmp|=((V&~0x7)<<2); tmp|=(V&7)<<12; + ppur._vt = V>>3; + ppur._fv = V&7; } TempAddr=tmp; vtoggle^=1; @@ -361,15 +570,23 @@ static DECLFW(B2005) static DECLFW(B2006) { - FCEUPPU_LineUpdate(); + if(!newppu) + FCEUPPU_LineUpdate(); + PPUGenLatch=V; if(!vtoggle) { TempAddr&=0x00FF; TempAddr|=(V&0x3f)<<8; + + ppur._vt &= 0x07; + ppur._vt |= (V&0x3)<<3; + ppur._h = (V>>2)&1; + ppur._v = (V>>3)&1; + ppur._fv = (V>>4)&3; } - else + else { TempAddr&=0xFF00; TempAddr|=V; @@ -378,6 +595,20 @@ static DECLFW(B2006) if(PPU_hook) PPU_hook(RefreshAddr); //printf("%d, %04x\n",scanline,RefreshAddr); + + ppur._vt &= 0x18; + ppur._vt |= (V>>5); + ppur._ht = V&31; + + ppur.install_latches(); + + if(RefreshAddr==0x18DE) { + int zzz=9; + } + } + + if(ppur._fv == 1) { + int zzz=9; } vtoggle^=1; } @@ -385,28 +616,53 @@ static DECLFW(B2006) static DECLFW(B2007) { uint32 tmp=RefreshAddr&0x3FFF; - PPUGenLatch=V; - if(tmp>=0x3F00) - { - // hmmm.... - if(!(tmp&0xf)) - PALRAM[0x00]=PALRAM[0x04]=PALRAM[0x08]=PALRAM[0x0C]=V&0x3F; - else if(tmp&3) PALRAM[(tmp&0x1f)]=V&0x3f; - } - else if(tmp<0x2000) - { - if(PPUCHRRAM&(1<<(tmp>>10))) - VPage[tmp>>10][tmp]=V; - } + + if(newppu) { + RefreshAddr = ppur.get_2007access(); + FFCEUX_PPUWrite(RefreshAddr,V); + if(RefreshAddr == 0x2679) { + int zzz=9; + } + if(RefreshAddr == 0x3f13 ) { + int zzz=9; + } + //printf("%04x ",RefreshAddr); + ppur.increment2007(INC32!=0); + RefreshAddr = ppur.get_2007access(); + } else { - if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) - vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; + //printf("%04x ",tmp); + if(tmp==0x2679) + { + int zzz=9; + } + if(tmp == 0x3f13 ) { + int zzz=9; + } + PPUGenLatch=V; + if(tmp>=0x3F00) + { + // hmmm.... + if(!(tmp&0xf)) + PALRAM[0x00]=PALRAM[0x04]=PALRAM[0x08]=PALRAM[0x0C]=V&0x3F; + else if(tmp&3) PALRAM[(tmp&0x1f)]=V&0x3f; + } + else if(tmp<0x2000) + { + if(PPUCHRRAM&(1<<(tmp>>10))) + VPage[tmp>>10][tmp]=V; + } + else + { + if(PPUNTARAM&(1<<((tmp&0xF00)>>10))) + vnapage[((tmp&0xF00)>>10)][tmp&0x3FF]=V; + } + // FCEU_printf("ppu (%04x) %04x:%04x %d, %d\n",X.PC,RefreshAddr,PPUGenLatch,scanline,timestamp); + if(INC32) RefreshAddr+=32; + else RefreshAddr++; + if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } - // FCEU_printf("ppu (%04x) %04x:%04x %d, %d\n",X.PC,RefreshAddr,PPUGenLatch,scanline,timestamp); - if(INC32) RefreshAddr+=32; - else RefreshAddr++; - if(PPU_hook) PPU_hook(RefreshAddr&0x3fff); } static DECLFW(B4014) @@ -1307,9 +1563,13 @@ void FCEUPPU_Power(void) BWrite[0x4014]=B4014; } - int FCEUPPU_Loop(int skip) { + if(newppu) { + int FCEUX_PPU_Loop(int skip); + return FCEUX_PPU_Loop(skip); + } + //Needed for Knight Rider, possibly others. if(ppudead) { @@ -1441,6 +1701,8 @@ int FCEUPPU_Loop(int skip) } } +int (*PPU_MASTER)(int skip) = FCEUPPU_Loop; + static uint16 TempAddrT,RefreshAddrT; void FCEUPPU_LoadState(int version) @@ -1471,3 +1733,337 @@ void FCEUPPU_SaveState(void) TempAddrT=TempAddr; RefreshAddrT=RefreshAddr; } + + +//--------------------- +int pputime=0; +int totpputime=0; +const int kLineTime=1364; +const int kFetchTime=8; +int idleSynch = 0; +void runcpu() { + //cpu runs 1/12 as fast as ppu. + if(pputime<12) return; + int cputodo = pputime/12; + X6502_Run(cputodo*3); //why *3 i dunno thats how the cpu core is setup + pputime -= cputodo*12; +} +void runppu(int x) { + pputime+=x; + totpputime+=x; + runcpu(); +} + +struct BGData { + struct Record { + uint8 nt, at, pt[2]; + + void Read(bool incr) { + RefreshAddr = ppur.get_ntread(); + nt = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + + RefreshAddr = ppur.get_atread(); + at = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + + //modify at to get appropriate palette shift + if(ppur.vt&2) at >>= 4; + if(ppur.ht&2) at >>= 2; + at &= 0x03; + at <<= 2; + + ppur.par = nt; + RefreshAddr = ppur.get_ptread(); + pt[0] = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + RefreshAddr |= 8; + pt[1] = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + + if(PPUON && incr) + ppur.increment_hsc(); + } + }; + + Record main[34]; //one at the end is junk, it can never be rendered + } bgdata; + + +int framectr=0; +int FCEUX_PPU_Loop(int skip) { + //262 scanlines + + if(ppudead) + { + memset(XBuf, 0x80, 256*240); + runppu(262*kLineTime); + ppudead--; + goto finish; + } + + //vblank + PPU_status |= 0x80; + if(VBlankON) TriggerNMI(); + + //TRICKY: + //even though the timing doc says that every scanline is 1364 except for the first dummy scanline, + //i need these to be 1360 in order to get marble madness and pirates! to work. + runppu(20*1360); + + //no longer in vblank + PPU_status &= ~0x80; + //fceu cleared the spritehit and spriteoverflow flags at the same time.. it is reasonable here. or, it is resaonable at vblank interrupt time + PPU_status &= ~0x40; + PPU_status &= ~0x20; + + //todo - this early out might cause a problem. + //we might need to check it over and over in the main rendering loop (fceu does this) + if(!ScreenON && !SpriteON) { + runppu(kLineTime*242); + goto finish; + } + + //There are 2 conditions that update all 5 PPU scroll counters with the + //contents of the latches adjacent to them. The first is after a write to + //2006/2. The second, is at the beginning of scanline 20, when the PPU starts + //rendering data for the first time in a frame (this update won't happen if + //all rendering is disabled via 2001.3 and 2001.4). + + if(PPUON) + ppur.install_latches(); + + uint8 oams[2][8][7]; + int oamcounts[2]={0,0}; + int oamslot=0; + int oamcount; + + //capture the initial xscroll + int xscroll = ppur.fh; + + //render 241 scanlines (including 1 dummy at beginning) + for(int sl=0;sl<241;sl++) { + int yp = sl-1; + + if(sl != 0) { + FCEUD_UpdatePPUView(scanline=yp,1); + FCEUD_UpdateNTView(scanline=yp,1); + } + + //twiddle the oam buffers + int scanslot = oamslot^1; + int renderslot = oamslot; + oamslot ^= 1; + + oamcount = oamcounts[renderslot]; + + //the main scanline rendering loop: + //32 times, we will fetch a tile and then render 8 pixels. + //two of those tiles were read in the last scanline. + for(int xt=0;xt<32;xt++) { + bgdata.main[xt+2].Read(true); + + //ok, we're also going to draw here. + //unless we're on the first dummy scanline + if(sl != 0) { + int xstart = xt<<3; + oamcount = oamcounts[renderslot]; + uint8 *target=XBuf+(yp<<8)+xstart; + uint8 *ptr = target; + int rasterpos = xstart; + for(int xp=0;xp<8;xp++,rasterpos++) { + + //bg pos is different from raster pos due to its offsetability. + //so adjust for that here + int bgpos = rasterpos + xscroll; + int bgpx = bgpos&7; + int bgtile = bgpos>>3; + + //generate the BG data + uint8 pixel=0, pixelcolor; + + if(ScreenON && renderbg) + { + uint8* pt = bgdata.main[bgtile].pt; + pixel = ((pt[0]>>(7-bgpx))&1) | (((pt[1]>>(7-bgpx))&1)<<1) | bgdata.main[bgtile].at; + } + pixelcolor = PALRAM[pixel]; + + //look for a sprite to be drawn + if(SpriteON) + for(int s=0;s=x && rasterpos>= 1; + oam[5] >>= 1; + + //transparent pixel bailout + if(spixel==0) continue; + + //spritehit: + //1. is it sprite#0? + //2. is the bg pixel nonzero? + //then, it is spritehit. + if(oam[6] == 0 && pixel != 0) + PPU_status |= 0x40; + + //bring in the palette bits and palettize + spixel |= (oam[2]&3)<<2; + pixelcolor = PALRAM[0x10+spixel]; + break; + } + } + + //fceu rendering system requires that this be set + //(so that it knows there is a valid pixel there?) + pixelcolor |= 0x80; + *ptr++ = pixelcolor; + } + } + } + + //look for sprites (was supposed to run concurrent with bg rendering) + oamcounts[scanslot] = 0; + oamcount=0; + int spriteHeight = Sprite16?16:8; + for(int i=0;i<64;i++) { + uint8* spr = SPRAM+i*4; + if(yp >= spr[0] && yp < spr[0]+spriteHeight) { + //if we already have 8 sprites, then this new one causes an overflow, + //set the flag and bail out. + if(oamcount == 8) { + PPU_status |= 0x20; + break; + } + + //just copy some bytes into the internal sprite buffer + for(int j=0;j<4;j++) + oams[scanslot][oamcount][j] = spr[j]; + + //note that we stuff the oam index into [6]. + //i need to turn this into a struct so we can have fewer magic numbers + oams[scanslot][oamcount][6] = (uint8)i; + oamcount++; + } + } + oamcounts[scanslot] = oamcount; + + //todo - think about clearing oams to a predefined value to force deterministic behavior + + //fetch sprite patterns + for(int s=0;s<8;s++) { + + if(s==1 && sl != 0) { + //begin hblank! + //the screen is always behind the ppu operation so we need to wait a little longer before triggering it + //NOTE: SMB3 is very sensitive about this timing, for the statusbar + if(PPUON) { + if(GameHBIRQHook) + GameHBIRQHook(); + } else { + int zzz=9; + } + } + + uint8* oam = oams[scanslot][s]; + uint32 line = yp - oam[0]; + if(oam[2]&0x80) //vflip + line = spriteHeight-line-1; + + uint32 patternNumber = oam[1]; + uint32 patternAddress; + + //8x16 sprite handling: + if(Sprite16) { + uint32 bank = (patternNumber&1)<<12; + patternNumber = patternNumber&~1; + patternNumber |= (line>>3); + patternAddress = (patternNumber<<4) | bank; + } else { + patternAddress = (patternNumber<<4) | (SpAdrHI<<9); + } + + //offset into the pattern for the current line. + //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above. + //so we just need the line offset for the second pattern + patternAddress += line&7; + + //garbage nametable fetches + runppu(kFetchTime); + runppu(kFetchTime); + + //pattern table fetches + RefreshAddr = patternAddress; + oam[4] = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + RefreshAddr += 8; + oam[5] = FFCEUX_PPURead(RefreshAddr); + runppu(kFetchTime); + + //hflip + if(!(oam[2]&0x40)) { + oam[4] = bitrevlut[oam[4]]; + oam[5] = bitrevlut[oam[5]]; + } + } + + // FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline. + //well, according to my tests, maybe at the end of hblank. + //but where is the end of hblank? + //well, i imagine it needs to be before the BG fetches for the next line. + if(PPUON && sl != 0) + ppur.increment_vs(); + + //so.. this is the end of hblank. latch horizontal scroll values + if(PPUON && sl != 0) + ppur.install_h_latches(); + + //capture the next xscroll + xscroll = ppur.fh; + + //fetch BG: two tiles for next line + for(int xt=0;xt<2;xt++) + bgdata.main[xt].Read(true); + + //I'm unclear of the reason why this particular access to memory is made. + //The nametable address that is accessed 2 times in a row here, is also the + //same nametable address that points to the 3rd tile to be rendered on the + //screen (or basically, the first nametable address that will be accessed when + //the PPU is fetching background data on the next scanline). + //(not implemented yet) + runppu(kFetchTime); + runppu(kFetchTime); + + + //After memory access 170, the PPU simply rests for 4 cycles (or the + //equivelant of half a memory access cycle) before repeating the whole + //pixel/scanline rendering process. If the scanline being rendered is the very + //first one on every second frame, then this delay simply doesn't exist. + if(sl==0 && idleSynch==0) + {} + else + runppu(4); + } + + idleSynch ++; + if(idleSynch==2) idleSynch = 0; + + //idle for one line + //why 1360? see the note up top at vblank + runppu(1360); + + framectr++; + + +finish: + FCEU_PutImage(); + + return 0; +} diff --git a/src/utils/xstring.cpp b/src/utils/xstring.cpp index 04ee8e4f..363bf564 100644 --- a/src/utils/xstring.cpp +++ b/src/utils/xstring.cpp @@ -691,3 +691,18 @@ std::string wcstombs(std::wstring str) return UtfConverter::ToUtf8(str); } + +//TODO - dont we already have another function that can do this +std::string getExtension(const char* input) { + char buf[1024]; + strcpy(buf,input); + char* dot=strrchr(buf,'.'); + if(!dot) + return ""; + char ext [512]; + strcpy(ext, dot+1); + int k, extlen=strlen(ext); + for(k=0;k