周三下午三点,我在 MartyPC 的调试控制台里盯着那行输出,心里清楚:有些秘密藏不住了。尽管这个模拟器因为超高的周期精度收获不少好评,让它跑起 Area 5150 的“Wibble”和“Lake”两个场景时,其实一直都带着一把专属的“后门钥匙”。“Wibble”是查理·卓别林、那个绿家伙和大象同台的画面,“Lake”则是演示最后的片尾,兼具水波特效和 PCM 音频播放。说白了,我作弊了。
这么说可能有点严厉。针对性修补在模拟器圈子里一点儿也不新鲜。翻看历史,许多知名模拟器都靠游戏专属补丁来绕过 bug 或精度不足,好让特定游戏跑起来。等模拟器精度慢慢提升、研究者对系统内部机制挖得更深,这类补丁才逐步退出舞台。拿 DOSBOX-X 的代码仓库搜一下“hack”这个关键词,跳出来的结果够你翻上半天——我并不是一个人。
替自己分辨一句:MartyPC 对特效本身的执行,从一开始就是周期精确的;真正的难题在于,怎么让特效开始。这篇文章就摊开来讲,难在哪儿,以及我最后怎么修掉了那个 hack。
IBM 的 CGA(彩色图形适配器)实在是个被束缚住手脚的设备。缺乏垂直同步中断让它生来就不宜用于游戏。当年其他计算机系统和绝大多数游戏机,都会提供某种帧级别或多帧级别的中断,由此告诉运行中的程序,显示器的电子束当前处于屏幕的哪个已知位置。这个信息能帮程序避免在显存正被扫描输出时进行读写,否则轻则画面闪烁,重则出现 CGA 上最扎眼的雪状噪点。
雪状噪点对 Area 5150 而言,几乎是贯穿全场的主敌。该演示绝大多数效果运行在 80 列文本模式下,而这一模式下雪状噪点的威胁始终高悬。于是,每个效果都必须小心选择访问显存的时机,限定在水平和垂直消隐期内。另一些效果还要精确调整每个扫描行的显存起始地址。那么,在没有垂直同步中断的情况下,程序要如何知道当前扫描位置?最直接的办法,就是轮询 CGA 的 03DAh 端口状态寄存器。
这个状态寄存器里藏着两个关键位。第 3 比特指明是否处于垂直消隐期。第 0 比特则是 CRTC(阴极射线管控制器)的“显示使能”信号的反相值——当该位被置 1,意味着电子束正处在屏幕的可见区域之外的某处。Area 5150 就是通过对这两比特持续的、精打细算的轮询,才能在几乎没有中断配合的硬件上,卡准每一个消隐窗口,实现那些看似不可能的效果。
热门跟贴