原文标题:Nintendo DS Architecture - A Practical Analysis[1 ] 。原作者:Rodrigo Copetti,译:Cerallin(我自己)。
译者序 两个月以来我一直在开发 NDS 的自制游戏,从零开始。当初这篇文章带我入门了 NDS 的底层架构,除 libnds 的文档外,就属这篇文章对我帮助最深。每当我想放弃时就来看看,然后就会发现自己之前思路的错误之处,就又能继续下去了。
为了让更多的人看到这篇充满希望(?)的文章,这周我花时间翻译了一下。没想到很快就翻译完了。原网站不久就会把我的翻译部署上去吧,在那之前,我先水篇博客(
实际上国内也不乏神贴,例如NDS 平台破解 & 烧录史 。但这些文章一般从玩家的角度考察 NDS 世界,自制游戏开发者视角的文档少之又少。想来国内就没有 NDS 自制游戏的土壤,因为 NDS 不算正式进入过中国市场,极客们都去搞破解汉化和改版去了。时至今日我才试探 NDS 编程,应该算是逆时代而行了。
总而言之,本文讨论的范围甚广,对于自制游戏来说,很多细节还需要我自己去深入研究。如果你想了解 NDS 运行的原理,那么这篇文章正适合你。如果好奇 PC 以外的计算机的架构,也可以看看原作者该系列的其他文章。如果觉得本文对你有所帮助,可以去原文网址底部捐赠 ,或购买电子书 。
顺便一提,本译文遵循 CC 4.0 协议。虽然本网站的全部文章都有版权声明四个字,但还是强调一下。
快速入门 这款游戏机回应了掌机生态中无法满足的许多需求,有一些创新也有一些妥协,不过这种组合可能会为新颖而独创的内容铺平道路。
CPU 和任天堂的上一代掌机 一样,NDS 的系统围绕一个名为CPU NTR 的大芯片展开。其中 “NTR” 是 “Nitro” 的缩写,是最初的 NDS 的代号。
CPU NTR 使用两个不同的 ARM CPU 实现了一个有趣的多处理器架构。在 ARM Holdings 正式发布多处理器解决方案之前这个设计就完成了。因此,考虑到当前的技术实现,可能有人认为 NDS 的多处理器架构不是很正统。
设计 虽然这不是本系列 第一次分析游戏机的并行系统,但是 NDS 的设计确与其他游戏机有很大不同。例如,NDS 的设计有别于世嘉土星 “实验性质” 的主从配置,也不同于PS1 或者N64 的 “协处理器” 方案。NDS 包括两台非常独立的计算机,可以分别独立执行操作。每台计算机都有一条专用的总线。这种设计方法被称为非对称多处理(Asymmetric multiprocessing) 。 CPU 因此而相互依赖,并将左右这款游戏机的整体性能。
话是这么说,现在我们来看看这两个 CPU:
ARM7TDMI ARM7 的结构与组成部分。 首先说说我们较为熟悉的 CPU。ARM7TDMI 与GBA 的 CPU 相同,但现在运行速度提高到约 34 MHz (原速度的两倍)。它仍然包含所有原有功能(特别是Thumb )。
然后讲讲新变化:由于任天堂的工程师将 ARM7 放置在大多数 I/O 端口旁边,这个 CPU 将负责调节和协助 I/O 操作。事实上,另一个处理器不能直接连接 I/O。如你所见,ARM7 不是负责整个系统的 “主” 处理器。它负责在多个组件之间传递数据,是用来分担一部分主 CPU 负载的 “子处理器”。
ARM946E-S ARM9 的结构与组成部分。 这是运行频率约 67 MHz 的 “主”CPU。如果忽略掉时运不济的 ARM8 系列,可以认为 ARM946E-S 是 ARM7 的 “下一代” 处理器。作为ARM9 系列 的一部分,它不仅继承了ARM7TDMI 的所有特性,还包括一些额外的部分[2 ] :
ARMv5TEISA :与以前的 4 代相比,支持了一些新的指令,乘法器也更快。如果仔细观察核心的名称,字母 “E” 表示增强型(Enhanced)DSP 。这意味着这些新指令大多与信号处理有关。 5 级流水线 :与之前的 3 级流水线相比,这是另一个提升。12 KB L1 高速缓存 :该核心新增有高速缓存,其中 8 KB 用于指令,4 KB 用于数据。48 KB 紧耦合存储器(Tightly-Coupled Memory, TCM) :类似于Scratchpad 内存 ,但该内存分别存储指令(32 KB)和数据(16 KB)。任天堂在 ARM9 周围还添加了以下组件:
一个除法器和平方根单元 ,以加速这些类型的操作(ARM9 本身不能执行这种类型的数学计算)。 一个直接内存访问控制器(Direct Memory Access Controller, DMAC) :独立于 CPU 加速内存传输。结合使用高速缓存,CPU 和 DMA 有并发运行的潜力。缓存和 DMA 可以提供很多性能,但也会造成新的问题,如数据可靠性。开发者需要手动维护内存一致性,例如,先刷新write-buffer 然后再触发 DMA。 就 NDS 这硬件,也许不难发现孩子们喜欢这款游戏机的真正 原因?
互连 到目前为止,我已经介绍了这两个 CPU 如何单独工作。但是作为一个整体运行,它们需要不断合作。为了实现这一点,两个 CPU 直接使用专用的FIFO 单元 [3 ] 进行 “对话”,该数据块包含两个 64 字节队列(最多 16 个元素),用于双向通信 。
FIFO 单元示意图。 具体工作方式如下:发送方 CPU(发送消息的 CPU)将一个 32 位数据块放入队列中,接收方 CPU 可以从队列中拉取该块并对其执行所需的操作。
每当队列上写入一个值,它可以被任一 CPU 主动获取(轮询 ),但这需要不间断地检查新消息(开销会很大)。或者,也可以激活一个中断单元 ,以便在队列中有新消息时通知接收方。
主存储器 就像 GBA 一样,NDS 的 RAM 也被分散在许多不同的位置,以便根据访问速率来安排数据远近。总之,可用通用内存如下:
该掌机的 RAM 模型。 32 位 总线的32 KB WRAM (Work RAM):用于存储在 ARM7 和 ARM9 之间共享的快速数据。32 位 总线的64 KB WRAM :同样用于快速数据,但只能从 ARM7 访问,就像 GBA 一样。16 位 总线的4 MB PSRAM :速度较慢,可以由任一 CPU 访问,由内存接口单元控制。伪静态随机存取存储器(Pseudo SRAM, PSRAM)是 DRAM 的一种变种,与 DRAM 相比,PSRAM 芯片自己就能执行刷新操作。因此,它的行为类似于 SRAM(DRAM 的一种替代品,更快但更昂贵)。这种设计让我想起了1T-SRAM 。 向后兼容性 尽管架构与前代有很大不同,但 NDS 仍成功地保留了关键部分,从而原生支持 GameBoy Advance (GBA) 游戏。
但是,为了将 NDS 转为 “内部”GBA,前者包括一组软件例程(software routines),可以将游戏机设置为AGB 兼容模式(AGB Compatibility Mode) 。此过程中,实际上会终止 ARM9,禁用大部分 “特殊” 硬件,重定向总线,将 ARM7 置于控制位置,并将其频率降至 16.78 MHz。最后,ARM7 开始引导初始的 AGB BIOS,该 BIOS 引导 GamePak 卡带(就像本来的 GBA 一样)。这种模式仍然展示出一些 GBA 所没有的特征,例如游戏显示时有黑边框(在下一节中我们将看到新屏幕分辨率恰好更大)。此外,由于 NDS 有两个屏幕,所以用户可以设置用哪个屏幕显示 GBA 游戏。
一旦进入 GBA 模式,就没有退路 了,必须重置游戏机才能重新激活其余硬件。
秘密和限制 单个廉价芯片中安装了这么多复杂组件,强制他们协作导致出了一些问题,这并不是什么神秘的事情。
性能阉割 有时候我真想知道任天堂计划如何使用这两个 CPU,以及他们是否清楚这种设计会损失一些性能。
让我们从 ARM9 开始。该 CPU 的运行速度是 ARM7 的两倍,但大部分(甚至是全部)的 I/O 都依赖于 ARM7。因此等待 ARM7 的响应可能导致 ARM9 频繁阻塞。不仅如此,ARM9 的外部总线速度只有一半 ,因此存在一些瓶颈问题。
此外,主存储器总线只有16 位 。无论哪个 CPU 需要从存储器中提取一个字(32 位宽),接口都会阻塞 CPU ,然后占用高达 3 个 “等待” 周期,直到拼成一个完整的字。内存访问不连续时影响最严重,每次内存访问都会阻塞。这个问题在指令获取时也会出现。不幸的是,当时的 ARM 不支持顺序操作码获取(sequential opcode fetching),这也影响到了 Thumb 指令(因为每次获取 16 位数据都要以 32 位的块为单位进行)。另一方面,这个一些资料所谓的 “penalty”,可以充分利用缓存和 TCM 来缓解。
总的来说,这意味着在最坏的情况下,这款 “猛烈” 的 ARM9 66 MHz 的处理能力实际上会被降低到只有约 8 MHz,如果程序的缓存 /TCM 的利用率极低的话。
如果需要详细报告,我建议查看 Martin Korth 的文档[4 ] ,尤其是 “DS Memory Timings” 一节。
关于硬件选择的问题 当初研究 GBA 的 CPU 时,我对 ARM7 的潜力感到非常惊讶:该 CPU 不仅可以完成设计之内的任务,还可以协助完成其他任务,例如提供音频序列或伪 3D 图形。
与此相关的是,在商业化 ARM7 的过程中,ARM Holdings 与 DEC 合作设计了 ARM 芯片的高端版本。为此,DEC 采用了他们的Alpha 处理器的数据通路设计,并结合了 ARM 的设计[5 ] 。研发出的一系列名为StrongARM 的新 CPU,速度快 得令人惊讶。以去除某些功能(如 Thumb 指令集和调试功能)为代价,DEC 成功地跨越了兆赫门槛,触及高达 233 MHz 的运行速度。作为一名普通用户,如果你准备购买新的 ARM 电脑(例如RiscPC ),摆在你面前的是两个选择:一台搭载 40 MHz ARM710 老旧处理器的电脑,或者一台运行速度快约 582% 的 StrongARM 电脑。 StrongARM 的影响如此巨大,以至于 ARM Holdings 吸收了一些 StrongARM 的特性,用于生产他们的下一代 CPU,也就是 ARM9 及之后的系列。其余就是些老黄历了。
但这里就是我的问题所在:明明见到了 ARM 世日新月异的发展,为什么任天堂最终选择了一个速度极慢的 ARM9 处理器,再加上一个速度更慢的 ARM7,而不是选择一个更快的 ARM9(甚至是一个 StrongARM)呢?抛砖引玉,其他公司(例如苹果)就在新出的 Newton PDA 系列中采用了 StrongARM 处理器。
我不是要批评任天堂的决策,但我认为新兴技术数量多到很难让人视而不见。我猜可能是为了保全电池寿命(译注:从充满电到用到没电的使用时长)并且控制生产成本(通过使用与 GBA 相同的 CPU)。
图形 这一部分有些不太寻常,因为这款游戏机不仅有多个屏幕需要绘制,而且把传统的图块(tile)引擎与现代渲染器结合在了一起。
首先说说硬件:NDS 包含两个 LCD 屏幕,每个屏幕的都有256x192 个像素 ,比 GBA 多约 20%个像素点。它们可以显示 262144 种颜色(18 位),刷新率约 60 Hz。
架构 图形子系统可以绘制 2D 和 3D 对象。前者由二维几何图形组成,其中填充了 8x8 像素的位图(称为 “图块”),而后者则使用顶点绘制三维对象(多边形)。
深入了解操作这些屏幕的内部芯片,我们可以观察到这个掌机具有独特的 2D 和 3D 几何图形硬件。2D 数据由我们熟悉的引擎 PPU(现在只称之为2D 引擎 )操作,而 3D 数据由一个全新的子系统处理。值得一提的是,虽然这并不是第一款 推出 3D 图形功能的游戏机,但它仍然是第一款使用自研图形渲染器来呈现 3D 图形的游戏机。
不同图形单元的布局。 现在,这两个引擎必须连接到两个屏幕其中之一。对于只有 2D 图形的游戏来说,这不是问题,因为每个屏幕都有一个 2D 引擎。然而,那些想展示前沿特性的游戏却只有一个 3D 引擎可用。因此,每次只能在一个屏幕上使用 3D 功能。但是 2D 和 3D 对象如何混合显示?别急,我先分别解释清楚这两个引擎,之后再讨论这个问题。
使用 2D 图形构建帧 在我们检查各阶段之前,我建议先阅读有关 GBA PPU 的这篇文章 ,因为在这里我只会提到构建 “下一代”2D 游戏的变化。另外,由于有两个引擎,这里我们把第一个引擎称为主引擎(Main) ,第二个则称为辅助引擎(Sub) 。虽然这并不一定意味着哪个屏幕连接了哪个引擎,而且主引擎比辅助引擎包含更多的功能。
我将借用新超级马里奥兄弟(New Super Mario Bros) 的资源(assets)来帮助解释。
图块(Tiles) 从 VRAM 中提取的图块。出于演示目的,使用了默认调色板。 到目前为止,我们都知道基本的图块系统是如何工作的,但是 NDS 的图块处理有什么特别之处呢?其实,总共有656 KB VRAM 可用,这一块被分割成不同的 bank:四个 128 KB bank、一个 64 KB bank、一个 32 KB bank 和三个 16 KB bank。开发者可以随便往 bank 里填充数据并将引擎指向所需数据的位置。两个引擎都可以从这些 bank 读数据,但是它们不能同时访问同一个 bank。
尽管如此,数据排布方式还是有一些限制。例如,ARM7 只能访问两个 128 KB 的 bank,这两个 bank 不能存储 sprite;最后 16 KB bank 只能被 “辅助引擎” 访问;等等等等……你懂得。
最后一句,3D 引擎我们稍后再讨论,它可以访问其中某些 bank 以获取纹理(texture)。
背景类型 从Super Nintendo 时代开始,PPU 一直致力于为构建背景图层提供更多的灵活性。 14 年后,有这样一款芯片,可以获取图块并应用许多仿射变换 。甚至,最终可以从 frame buffer 构建图层。
在我们讨论 2D 引擎显示背景的不同模式之前,让我们先了解一下引擎可以生成哪些类型的背景:
字符类型(Character type)组 :这些背景类型遵循传统的图块系统,通过填充图块来渲染帧。静态(Static) 或者说 “文本”(text)背景:普通背景,最大支持 512x512 像素,256 色和 16 个调色板。包括了所有典型的特效(effects) (H/V 翻转、H/V 滚动、马赛克、alpha 混合),外加一个额外的 "褪色” 效果。最多可使用 1024 个图块。仿射(Affine) 背景:一个带有仿射变换(affine transformations) 的背景,不支持水平 / 垂直翻转,并且只能获取 256 个图块 (最大值的四分之一)。图层大小为 1024x1024 像素。仿射扩展(Affine Extended) 背景:与 affine 背景相同,但是可使用 1024 个图块,并且支持水平 / 垂直翻转。位图(Bitmap)类型组 :引擎不再处理图块,直接把 VRAM 用作 frame buffer。所有的位图类型都继承了 character affine 背景的所有效果。256 色扩展(256 colours Extended) :使用 512x512 px 的帧缓冲区。直接颜色扩展(Direct colour Extended) :与 256 色扩展类似,但 frame buffer 支持的颜色提高到 32768 种(15 位)。大屏幕(Large screen) :利用所有四块 128 KB 大的 VRAM 块来渲染一个 1024x512 px 大的 frame-buffer。开发者不能任意选择背景类型。但是游戏机提供了一系列背景模式 ,为各类型设置了不同的组合。
背景模式 背景图层 0 (BG0)。这个特定的图层将在某些扫描线上水平位移以模拟云的移动。 背景图层 2 (BG2)。 背景图层 3 (BG3)。 背景类型实战演示。主引擎和辅助引擎都提供了六种操作模式,所有模式都能生成四个背景图层,但每个图层的能力有所不同:
模式 0 :4 个 static 图层。模式 1 :3 个 static 图层 +1 个 affine 图层。模式 2 :2 个 static 图层 +2 个 affine 图层。模式 3 :3 个 static 图层 +1 个 extended affine 图层。模式 4 :2 个 static 图层 +1 个 affine 图层 +1 个 extendine affine 图层。模式 5 :2 个 static 图层 +2 个 extended affine 图层。模式 6 :3D 引擎渲染的 1 个 static 图层 + 1 个 large screen。由于 VRAM bank 的空间只够一个 frame buffer,因此只有主引擎能使用该模式。 Sprites 渲染的 Sprite 图层. Sprites 或者说 “objects” 继承了 GBA PPU 的功能,但新增两个重要功能。
首先,OAM(存储 sprite 条目(sprites entries)的区域)的大小现在有2 KB ,使每屏每帧可以显示多达 128 个 sprite。因此,两个引擎都分配有 1 KB。
其次,OAM 现在可以引用VRAM 中的位图,而不仅仅使用图块和调色板。这是一种与 tile 系统不同的方法。实际上,同一帧中可以同时存在这两种 sprite“模式”,因为此选项是在每个单独的 sprite 上设置的。
结果 合并所有图层……是不是少了什么? 由于每个图层都是即时渲染的,最后阶段需要合并所有内容并将其发送到所选屏幕。以前基于 PPU 渲染游戏机基本如此,但这是否意味着 NDS 也到这里结束了?
还没呢!主引擎仍然必须从另一个引擎——最强大的引擎——获取图层。
3D 加速 如果你之前玩过 NDS,那么你就知道这款游戏机可以显示一定 数量的 3D 图形。不同于 GBA 游戏,这些 3D 图形并非由 CPU 处理。不过,CPU-NTR 包括两个组件 来构建3D 引擎 。有趣的是,任天堂采用的设计让我想起了SGI 的 RCP 。
回顾 “背景模式” 部分,你会注意到每个模式都至少有一个 static 背景,这是因为你可以用 3D 引擎生成的图形来填充该图层。唯一需要注意的是只有主引擎可以这样做,这也是模式 6 仅适用于主引擎的原因之一。
几何引擎 几何引擎的架构。 如果读过第四代或第五代游戏机的文章,你可能会想知道……SIMD 处理器 哪去了?这是个好问题,因为 ARM9 并不擅长矢量运算,而且我不认为专用除法器很够用。这就是为什么任天堂嵌入了一个叫做几何引擎 的组件,它负责顶点变换 、投影 、光线 、裁剪 、剔除 和多边形排序 ,后者对于正确使用透明特性是必不可少的。
这个引擎有一些严格的限制,特别是它能够处理的多边形数量:有额外的 248 KB RAM 可用于存储处理过的几何体,数量可以达到 2048 个三角形或 1706 个四边形。使用多边形条带的话(而不是单个多边形)还可以存储更多。为了对这个数字有一些概念,我建议查看之前文章中的 “交互模型” 部分,你会发现这个限制令人担忧,但不要忘记掌机的屏幕分辨率也要小得多……所以算是抵消了一点。
无论如何,这个引擎是通过一个Command FIFO 来控制的,其数据是由 CPU 或 DMA 填充。该 FIFO 可以存储 256 个条目,并且它还有一个叫做PIPE 的缓冲区,用于存储额外的四个命令(总共 260 个命令)。
渲染引擎 渲染引擎的架构。 渲染引擎负责将向量转换为像素(光栅化),着色(纹理映射)并应用光照和其他效果。它依靠透视校正 和Gouraud 着色 分别用来处理插值纹理和光照。此外,该单元提供一些现代特性,例如fog ,alpha 混合 ,深度缓冲 (Z 缓冲 ,或是一种被称为 W 缓冲的变体),模板测试 和抗锯齿 。虽然最后一项非常原始(将多边形的外部边缘设置透明而已),而且只能用于不透明像素。
渲染系统采用了新旧结合的方式:它没有直接渲染到 frame buffer,而是采用行缓存渲染(line buffer rendering) ,在每一条扫描线上填充像素(类似于 2D 引擎),并将结果存储在一个较小的缓缓冲区中。这是因为 3D 引擎必须与 2D 绘图器同步工作。
没有传统的 frame buffer,光栅化器采用了扫描线渲染(scan-line rendering) ,遍历每个扫描线以处理其中的多边形边缘。 Arisotura(MelonDS 模拟器的开发者)称,对于每个四边形,渲染器只能在每条扫描线上填充一个 span [6 ] 。这可能会有隐患,因为如果四边形是凹四边形或者有交叉的边,结果会变得一团糟。
关于效果,该单元还提供shadowing 和一个被称为Toon Shading 的独特功能(又称Cel Shading ):该单元不可编程 ,但也可以通过改变照明参数达到卡通效果。
结果 这就对了嘛! 渲染引擎将不会将结果写回 frame buffer 以供显示,而是写入一个名为Color Buffer 的块中,该块可以存储多达 48 条扫描线。 2D 引擎会按照先进先出(FIFO)的方式获取每条扫描线,以填充 BG0 图层。
3D 渲染在 2D 渲染之前开始,使后者在必要时能够应用图形变换到新图层。主引擎还允许捕获生成的 2D 帧、3D 帧或组合帧,将其与 VRAM 中的另一个帧混合(blend),并将结果写回 VRAM,之后可以用于显示。
在控制方面,由于采用了基于双缓冲的机制,渲染引擎还允许在帧中间改变参数,该机制可以保留旧状态的副本,直到当前帧绘制完成。因此,不会出现图像撕裂的现象。
知名游戏比较 一些最初在 NDS 上发布的游戏试图模仿另一台游戏机(即 N64)上的游戏。玩家可能会看到两种版本之间存在重大差异,我想简要总结一下原因:
第一个例子 超级马力欧 64(1996) 以 320×240 像素渲染。 超级马力欧 64 DS(2004) 以 256x192 像素渲染。 第二个例子 马力欧卡丁车 64(1996) 以 320×240 像素渲染。 马力欧卡丁车 DS(2005) 以 256x192 像素渲染。 所以,为了解释这里发生了什么,我将根据一些论坛用户的说法,整理出几条不同的解释:
NDS 版的纹理看起来更加方块 → 渲染引擎没有使用任何滤波器,因此纹理使用 “最近邻” 方法进行插值。NDS 版的纹理看起来更加丰富 → 渲染引擎没有4KB TMEM 的限制。相反,除了提供的压缩机制外,最多还有 512KB 的 VRAM 可用,因此可以加载更多数据。NDS 模型的边缘出现了锯齿 → 与 N64 相比,NDS 模型的分辨率较低。NDS 的纹理当从远处看起来会出现失真 的情况 → 光栅化器使用的是定点 坐标。低分辨率和 mip-mapping 的缺乏也加剧了走形程度。简要的概述这就是这些。如果想要了解更专业的情况,你可能需要深入研究两个引擎,甚至反汇编这两个游戏来研究所使用的功能以及他们的用法。
交互模型 我更新了 wee model 查看器,添加了 “最近邻插值” 功能。这样你就可以使用你自己的 GPU 来查看 NDS 模型了。
请前往原网站体验交互式小部件。
新马力欧兄弟(2004) 636 个三角形。 任天狗狗(2005) 750 个三角形。 尽管我们讨论了图形子系统的诸多限制,但很多游戏确实充分利用了它的功能。
音频 大多数音频改进都集中在增强 GBA 所提供 的那些 PCM 声道上。我们之前看到过,GBA 游戏最终将软件序列优先于 PSG,结果非常令人印象深刻。
最后之窗真夜中的约束(2010)。展示混合立体声输出。 最后之窗真夜中的约束(2010)。展示混合立体声输出。 因此,新的音频系统总共有16 个 PCM 声道(channels) ,可以将混音任务转移到硬件上。 PCM 采样可以是8 比特 (GBA 风格 )、16 比特 (最高解析度)或 ACPCM(压缩形式)。无论如何,混音器都会产生一个立体声信号,可以通过扬声器(现在是立体声)或耳机播放。它还可以将生成的立体声数据写入 WRAM,让子处理器(ARM7)能够应用一些效果,例如混响。
尽管以上内容已经介绍了不少,但这是否意味着 NDS 终于可以播放编码音乐(例如 MP3)?这是可行的(实际上,许多自制程序都实现了某种形式的音频解码),但音频解码需要大量的带宽和处理能力[7 ] 。所以,音频序列仍然是可行最高的选项。
有关 PSG 的一个(也许两个)古怪之处 NDS 可以运行 GBA 游戏,因此它应该具有类似于前代 PSG 的功能(无论是通过硬件还是软件实现)。正好,最后 6 个声道包含一个 “PSG 模式”,允许其中任何一个合成脉冲或自定义波形,并且其中只有两个声道可以产生噪声。但是 GBA 游戏没有使用其中任何一个功能!
你看,混音器的输出频率是 32 kHz,解析度为 10 比特(远低于输入样本的质量)。此外,混音器没有执行任何形式的插值 来平滑精度的损失。这些限制对样本来说并不理想,因为它会引入噪声。尽管这种现象的实际感知取决于你的听觉能力(我不会注意到有 “噪声”,除非我把音量调高并将其与 16 比特版本放到一起比较),但这仍然是从使用 8 比特解析度软件混合采样以来的一大进步。与之相比,PSG 的声音的混叠效应更为棘手,因为降采样信号可能会引入错误的谐波,使原始的 PSG 音调失真。然而,像《新超级马里奥兄弟》这样的游戏,脉冲波伴奏就用得很欢,所以我不认为 PSG 完全没有用处。
回到主题,GBA 游戏是如何处理的呢?答案是没有处理。任天堂为 GBA 模式适配了一个单独的声音系统(在同一外壳里),其中包括符合 GBA 的规格的私有的声道和混音器。这样,GBA 游戏就不会被新混音器限制所影响。不幸的是,NDS 游戏无法使用这个子系统,因为它与 NDS 的系统相隔离。换句话说,它不能输出到 NDS 的混音器。
交互式比较 我构建了这个交互式小部件,让你可以自己来回比较,从而理解新的音频系统如何影响新一代游戏的配乐。每个小部件都可以播放相同的曲子,但允许你在旧版和新版之间切换(建议戴上耳机以更好地体验差异)。试一试!
请前往原网站体验交互式小部件。
不得不说,我得稍微增加 GBA 原声音乐的增益才能把音量调整正常,这往往会影响信噪比(来回切换时请记住这一点)。无论如何,我希望您能感受到声音子系统是如何演变的。
提升难度 现在请让我展示一些棘手的情况。移植游戏的原平台有一些独特的音频功能,在 NDS 上重现这些功能可不简单。现在请你来当评委:
请前往原网站体验交互式小部件。
正如你从第一个示例中听到的(特别是在最后 10 秒钟内),要与 SNES 的 S-SMP 所提供的功能相竞争有些困难。
必须承认,第二个例子是我故意放上去的。我的意思是,究竟 发生了什么?就好像这个新的编排最初就是为雅达利游戏机而设计的一样。如果你问我,我认为 NDS 可以处理某种形式的 FM 到 PCM 重新采样,所以这个新的极简主义 编排可能只是一种创造性的方法。
I/O 长话短说,I/O 严格交由 ARM7 处理。事实上,除传递数据之外,你不会看到 ARM7 处理器有多少操作……这真是太可惜了。
卡带与内存的访问 有一个连接三个 endpoint 的外部内存接口:Slot-1 (用于放置 NDS 卡带)、Slot-2 (用于放置 GBA 卡带或配件)以及4 MB PSRAM (主内存)。两个 CPU 均可访问该接口。接口中包含可以修改的寄存器,以便设定 CPU 优先级,在同一时间有两个总线请求时优先哪颗 CPU。
外部存储器模型,标记了数据总线的宽度。 现在要说重要的一点:NDS 卡带没有内存映射 。为了让任一 CPU 读取游戏数据,首先必须把内容复制到 RAM 中。这是通过向卡带发送包含 8 位命令和 32 位地址引用的数据块来实现的。之后,可以通过 32 位寄存器或 DMA 手动获取数据。数据总线宽度只有 8 位,但速度最高可达5.96 MB 每秒 (如任天堂所述)。
用于保存备份的芯片(如 EEPROM、FLASH 和 FRAM)可以通过 SPI 总线(串行)访问。该总线使用自己的一套命令集,并连接到一个 24 位地址总线。
使用本来的引脚排布的话,Slot-2 卡槽是内存映射的。但在 DS 模式下,为了适配提供扩展功能的硬件(额外的 RAM、震动等),地址会被移位。就像 GBA 一样,ROM 总线为 16 位宽,RAM 总线则有 8 位宽。
外部设备 ARM7 也连接到另一个 SPI 节点,即触摸屏控制器 的接口。该接口可以操作底部屏幕(电阻式,需要使用触控笔)和闪存存储器(固件 就存储在这里,稍后再做详细介绍)。
愿望之屋 天使的记忆(2007)前面提到的总机难题。为了营救小姑娘,玩家必须同时用两根手指滑动屏幕才能开灯。 但如果你做错了…… 这个触摸屏的一个有趣的特点,除了检测 X/Y 位置的功能,它还可以返回对角线位置 (用于计算 “压力值”,表示施加压力的区域)。不幸的是,这个特性从未在官方 SDK 中公开。据我所知,没有游戏最终使用了这个未记录的特性,除了自制游戏。
许多人指出,《愿望之屋 天使的记忆》依靠这个特性来解决其中一个谜题,需要用户同时使用两个手指。然而,情况并非如此。在使用 no$gba debugger 实验之后,可以发现这个谜题并没有用到压力数据,而是检查 X/Y 值是否交替剧烈变化。游戏将这种效果解释为用户用两个手指按压屏幕的结果。
最后,同一堆东西里还有实时时钟 (real-time clock, RTC)。
无线网络 最后但同样重要的是,掌机包含一个运行在 2.4 GHz 频段的无线控制器 ,提供了一些创新功能:
Internet Play :使任何游戏都能使用标准 Wi-Fi 连接连接到 LAN 网络。Multi-card Play :使用专有协议,最多可以联机 16 台掌机。Single-card Play :游戏可以将上传程序到另一个没装游戏卡的 DS。操作系统 我觉得基本可以认为,这一世代的每款游戏机都带有了某种交互界面。 NDS 继承了以轻量级 API 为基础的操作系统模型,以简化 I/O 访问;但同时也提供了一个精简的用户界面,以供调整设置或者运行 “应用程序”。
话虽如此,它的操作系统分散在多个芯片中。让我们从启动时读取的芯片开始。
入口点(Entry point) 在某一时刻,ARM7 和 ARM9 将需要初始化硬件。 NTR-CPU 包括两个不同的小型 ROM 芯片来做这件事:
一个连接到 ARM9 总线的4 KB BIOS 。 一个连接到 ARM7 总线的16 KB BIOS 。 当掌机启动时,每个 CPU 从各自的 ROM 启动引导。这是因为它们的复位向量(reset vector) 指向每个芯片(作为参考,ARM9 的向量位于0xFFFF0000
,而 ARM7 的向量位于0x00000000
)[8 ] 。
继续推进,两个 BIOS 都存储两组例程:引导代码和中断调用。考虑到前代掌机 的历史,后者我们并不陌生,然而前者的复杂度却增加了:除了硬件初始化之外,ARM7 的代码还将对 DS 卡带(如果插入了的话)进行一些检查来确保安全性。
运行引导代码后,两个 CPU 将同步,以便它们可以开始充当 “单个机器”:事实证明,ARM9 的加载完成要比 ARM7 快得多。于是 ARM9 向 ARM7 发送一个 4 比特值,在半无限循环中阻塞等待 ARM7 的响应。一旦 ARM7 响应,两者同时 “越过终点线”,也就是说,它们现在同步了。
机会之窗 如果您现在拥有或者曾经拥有 NDS,您可能会注意到只能 在游戏机开机前插入卡带才能玩游戏。这是因为 ARM7 的 BIOS 在启动时对卡带进行了一些检查(更多详情参见最后一节),如果所有测试都通过,ARM7 的游戏可执行程序将被复制到 WRAM,ARM9 的程序则被复制到主内存。
如果出于某些原因未能复制可执行文件(卡带无效或在启动时未找到可执行文件),则无法启动游戏。用户将不得不重置游戏机才能玩游戏。
交互界面 无论是否插卡,系统都将加载交互界面完成引导。这只是一个驻留在外部256 KB 闪存 存储器上的程序[9 ] 。
主界面。 每次 NDS 开机时都会看到此界面。在有效插入一张卡时 “Nintendo” logo 会显示出来。 设置界面。 存储界面的芯片还存储着固件、一些用户设置(语言、昵称、生日、闹钟和欢迎消息)以及一些系统设置(触摸屏校准、首次启动标志、固件版本和 Wi-Fi 设置)。
这个界面与其他同期的游戏机界面基本上差不多。用户依靠它来启动游戏、更改设置、下载游戏(使用 “Download Play” 功能)或者玩玩Pictochat :一个开放的聊天室软件,可以与附近的 NDS 交流。
值得强调的是,只读数据和可写数据都驻留在同一个可重写的芯片中,因此理论上固件可以被覆盖!幸运的是(或者出于显而易见的原因),任天堂在主板上的一个点(称为SL1
)上放置了跳线来保护芯片的上四分之一(64 KB)不被写入,拆卸电池仓后可以看到这个点。然而,仍然可以覆盖闪存的其余部分,虽然结果也是灾难性的[10 ] !
可更新性 任天堂为了修复一些安全漏洞,曾多次更新这个固件(确切地说是 5 次)。用户无法自行安装这些更新(回想一下SL1
的保护)。但是,任天堂在下一批生产的产品中嵌入了更新后的固件。
游戏 这里能说的可就多了,首要原因是这个游戏机确实启发开发者和艺术家们想出了非常创新的设计。让我们来看看……
存储介质 这个游戏机可以从三个来源运行游戏,其中只有两个可以充分地利用硬件:
一款零售游戏的例子。 NDS 或者说 “Slot-1” 卡带 :用于加载原生 NDS 游戏的主要介质,这是用于发行 NDS 游戏的唯一介质。GBA 或者说 “Slot-2” 卡带 :该插槽使得掌机能够原生游玩 GBA 游戏。Slot-1 游戏也可以访问此插槽,因此可以插入扩展卡带 以增强 NDS 游戏。这提供了诸多功能如更多 RAM、更丰富的输入控制或反馈设备(例如振动包 )。Download Play 或 “无线多重启动(Wireless MultiBoot)” :原多重启动(MultiBoot) 功能的升级版,使另一个带有 NDS 游戏的掌机能够使用无线通信上传程序。下载的内容存储在 WRAM 中,传输完成后启动。由于 WRAM 是易失性存储器,数据在关机时将丢失。获得授权的零售商可以使用此功能部署下载站 ,邀请用户在到店参观时下载演示版游戏。 程序的结构 你已经见过 BIOS 需要分别为 ARM9 和 ARM7 编写不同的代码,在游戏中基本也是同样的情况。NDS 卡的结构如下:
Header(4 KB) :包含元数据(各个可执行文件的位置、序列号等)。Secure Area(16 KB) :出于复制保护的目的而设置。我们将在本文的最后一部分详细介绍相关内容。Main content(大小可变) :卡带的其余部分,仅包含可执行文件和游戏数据(图形、声音等)。使用任天堂的 SDK 制作的零售游戏使用内置的文件系统将数据层次化地组织为文件和目录。开发生态 对于有意为该掌机开发游戏的游戏工作室,任天堂分发了硬件套件和 SDK,其中包括许多实用工具。
硬件 被称为IS-NITRO-EMULATOR 的开发套件由一个中等大小的蓝色盒子组成,包含 DS 的大部分内部硬件和 I/O 接口[11 ] 。套件通过一根粗电缆连接到一个充当 “控制器” 和显示器的伪 NDS 外壳。根据需求,开发工具包还可以增加可选功能,例如音频 / 视频输出、Wi-Fi(默认情况下使用以太网模拟)和调试功能。我原以为后者已经包含在内了,但我意识到这些设备也可以被测试团队使用。
该套件可以读取 DS 卡带,但是需要使用较大的卡片和可更换的备份芯片。这些卡片使用被称为IS-NITRO-WRITER 的另一个单元刷写。
软件 软件套件包括用于与开发工具包交互的实用程序、C/C++ 工具链和硬件 API。需要提到的一点是,文档明确禁止绕过提供的 API 直接访问硬件:尽管游戏将在裸机上运行 ,但如果它执行 “违禁” 操作,任天堂将不会批准其分发。违禁操作包括直接控制 ARM7,超出显示分辨率或者关闭 LCD 屏幕。
这些措施情有可原,例如为了保证质量水平。尽管在我看来,把 ARM7 拘束在 I/O 任务里纯属浪费……
交互的自由 脑锻炼(2005) 新品类的游戏吸引了青少年之外的受众。 随着新型交互形式的出现,工作室就有机会优先考虑游戏体验而不是图形显示。
在电子消费品中,一款把触摸屏、麦克风、Wi-Fi 和一个实时时钟封装到一起的掌机第一次出现。一些游戏甚至提出了新的互动形式,比如指示用户侧握掌机。
网络服务 在以前的竞争对手 获得成功之后,任天堂也加入了在线多人游戏俱乐部,并部署了他们的集中式基础设施。使用 “Internet Play” 的游戏可以连接到任天堂的服务器(称为Nintendo Wi-Fi Connection )在线游玩游戏。
反盗版和自制游戏 即使 DS 卡没有受到光盘诅咒 的影响,任天堂还是实施了一些保护措施以保持对游戏发行的控制。
安全机制 让我们看看各个领域的安全机制:
加密系统 NDS 主要使用对称加密系统对内存接口和 Slot-1 卡之间的通信进行加密。在讨论如何加密之前,我们需要先聊一聊所使用的算法以及如何生成用于加密的密钥。
卡片的 “Header” 区域包含一个名为Gamecode 的值(游戏的唯一标识符),内存接口抓取这个块来生成KEY1 并使用它来加密进一步发送到卡片的命令。 KEY1 加密基于Blowfish 算法 。
之后,KEY1 与内部时钟和卡带 Header 的一些其他值混合,生成一个新的密钥,称为KEY2 。 KEY2 与 KEY1 的根本区别在于,前者使用随机值所以无法被预测。 KEY2 加密使用多个异或与移位操作来混淆数据。
DS 卡的校验 正如我们上面看到的那样,BIOS 包括一些例程,在启动时校验 NDS 游戏卡。工作原理如下:
掌机检索卡带的芯片 ID,将其保存在 RAM 上,然后启用 KEY1 加密。 “安全区” 的前 2KB 也被复制到 RAM 上。该块的前 8B 存储了一个名为Secure Area ID 的字符串,后面的数据包含校验和(CRC16 类型)与一些其他元数据。 虽然 “Secure Area ID” 已经用 KEY1 加密过了,但是还要用 KEY2 再次加密,之后用到时再解密。如果解密后的值与字符串encryObj
相匹配,说明卡片通过了校验第一次关 。此后,该字符串被销毁,防止算法泄露。如果验证失败,Secure Area 的 2KB 就会被无意义数据填满,阻止读取卡片的其他部分。 第二个测试流程为再次检索芯片 ID,然后用 KEY2 随机加密若干次(使用内部时钟做种)。如果芯片 ID 的值与存储的第一个芯片 ID 相匹配,则第二次测试通过 。 最后,Secure area 的其余部分以随机顺序被取走并在 RAM 中重新构建。之后就该执行固件了。 如果一切顺利,固件将在 RAM 中找到该卡所需的可执行文件,从而成功启动游戏。否则,游戏选择框将显示为灰色。
Download Play 保护 通过 Download Play 收到的程序必须由任天堂使用RSA 签名 (只有任天堂知道私钥)进行签名。
该检查由固件执行。
击败 如果你是自制软件使用者,那么你可能会遇到可以运行该软件的选项。事实是,在发现目前的破解方法之前,黑客们很难绕过任天堂复杂的反盗版系统。
Existing Slot-2 由于 GBA 子系统仍然在没有实施任何保护的情况下执行卡带(除了trademark tricks ),现有的 GBA 闪存卡仍然与 NDS 兼容。这使得运行 GBA 自制软件成为可能,如果你不介意错过 DS 游戏独有的所有新功能,那就可以正常运行。
一如既往,烧录卡带也能运行盗版 ROM,但由于任天堂不能改变 GBA 的保护系统(因为它有可能使现有游戏无法使用),所以只得处理这个问题。
Enhanced Slot-2 秘密地深入研究 DS BIOS 和固件后,最终发现可以把 NDS 卡的执行重定向到 GBA 插槽。尽管 NDS 卡还没有被破解,但这种方法允许暂时绕过 Slot-1 卡的安全系统,并在DS 模式下执行 Slot-2 程序 。
因此,市面上出现了一种新一代的 Slot-2 烧录卡。它们内嵌了 ARM9 代码,一旦从 Slot-1 启动就会执行。引导本身是使用发现的上述方法之一(被称为 “passthrough 方法”)实现的:
使用PassMe 卡:该方法需要一个真正的 Slot-1 卡。 PassMe 是一种处于 Slot-1 和真正的游戏卡之间的适配器。它基本上篡改了从卡片发送到游戏机的 header,以欺骗控制台执行 Slot-2 代码,并在此过程中加载 Slot-2 卡带。 使用WifiMe :该方法需要一台带有兼容 Wi-Fi 卡的 PC。修改驱动程序,然后可以广播自定义程序让 NDS 通过 Download Play 下载。[12 ] 一旦启动,它就会重定向执行到 Slot-2。这种方法利用了固件不会检查二进制文件特定区域的 RSA 签名这件事。 使用 FlashMe :一劳永逸的方案。通过连接先前的方法并桥接SL1
终端,可以运行一个自制程序,该程序可以在闪存卡存在的情况下覆盖固件以从 Slot-2 引导。 这个破解还删除了从 Download Play 引导自制程序时的数字签名检查。 如预期的那样,任天堂推出了包含新固件的新版 NDS,修补了这些漏洞。因此,黑客更多地侧重于寻找 BIOS 的漏洞(很难修补)。
Native Slot-1 著名 NDS 模拟器 “NO$GBA” 的开发者 Martin Korth 随后成功提取了 BIOS 并逆向工程了 Slot-1 的安全机制。这样一来,就可以用更新的工具和文档揭示 NDS 真实的安全机制。正如你在本文中看到的那样,加密系统只要没有被破解就可以运行。这是对称加密系统的一个局限,非对称加密系统(例如 RSA,永远不会存储私钥)就不会。
无论如何,这导致市场涌入了大量的即插即用 Slot-1 烧录卡 。这些卡可以在任何类型的掌机上原生 运行 NDS 程序。随着 “NoPass” 卡的推出,passthrough 方法也得到了改进。这些烧录卡可以在没有真正的游戏的情况下加载 Slot-2 烧录卡。
由于加密系统无法更改除法对现有的所有零售游戏造成影响,任天堂最终输掉了这场战斗。现在,他们唯一剩下的选择就是走法律途径,就像为前代游戏机所做的一样。
在我看来,与以前的游戏机的自制方法相比,烧录卡确实简单得引人注目。在之前的文章中我曾经描述过,用户必须深入迷宫般繁琐的步骤才能运行自制或盗版游戏。
就 NDS 而言,烧录卡就像零售游戏一样销售。我觉得游戏工作室看到用户轻松就能诉诸盗版,情况确实堪忧。
另一件事是,市场上出现的闪存卡品牌的数量也令人惊讶(不计所有仿冒品)。从技术角度看,烧录卡只不过是 SD 卡适配器[13 ] 。每张卡与其他卡的唯一区别是引导代码和 SD 读卡器。一些制造商还投入更多的精力设计更好的文件浏览器(称为内核 / 固件),还加入了一些额外的硬件。
结语 到这里就结束了!
我目前 用于本研究的 NDS。 实际上我的第一台很久以前就被我卖掉了...... 好奇它现在在哪。 好嘞!我觉得我已经谈到了我想说的内容的 99%……
我希望我对某些部分的批评听起来不会太苛刻。不要误会我的意思,我仍然认为这款游戏机是优秀的工程!只不过有一些缺陷让我怀疑它是否真的是一种对性价比的妥协,还是设计缺陷的一部分。说实话,这并不妨碍当时 11 岁的我想要一台 NDS。所以我觉得这对公司来说已经算是 “任务完成” 了!
还要感谢我的朋友们和 MelonDS 社区的大家抽出时间检查我的草稿并提出大量 更正。 NDS 给我一个机会让我写下之前我就感兴趣的许多话题。虽然担心我一口吃了个胖子,但希望你能够喜欢这篇文章。
下篇文章见! Rodrigo
参考文献