上个月在 V 站看到有同学发布了一个 Rust 写的 Gameboy 模拟器。当时我恰好在系统化学习 Go 语言,作为马里奥 /马力欧 /玛丽(误)的忠实粉丝,就想着自己能不能用 Go 写个模拟器玩玩。于是我瞄准了 NES 模拟器,考虑到 NES 是上个世纪 80 年代的古董硬件,可能比较容易实现。
接下来我开始在网上搜索关于 NES 硬件的信息。忽然有一个神奇的网站出现在我眼前,它就是 nesdev.com 。这个网站有一个Wiki,里面的内容包罗万象,有 NES 的硬件细节,也有 NES 游戏开发的教程。对照着文档,大概用了一天的时间完成了 INES ROM 文件(网上下到的.nes 文件都是这种格式)的读取,然后用了不到一周的业余时间就基本实现了 NES CPU ( 6502 )指令解释器。NES 的 PPU (类似显卡)是难点,大概陆陆续续写个几个星期才完成基本功能。主要因为 NES 的硬件太弱,很多游戏为了压榨机能在开发过程中用了大量的 hack,如果对 PPU 的模拟必须不够精确,游戏运行时就会出现各种奇怪的现象。用的 GUI 库是 fyne.io ,在部分操作系统上会内存泄漏,打算之后换个其他的库试试( Go 语言目前好像没有成熟的 GUI 库)。另外 APU (类似声卡)的模拟还没有实现(主要我对声学一窍不通,也暂时不想随便引用个第三方库解决,就先拖着了)。
另外一个难点是卡带的模拟。NES 原生只有 2kB 内存+2kB 显存,为了拓展内存和显存的容量,很多游戏的卡带带有一个俗称 Mapper 的芯片。配合卡带上额外的 ROM 或 RAM 芯片,Mapper 可以将卡带上 ROM 或 RAM 的一部分空间(称为 bank )映射进 NES 的内存或显存地址空间,还可以在游戏运行中动态切换。麻烦的是,不同的游戏使用的 Mapper 芯片是不同的,你必须先实现了对某种 Mapper 的模拟,才能运行使用这种 Mapper 的游戏 ROM,然而已知的 Mapper 种类就有几百种。目前我的模拟器只实现了 INES 文件格式定义的 0-3 号 Mapper,但已经可以运行不少游戏了(包括超级马力欧兄弟一代和魂斗罗)。
目前这个项目还比较初期,只能慢慢完善了。如果很多人感兴趣的话我打算写几篇文章,做个自制 NES 模拟器的教程。
演示
演示游戏为 Nova The Squirrel,是一个仿星之卡比系列的开源山寨游戏。