(继上篇:原理篇,下:实现篇)

2. Cartridge 与 Mapper的实现

首先我们在QT中创建两个类,Cartridge 与 Mapper类:

  • Cartridge 类负责加载和解析ROM,因为CPU和PPU的内存映射都有指向卡带的部分(如果你忘了请看上篇:原理篇),因此需要分别提供CPU和PPU的读写接口。
  • Mapper 类负责地址空间与ROM的实际映射关系。

2.1 Cartridge 类实现

在Qt中创建Cartridge类并生成cartridge.h与cartridge.cpp两个文件。

cartridge.h内容如下:

#ifndef CARTRIDGE_H
#define CARTRIDGE_H#include "stdint.h" //需包含STDINT.h头文件才能支持uint_8这些类型typedef struct{uint8_t prgrom_count;uint8_t chrrom_count;uint8_t * prgrom; //程序镜像指针uint32_t prgrom_size;uint8_t * chrrom;//图像资源指针uint32_t  chrrom_size;bool is_vmirroring;   // 是否Vertical Mirroring(否则为水平)bool is_fourscreen;   // 是否FourScreenbool has_battery_backed; // 是否有SRAM(电池供电的)bool has_trainer; //是否有Trainer部分
}rominfo_t;typedef struct {uint8_t nes[4];uint8_t prg_bank_count;uint8_t chr_bank_count;uint8_t flag1;uint8_t flag2;}nesheader_t;class Mapper; //这里没有直接包含mapper.hclass Cartridge
{Mapper* mapper; //mapper指针rominfo_t rominfo; //rom信息
public:Cartridge();//加载并解析ROMbool loadRom(char * rom,int size);//提供CPU读的接口uint8_t ReadViaCpu(uint16_t address);//提供PPU读的接口uint8_t ReadViaPPU(uint16_t address);//提供CPU写的接口void WriteViaCpu(uint16_t address,uint8_t data);//提供PPU写的接口void WriteViaPPU(uint16_t address, uint8_t data);
};#endif // CARTRIDGE_H

小提示:

这是首次出现代码,因此为了方便理解我把头文件完整的贴了出来。后面的代码中则只出现主要代码,像宏定义、引入头文件这些则会省略。

需要解释的东西都已经写在了注释,这里只特殊强调两个地方:

一个是这里声明了两个结构体:rominfo_tnesheader_t

  • rominfo_t :存放解析后的rom信息,如uint8_t prgrom_count表示PRG ROM的数量,而uint8_t * prgrom则是指向PRG ROM的指针。其他几个成员可以对照注释理解。
  • nesheader_t :这个则表示的是ROM文件头部信息的16字节。方便我们解析ROM头使用。

另一个则是class Cartridge前面有一个前置定义:class Mapper。为什么我们不直接引用mapper.h的头文件呢?这个我们在实现mapper的时候会说到。总之这里姑且这么写。

其他的成员变量和方法可以参看注释。

cartridge.cpp主要内容如下:

首先是根据上面讲过的.nes文件格式去解析rom信息:

bool Cartridge::loadRom(char *rom, int size)
{//rom如果小于16字节则一定是错误的文件if(size < 16){return false;}nesheader_t * header = (nesheader_t*)rom;//验证文件头,检测文件前四个字节是否是NES,否则为错误文件if(!(header->nes[0] == 'N'&& header->nes[1] == 'E'&& header->nes[2] == 'S'&& header->nes[3] == 0x1a)){return false;}uint8_t map = header->flag1 >> 4; //获取Mapper第四位map |= (header->flag2 & 0xf0); //获取Mapper高四位// 获取程序镜像块数量rominfo.prgrom_count = header->prg_bank_count;// 获取图像镜像块数量rominfo.chrrom_count = header->chr_bank_count;//PRG ROM大小 = 数量 * 16KBrominfo.prgrom_size = rominfo.prgrom_count * 16 * 1024;//CHR ROM大小 = 数量 * 8 KBrominfo.chrrom_size = rominfo.chrrom_count *  8 * 1024;//动态内存分配方便后面使用rominfo.prgrom = new uint8_t[rominfo.prgrom_size];rominfo.chrrom = new uint8_t[rominfo.chrrom_size];memcpy(rominfo.prgrom,rom + 16,rominfo.prgrom_size);memcpy(rominfo.chrrom,rom + 16 + rominfo.prgrom_size ,rominfo.chrrom_size);//杂项设置rominfo.is_vmirroring = (header->flag1) & 0x1;rominfo.has_battery_backed = (header->flag1 >> 1) & 0x1;rominfo.has_trainer = (header->flag1 >> 2) & 0x1;rominfo.is_fourscreen = (header->flag1 >> 3) & 0x1;qDebug("MAPPER %d,PRG BANK COUNT %d,CHR BANK COUNT %d\n",map,rominfo.prgrom_count,rominfo.chrrom_count);this->mapper = new Mapper(&rominfo);return true;
}

需要注意的是函数return前面的 this->mapper = new Mapper(&rominfo);,这里实例化了我们稍后会首先的Mapper,不要忘记这一步。正常来说应该是根据rom头文件中记录的mapper号实例化不同的Mapper。不过这里简单起见我们暂时直接实例化Mapper,后续再修改。

然后就是对CPU和PPU提供的读写接口,因为地址空间和rom的映射由Mapper负责,所以我们直接调用Mapper的接口即可。

uint8_t Cartridge::ReadViaCpu(uint16_t address)
{return this->mapper->ReadViaCpu(address);
}uint8_t Cartridge::ReadViaPPU(uint16_t address)
{return this->mapper->ReadViaPPU(address);
}void Cartridge::WriteViaCpu(uint16_t address, uint8_t data)
{this->mapper->WriteViaCpu(address,data);
}void Cartridge::WriteViaPPU(uint16_t address, uint8_t data)
{this->mapper->WriteViaPPU(address,data);
}

2.2 Mapper的实现

同样是创建一个Mapper类,分别生成mapper.h和mapper.cpp

mapper.h内容如下:

#ifndef MAPPER_H
#define MAPPER_H
#include "stdint.h"
#include "cartridge.h"
class Mapper
{rominfo_t * rominfo;
public:Mapper(rominfo_t * rominfo);uint8_t ReadViaCpu(uint16_t address);uint8_t ReadViaPPU(uint16_t address);void WriteViaCpu(uint16_t address,uint8_t data);void WriteViaPPU(uint16_t address, uint8_t data);
};#endif // MAPPER_H

需要注意的是,因为初始化的时候需要传入rominfo_t结构体,因此引用了cartridge.h。这也是为什么cartridge.h中没有直接包含mapper.h而使用了前置定义。因为如果不这么做就会造成循环包含,编译出错!当然你可以把rominfo_t结构体定义在一个单独的头文件中。

mapper.cpp内容如下:

#include "mapper.h"Mapper::Mapper(rominfo_t *rom):rominfo(rom)
{}/*** CPU读取PRG ROM 地址空间:0x8000-0xFFFF* @brief Mapper::ReadViaCpu* @param address* @return*/
uint8_t Mapper::ReadViaCpu(uint16_t address)
{if(address >= 0x8000){//如果PRG ROM只有一个,则//0xc000~0xFFFF地址是0x8000~0xbFFF的镜像if(rominfo->prgrom_count == 1){address -= 0x4000;}return this->rominfo->prgrom[address - 0x8000];}return 0;
}/*** CPU写入PRG ROM 地址空间:0x8000-0xFFFF* @brief Mapper::WriteViaCpu* @param address* @param data*/
void Mapper::WriteViaCpu(uint16_t address, uint8_t data)
{//实际上通常这里不会有写入,因此可以不实现if(address >= 0x8000){//如果PRG ROM只有一个,则//0xc000~0xFFFF地址是0x8000~0xbFFF的镜像if(rominfo->prgrom_count == 1){address -= 0x4000;}this->rominfo->prgrom[address - 0x8000] = data;}
}/*** PPU 读取CHR ROM 地址空间:0x0000-0x1FFF* @brief Mapper::ReadViaPPU* @param address* @return*/
uint8_t Mapper::ReadViaPPU(uint16_t address)
{return this->rominfo->chrrom[address];
}/*** PPU 写入CHR ROM 地址空间:0x0000-0x1FFF* @brief Mapper::WriteViaPPU* @param address* @param data*/
void Mapper::WriteViaPPU(uint16_t address, uint8_t data)
{this->rominfo->chrrom[address] = data;
}

内容比较简单不过多赘述,主要别忘记构造方法中初始化rominfo

2.3 调用

修改mainwindows.cpp

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//文件操作std::fstream fs;fs.open("D:\\Qt\\Project\\STUFC\\roms\\nestest.nes",std::ios::binary|std::ios::in);if(!fs.is_open()){QMessageBox::critical(this,"ERROR","rom open failed",QMessageBox::Ok);return;}//内部指针移动到文件尾部fs.seekg(0,fs.end);//获取文件的长度int romsize = fs.tellg();//重新把内部指针移动到开始的位置fs.seekg(0, fs.beg);//开辟一个缓冲区char* rommem = new char[romsize];//读取全部内容到缓冲区内fs.read(rommem,romsize);//读完及时关闭fs.close();//实例化并调用Cartridge类Cartridge cartridge;if(!cartridge.loadRom(rommem,romsize)){QMessageBox::critical(this,"ERROR","rom load failed",QMessageBox::Ok);}//释放缓冲区delete [] rommem;
}

我为了图方便直接在构造方法中调用了,您可以可以选择其他地方。例如按钮点击信号的槽函数中。

这里首先打开了测试ROM文件:nestest.nes,然后读取他的全部并传给我们之前实现的Cartridge 类的loadRom方法。

//...省略...
Cartridge cartridge;
if(!cartridge.loadRom(rommem,romsize))
{//...省略....
}
//...省略...

至此我们这部分就完全实现了。遗憾的是,除了打印一些ROM信息外,我们暂时无法看到任何现象。这多少令人遗憾,但是我们已经卖出了重要的一步。加油。

3. 本章完整代码:

待上传....

【小提示】

文中有到的测试ROM可以百度网盘中下载:

https://pan.baidu.com/s/1ZrlJUlbGcOs4CDalehkXnw

提取码:3qg1

使用C++实现FC红白机模拟器 Cartridge 与 Mapper(实现篇)相关推荐

  1. 使用C++实现FC红白机模拟器 Cartridge 与 Mapper(原理篇)

    1. 认识nes文件 我们既然是模拟,就不可能使用实体的卡带硬件.那我们如何获取游戏文件呢?好在已经有人为我们准备好了(心怀感恩). .nes文件是NES(FC)的rom文件,关于它的来龙去脉这里就不 ...

  2. fc安卓模拟器_安利一款手机上的红白机模拟器

    戳上面的蓝字关注我哦! 使用平台:安卓 软件简介: NES.emu是一款任天堂红白机(NES.FC)模拟器,软件支持横竖屏.自动保存游戏进度.按键自定义等功能,还可以自行编辑作弊文件,小编为大家带来的 ...

  3. 撸一个VS Code插件——红白机模拟器 支持手柄 支持保存

    分享我自己写的VS Code红白机模拟器 前言 我曾经利用 jense 这个库封装了一个vue组件的nes模拟器:nes-vue: Vue 3 的NES(FC)模拟器组件 (gitee.com),最近 ...

  4. 小霸王其乐无穷~FC红白机游戏600合集(支持mac 12.x系统)

    FC游戏是任天堂红白机一FC(Family Computer),代表作品主要有<魂斗罗系列>.<93超级魂>.<沙罗曼蛇系列>.<超级玛丽>.<赤 ...

  5. 计算机丢失fc64,FC红白机64合1

    软件简介 Soft Introduction FC红白机64合1 64in1.nes 01Islander冒险岛1 02Grading沙罗曼蛇1 03Star Soldier星际战士 04Goonie ...

  6. 【转】FC(红白机)游戏nes文件的汉化技术

    FC大字体汉化方法 作者:madcell 一.前言: 本文以FC上第一个发售的游戏<大金刚>为例,介绍如何对标题画面进行大字体汉化. 阅读本文,必须具备一定的的条件,否则看了也是不知所云. ...

  7. ADI Blackfin DSP处理器-BF533的开发详解70:NES 红白机模拟器(含源码)

    硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP++软件 硬件链接 代码实现功能 代码实现了 NES 游戏模拟器在 ...

  8. nes 红白机模拟器 第6篇 声音支持

    InfoNES 源码中并没有包含 linux 的声音支持. 但提供 wince 和 win 的工程,文件,通过分析,win 的 DirectSound 发声,在使用 linux ALSA 实现. 先使 ...

  9. linux终端玩fc游戏,在UBuntu下玩FC和街机模拟器

    我不是太爱玩游戏,所以Linux下我并没有特别关心游戏的问题.不过有一天,突然觉得想起以前在Windows下玩过的VirtualNES FC(红白机)模拟器和Nebula街机模拟器了.也许是怀旧的缘故 ...

最新文章

  1. python每日一类(3):os和sys
  2. 霍夫变换直线检测理解
  3. java单链表节点翻转_单链表Java实现
  4. mysql约束_从零开始学 MySQL - SQL 约束分类
  5. JavaScript GetAbsoultURl
  6. php获取模型错误,php – 解析错误,期望activecollab模型类中出现“T_PAAMAYIM_NEKUDOTAYIM”错误...
  7. numpy.argmax/argmin/max/min
  8. Linux cat 命令用法
  9. [转载] python学习笔记numpy(一)np.zero
  10. PHP数组键不存在给默认值
  11. [C++] intptr_t
  12. 前台CSS颜色代码大全
  13. 网页版bpc电波对时_bpc电波对时app下载
  14. 着迷英语900句_字体令人着迷
  15. N phpspider爬虫获取网站内容demo
  16. scratch编程体感游戏
  17. 算法分析怎么写_区位分析怎么写
  18. Pr---文字过多时显示不全和背景音乐过度处理(简单记录)
  19. Android复制assets文件到SD卡
  20. SAP PS 第0节 PS PA有哪些知识点及IDES练习

热门文章

  1. PageNow企业级数据可视化开发平台
  2. 山东科技大学第二届ACM校赛解题报告
  3. 故宫景点功课3:太和殿院落上
  4. numpy 求矩阵非零元素的均值
  5. 输入法不见了,咋办?输入法不能开机启动咋办?
  6. Win7安装hp1020打印机方法
  7. 【单片机笔记】基于STM32F103C8的 USB 外部flash虚拟U盘
  8. 北京中医药大学22春《生理学Z》平时作业3【辅导答案】
  9. 50行实现C语言FM收音机-Taskbus Stdio封装器在SDR课程中的应用
  10. NYOJ 1248 海岛争霸 河南省赛真题 Floyd 或者 并查集