NDS架构简要分析

原文标题: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。ARM7TDMIGBACPU相同,但现在运行速度提高到34 MHz(原速度的两倍)。它仍然包含所有原有功能(特别是Thumb)。

然后讲讲新变化:由于任天堂的工程师将ARM7放置在大多数I/O端口旁边,这个CPU将负责调节和协助I/O操作。事实上,另一个处理器不能直接连接I/O。如你所见,ARM7不是负责整个系统的“主”处理器。它负责在多个组件之间传递数据,是用来分担一部分主CPU负载的“子处理器”。

ARM946E-S
ARM9 的结构与组成部分。

这是运行频率67 MHz“主”CPU。如果忽略掉时运不济的ARM8系列,可以认为ARM946E-SARM7“下一代”处理器。作为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加速内存传输。结合使用高速缓存,CPUDMA有并发运行的潜力。
    • 缓存和DMA可以提供很多性能,但也会造成新的问题,如数据可靠性。开发者需要手动维护内存一致性,例如,先刷新write-buffer然后再触发DMA。

NDS这硬件,也许不难发现孩子们喜欢这款游戏机的真正原因?

互连

到目前为止,我已经介绍了这两个CPU如何单独工作。但是作为一个整体运行,它们需要不断合作。为了实现这一点,两个CPU直接使用专用的FIFO单元[3]进行“对话”,该数据块包含两个64字节队列(最多16个元素),用于双向通信

FIFO单元示意图。

具体工作方式如下:发送方CPU(发送消息的CPU)将一个32位数据块放入队列中,接收方CPU可以从队列中拉取该块并对其执行所需的操作。

每当队列上写入一个值,它可以被任一CPU主动获取(轮询),但这需要不间断地检查新消息(开销会很大)。或者,也可以激活一个中断单元,以便在队列中有新消息时通知接收方。

主存储器

就像GBA一样,NDSRAM也被分散在许多不同的位置,以便根据访问速率来安排数据远近。总之,可用通用内存如下:

该掌机的RAM模型。
  • 32总线的32 KB WRAM(Work RAM):用于存储在ARM7ARM9之间共享的快速数据。
    • 请记住,同一地址一次只能由一个 CPU 访问。
  • 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”一节。

关于硬件选择的问题

当初研究GBACPU时,我对ARM7的潜力感到非常惊讶:该CPU不仅可以完成设计之内的任务,还可以协助完成其他任务,例如提供音频序列或伪3D图形。

与此相关的是,在商业化ARM7的过程中,ARM HoldingsDEC合作设计了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。

架构

图形子系统可以绘制2D3D对象。前者由二维几何图形组成,其中填充了8x8像素的位图(称为“图块”),而后者则使用顶点绘制三维对象(多边形)。

深入了解操作这些屏幕的内部芯片,我们可以观察到这个掌机具有独特的2D3D几何图形硬件。2D数据由我们熟悉的引擎 PPU(现在只称之为2D引擎)操作,而3D数据由一个全新的子系统处理。值得一提的是,虽然这并不是第一款推出3D图形功能的游戏机,但它仍然是第一款使用自研图形渲染器来呈现3D图形的游戏机。

不同图形单元的布局。

现在,这两个引擎必须连接到两个屏幕其中之一。对于只有2D图形的游戏来说,这不是问题,因为每个屏幕都有一个2D引擎。然而,那些想展示前沿特性的游戏却只有一个3D引擎可用。因此,每次只能在一个屏幕上使用3D功能。但是2D3D对象如何混合显示?别急,我先分别解释清楚这两个引擎,之后再讨论这个问题。

使用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 KBbank,这两个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:4static图层。
  • 模式1:3static图层+1affine图层。
  • 模式2:2static图层+2affine图层。
  • 模式3:3static图层+1extended affine图层。
  • 模式4:2static图层+1affine图层+1extendine affine图层。
  • 模式5:2static图层+2extended affine图层。
    • 这是最常用的模式,因为极其灵活。
  • 模式6:3D引擎渲染的1static图层+ 1large screen。
    • 由于VRAM bank的空间只够一个frame buffer,因此只有主引擎能使用该模式。
Sprites
渲染的Sprite图层.

Sprites或者说“objects”继承了GBA PPU的功能,但新增两个重要功能。

首先,OAM(存储sprite条目(sprites entries)的区域)的大小现在有2 KB,使每屏每帧可以显示多达128sprite。因此,两个引擎都分配有1 KB。

其次,OAM现在可以引用VRAM中的位图,而不仅仅使用图块和调色板。这是一种与tile系统不同的方法。实际上,同一帧中可以同时存在这两种sprite“模式”,因为此选项是在每个单独的sprite上设置的。

结果
合并所有图层……是不是少了什么?

由于每个图层都是即时渲染的,最后阶段需要合并所有内容并将其发送到所选屏幕。以前基于PPU渲染游戏机基本如此,但这是否意味着NDS也到这里结束了?

还没呢!主引擎仍然必须从另一个引擎——最强大的引擎——获取图层。

3D 加速

如果你之前玩过NDS,那么你就知道这款游戏机可以显示一定数量的3D图形。不同于GBA游戏,这些3D图形并非由CPU处理。不过,CPU-NTR包括两个组件来构建3D引擎。有趣的是,任天堂采用的设计让我想起了SGIRCP

回顾“背景模式”部分,你会注意到每个模式都至少有一个static背景,这是因为你可以用3D引擎生成的图形来填充该图层。唯一需要注意的是只有主引擎可以这样做,这也是模式6仅适用于主引擎的原因之一。

几何引擎
几何引擎的架构。

如果读过第四代或第五代游戏机的文章,你可能会想知道……SIMD处理器哪去了?这是个好问题,因为ARM9并不擅长矢量运算,而且我不认为专用除法器很够用。这就是为什么任天堂嵌入了一个叫做几何引擎的组件,它负责顶点变换投影光线裁剪剔除多边形排序,后者对于正确使用透明特性是必不可少的。

这个引擎有一些严格的限制,特别是它能够处理的多边形数量:有额外的248 KB RAM可用于存储处理过的几何体,数量可以达到2048个三角形或1706个四边形。使用多边形条带的话(而不是单个多边形)还可以存储更多。为了对这个数字有一些概念,我建议查看之前文章中的“交互模型”部分,你会发现这个限制令人担忧,但不要忘记掌机的屏幕分辨率也要小得多……所以算是抵消了一点。

无论如何,这个引擎是通过一个Command FIFO来控制的,其数据是由CPUDMA填充。该FIFO可以存储256个条目,并且它还有一个叫做PIPE的缓冲区,用于存储额外的四个命令(总共260个命令)。

渲染引擎
渲染引擎的架构。

渲染引擎负责将向量转换为像素(光栅化),着色(纹理映射)并应用光照和其他效果。它依靠透视校正Gouraud 着色分别用来处理插值纹理和光照。此外,该单元提供一些现代特性,例如fogalpha混合深度缓冲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的限制。相反,除了提供的压缩机制外,最多还有512KBVRAM可用,因此可以加载更多数据。
  • NDS模型的边缘出现了锯齿 → 与N64相比,NDS模型的分辨率较低。
  • NDS的纹理当从远处看起来会出现失真的情况 → 光栅化器使用的是定点坐标。低分辨率和mip-mapping的缺乏也加剧了走形程度。

简要的概述这就是这些。如果想要了解更专业的情况,你可能需要深入研究两个引擎,甚至反汇编这两个游戏来研究所使用的功能以及他们的用法。

交互模型

我更新了wee model查看器,添加了“最近邻插值”功能。这样你就可以使用你自己的GPU来查看NDS模型了。

请前往原网站体验交互式小部件。

新马力欧兄弟(2004) 636个三角形。
任天狗狗(2005) 750个三角形。

尽管我们讨论了图形子系统的诸多限制,但很多游戏确实充分利用了它的功能。

音频

大多数音频改进都集中在增强GBA提供的那些PCM声道上。我们之前看到过,GBA游戏最终将软件序列优先于PSG,结果非常令人印象深刻。

因此,新的音频系统总共有16PCM声道(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秒钟内),要与SNESS-SMP所提供的功能相竞争有些困难。

必须承认,第二个例子是我故意放上去的。我的意思是,究竟发生了什么?就好像这个新的编排最初就是为雅达利游戏机而设计的一样。如果你问我,我认为NDS可以处理某种形式的FMPCM重新采样,所以这个新的极简主义编排可能只是一种创造性的方法。

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)

在某一时刻,ARM7ARM9将需要初始化硬件。 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快得多。于是ARM9ARM7发送一个4比特值,在半无限循环中阻塞等待ARM7的响应。一旦ARM7响应,两者同时“越过终点线”,也就是说,它们现在同步了。

机会之窗

如果您现在拥有或者曾经拥有NDS,您可能会注意到只能在游戏机开机前插入卡带才能玩游戏。这是因为ARM7BIOS在启动时对卡带进行了一些检查(更多详情参见最后一节),如果所有测试都通过,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需要分别为ARM9ARM7编写不同的代码,在游戏中基本也是同样的情况。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。 KEY2KEY1的根本区别在于,前者使用随机值所以无法被预测。 KEY2加密使用多个异或与移位操作来混淆数据。

DS卡的校验

正如我们上面看到的那样,BIOS包括一些例程,在启动时校验NDS游戏卡。工作原理如下:

  1. 掌机检索卡带的芯片ID,将其保存在RAM上,然后启用KEY1加密。
  2. “安全区”的前2KB也被复制到RAM上。该块的前8B存储了一个名为Secure Area ID的字符串,后面的数据包含校验和(CRC16类型)与一些其他元数据。
  3. 虽然“Secure Area ID”已经用KEY1加密过了,但是还要用KEY2再次加密,之后用到时再解密。如果解密后的值与字符串encryObj相匹配,说明卡片通过了校验第一次关。此后,该字符串被销毁,防止算法泄露。如果验证失败,Secure Area2KB就会被无意义数据填满,阻止读取卡片的其他部分。
  4. 第二个测试流程为再次检索芯片ID,然后用KEY2随机加密若干次(使用内部时钟做种)。如果芯片ID的值与存储的第一个芯片ID相匹配,则第二次测试通过
  5. 最后,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

参考文献

[1]
R. Copetti, “Nintendo DS architecture - a practical analysis.” 2020. Available: https://www.copetti.org/writings/consoles/nintendo-ds/
[2]
A. Holdings, “ARM946E-s technical reference manual.” 2022. Available: https://developer.arm.com/documentation/ddi0201/d/introduction/about-the-arm946e-s-processor
[3]
C. Double, “ARM9-ARM7 FIFO.” 2005. Available: http://double.nz/nintendo_ds/nds_develop7.html
[4]
M. Korth, “Gameboy advance / nintendo DS.” 2014. Available: https://problemkaputt.de/gbatek.htm
[5]
D. Jaggar, “ARM microprocessor rise and fall.” 2020. Available: https://www.youtube.com/watch?v=%5c_6sh097Dk5k
[6]
Arisotura, “The DS GPU and its fun quirks.” 2018. Available: http://melonds.kuribo64.net/comments.php?id=56
[7]
osdl.sourceforge.net, “A guide to homebrew development for the nintendo DS.” 2008. Available: http://osdl.sourceforge.net/main/documentation/misc/nintendo-DS/homebrew-guide/HomebrewForDS.html
[8]
psiloveomega, “Booting the nintendo DS – a technical summary.” 2010. Available: https://corgids.wordpress.com/2017/07/28/booting-the-nintendo-ds-a-technical-summary/
[9]
[10]
[11]
nsmbhd.net, “IS NITRO EMULATOR listing.” 2018. Available: https://nsmbhd.net/thread/4438-nintendo-ds-dev-hardware-is-nitro-emulator-and-co/#61422
[12]
[13]
Acekard, “Acekard RPG 4.06 source code.” 2007. Available: https://gbatemp.net/threads/acekard-rpg-os-menu-4-06-and-sourcs-code-out.69656/
0条搜索结果。