2个版本

0.1.1 2023年9月10日
0.1.0 2023年9月8日

#78 in 模拟器

MPL-2.0 许可证

5.5MB
7K SLoC

用Rust编写的Gameboy模拟器到WebAssembly

免责声明1.0:本项目不支持或推广任何类型的盗版活动。构建或安装模拟器的行为并不违法。正如过去几十年中创建的其他模拟器一样,本项目只是一个研究案例。

免责声明1.1:虽然模拟器已经可以运行许多游戏,但它仍在开发中,您可以检查测试部分以跟踪其开发。

WebGL与WebAssembly 桌面GUI与Rust
Demo Web with Assembly First demo desktop GUI Second demo desktop GUI
播放leafthief的Cookies Bakery 播放benjelter的Unearthed
Firefox 116.0.3 (64位) macOS 11.6.2, 16GB, 四核Intel Core i7, 1.2 GHz
makeweb make桌面

WebAssembly快速演示

WebAssembly quick demo

内部结构

GameBoy Internals GameBoy Internals

模拟器和一些Game Boy历史...

模拟器通常允许宿主系统运行为访客系统设计的软件或使用外围设备。模拟是指电子设备中的计算机程序能够模拟(或模仿)另一个程序或设备的能力。在Game Boy的情况下,大部分工作涉及处理围绕(Z80 CPU的)8位总线(Z80 CPU的一种变体)。Game Boy CPU是Intel 8080和Zilog Z80的混合体。在计算机科学中,模拟器是使一个计算机系统(称为宿主)表现得像另一个计算机系统(称为访客)的硬件或软件。

关于Z80...

Zilog Z80

Z80 是由 Zilog 公司推出的第一款产品,一款 8 位微处理器。Z80 是由 Federico Faggin 在 1974 年末构思的,并在 1975 年初由他和他的 11 名员工开始开发。第一批样品于 1976 年 3 月交付,并于 1976 年 7 月正式上市。通过 Z80 的收入,公司在接下来的两年里建立了自己的芯片工厂,员工人数增长到超过一千人。

这意味着需要操作大量的单个字节,尤其是在导航通过大量的 ROM 和 RAM 时。Game Boy 的架构相当简单——获取按钮输入需要读取特定的内存地址,将像素写入屏幕需要在 VRAM 的特定位置推送字节。

CPU          - 8-bit (Similar to the Z80 processor)
Clock Speed  - 4.194304MHz (4.295454MHz for SGB, max. 8.4MHz for CGB)
Work RAM     - 8K Byte (32K Byte for CGB)
Video RAM    - 8K Byte (16K Byte for CGB)
Screen Size  - 2.6"
Resolution   - 160x144 (20x18 tiles)
Max sprites  - Max 40 per screen, 10 per line
Sprite sizes - 8x8 or 8x16
Palettes     - 1x4 BG, 2x3 OBJ (for CGB: 8x4 BG, 8x3 OBJ)
Colors       - 4 grayshades (32768 colors for CGB)
Horiz Sync   - 9198 KHz (9420 KHz for SGB)
Vert Sync    - 59.73 Hz (61.17 Hz for SGB)
Sound        - 4 channels with stereo sound
Power        - DC6V 0.7W (DC3V 0.7W for GB Pocket, DC3V 0.6W for CGB)
  • 来源:[http://bgb.bircd.org/pandocs.htm](http://bgb.bircd.org/pandocs.htm)

Game Boy 有四个操作按钮,分别标记为 “A”、“B”、“SELECT” 和 “START”,还有一个方向垫(d-pad)。设备右侧有一个音量控制旋钮,左侧有一个类似的旋钮来调整对比度。在 Game Boy 的顶部,有一个滑动开关和 Game Boy 卡带插槽。开关包括一个物理锁定功能,防止用户在设备开启时插入或取出卡带。任天堂建议用户将卡带留在插槽中,以防止灰尘和污垢进入系统。

pub enum Button {
    A,
    B,
    Left,
    Right,
    Up,
    Down,
    Start,
    Select,
}

Game Boy 包含可选的输入或输出连接器。系统左侧有一个外部 3.5 毫米 × 1.35 毫米直流电源插孔,允许用户使用外部可充电电池组或交流适配器(需单独购买)而不是四节 AA 电池。Game Boy 需要 6 伏直流电压,至少 150 毫安。设备底部有一个 3.5 毫米立体声耳机插孔,允许用户使用附带耳机或外部扬声器收听音频。

设备的右侧提供了一个端口,允许用户通过连接线连接到另一个 Game Boy 系统,前提是两位用户都在玩支持相互连接的游戏(通常是同一款游戏的副本,例如,宝可梦游戏可以在不同世代之间连接)。该端口还可以连接 Game Boy 打印机。连接线最初是为玩家玩双人对战游戏(如俄罗斯方块)而设计的。然而,游戏开发者樱井政博后来将连接线技术用作流行的宝可梦电子游戏系列的通信和网络方法。

总之,关于 Game Boy 的历史就说到这里。

Z80 是为了与已存在的英特尔 8080 兼容而设计的。这意味着 8080 中发现的指令集也由 Z80 实现(实质上,8080 可以被视为 Z80 的子集)。Game Boy 的定制混合芯片官方名称为 Sharp LR35902

LR35902 ~ 高级架构

CPU (src/cpu)

中央处理单元(CPU),也称为中央处理器、主处理器或简称为处理器,是执行计算机程序指令的电子电路。CPU 执行程序指令中指定的基本算术、逻辑、控制和输入/输出(I/O)操作。这与外部组件(如主存储器和 I/O 电路)以及图形处理单元(GPU)等专用处理器形成对比。

Game Boy CPU 由8个不同的“寄存器”组成。寄存器负责在CPU执行各种指令时保存可以操作的小数据片段。Game Boy的CPU是8位CPU,这意味着它的每个寄存器可以存储8位(1字节)的数据。CPU有8个不同的寄存器,分别标记为 abcdefhl

每个寄存器都是1字节,因此每个寄存器可以存储从0到255的值。一个u8可以完成这个工作,因为它可以存储从0(2^8 - 1)

$$ u8PossibleValues=(2^8) $$

assert_eq!(u8::MIN, 0);
assert_eq!(u8::MAX, 255);

以下是一个示例

pub struct Registers {
    pub a: u8,
    pub b: u8,
    pub c: u8,
    pub d: u8,
    pub e: u8,
    pub h: u8,
    pub l: u8
}

如果指令的第一个字节有256种可能的值,那么基本表中有256种可能的指令。这个表在Gameboy Z80操作码映射中详细介绍:http://imrannazar.com/Gameboy-Z80-Opcode-Map

内存(src/mmu

MMU map

Game Boy有一个16位地址总线。提供了65535字节(或64KB)的寻址范围。Game Boy的所有硬件组件,包括RAM、ROM、视频RAM和I/O端口,都按以下方式映射到内存。

Memory map
64KB
内存映射
  • 0x0000 - 0x00FF:启动ROM
  • 0x0000 - 0x3FFF:游戏ROM Bank 0
  • 0x4000 - 0x7FFF:游戏ROM Bank N
  • 0x8000 - 0x97FF:瓦片RAM
  • 0x9800 - 0x9FFF:背景地图
  • 0xA000 - 0xBFFF:卡带RAM
  • 0xC000 - 0xDFFF:工作RAM
  • 0xE000 - 0xFDFF:回声RAM
  • 0xFE00 - 0xFE9F:OAM(对象属性内存)
  • 0xFEA0 - 0xFEFF:未使用
  • 0xFF00 - 0xFF7F:I/O寄存器
  • 0xFF80 - 0xFFFE:高RAM区域
  • 0xFFFF:中断使能寄存器

更多信息:https://rylev.github.io/DMG-01/public/book/memory_map.html

您可以像这样设置映射初始内存结构

fn power_on(&mut self) {
    self.write_byte(0xFF05, 0);
    self.write_byte(0xFF06, 0);
    self.write_byte(0xFF07, 0);
    self.write_byte(0xFF10, 0x80);
    self.write_byte(0xFF11, 0xBF);
    self.write_byte(0xFF12, 0xF3);
    self.write_byte(0xFF14, 0xBF);
    self.write_byte(0xFF16, 0x3F);
    self.write_byte(0xFF16, 0x3F);
    self.write_byte(0xFF17, 0);
    self.write_byte(0xFF19, 0xBF);
    self.write_byte(0xFF1A, 0x7F);
    self.write_byte(0xFF1B, 0xFF);
    self.write_byte(0xFF1C, 0x9F);
    self.write_byte(0xFF1E, 0xFF);
    self.write_byte(0xFF20, 0xFF);
    self.write_byte(0xFF21, 0);
    self.write_byte(0xFF22, 0);
    self.write_byte(0xFF23, 0xBF);
    self.write_byte(0xFF24, 0x77);
    self.write_byte(0xFF25, 0xF3);
    self.write_byte(0xFF26, 0xF1);
    self.write_byte(0xFF40, 0x91);
    self.write_byte(0xFF42, 0);
    self.write_byte(0xFF43, 0);
    self.write_byte(0xFF45, 0);
    self.write_byte(0xFF47, 0xFC);
    self.write_byte(0xFF48, 0xFF);
    self.write_byte(0xFF49, 0xFF);
    self.write_byte(0xFF4A, 0);
    self.write_byte(0xFF4B, 0);
}

GPU(src/gpu

原始Game Boy的屏幕分辨率为160×144像素

pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144;

https://github.com/raphamorim/gameboy/blob/main/src/gb.rs#L3-L4

[...] TODO: 在此处添加更多关于GPU的说明。

使用示例

Web ~ JavaScript/WASM使用

  1. 使用Yarn或NPM安装lr35902
npm install lr35902
# yarn install lr35902
  1. 就这样
import { render } from 'lr35902';

const romFile = document.querySelector('#rom-file');
romFile.addEventListener('change', function readFile() {
  loadRom(this.files[0]);
});

async function loadRom(file) {
  const { size, name } = file;
  const arrayBuffer = await file.arrayBuffer();
  const u8View = new Uint8Array(arrayBuffer);
  render(rom);
}

桌面 ~ Rust使用

简单来说:您可以在示例文件夹中查看桌面示例(/examples/desktop

  1. lr35902添加为依赖项,并确保desktop作为功能启用
[dependencies]
gameboy = { version = "0.1.0", features = ["desktop"] }
  1. 就这样

默认缩放为1(160x144)。

use gameboy::gameboy::{Gameboy, RenderMode::Desktop};

fn main() {
    let mut gb = Gameboy::new();
    match gb.load_rom("./sample-rom.gb") {
        Ok(..) => {
            gb.render(Desktop);
        }
        Err(err) => {
            println!("{:?}", err);
        }
    }
}

测试

测试基于Blargg的Gameboy硬件测试ROM。

仓库链接: https://github.com/retrio/gb-test-roms.git

测试(最初在Openemu上运行) LR35902状态
CPU instructions test
Bits Unused test 失败
Halt Bug test 失败
Bits bank1 test 失败
GGB Sound test 失败

资源 & 参考文献

依赖项