+---------------------------+ | 任天堂红白机 ( NES ) 文档 | | 版本.2.00 | +---------------------------+ +------+ | 目录 | +------+ 1. 说明 A. 弃权声明 B. 为什么? C. 任务 D. 献给 E. 鸣谢 2. Acronymns A. 内部 B. 硬件 3. CPU A. 一般信息 B. 内存地址 C. 中断 D. NES 定制细节 E. 注意 4. PPU A. 概述 B. 内存映射 C. Name Tables D. Pattern Tables E. Attribute Tables F. 调色板 G. Name Table 镜像 H. 调色板镜像 I. 背景卷轴 J. 屏幕和子图形的层 K. 子图形和 SPR-RAM L. 子图形 #0 点击标记 M. 水平和竖直空白 N. $2005/2006 矩阵编码 O. PPU 怪癖 P. 注意 5. pAPU 6. 手柄, 摇杆, 扩展端口 A. 概述 B. 光线枪 C. 四人分插 D. 摇杆 E. Power Pad F. R.O.B (Robot Operated Buddy) G. 信号 H. 扩展端口 I. 注意 7. 硬件内存镜像 8. 寄存器 9. 文件格式 A. iNES 格式 (.NES) 10. 为 NES 设计程序 A. 概述 B. CPU 注意事项 C. PPU 注意事项 11. 模拟器 A. 概述 B. CPU 注意事项 C. PPU 注意事项 D. APU 注意事项 12. 引用材料 A. CPU Information B. PPU Information C. APU Information D. Memory Mapper Information E. Mailing Lists F. WWW Sites G. Hardware Information +---------+ | 1. 说明 | +---------+ A. 弃权声明 ----------- 我绝不为本文的信息所造成的结果负责. 这些都是公开的信息, 并且不应当被用于商业用途. 如果你打算将本文用于商业用途, 请在开发之前与我联系, 使我能够和你商讨你的项目的大纲. 我并没有打算在资金上阻碍任何人: 如果你打算进行真的NES开发, 与任天堂美国公司或任天堂 公司联系将是明智的. 它们的地址是: Nintendo of America Nintendo Company, Ltd. P.O. Box 957 60 Fukuine Redmond, WA 98073 Kamitakamatsu-cho, USA Higashiyama-ku, Koyoto 602, Japan All titles of cartridges and console systems are registered trademarks of their respective owners. ( 我不认为有必要把他们一个一个的单独列出来 ). B. 为什么? ---------- 在本文完成时, 只有一片概括了NES内部的文章: Marat Fayzullin 的文章, 也就是 "NES.DOC". 虽然 Fayzullin 的文章在很多地方有缺陷, 它提供了一个强大的基础, 并且它里面确实有对如 何完成那个小黑匣子有一定的陈述. 我抓住了扩展 "NES.DOC" 的机会, 是以其他人的发现和我的经验为基础. 这些经验使得这篇文 章变成它今天的样子. 本文开头部分像是 Fayzullin 的文章经过了缩写和一些修改的复制品. Marat Fayzullin 本人后来得到了我的文章, 之后他就像别人推荐这篇文章. 在我开来, 如果没有 Marat 的 "NES.DOC", 我将永远没有写这一篇的动机. C. 任务 ------- 本文的目标很简单: 提供关于NES的最准确和最新的信息, 以及 Famicom 的相关信息. D. 献给 ------- 我把本文献给 Alex Krasivsky. Alex 是一个很好的朋友, 并且在我眼里, truly started the ball of emulation rolling. 开心的时间和悲伤的时间, Alex 都在. Spasibo, Alex; umnjy Russki... E. 鸣谢 ------- 感谢所有帮助使本文成文今天的样子的人. 没有你们我将无所作为. Alex Krasivsky - bcat@lapkin.rosprint.ru Andrew Davie Avatar Z - swahlen@nfinity.com Barubary - barubary@mailexcite.com Bluefoot - danmcc@injersey.com CiXeL Chi-Wen Yang - yangfanw@ms4.hinet.net Chris Hickman - typhoonz@parodius.com D - slf05@cc.usu.edu Dan Boris - dan.boris@coat.com David de Regt - akilla@earthlink.net Donald Moore - moore@futureone.com Fredrik Olsson - flubba@hem2.passagen.se Icer Addis - bldlust@maelstrom.net Jon Merkel - jpm5974@omega.uta.edu Kevin Horton - khorton@iquest.net Loopy - zxcvzxcv@netzero.net Marat Fayzullin - fms@cs.umd.edu Mark Knibbs - mark_k@iname.com Martin Nielsen - mnielsen@get2net.dk Matt Conte - itsbroke@classicgaming.com Matthew Richey - mr6v@andrew.cmu.edu Memblers - 5010.0951@tcon.net MiKael Iushin - acc@tulatelecom.ru Mike Perry - mj-perry@uiuc.edu Morgan Johansson - morgan.johansson@mbox301.swipnet.se Neill Corlett - corlett@elwha.nrrc.ncsu.edu Pat Mccomack - splat@primenet.com Patrik Alexandersson - patrikus@hem2.passagen.se Paul Robson - AutismUK@aol.com Ryan Auge - rauge@hay.net Stumble - stumble@alpha.pulsar.net Tennessee Carmel-Veilleux - veilleux@ameth.org Thomas Steen - Thomas.Steen@no.jotankers.com Tony Young - KBAAA@aol.com Vince Indriolo - indriolo@nm.picker.com \FireBug\ - lavos999@aol.com 特别感谢 Stumble, 他通过IRC提供了无限的信息, 甚至不睡觉. +--------------+ | 2. Acronymns | +--------------+ A. 内部 ------- CPU - 中央处理器: Self-explanitory. NES使用一个标准6502 ( NMOS ) PPU - 图形处理器: 用来控制图形,活动块和其他视频相关特点 pAPU - pseuedo-Audio 处理器: 固化于CPU; 产生 (5) 声音通道的波形:: 四个 (4) 模拟 和一个 (1) 数字. 在NES内部没有处理音频的物理芯片. MMC - 大量内存控制器: 微型控制器, 用来控制使NES游戏使用6502的64Kbyte以外的存储器. 他们也可以被用来控制使用CHR-ROM,也许被用来产生“特别效果”,比如强制和中断, 以及其他一些. VRAM - 图形储存器: 这个储存器在PPU内部. NES中安装了16kbits 的VRAM. SPR-RAM - 子画面储存器: 用来储存子画面,共256 bytes. 虽然他也在PPU内部,但不是VRAM或者 ROM的一部分. PRG-ROM - 程序只读储存器: 存储程序代码的存储器. 也可以认为是通过MMC控制的扩展存储器中 的代码部分. PRG-RAM - 程序可写存储器: 于PRG-ROM同义,不过这个是RAM. CHR-ROM - 角色只读存储器: 在PPU外部的VRAM数据, 通过MMC在PPU内部与外部交换,或者在启动队 列中“读入”VRAM. VROM - 与CHR-ROM同义. SRAM - 存档可写存储器: 一般用来保存RPG游戏的进度. 就像最终幻想系列的“水井”,和“塞 尔达传说”. WRAM - 与SRAM同义. DMC - δ调制通道: APU中处理数字信号的通道. 通常被认为是PCM (Pulse信号调制器)通道. EX-RAM - 扩展存储器: 在任天堂的MMC5中使用的,允许游戏扩展VRAM的容量. B. 硬件 ------- NES - 任天堂娱乐系统: Self-explanitory. Dany - 与Famicom同义(硬件范围). Famicom - 与NES同义,但不支持原始的DMC数字音频重放. FDS - Famicom磁盘系统: 安装在Famicom顶部,支持3"双面游戏软盘. +--------+ | 3. CPU | +--------+ A. 一般信息 ----------- NES使用一个定制的NMOS 6502 CPU, 由Ricoh设计制造. 他最初的定制是添加了音频. NTSC制式的NES频率是 1.7897725MHZ, PAL的是 1.773447MHZ. B. 内存地址 ----------- +---------+-------+-------+-----------------------+ | 地址 | 大小 | 标记 | 描述 | +---------+-------+-------+-----------------------+ | $0000 | $800 | | RAM | | $0800 | $800 | M | RAM | | $1000 | $800 | M | RAM | | $1800 | $800 | M | RAM | | $2000 | 8 | | Registers | | $2008 | $1FF8 | R | Registers | | $4000 | $20 | | Registers | | $4020 | $1FDF | | Expansion ROM | | $6000 | $2000 | | SRAM | | $8000 | $4000 | | PRG-ROM | | $C000 | $4000 | | PRG-ROM | +---------+-------+-------+-----------------------+ 标记图例: M = $0000的镜像 R = $2000-2008 每 8 bytes 的镜像 (e.g. $2008=$2000, $2018=$2000, etc.) C. 中断 ------- 6502有三种 (3) 中断: IRQ/BRK, NMI和RESET. 每一种中断都有一个向量. 向量是当中断触发时“转到”的指定位置的16位地址. IRQ/BRK在两种情况下触发,因此它有分开的名字: 当软件中断请求被执行 (BRK指令), 或者硬件中断请求被执行 (通过IRQ语句). RESET在启动时被触发. ROM被读入内存,并且6502转到指定的RESET向量. 没有寄存器被 修改,没有内存被清空; 这些仅仅发生在启动时. NMI的意思是 Non-Maskable Interrupt (不可屏蔽中断--译注),发生在每次刷新时 (VBlank). 这些刷新的间隔依赖于所用的系统 (PAL/NTSC). NMI在NTSC控制下刷新次数为 60次/秒, PAL为50次/秒. 6502的中断潜伏期为七 (7) 个周 期; 这也就是说需要需要七 (7) 个周期来移入和移出一个中断. 大多数中断应当返回RTI语句. 一些NES游戏不使用这种方法,比如SquareSoft的"Final Fa- ntasy 1"的标题. 他们的中断使用一种非常奇怪的方式返回: 通过手动操纵堆栈,然后返 回一个RTS. 从技术上说这是有效的,但从精神上说这应当被避免. 上述的中断使用下列的向量地址,它们在内存中的地址为: $FFFA = NMI $FFFC = RESET $FFFE - IRQ/BRK 中断优先权如下: Highest = RESET NMI Lowest = IRQ/BRK D. NES订制细节 -------------- NES的6502并不包括对decimal模式的支持. CLD和SED opcodes function normally, 但是p中 的 'd' bit在ADC和SBC中并未被使用. 在游戏中将CLD先执行于代码是普遍的行为,就像启动 和RESET时的 'd' 状态并不为人知一样. 音频寄存器被放置于CPU内部; 所有波形的发生也都在CPU的内部. E. 注意 ------- 请注意那两个独立的16K PRG-ROM片断; 他们也许是线性的,但是他们是否独立依赖于游戏容 量的大小. 有些游戏只用一 (1) 个16K的PRG-ROM,这就需要将他读入到$C000和$8000之中 (两块内存都被写入--译注). 大多数游戏将他们自己读入到$8000, 使用32K PRG-ROM 空间. 第一个这样做的游戏是Super Mario Brothers. 然而所有大于一 (1) 个 16K 容量的PRG-ROM也都把他们自己读入$8000. 这些游戏使用Memory Mappers在PRG-ROM内外交换数据,CHR-ROM中也一样. 当遇到BRK时,NES的6502将CPU状态标记推入有 'b' CPU bit 集合的堆栈中. 在IRQ或者NMI时, CPU将状态标记推入清除了 'b' bit的堆栈中. 这样做是因为事实上硬件IRQ (IRQ) 和软件IRQ (BRK) 使用同样的向量. 例如,一个中断会使用下面的代码来区别上述两种中断: C134: PLA ; 拷贝CPU状态标记 C135: PHA ; 将状态标记返回到堆栈 C136: AND #$10 ; 检查 D4 ('b' CPU bit) C138: BNE is_BRK_opcode ; 如果固定了,则它是一个软件IRQ (BRK) 执行NMI中的BRK将导致被推入的 'b' bit被固定. 6502在opcode $6C 有一个bug (绝对的间接跳跃). CPU并没有正确考虑到当 低字节 [low-byte] 是$FF时的有效地址. 例如: C100: 4F C1FF: 00 C200: 23 .. D000: 6C FF C1 - JMP ($C1FF) 逻辑上说,这样将转到地址$2300. 然而,事实上计算中的高字节 *NOT* increased on a page- wrap, 事实上转到了$4F00. 应当被注意的是page wrappig并不发生在 间接地址索引模式 [indexed-indirect addressing modes].由于 0页面 [zero-page],所有 间接索引 [indexed-indirect] 的读写必须在计算后申请 一个到有效地址的一个逻辑 与 [AND] #$FF. 例如: C000: LDX #3 ; 从 $0002+$0003 读间接地址 [indirect address], C002: LDA ($FF,X) ; 不是 $0102+$0103. +--------+ | 4. PPU | +--------+ A. 概述 ------- 镜像 (也被称为"shadowing") 是将特殊的地址或抵制范围通过硬件映射到其他地址的一种处理. B. 内存映射 ----------- 这里有两 (2) 片内存映射. 第一部分被称为 "RAM Memory Map",是一段不算长的指向NES本身 物理RAM的区域。第二部分是 "Programmer Memory Map",是比较长的描述全部NES和他如何被使 用以及如何被操作的内存区域. RAM Memory Map +---------+-------+--------------------+ | Address | Size | Description | +---------+-------+--------------------+ | $0000 | $1000 | Pattern Table #0 | | $1000 | $1000 | Pattern Table #1 | | $2000 | $800 | Name Tables | | $3F00 | $20 | Palettes | +---------+-------+--------------------+ Programmer Memory Map +---------+-------+-------+--------------------+ | Address | Size | Flags | Description | +---------+-------+-------+--------------------+ | $0000 | $1000 | C | Pattern Table #0 | | $1000 | $1000 | C | Pattern Table #1 | | $2000 | $3C0 | | Name Table #0 | | $23C0 | $40 | N | Attribute Table #0 | | $2400 | $3C0 | N | Name Table #1 | | $27C0 | $40 | N | Attribute Table #1 | | $2800 | $3C0 | N | Name Table #2 | | $2BC0 | $40 | N | Attribute Table #2 | | $2C00 | $3C0 | N | Name Table #3 | | $2FC0 | $40 | N | Attribute Table #3 | | $3000 | $F00 | R | | | $3F00 | $10 | | Image Palette #1 | | $3F10 | $10 | | Sprite Palette #1 | | $3F20 | $E0 | P | | | $4000 | $C000 | F | | +---------+-------+-------+--------------------+ C = Possibly CHR-ROM N = Mirrored (see Subsection G) P = Mirrored (see Subsection H) R = Mirror of $2000-2EFF (VRAM) F = Mirror of $0000-3FFF (VRAM) C. Name Tables -------------- NES使用马赛克矩阵进行图形显示; 这样的格子被叫做 Name Table. 马赛克是 8x8像素 [pixels]. 完整的 Name Table 有 32*30 个马赛克 (256*240 像素). 紧记: NTSC和PAL单元在显示上有不同 的刷新率. Name Tables 之中的马赛克的资料被保存在 Pattern Table 之中 (continue on). D. Pattern Tables ----------------- Pattern Table 包括了 Name Table 所需要的 8x8 的马赛克. 他保存了NES调色板中所有16色的 4-bit 颜色矩阵的低两 (2) 位 [bit]. 例如: VRAM Contents of Colour Addr Pattern Table Result ------ --------------- -------- $0000: %00010000 = $10 --+ ...1.... Periods are used to .. %00000000 = $00 | ..2.2... represent colour 0. .. %01000100 = $44 | .3...3.. Numbers represent .. %00000000 = $00 +-- Bit 0 2.....2. the actual palette .. %11111110 = $FE | 1111111. colour #. .. %00000000 = $00 | 2.....2. .. %10000010 = $82 | 3.....3. $0007: %00000000 = $00 --+ ........ $0008: %00000000 = $00 --+ .. %00101000 = $28 | .. %01000100 = $44 | .. %10000010 = $82 +-- Bit 1 .. %00000000 = $00 | .. %10000010 = $82 | .. %10000010 = $82 | $000F: %00000000 = $00 --+ 上面的 Pattern Table 的结果就是字符 'A',就像右上角的 "Colour Result" 部分显示的. E. Attribute Tables ------------------- Attribute Table 的每一个字节都描述了显示器上的一个 4*4 马赛克组. 有许多种方法来描述 Attribute Table 中一 (1) 个字节的函数是怎样的: * 保存了 32*32 像素格子 中每 16*16 像素 的高两 (2) 位. * 保存了 十六 (16) 个 8x8 马赛克中的 高两 (2) 位. * 保存了 四 (4) 个 4*4 像素格子 的高两 (2) 位. 这样说确实很乱; 下面的两个图表将有所帮助: +------------+------------+ | Square 0 | Square 1 | #0-F represents an 8x8 tile | #0 #1 | #4 #5 | | #2 #3 | #6 #7 | Square [x] represents four (4) 8x8 tiles +------------+------------+ (i.e. a 16x16 pixel grid) | Square 2 | Square 3 | | #8 #9 | #C #D | | #A #B | #E #F | +------------+------------+ 真正的 attribute byte 格式如下 (与上面的相比较): Attribute Byte (Square #) ---------------- 33221100 ||||||+--- Upper two (2) colour bits for Square 0 (Tiles #0,1,2,3) ||||+----- Upper two (2) colour bits for Square 1 (Tiles #4,5,6,7) ||+------- Upper two (2) colour bits for Square 2 (Tiles #8,9,A,B) +--------- Upper two (2) colour bits for Square 3 (Tiles #C,D,E,F) F. 调色板 --------- NES有两个 16色 "调色板": 图形调色板和子图形调色板. 因为他们不储存物理RGB值,所以 比起一个真正的调色板,它们更像 "查找表格". 写到 $3F00-3FFF 的 D7-D6 字节将被忽略. G. Name Table 镜像 ------------------ 需要紧记的一点是在理解NES的时候,有许多镜像表格. 即使在使用 CHR-ROM-mapped Name Tables (mapper-specific). NES本身只有 2048 ($800) 字节的RAM给 Name Tables. 然而,就像在 Subsection B 中所表现 得那样 NES 有升至 四 (4) 个 Name Tables 的地址容量. 缺省的是许多 carts 伴随的是 "水平" 和 "垂直" 的镜像,允许你修改 Name Tables 所指向的NES PPU RAM 的位置. 这个镜像表格同时作用于两 (2) 个Name Tables; 你不可以独自选择 Name Tables. 下面的表格将帮助理解NES中遇到的所有镜像类型. 请注意显示的地址 (大小为 12-bit) 都是 NES PPU RAM 的 Name Table 的一部分; 有人为认为这些与VRAM区域中的 "$2xxx" 同义: Name NT#0 NT#1 NT#2 NT#3 Flags +--------------------------+------+------+------+------+-------+ | Horizontal | $000 | $000 | $400 | $400 | | | Vertical | $000 | $400 | $000 | $400 | | | Four-screen | $000 | $400 | $800 | $C00 | F | | Single-screen | | | | | S | | CHR-ROM mirroring | | | | | C | +--------------------------+------+------+------+------+-------+ F = 依赖于扩展的 2048 ($800) RAM (kept on the cart) 的四屏镜像,导致四 (4) 个独立的物理 Name Tables. S = 拥有映射表 [mapper] 的单屏游戏,允许你选择哪个PPU RAM区域来使用 ($000, $400, $800, $C00); 所有的 NT 都指向同样的 PPU RAM 地址. C = 映射表 #68 (Afterburner 2),允许你将 CHR-ROM 镜像到 NES 的 PPU RAM 区域中 Name Tables 区域. 很自然的这使得 Name Table 成为 ROM 基础,并且不能对它写入. 然而,这个特点可以通 过映射表本身进行控制,使得你打开或关闭这个特点. H. 调色板镜像 ------------- 镜像发生在图形调色板和子图形调色板之间. 任何写入 $3F00 的数据都被镜像到 $3F10. 任何写入 $3F04 的数据都被镜像到 $3F14,如此. 如此... 在图形调色板和子图形调色板的高三 (3) 色 Colour #0 被定义为透明 (实际上那里储存的颜色不被显示). PPU 使用 $3F00 来定义背景色. 另一个长一些的解释,如下: * $0D 被写入 $3F00 (镜像到 $3F00) * $03 被写入 $3F08 (镜像到 $3F08) * $1A 被写入 $3F18 * $3F08 被读入 累加器 [accumulator] PPU 使用 $0D 作为背景色,不论 $3F08 饱含了 $03 的值 (因为所有的调色板中的 colour #0 都被定义为 透明色,它不被显示). 最后,累加器将保存 $1A 的值,它是 $3F18 的镜像. 然后 $1A 不被显示,因为 colour #0 为透明色. 图形和子图形调色板都被镜像到其他的VRAM区域; $3F20-3FFF 分别是他们的镜像. 被写入 $3F00-3FFF 的 D7-D6 字节被忽略. I. 背景卷轴 ----------- NES能够独立于在背景之上的字画面来卷动背景 (pre-rendered Name Table + Pattern Table + Attribute Table). 背景能被水平和竖直卷动. 卷轴工作如下: Horizontal Scrolling Vertical Scrolling 0 512 +-----+-----+ +-----+ 0 | | | | | | A | B | | A | | | | | | +-----+-----+ +-----+ | | | B | | | +-----+ 480 Name Table "A" 是通过在寄存器 $2000 中的 Bits D1-D0 来指定的,"B" 接下来的 Name Table (由于镜 像,这是动态的). 它在同时使用水平和竖直卷轴的游戏时不工作. 背景将跨越多个 Name Table,就像这里展示的: +---------------+---------------+ | Name Table #2 | Name Table #3 | | ($2800) | ($2C00) | +---------------+---------------+ | Name Table #0 | Name Table #1 | | ($2000) | ($2400) | +---------------+---------------+ 写到水平卷轴地址为 $2005 的值得范围是 0 至 256. 写到垂直卷轴的值是 0-239; 超过 239 的值时不被考虑的 (例如: 248 被认为是 -8). J. 屏幕和子图形的层 ------------------- 在NES显示的时候使用的是一种特殊的规则: FRONT BACK +----+-----------+----+-----------+-----+ | CI | OBJs 0-63 | BG | OBJs 0-63 | EXT | +----+-----------+----+-----------+-----+ | SPR-RAM | | SPR-RAM | | BGPRI==0 | | BGPRI==1 | +-----------+ +-----------+ CI 的意思是 'Colour Intensity' [颜色亮度], 与 $2001 的 D7-D5 等价. BG 是 背景 [BackGround],EXT 是 扩展端口视频信号 [EXTension port video signal]. 'BGPRI' 描述的是 SPR-RAM 中的 'Background Priority' [背景优先权] bit,在 per-sprite 基础上 (D5, Byte 2). OBJ 数目描述真正的子图形数目,不是 马赛克索引值 [Title Index values]. FRONT 被认为是在其他所有层上被看到的 (最后绘制),BACK 被认为是其他所有层之下的 (最先绘制). K. 子图形和 SPR-RAM ------------------- NES支持64个子图形. 每个子画面的大小可以是 8x8 或者 8x16 像素. 子画面数据被保存在 VRAM 的 Partt- ern Table 区域. 子画面的特征,比如 flipping 和 优先权,被保存在 SPR-RAM. SPR-RAM 的格式如下: +-----------+-----------+-----+------------+ | Sprite #0 | Sprite #1 | ... | Sprite #63 | +-+------+--+-----------+-----+------------+ | | +------+----------+--------------------------------------+ + Byte | Bits | Description | +------+----------+--------------------------------------+ | 0 | YYYYYYYY | Y Coordinate - 1. Consider the coor- | | | | dinate the upper-left corner of the | | | | sprite itself. | | 1 | IIIIIIII | Tile Index # | | 2 | vhp000cc | Attributes | | | | v = Vertical Flip (1=Flip) | | | | h = Horizontal Flip (1=Flip) | | | | p = Background Priority | | | | 0 = In front | | | | 1 = Behind | | | | c = Upper two (2) bits of colour | | 3 | XXXXXXXX | X Coordinate (upper-left corner) | +------+----------+--------------------------------------+ Tile Index # 被获得的方法与 Name Table 数据一样. 大小为 8x16 的子画面函数有些不同. 一个有偶数 Tile Index # 的 8x16 子画面使用在 VRAM 中 $2000 的 Pattern Table; 奇数 Tile Index #s 使用 $1000. *注意*: 寄存器 $2000 对 8x16子画面无效. 所有 64 个子图形都包括一个内部优先权; 子画面 #0 的优先权比 #63高 (子画面 #0 应当被最后绘制, etc...). 只有八 (8) 个子图形可以被显示在同一个扫描线 [scan-line] 上. 每个 SPR-RAM 入口都被检测来知道他 是不是与其他的子图形在同一水平线上. 记得,这是在每个扫描线的基础上被执行的,不是在每个子画面 的基础上 (例如,做256次,而不是 256/8 或者 256/16次). (注意: 在一个真正的NES单元,如果子画面被关闭了很长一段时间 ($2001的D4是0),SPR-RAM 将被降低. 一 个被建议的观点是 SPR-RAM 是一个真正的 DRAM,D4 控制这个 DRAM 的刷新周期). L. 子图形 #0 点击标记 --------------------- PPU有能力演算出子图形 #0的位置,并且把它的发现储存到 $2002 的 D6. 工作的方式如下: PPU扫描第一个真正的不透明 "子图形像素" 和第一个不透明 "背景像素". 背景像素是被Name Table使用中 的马赛克. 记得colour #0 被定义为透明. 导致 D6 被设置为 *IS* 的像素被绘制. 下面的例子也许有所帮助. 下面是两个马赛克. 透明色 (colour #0) 被通过下划线字符 ('_') 定义. 一个 星号 ('*') 在 D6 被设置时描述. Sprite BG Result ------ -- ------ __1111__ ________ __1111__ _111111_ _______2 _1111112 11222211 ______21 11222211 112__211 + _____211 = 112__*11 '*' will be drawn as colour #2 112__211 ____2111 112_2211 11222211 ___21111 11222211 _111111_ __211111 _1111111 __1111__ _2111111 _2111111 这同样适用于那些在 BG 下面 (通过 'Background Priority' SPR-RAM bit),可是上面的例子应当为 'BG+Sprite'. 同样,D6在每个VBlank之后清空 (设置为0). M. 水平和竖直空白 ----------------- 就像其他的控制台,NES有一个更新: 显示设备重新定位电子枪来显示可视数据. 最普通的显示设备是电视机. NTSC 设备每秒钟刷新60次,PAL每秒50次. 电子枪绘制像素时从左向右: 这种过程导致一 (1) 条水平扫描线被绘制. 在电子枪绘制完成一条完整的扫描线后,电 子枪必须回到显示设备的左边,准备好去绘制下一条扫描线. 电子枪回到左边的过程叫做 水平空白期 [Horizontal Blank period] (HBlank). 当电子枪完成绘制所有扫描线,它必须回到显示设备的最上端; 电子枪复位回到显示设备的最上端的时间叫做 竖直空 白期 [Vertial Blank period] (VBlank). 就像你能从下面的图表中看到的,电子枪或多或少的工作在 'Z' 字形轨迹直到到达 VBlank,然后过程重复: +-----------+ +--->|***********| <-- Scanline 0 | | ___---~~~ | <-- HBlank V |***********| <-- Scanline 1 B | ___---~~~ | <-- HBlank l | ... | ... a | ... | ... n |***********| <-- Scanline 239 k +-----+-----+ | | +--VBlank--+ NTSC的NES是下面的刷新方式和屏幕格式: +--------+ 0 ----+ | | | | | | | Screen | +-- (0-239) 256x240 on-screen results | | | | | | +--------+ 240 --+ | ?? | +-- (240-242) Unknown +--------+ 243 --+ | | | | VBlank | +-- (243-262) VBlank | | | +--------+ 262 --+ 竖直空白 (VBlank) 标记被包括在 $2002 的 D7中. 它指出PPU是否处于VBlank. 一段程序可以通过读 $2002 重置D7. N. $2005/2006 矩阵编码 ---------------------- 有关于 $2005 和 $2006 寄存器的详细信息,参考 Loopy 的 $2005/2006 文档. 他的文档提供了的关于这些寄存器 如何工作的完整准确的信息. 联系 Loopy 获得更多信息. O. PPU 怪癖 ----------- 第一次读VRAM是无效的. 由于这种现象,NES返回 pseudo 缓冲值,而不是期待的线性值. 看下面的例子: VRAM $2000 contains $AA $BB $CC $DD. VRAM incrementation value is 1. The result of execution is printed in the comment field. LDA #$20 STA $2006 LDA #$00 STA $2006 ; VRAM address now set at $2000 LDA $2007 ; A=?? VRAM Buffer=$AA LDA $2007 ; A=$AA VRAM Buffer=$BB LDA $2007 ; A=$BB VRAM Buffer=$CC LDA #$20 STA $2006 LDA #$00 STA $2006 ; VRAM address now set at $2000 LDA $2007 ; A=$CC VRAM Buffer=$AA LDA $2007 ; A=$AA VRAM Buffer=$BB 就像演示的,PPU将在第一次读入执行后传递增加他的内部地址. 这 *仅适用* 于 VRAM $0000-3FFF (例如: 调色板信 息和他们各自的镜像不必忍受这种现象). P. 注意 ------- PPU 将会在访问 $2007 后自动增加VRAM地址,加1或者32 (基于 $2000 的 D2). +---------+ | 5. pAPU | +---------+ To be written. 现在的信息都不准确或不正确. 没有人现在有关于声音的100%准确的信息. 这一部分将在有人反编译 NES的pAPU引擎并且提供给我信息 (或者信息的引用) 之后完成. +-------------------------+ | 6. 手柄, 摇杆, 扩展端口 | +-------------------------+ A. 一般信息 ----------- NES支持无数的输入设备,包括手柄, 光线枪 (light guns),和四人娱乐设备 (四手柄分插). 手柄 #1 和 #2 通过 $4016 和 $4017 分别访问. 手柄通过strobing-method重置: 写入1,然后0 到 $4016. 阅读Subsection H以获得关于 "half-strobing" 信息. 在一个完整的 strobe,手柄按键状态将会被作为一个单位流 [single-bit stream] (D0) 返回. 多重读取器需要 读取关于控制器的所有信息. 1 = A 9 = Ignored 17 = +--+ 2 = B 10 = Ignored 18 = +-- Signature 3 = SELECT 11 = Ignored 19 = | 4 = START 12 = Ignored 20 = +--+ 5 = UP 13 = Ignored 21 = 0 6 = DOWN 14 = Ignored 22 = 0 7 = LEFT 15 = Ignored 23 = 0 8 = RIGHT 16 = Ignored 24 = 0 阅读 Subsection G 以获得关于信号的信息. B. 光线枪 --------- 光线枪简单的使用 $4017 和 $4017 中的位,在 Section 8 中有表述. 阅读 D4, D3 和 D0. 可以将两把光线枪同时接到两个手柄接口上. C. 四人分插 ----------- NES支持四人游戏适配器,来讲两 (2) 个手柄扩展至四 (4) 个. 使用四人分插的游戏有 Tengen 的 "Gauntlet II", 和 Nintendo 的 "RC Pro Am 2". 全部四 (4) 个控制器通过 $4016 或者 $4017 的 D0 读入状态,Subsection A 中有描述. 对于寄存器 $4016,地址 #1-8 为手柄 #1,地址 #9-16 为手柄 #3. 对于寄存器 $4017,为手柄 #2 和 #4. 下面是地址 #s 的列表和他们的结果. 1 = A 9 = A 17 = +--+ 2 = B 10 = B 18 = +-- Signature 3 = SELECT 11 = SELECT 19 = | 4 = START 12 = START 20 = +--+ 5 = UP 13 = UP 21 = 0 6 = DOWN 14 = DOWN 22 = 0 7 = LEFT 15 = LEFT 23 = 0 8 = RIGHT 16 = RIGHT 24 = 0 阅读 Subsection G 以获得关于信号的信息. D. 摇杆 ------- Taito 的 "Arkanoid" 使用摇杆作为首选控制器. 摇杆的位置是通过 $4017 的 D1 读取; 读取的数据是相反的 (0=1, 1=0). 第一个读取的值是 MSB,第八个读取值是 (明显的) LSB. 有效值的范围是 98 至 242,98描述的是摇杆被完全逆时针转动. 例如,如果读入 %01101011, 值将被按位反,变成 %10010100 也就是 146. 摇杆也有一个按键,地址是 $4016的 D1. 值 1 表明键被按下. E. Power Pad ------------ 目前无可用信息. F. R.O.B (Robot Operated Buddy) ------------------------------- 目前无可用信息. G. 信号 ------- 信号允许程序员探测设备是否被连接到四 (4) 个端口,或者没有连上,如果连上了,设备是哪种类型. 正确的/已知 的信号有: %0000 = Disconnected %0001 = Joypad ($4016 only) %0010 = Joypad ($4017 only) H. 扩展端口 ----------- 手柄门处理需要双重书写: 1,然后0. 如果门处理并不完整,或者发生了不标准顺序,手柄就不再是通讯工具: 是扩 展端口. 对于NES用户,扩展端口在游戏机的底部被一块灰色小塑料覆盖. Famicom 用户在游戏机的前部有一个有限的扩展端口. 通常被用来接手柄或者快速手柄 [turbo-joypads]. 这样的一个通讯的例子应当是下面的代码: LDA #%00000001 STA $4016 STA $4017 ; Begin read mode of expansion port LDA #%00000011 ; Write %110 to the expansion port STA $4016 我从来没有遇到过使用这种方法交流的例子 [cart]. I. 注意 ------- 无. +-----------------+ | 7. 硬件内存镜像 | +-----------------+ 虽然有大量的镜像被使用 (超过64), 本文档中0.53版的 "MMC" 章节依旧不固定,现在被删除了. 所有的并不仅仅是损失,另一个由 \FireBug\ 写的文档 Vertigo 2099 包括几乎所有的存在的镜像的准确信息. 你可 以通过下面的URL获得一份拷贝: http://free.prohosting.com/~nintendo/mappers.nfo 请注意,我对上述文档中包含的信息没有任何责任. 联系 lavos999@aol.com 以获得更多信息. +-----------+ | 8. 寄存器 | +-----------+ 程序员通过寄存器与PPU和pAPU通讯,并不比通过允许代码修改NES的内存预设置多些什么. 如果没有寄存器,程序就 无法工作: 时代. 每个寄存器都有16位地址. 每个仅存期都在他有描述之后很快在 parentheses 中有一个静态域. 例如: R = Readable W = Writable 2 = Double-write register 16 = 16-bit register 注意: 16位寄存器实际上包含了两个线性8位寄存器,能够被 *独立* 指派. The reason for specifying them as 16- bit is for ease of documentation. 例如,"$4002+$4003" 意思是 D15-D8 应当在 $4003 中, D7-D0 应当在 $4002 之中. 注意: 表中未被列出的位认为未被使用. +---------+----------------------------------------------------------+ | Address | Description | +---------+----------------------------------------------------------+ | $2000 | PPU Control Register #1 (W) | | | | | | D7: Execute NMI on VBlank | | | 0 = Disabled | | | 1 = Enabled | | | D6: PPU Master/Slave Selection --+ | | | 0 = Master +-- UNUSED | | | 1 = Slave --+ | | | D5: Sprite Size | | | 0 = 8x8 | | | 1 = 8x16 | | | D4: Background Pattern Table Address | | | 0 = $0000 (VRAM) | | | 1 = $1000 (VRAM) | | | D3: Sprite Pattern Table Address | | | 0 = $0000 (VRAM) | | | 1 = $1000 (VRAM) | | | D2: PPU Address Increment | | | 0 = Increment by 1 | | | 1 = Increment by 32 | | | D1-D0: Name Table Address | | | 00 = $2000 (VRAM) | | | 01 = $2400 (VRAM) | | | 10 = $2800 (VRAM) | | | 11 = $2C00 (VRAM) | +---------+----------------------------------------------------------+ | $2001 | PPU Control Register #2 (W) | | | | | | D7-D5: Full Background Colour (when D0 == 1) | | | 000 = None +------------+ | | | 001 = Green | NOTE: Do not use more | | | 010 = Blue | than one type | | | 100 = Red +------------+ | | | D7-D5: Colour Intensity (when D0 == 0) | | | 000 = None +--+ | | | 001 = Intensify green | NOTE: Do not use more | | | 010 = Intensify blue | than one type | | | 100 = Intensify red +--+ | | | D4: Sprite Visibility | | | 0 = Sprites not displayed | | | 1 = Sprites visible | | | D3: Background Visibility | | | 0 = Background not displayed | | | 1 = Background visible | | | D2: Sprite Clipping | | | 0 = Sprites invisible in left 8-pixel column | | | 1 = No clipping | | | D1: Background Clipping | | | 0 = BG invisible in left 8-pixel column | | | 1 = No clipping | | | D0: Display Type | | | 0 = Colour display | | | 1 = Monochrome display | +---------+----------------------------------------------------------+ | $2002 | PPU Status Register (R) | | | | | | D7: VBlank Occurance | | | 0 = Not occuring | | | 1 = In VBlank | | | D6: Sprite #0 Occurance | | | 0 = Sprite #0 not found | | | 1 = PPU has hit Sprite #0 | | | D5: Scanline Sprite Count | | | 0 = Eight (8) sprites or less on current scan- | | | line | | | 1 = More than 8 sprites on current scanline | | | D4: VRAM Write Flag | | | 0 = Writes to VRAM are respected | | | 1 = Writes to VRAM are ignored | | | | | | NOTE: D7 is set to 0 after read occurs. | | | NOTE: After a read occurs, $2005 is reset, hence the | | | next write to $2005 will be Horizontal. | | | NOTE: After a read occurs, $2006 is reset, hence the | | | next write to $2006 will be the high byte portion. | | | | | | For detailed information regarding D6, see Section 4, | | | Subsection L. | +---------+----------------------------------------------------------+ | $2003 | SPR-RAM Address Register (W) | | | | | | D7-D0: 8-bit address in SPR-RAM to access via $2004. | +---------+----------------------------------------------------------+ | $2004 | SPR-RAM I/O Register (W) | | | | | | D7-D0: 8-bit data written to SPR-RAM. | +---------+----------------------------------------------------------+ | $2005 | VRAM Address Register #1 (W2) | | | | | | Commonly used used to "pan/scroll" the screen (sprites | | | excluded) horizontally and vertically. However, there | | | is no actual panning hardware inside the NES. This | | | register controls VRAM addressing lines. | | | | | | Refer to Section 4, Subsection N, for more information. | +---------+----------------------------------------------------------+ | $2006 | VRAM Address Register #2 (W2) | | | | | | Commonly used to specify the 16-bit address in VRAM to | | | access via $2007. However, this register controls VRAM | | | addressing bits, and therefore should be used with | | | knowledge of how it works, and when it works. | | | | | | Refer to Section 4, Subsection N, for more information. | +---------+----------------------------------------------------------+ | $2007 | VRAM I/O Register (RW) | | | | | | D7-D0: 8-bit data read/written from/to VRAM. | +---------+----------------------------------------------------------+ | $4000 | pAPU Pulse #1 Control Register (W) | | $4001 | pAPU Pulse #1 Ramp Control Register (W) | | $4002 | pAPU Pulse #1 Fine Tune (FT) Register (W) | | $4003 | pAPU Pulse #1 Coarse Tune (CT) Register (W) | | $4004 | pAPU Pulse #2 Control Register (W) | | $4005 | pAPU Pulse #2 Ramp Control Register (W) | | $4006 | pAPU Pulse #2 Fine Tune Register (W) | | $4007 | pAPU Pulse #2 Coarse Tune Register (W) | | $4008 | pAPU Triangle Control Register #1 (W) | | $4009 | pAPU Triangle Control Register #2 (?) | | $400A | pAPU Triangle Frequency Register #1 (W) | | $400B | pAPU Triangle Frequency Register #2 (W) | | $400C | pAPU Noise Control Register #1 (W) | | $400D | Unused (???) | | $400E | pAPU Noise Frequency Register #1 (W) | | $400F | pAPU Noise Frequency Register #2 (W) | | $4010 | pAPU Delta Modulation Control Register (W) | | $4011 | pAPU Delta Modulation D/A Register (W) | | $4012 | pAPU Delta Modulation Address Register (W) | | $4013 | pAPU Delta Modulation Data Length Register (W) | +---------+----------------------------------------------------------+ | $4014 | Sprite DMA Register (W) | | | | | | Transfers 256 bytes of memory into SPR-RAM. The address | | | read from is $100*N, where N is the value written. | +---------+----------------------------------------------------------+ | $4015 | pAPU Sound/Vertical Clock Signal Register (R) | | | | | | D6: Vertical Clock Signal IRQ Availability | | | 0 = One (1) frame occuring, hence IRQ cannot | | | occur | | | 1 = One (1) frame is being interrupted via IRQ | | | D4: Delta Modulation | | | D3: Noise | | | D2: Triangle | | | D1: Pulse #2 | | | D0: Pulse #1 | | | 0 = Not in use | | | 1 = In use | | +----------------------------------------------------------+ | | pAPU Channel Control (W) | | | | | | D4: Delta Modulation | | | D3: Noise | | | D2: Triangle | | | D1: Pulse #2 | | | D0: Pulse #1 | | | 0 = Channel disabled | | | 1 = Channel enabled | +---------+----------------------------------------------------------+ | $4016 | Joypad #1 (RW) | | | | | | READING: | | | D4: Zapper Trigger | | | 0 = Pulled | | | 1 = Released (not held) | | | D3: Zapper Sprite Detection | | | 0 = Sprite not in position | | | 1 = Sprite in front of cross-hair | | | D0: Joypad Data | | +----------------------------------------------------------+ | | WRITING: | | | Joypad Strobe (W) | | | | | | D0: Joypad Strobe | | | 0 = Clear joypad strobe | | | 1 = Reset joypad strobe | | +----------------------------------------------------------+ | | WRITING: | | | Expansion Port Latch (W) | | | | | | D0: Expansion Port Method | | | 0 = Write | | | 1 = Read | +---------+----------------------------------------------------------+ | $4017 | Joypad #2/SOFTCLK (RW) | | | | | | READING: | | | D7: Vertical Clock Signal (External) | | | 0 = Not occuring | | | 1 = Occuring | | | D6: Vertical Clock Signal (Internal) | | | 0 = Occuring (D6 of $4016 affected) | | | 1 = Not occuring (D6 of $4016 untouchable) | | | D4: Zapper Trigger | | | 0 = Pulled | | | 1 = Released (not held) | | | D3: Zapper Sprite Detection | | | 0 = Sprite not in position | | | 1 = Sprite in front of cross-hair | | | D0: Joypad Data | | +----------------------------------------------------------+ | | WRITING: | | | Expansion Port Latch (W) | | | | | | D0: Expansion Port Method | | | 0 = ??? | | | 1 = Read | +---------+----------------------------------------------------------+ +-------------+ | 9. 文件格式 | +-------------+ A. iNES 格式 (.NES) ------------------- +--------+------+------------------------------------------+ | Offset | Size | Content(s) | +--------+------+------------------------------------------+ | 0 | 3 | 'NES' | | 3 | 1 | $1A | | 4 | 1 | 16K PRG-ROM page count | | 5 | 1 | 8K CHR-ROM page count | | 6 | 1 | ROM Control Byte #1 | | | | %####vTsM | | | | | ||||+- 0=Horizontal mirroring | | | | | |||| 1=Vertical mirroring | | | | | |||+-- 1=SRAM enabled | | | | | ||+--- 1=512-byte trainer present | | | | | |+---- 1=Four-screen mirroring | | | | | | | | | | +--+----- Mapper # (lower 4-bits) | | 7 | 1 | ROM Control Byte #2 | | | | %####0000 | | | | | | | | | | +--+----- Mapper # (upper 4-bits) | | 8-15 | 8 | $00 | | 16-.. | | Actual 16K PRG-ROM pages (in linear | | ... | | order). If a trainer exists, it precedes | | ... | | the first PRG-ROM page. | | ..-EOF | | CHR-ROM pages (in ascending order). | +--------+------+------------------------------------------+ +-------------------+ | 10. 为NES设计程序 | +-------------------+ A. 一般信息 ----------- 无. B. CPU 注意事项 --------------- 无. 阅读 Section 11, Subsection B 以获得更多信息. C. PPU 注意事项 --------------- 读写 VRAM 由一组处理过程组成: Writing to VRAM Reading from VRAM --------------- ----------------- 1) Wait for VBlank 1) Wait for VBlank 2) Write upper VRAM address 2) Write upper VRAM address byte into $2006 byte into $2006 3) Write lower VRAM address 3) Write lower VRAM address byte into $2006 byte into $2006 4) Write data to $2007 4) Read $2007 (invalid data once) 5) Read data from $2007 注意: 读VRAM的步骤 #4 只有在 读VRAM数据不再 $3F00-3FFF 范围时才必要. 注意: 访问 VRAM 只有在 VBlank 时才被执行. 尝试不在 VBlank 中访问 VRAM 将导致屏幕上出现垃圾. 阅读 Section 4, Subsection N 以获得关于这样的现象发生的信息. 等待 VBlank 是很简单的: 8000 : LDA $2002 BPL $8000 读 $2002 将导致所有位被返回; 然而, 在读执行后,D7将被重置为0. 就像前面说过的一样,事实上NES在屏幕上显示的调色板不是RGB. 然而,近似精确的代替品可以在通常的NES模拟器中找 到. 联系合适的模拟器作者以获得一个正确的RGB调色板. 确保不经常地通过 $2006 清空内部 VRAM 地址. 你会经常遇到由于调色板退色或者 VRAM 更新导致的显示器 "出现垃 圾" (显示器上有方块,或者看起来像图形 "小故障" 的情况). 这种情况的原因是你的代码花费的时间比 VBlank 长. 当 VBlank 通过 PPU 中的数据刷新显示器时,它读取所有的内部 VRAM 地址,并且把它们作为 Name Table #0 的开始. 解决方法是修改你的代码以重新指派 VRAM 地址到 $0000 (或者 $2000),这样刷新将成功. 代码可以是: LDA #$00 STA $2006 STA $2006 你将在商业游戏经常发现这样的代码. +------------+ | 11. 模拟器 | +------------+ A. 一般信息 ----------- 如果你准备用 C 或 C++ 编写模拟器,请熟悉指针. 熟悉指针将在处理镜像和 VRAM 地址时对你有很大帮助. 如果你在 这方面非常迷惑,很明显的指针只不过是间接地址 -- 它比在全部的 64K 数据交换更容易改变 32-bit 值. 如果 SRAM ($6000-7FFF) 被禁用了,程序将忽略对这段内存的写入. 读入时将返回 总线 [bus] 上先前留下的数据,因 此,模拟器将返回 0 (或者被 中断 [be trapped]). 以 RAM 为基础的内存区域 ($0000-07FF) 在重置时 *不* 应当被置为 0; 它们应当在开关机时被设置为 0. (技术上说, the RAM is not zeroed on power on/off either: the RAM will slowly dissapate over time when the unit it off. 然而,对于一个模拟器来说,请确保冷启动和热启动做不同的事情). 阅读 Section 12, Subection E 以获得邮件列表信息. B. CPU 注意事项 --------------- NES不使用传言中的 65c02 (CMOS) CPU. 忽略 bad opcodes (或者支持选项来捕获它). 一些 ROM 在这方面非常典型,例如 "Adventures of Lolo" 包含 bad opcodes,这是由于在析取处理时 cartridge 上的拙劣的连接器所导致 (或者其他原因). (在总共256个 opcodes之外) NES中有154个正确的 opcodes. C. PPU 注意事项 --------------- 计算 Name Table 中马赛克数目的基本地址的公式是: (TILENUM * 16) + PATTERNTABLE TILENUM 是 Name Table 中的马赛克数目,PATTERNTABLE 是经由寄存器 $2000 定义的 Pattern Table Address. 推荐 DOS 程序员使用 被称为 "MODE-Q" 的,一个 256x256x256 "tweaked" 视频模式来写模拟器. 尽量避免 Mode-X 模 式,虽然它们不是链式的,但却导致慢的痛苦的图形处理. 链式模式 (像 MODE-13h) 是线性的,对高速图形工作很好. 虽然 NES 分辨率是 256x240,但是上述册 "MODE-Q" 市和所有合适的需求. 大多数模拟器不限制统一扫描线上的子图形个数,然而真正的 NES 在同一线上超过八 (8) 个子图形时会有低速闪烁。 {放更多的垃圾在这里; 很容易...} 模拟器 _不应当_ 屏蔽未使用的寄存器; 这样做将导致 cart 不工作. D. APU 注意事项 --------------- To be written. +--------------+ | 12. 引用材料 | +--------------+ A. CPU Information ------------------ None. B. PPU Information ------------------ None. C. APU Information ------------------ None. D. MMC Information ------------------ None. E. Mailing Lists ---------------- There is a NES Development Mailing List in existence. Contact Mark Knibbs for more information. This list is for anyone who wishes to discuss tech- nical issues about the NES; it is not a list for picking up the latest and greatest information about NES emulators or what not. F. WWW Sites ------------ The following are a list of WWW sites which contain NES-oriented material. If you encounter errors, bad links, or other anomolies while visiting these sites, contact the site authors/owners, NOT me. Thanks. http://nesdev.parodius.com/ Contains a verbose amount of documentation regarding anything NES-oriented, including hard-to-find mapper documentation. Seems to be a decent NES information depository. http://www.ameth.org/~veilleux/NES_info.html Currently only contains hardware-oriented material, such as overviews of cart and unit ASICs, mappers, and MMCs. Many pinout diagrams for mappers and NES units are available here. Also provides documentation on NES repair, modifying your NES to give stereo output, applying stereo mixing to your NES, and much much more. G. Hardware Information ----------------------- The following security bits may be purchased from MCM Electronics (http://www.mcmelectronics.com/): For NES carts: 22-1145 (3.8mm security bit) For NES units: 22-1150 (4.5mm security bit) The 4.5mm security screw is also used for the Super Nintendo Enter- tainment System (SNES), and Nintendo 64. *Translated by 天之川 (Blue Potato) [bspotato@yahoo.com.cn] [http://bspotato.51.net]* *虽然已经尽全力翻译了,不过还是会有很多不恰当的地方. 如果你有很好的建议,请发信到上面的信箱中. 非常感谢* *Chinese Edition Version: V1*