VirtuaNES.v0.97源碼探究<7> NES文件格式

這一篇說說跟NES文件格式相關的內容。

NES文件,在真機上相當於就是遊戲卡帶了。

下面是NES文件格式的說明表。


偏移

字節數

內容

0-3

4

字符串“NES^Z”用來識別.NES文件

4

1

16kB ROM的數目

5

1

8kB VROM的數目

6

1

D0:1=垂直鏡像,0=水平鏡像



D1:1=有電池記憶,SRAM地址$6000-$7FFF



D2:1=在$7000-$71FF有一個512字節的trainer



D3:1=4屏幕VRAM佈局



D4-D7:ROM  Mapper的低4位

7

1

D0-D3:保留,必須是0(準備作爲副Mapper號^_^)



D4-D7:ROM  Mapper的高4位

8-F

8

保留,必須是0

16-

16KxM

ROM段升序排列,如果存在trainer,它的512字節擺在ROM段之前

-EOF

8KxN

VROM段, 升序排列


結合代碼來看看程序中是如何讀取NES文件的。

代碼所在的文件:NES\ROM.cpp ROM.h

所在函數:ROM::ROM( constchar* fname )

代碼比較長,所以分開來一部分一部分的看,有些無關緊要,或者和NES文件格式主題關係不大的代碼,我就略過了。

FILE         *fp = NULL;
LPBYTE  temp = NULL;
LPBYTE  bios = NULL;
LONG       FileSize;
ZEROMEMORY( &header, sizeof(header) );
ZEROMEMORY( path, sizeof(path) );
ZEROMEMORY( name, sizeof(name) );
bPAL = FALSE;
bNSF = FALSE;
NSF_PAGE_SIZE = 0;
lpPRG = lpCHR = lpTrainer = lpDiskBios= lpDisk = NULL;
crc = crcall = 0;
mapper = 0;
diskno = 0;


一些變量的初始化,具體含義用到了再詳細研究。


if( !(fp = ::fopen( fname, "rb")) )
{
     LPCSTR   szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
     ::wsprintf( szErrorString,szErrStr, fname );
     throw  szErrorString;
}

打開文件,fp是文件句柄


::fseek( fp, 0,SEEK_END );
FileSize = ::ftell(fp );
::fseek( fp, 0,SEEK_SET );


求文件大小,賦值給FileSize


if( FileSize < 17 )
{
throw        CApp::GetErrorString(IDS_ERROR_SMALLFILE );
}

NES文件頭大小16,文件中至少得有個文件頭纔好繼續。



if( !(temp = (LPBYTE)::malloc( FileSize )) )
{
throw        CApp::GetErrorString( IDS_ERROR_OUTOFMEMORY);
}
if( ::fread( temp, FileSize, 1, fp ) != 1 )
{
throw        CApp::GetErrorString( IDS_ERROR_READ );
}
FCLOSE( fp );

文件中的所有內容讀取出來放到temp緩衝區中,之後關閉文件。


::memcpy(&header, temp, sizeof(NESHEADER) );

讀取NES文件頭,存儲到header中。NESHEADER定義如下:

可以結合文章開頭的表格查看。

typedefstruct         tagNESHEADER{
         BYTE       ID[4];  //NES文件標記
         BYTE       PRG_PAGE_SIZE;  //16kB ROM的數目就是ROM大小
         BYTE       CHR_PAGE_SIZE;  //8kB VROM的數目就是VROM大小
         BYTE       control1;      
         BYTE       control2;
         BYTE       reserved[8];   //保留,必須是0
} NESHEADER;




DWORD   PRGoffset, CHRoffset;  //Rom和VRom數據距離文件頭的偏移量
LONG       PRGsize, CHRsize;            //Rom和VRom的大小


根據文件類型的不同(NES,FDS等),接下來的代碼產生了分支,我就只管NES了(NES還沒搞明白,其它就暫時無視吧)。

if( header.ID[0] == 'N'&& header.ID[1] == 'E'
                    && header.ID[2] == 'S' && header.ID[3] == 0x1A )
{
    PRGsize =(LONG)header.PRG_PAGE_SIZE*0x4000;
    CHRsize =(LONG)header.CHR_PAGE_SIZE*0x2000;
    PRGoffset = sizeof(NESHEADER); //ROM數據緊跟在文件頭後面
    CHRoffset =PRGoffset + PRGsize; //VROM數據緊跟在ROM數據後面
    if( PRGsize <= 0 || (PRGsize+CHRsize) >FileSize )
    {
        throw CApp::GetErrorString(IDS_ERROR_INVALIDNESHEADER );
    }
    if( !(lpPRG = (LPBYTE)malloc( PRGsize )) )
    {
    throw CApp::GetErrorString(IDS_ERROR_OUTOFMEMORY );
    }
    ::memcpy( lpPRG,temp+PRGoffset, PRGsize );
    if( CHRsize > 0 )
    {
        if( !(lpCHR = (LPBYTE)malloc( CHRsize )) )
        {
            throw CApp::GetErrorString(IDS_ERROR_OUTOFMEMORY );
        }
        if( FileSize >= CHRoffset+CHRsize )
        {
             memcpy(lpCHR, temp+CHRoffset, CHRsize );
        }
        else
        {
             CHRsize-= (CHRoffset+CHRsize - FileSize);
             memcpy(lpCHR, temp+CHRoffset, CHRsize );
        }
    }
    else
    {
        lpCHR =NULL;
    }
}



以上一長串的代碼,說白了就是,檢查文件數據有沒有異常,沒有異常就讀取ROM和VROM。

string         tempstr;
tempstr =CPathlib::SplitPath( fname );
::strcpy( path,tempstr.c_str() );
tempstr =CPathlib::SplitFname( fname );
::strcpy( name,tempstr.c_str() );
::strcpy( fullpath, fname );


path保存NES文件所在的目錄

name保存NES文件名

fullpath 保存NES文件的全路徑



接下來的代碼處理的是和mapper有關的東西。mapper這玩意兒水比較深,況且和NES文件格式這個主題關係不大,今後再細細研究吧。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章