這一篇說說跟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文件格式這個主題關係不大,今後再細細研究吧。