1. MAME是啥
MAME最初是一款街机模拟器,可以在现代的计算机上运行以前的街机游戏。不过按照官网的介绍来看,现在更偏向于一个模拟器平台,类似QEMU。 MAME官网。
2. 为什么研究MAME
原因其实比较简单,我想做一个掌上可以玩的街机游戏机🎮。
为什么不去买一个,如果这么想很多事情都会失去热情,自己做一个意义更大一点,另外还可以将成果开源出去。一句话,酷炫就完了。
3. MAME初探
本次探究采用的是MAME 0122版本,而不是最新的版本,因为街机本身就是80/90年代的流行的游戏软件,并不会由于模拟器越新支持的街机功能就越强,而且越早的MAME版本实现也越简单,可以减少代码阅读的复杂度。
后续的架构和流程总结均是建立在0122版本以及个人理解之上,如果有不对的地方还请留言指正。
3.1 MAME架构
MAME是一个模拟器,对街机所需的硬件进行了模拟,分为如下几个部分:
- CPU。使用软件对街机的CPU进行了模拟,不过以前CPU都是一些简单的芯片,比如摩托罗拉68000;
- 输入。输入这块对街机本身来说是内存中的一段地址,读取电平的高低。MAME在此基础之上进行了抽象,分为键盘/摇杆/光电枪等;
- 视频。视频是输出设备,这块比较复杂,因为涉及到显示的分层,这块后面再详细说;
- 音频。街机的音频一般是独立的芯片,MAME对不同的街机设备的芯片进行了模拟,再对喇叭进行了模拟,最终的输出是对音频进行编码后的数字信号。
而我目前需要做的事情就是对输入输出设备进行改造。输入设备修改成手柄输入,视频输出到TFT LCD屏幕,音频输出到喇叭硬件上。
3.2 代码移植
3.2.1 构建
MAME代码中对系统适配做了很好的隔离,所以系统适配都放在了osd
中,也就是下面目录中。其中osdmini
和windows
是原先代码仓就有的,unix
是我新建的目录。
为了构建出unix
版本的MAME,我们需要编译osd
的时候不将windows
编译进去,而编译unix
。这个修改Makefile就可以了,Makefile中已经预留了编译的目录跟随编译时OSD的值。
在src/osd
目录下新增unix.mak
,用来编译源码,其中内容如下:
#-------------------------------------------------
# object and source roots
#-------------------------------------------------
UNIXSRC = $(SRC)/osd/$(OSD)
UNIXOBJ = $(OBJ)/osd/$(OSD)
OBJDIRS += $(UNIXOBJ)
#-------------------------------------------------
# OSD core library
#-------------------------------------------------
OSDCOREOBJS = \
$(UNIXOBJ)/main.o
#-------------------------------------------------
# OSD UNIX library
#-------------------------------------------------
OSDOBJS = \
$(UNIXOBJ)/osd_stub.o
#-------------------------------------------------
# rules for building the libaries
#-------------------------------------------------
$(LIBOCORE): $(OSDOBJS)
$(LIBOSD): $(OSDOBJS) $(OSDCOREOBJS)
当前共分为两个文件,一个是main.c
,用来作为程序的入口;另外一个是osd_stub.c
,是用来打桩用的,因为真正要运行MAME,需要实现很多平台相关函数,如果只是为了编译过打桩即可。
至此,unix
版本的MAME即可构建出来,当然功能是不好使的。
3.2.2 原型验证适配
这里的原型验证只是为了验证在Linux(unix)下,MAME可以正常工作,至少街机的ROM可以正常加载,CPU模拟这块正常运行。
ROM
为了验证游戏ROM可以正常加载运行,我挑了一款简单的游戏,1942(ROM可以咕噜咕噜一下搜到)。下载后的ROM需要解压,文件夹命名为1942。
main函数
main函数中只需要适配一句话即可,其中mame_unix_options
拷贝自windows
osd中的定义。
int main(int argc, const char **argv)
{
return cli_execute(argc, argv, mame_unix_options);
}
视频输出
模拟器每次刷新屏幕都会调用osd_update
函数,所以这里需要对osd_update
函数进行实现。
由于我用的linux系统是没有图形界面的,所以采用将视频输出转储到图片,这里的图片类型采用PPM
格式。
在osd_update
中我们需要将待输出的图像原语转换为我们需要的格式,获取当前一帧图像采用的是如下函数:
render_target_get_primitives
获取之后采用MAME
自带的RGB转换函数进行转换:
drawdd_rgb888_draw_primitives(head, g_draw_buff, DRAW_WIDTH_MAX, DRAW_HEIGHT_MAX, DRAW_WIDTH_MAX);
其中head
为图像原语链表头,g_draw_buff
为输出的内存地址,我们再将这块内存写入文件就获得图片。
按键输入
按键输入需要注册键盘设备,以及按键的键值对应的处理函数。采用如下方式即可。
input_device *input_device = input_device_add(DEVICE_CLASS_KEYBOARD, "my keyboard", NULL);
input_device_item_add(input_device, "O", name_O, ITEM_ID_O, get_key_state);
input_device_item_add(input_device, "K", name_K, ITEM_ID_K, get_key_state);
其中第四个参数为注册的键值,键值和街机的按键功能映射可以参见src/emu/inptport.c
。
至此,也就完成了基本的图形输出和键盘输入了。
运行效果
街机菜单界面
1942游戏界面
试了下合金弹头也是OK的
不过都只是运行的截图图片,离显示视频还有一段距离。