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:
zeromus 2008-08-18 03:11:42 +00:00
parent 224d9268ef
commit 2cba0fd439
13 changed files with 1037 additions and 270 deletions

View File

@ -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();
}

View File

@ -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]),

View File

@ -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)

View File

@ -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;
}

View File

@ -2,6 +2,7 @@
#define _FCEUH
extern int fceuindbg;
extern int newppu;
void ResetGameLoaded(void);
#define DECLFR(x) uint8 x (uint32 A)

View File

@ -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: ;
}
}

View File

@ -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*);

View File

@ -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)

View File

@ -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);

View File

@ -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}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);