这一篇说说跟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文件格式这个主题关系不大,今后再细细研究吧。
转载于:https://blog.51cto.com/darhx/1334093