support autoloading from archives in cases where there is only one useful file in an archive.
add experimental ram-only emulua savestate code. now call savestate_persist(ss) if you want to cause it to go to disk. otherwise it will stay in memory. this might speed things up. also add experimental new ppu which can be enabled by newppu 1 in configfile. there is no reason to use it yet but i needed to do a checkin
This commit is contained in:
parent
224d9268ef
commit
2cba0fd439
|
@ -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;i<currFileSelectorContext->items.size();i++)
|
||||
for(uint32 i=0;i<currFileSelectorContext->size();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<VARIANTARG*>(&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;i<numFiles;i++)
|
||||
{
|
||||
FCEUARCHIVEFILEINFO::Item item;
|
||||
FCEUARCHIVEFILEINFO_ITEM item;
|
||||
item.index = i;
|
||||
|
||||
PROPVARIANT prop;
|
||||
|
@ -449,22 +467,14 @@ ArchiveScanRecord FCEUD_ScanArchive(std::string fname)
|
|||
if (FAILED(object->GetProperty( 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<VARIANTARG*>(&prop) );
|
||||
continue;
|
||||
}
|
||||
buf[ret] = 0;
|
||||
item.name = buf;
|
||||
|
||||
delete[] buf;
|
||||
|
||||
bool ok;
|
||||
item.name = wstringFromPROPVARIANT(prop.bstrVal,ok);
|
||||
::VariantClear( reinterpret_cast<VARIANTARG*>(&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;i<currFileSelectorContext->size();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;i<numFiles;i++)
|
||||
if(ret != LB_ERR)
|
||||
{
|
||||
FCEUARCHIVEFILEINFO_ITEM& item = (*currFileSelectorContext)[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))
|
||||
{
|
||||
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 = "<unnamed>";
|
||||
}
|
||||
else goto bomb;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
::VariantClear( reinterpret_cast<VARIANTARG*>(&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;i<fileSelectorContext.items.size();i++)
|
||||
if(i == (uint32)innerIndex || (innerFilename && fileSelectorContext.items[i].name == *innerFilename))
|
||||
{
|
||||
ret = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(numFiles==1)
|
||||
//or automatically choose the first file if there was only one file in the archive
|
||||
ret = 0;
|
||||
//if we extracted the file correctly
|
||||
fp = new FCEUFILE();
|
||||
fp->archiveFilename = 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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;k<extlen;k++)
|
||||
ext[k]=tolower(ext[k]);
|
||||
return ext;
|
||||
}
|
||||
|
||||
BOOL CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(uMsg)
|
||||
|
@ -496,32 +483,22 @@ BOOL CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
char filename [512];
|
||||
sprintf(filename, "%s%s", globBase, wfd.cFileName);
|
||||
|
||||
char* dot = strrchr(filename, '.');
|
||||
|
||||
ArchiveScanRecord asr = FCEUD_ScanArchive(filename);
|
||||
if(asr.numFiles>1) {
|
||||
for(int i=0;i<asr.numFiles;i++) {
|
||||
std::string ext = getExtension(asr.files.items[i].name.c_str());
|
||||
if(ext != "fm2")
|
||||
continue;
|
||||
FCEUFILE* fp = FCEU_fopen(filename,0,"rb",0,i);
|
||||
if(fp) {
|
||||
HandleScan(hwndDlg,fp, items);
|
||||
delete fp;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
if(asr.numFiles == 1) {
|
||||
std::string ext = getExtension(asr.files.items[0].name.c_str());
|
||||
if(ext != "fm2")
|
||||
continue;
|
||||
}
|
||||
if(!asr.isArchive()) {
|
||||
FCEUFILE* fp = FCEU_fopen(filename,0,"rb",0);
|
||||
if(fp) {
|
||||
HandleScan(hwndDlg,fp ,items);
|
||||
delete fp;
|
||||
}
|
||||
} else {
|
||||
asr.files.FilterByExtension(fm2ext);
|
||||
for(uint32 i=0;i<asr.files.size();i++) {
|
||||
FCEUFILE* fp = FCEU_fopen(filename,0,"rb",0,asr.files[i].index);
|
||||
if(fp) {
|
||||
HandleScan(hwndDlg,fp, items);
|
||||
delete fp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while(FindNextFile(hFind, &wfd));
|
||||
|
@ -602,19 +579,18 @@ BOOL CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
ofn.Flags = OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
|
||||
ofn.lpstrDefExt = "fm2";
|
||||
ofn.lpstrTitle = "Replay Movie from File";
|
||||
|
||||
|
||||
if(GetOpenFileName(&ofn))
|
||||
{
|
||||
char relative[MAX_PATH*2];
|
||||
AbsoluteToRelative(relative, szFile, BaseDirectory.c_str());
|
||||
|
||||
ArchiveScanRecord asr = FCEUD_ScanArchive(relative);
|
||||
FCEUFILE* fp = FCEU_fopen(relative,0,"rb",0);
|
||||
if(!fp) {
|
||||
delete fp;
|
||||
FCEUFILE* fp = FCEU_fopen(relative,0,"rb",0,-1,fm2ext);
|
||||
if(!fp)
|
||||
goto abort;
|
||||
}
|
||||
strcpy(relative,fp->fullFilename.c_str());
|
||||
delete fp;
|
||||
|
||||
LONG lOtherIndex = SendDlgItemMessage(hwndDlg, IDC_COMBO_FILENAME, CB_FINDSTRING, (WPARAM)-1, (LPARAM)relative);
|
||||
if(lOtherIndex != CB_ERR)
|
||||
|
|
127
src/fceu.cpp
127
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define _FCEUH
|
||||
|
||||
extern int fceuindbg;
|
||||
extern int newppu;
|
||||
void ResetGameLoaded(void);
|
||||
|
||||
#define DECLFR(x) uint8 x (uint32 A)
|
||||
|
|
28
src/file.cpp
28
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: ;
|
||||
}
|
||||
}
|
27
src/file.h
27
src/file.h
|
@ -71,14 +71,14 @@ struct FCEUFILE {
|
|||
}
|
||||
};
|
||||
|
||||
struct FCEUARCHIVEFILEINFO
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
std::string name;
|
||||
uint32 size, index;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
struct FCEUARCHIVEFILEINFO_ITEM {
|
||||
std::string name;
|
||||
uint32 size, index;
|
||||
};
|
||||
|
||||
class FCEUARCHIVEFILEINFO : public std::vector<FCEUARCHIVEFILEINFO_ITEM> {
|
||||
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*);
|
||||
|
|
18
src/ines.cpp
18
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)
|
||||
|
|
21
src/ines.h
21
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);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#ifdef __linux
|
||||
#include <unistd.h>
|
||||
|
@ -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}
|
||||
|
|
684
src/ppu.cpp
684
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<typename T, int BITS>
|
||||
struct BITREVLUT {
|
||||
|
||||
T* lut;
|
||||
BITREVLUT() {
|
||||
int bits = BITS;
|
||||
int n = 1<<BITS;
|
||||
lut = new T[n];
|
||||
|
||||
int m = 1;
|
||||
int a = n>>1;
|
||||
int j = 2;
|
||||
|
||||
lut[0] = 0;
|
||||
lut[1] = a;
|
||||
|
||||
while(--bits) {
|
||||
m <<= 1;
|
||||
a >>= 1;
|
||||
for(int i=0;i<m;i++)
|
||||
lut[j++] = lut[i] + a;
|
||||
}
|
||||
}
|
||||
|
||||
T operator[](int index) { return lut[index]; }
|
||||
|
||||
};
|
||||
BITREVLUT<uint8,8> 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)) >> 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<oamcount;s++) {
|
||||
uint8* oam = oams[renderslot][s];
|
||||
int x = oam[3];
|
||||
if(rasterpos>=x && rasterpos<x+8) {
|
||||
//build the pixel.
|
||||
//fetch the LSB of the patterns
|
||||
uint8 spixel = oam[4]&1;
|
||||
spixel |= (oam[5]&1)<<1;
|
||||
|
||||
//shift down the patterns so the next pixel is in the LSB
|
||||
oam[4] >>= 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;
|
||||
}
|
||||
|
|
|
@ -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<extlen;k++)
|
||||
ext[k]=tolower(ext[k]);
|
||||
return ext;
|
||||
}
|
|
@ -121,3 +121,7 @@ std::string mass_replace(const std::string &source, const std::string &victim, c
|
|||
std::wstring mbstowcs(std::string str);
|
||||
std::string wcstombs(std::wstring str);
|
||||
|
||||
|
||||
|
||||
//TODO - dont we already have another function that can do this
|
||||
std::string getExtension(const char* input);
|
||||
|
|
Loading…
Reference in New Issue